summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Hellkamp <marc@gsites.de>2011-04-28 00:19:14 +0200
committerMarcel Hellkamp <marc@gsites.de>2011-04-28 01:06:57 +0200
commitb06f0659d762741a1aed4c41014fac890d06516d (patch)
treeeb94dad8ebf0e2c72cdb03892da526baac874b52
parent3bb4d584bc95624c8bad8b6eb67f950b415033a7 (diff)
downloadbottle-b06f0659d762741a1aed4c41014fac890d06516d.tar.gz
Added a virtual package 'bottle.ext' that redirects imports to 'bottle_%s'.
Example:: import bottle.ext.sqlite # actually imports bottle_sqlite This gives us nice import statements and a common namespace for all plugins and might become an official plugin naming convention. It's not just a cool hack, but a real alternative to setuptools "namespace packages": + No setuptools dependency. + No overlapping __init__.py files. + Support for single-files (module) extensions. + Python standard since 2.3 (PEP 302).
-rwxr-xr-xbottle.py37
1 files changed, 36 insertions, 1 deletions
diff --git a/bottle.py b/bottle.py
index d17dc9a..2ba3a9b 100755
--- a/bottle.py
+++ b/bottle.py
@@ -24,6 +24,7 @@ import email.utils
import functools
import hmac
import httplib
+import imp
import itertools
import mimetypes
import os
@@ -1115,6 +1116,7 @@ class Response(threading.local):
###############################################################################
+
class JSONPlugin(object):
name = 'json'
@@ -1203,6 +1205,34 @@ class TypeFilterPlugin(object):
return wrapper
+#: Not a plugin, but part of the plugin API. TODO: Find a better place.
+class _ImportRedirect(object):
+ def __init__(self, name, impmask):
+ ''' Create a virtual package that redirects imports (see PEP 302). '''
+ self.name = name
+ self.impmask = impmask
+ self.module = sys.modules.setdefault(name, imp.new_module(name))
+ self.module.__dict__.update({'__file__': '<virtual>', '__path__': [],
+ '__all__': [], '__loader__': self})
+ sys.meta_path.append(self)
+
+ def find_module(self, fullname, path=None):
+ if '.' not in fullname: return
+ packname, modname = fullname.rsplit('.', 1)
+ if packname != self.name: return
+ return self
+
+ def load_module(self, fullname):
+ if fullname in sys.modules: return sys.modules[fullname]
+ packname, modname = fullname.rsplit('.', 1)
+ realname = self.impmask % modname
+ __import__(realname)
+ module = sys.modules[fullname] = sys.modules[realname]
+ setattr(self.module, modname, module)
+ module.__loader__ = self
+ return module
+
+
@@ -1375,7 +1405,7 @@ def send_file(*a, **k): #BC 0.6.4
def static_file(filename, root, mimetype='auto', guessmime=True, download=False):
""" Open a file in a safe way and return :exc:`HTTPResponse` with status
- code 200, 305, 401 or 404. Set Content-Type, Content-Encoding,
+ code 200, 305, 401 or 404. Set Content-Type, Content-Encoding,
Content-Length and Last-Modified header. Obey If-Modified-Since header
and HEAD requests.
"""
@@ -1428,6 +1458,7 @@ def static_file(filename, root, mimetype='auto', guessmime=True, download=False)
# HTTP Utilities and MISC (TODO) ###############################################
###############################################################################
+
def debug(mode=True):
""" Change the debug level.
There is only one debug level supported at the moment."""
@@ -2455,3 +2486,7 @@ local = threading.local()
# BC: 0.6.4 and needed for run()
app = default_app = AppStack()
app.push()
+
+#: A virtual package that redirects import statements.
+#: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`.
+ext = _ImportRedirect(__name__+'.ext', 'bottle_%s').module