summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2012-03-26 18:10:10 -0400
committerRyan Petrello <lists@ryanpetrello.com>2012-03-28 13:24:15 -0400
commitb18670228ffc303faae27b25584bc01388e7f145 (patch)
tree60d0a458ca68764926b959185dce5a23aac225e8
parenta7d4684ac5dc704b3fe8320618e71d462ee1209e (diff)
downloadpecan-b18670228ffc303faae27b25584bc01388e7f145.tar.gz
Working on extension support.
-rw-r--r--pecan/ext/__init__.py6
-rw-r--r--pecan/extensions.py82
2 files changed, 88 insertions, 0 deletions
diff --git a/pecan/ext/__init__.py b/pecan/ext/__init__.py
new file mode 100644
index 0000000..c768f9c
--- /dev/null
+++ b/pecan/ext/__init__.py
@@ -0,0 +1,6 @@
+def install():
+ from pecan.extensions import PecanExtensionImporter
+ PecanExtensionImporter().install()
+
+install()
+del install
diff --git a/pecan/extensions.py b/pecan/extensions.py
new file mode 100644
index 0000000..b9b5e5a
--- /dev/null
+++ b/pecan/extensions.py
@@ -0,0 +1,82 @@
+import sys
+import pkg_resources
+import inspect
+import logging
+
+log = logging.getLogger(__name__)
+
+
+class PecanExtensionMissing(Exception):
+ pass
+
+
+class PecanExtensionImporter(object):
+ """
+ Short circuits imports for extensions.
+
+ This is used in combination with ``pecan.ext`` so that when a user does
+ ``from pecan.ext import foo``, it will attempt to map ``foo`` to a
+ registered setuptools entry point in some other (Pecan extension) project.
+
+ Conversely, an extension developer may define an entry point in his
+ ``setup.py``, e.g.,
+
+ setup(
+ ...
+ entry_points='''
+ [pecan.extension]
+ celery = pecancelery.lib.core
+ '''
+ )
+
+ This is mostly for convenience and consistency. In this way, Pecan can
+ maintain an ecosystem of extensions that share a common namespace,
+ ``pecan.ext``, while still maintaining backwards compatability for simple
+ package names (e.g., ``pecancelery``).
+ """
+
+ extension_module = 'pecan.ext'
+ prefix = extension_module + '.'
+
+ def install(self):
+ if self not in sys.meta_path:
+ sys.meta_path.append(self)
+
+ def __eq__(self, b):
+ return self.__class__.__module__ == b.__class__.__module__ and \
+ self.__class__.__name__ == b.__class__.__name__
+
+ def __ne__(self, b):
+ return not self.__eq__(b)
+
+ def find_module(self, fullname, path=None):
+ if fullname.startswith(self.prefix):
+ return self
+
+ def load_module(self, fullname):
+ if fullname in sys.modules:
+ return self
+ extname = fullname.split(self.prefix)[1]
+ module = self.find_module_for_extension(extname)
+ realname = module.__name__
+ try:
+ __import__(realname)
+ except ImportError:
+ raise sys.exc_info()
+ module = sys.modules[fullname] = sys.modules[realname]
+ if '.' not in extname:
+ setattr(sys.modules[self.extension_module], extname, module)
+ return module
+
+ def find_module_for_extension(self, name):
+ for ep in pkg_resources.iter_entry_points('pecan.extension'):
+ if ep.name != name:
+ continue
+ log.debug('%s loading extension %s', self.__class__.__name__, ep)
+ module = ep.load()
+ if not inspect.ismodule(module):
+ continue
+ return module
+ raise PecanExtensionMissing(
+ 'The `pecan.ext.%s` extension is not installed.' % name
+ )