From 3bdac7122ae38aa973b8618cbb57303aa1dcc19c Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Mon, 26 Mar 2012 18:10:10 -0400 Subject: [PATCH 1/6] Working on extension support. --- pecan/ext/__init__.py | 6 ++++ pecan/extensions.py | 82 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 pecan/ext/__init__.py create mode 100644 pecan/extensions.py 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 + ) From 7a7c8f9f6349d40c2f3ac639138618b09e569135 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 27 Mar 2012 13:21:27 -0400 Subject: [PATCH 2/6] Removing some old validation cruft. --- pecan/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pecan/core.py b/pecan/core.py index dd81b98..2e0f29c 100644 --- a/pecan/core.py +++ b/pecan/core.py @@ -504,7 +504,7 @@ class Pecan(object): try: # add context and environment to the request state.request.context = {} - state.request.pecan = dict(content_type=None, validation_errors={}) + state.request.pecan = dict(content_type=None) self.handle_request() except Exception, e: From b18670228ffc303faae27b25584bc01388e7f145 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Mon, 26 Mar 2012 18:10:10 -0400 Subject: [PATCH 3/6] Working on extension support. --- pecan/ext/__init__.py | 6 ++++ pecan/extensions.py | 82 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 pecan/ext/__init__.py create mode 100644 pecan/extensions.py 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 + ) From 0c51f0cfed53b789f5c2523b8f42ee48bdf0253e Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 27 Mar 2012 13:21:27 -0400 Subject: [PATCH 4/6] Removing some old validation cruft. --- pecan/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pecan/core.py b/pecan/core.py index dd81b98..2e0f29c 100644 --- a/pecan/core.py +++ b/pecan/core.py @@ -504,7 +504,7 @@ class Pecan(object): try: # add context and environment to the request state.request.context = {} - state.request.pecan = dict(content_type=None, validation_errors={}) + state.request.pecan = dict(content_type=None) self.handle_request() except Exception, e: From 36d6e7a506181182aaf4f12585ae5584729ca946 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 28 Mar 2012 14:37:04 -0400 Subject: [PATCH 5/6] Changing PecanExtensionMissing to inherit from ImportError. --- pecan/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pecan/extensions.py b/pecan/extensions.py index b9b5e5a..ed11c06 100644 --- a/pecan/extensions.py +++ b/pecan/extensions.py @@ -6,7 +6,7 @@ import logging log = logging.getLogger(__name__) -class PecanExtensionMissing(Exception): +class PecanExtensionMissing(ImportError): pass From 8e0b04f5e27c320613cf4edf0f5e72526d71f2a0 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 28 Mar 2012 14:41:02 -0400 Subject: [PATCH 6/6] More logging... --- pecan/extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pecan/extensions.py b/pecan/extensions.py index ed11c06..51edf57 100644 --- a/pecan/extensions.py +++ b/pecan/extensions.py @@ -75,6 +75,7 @@ class PecanExtensionImporter(object): log.debug('%s loading extension %s', self.__class__.__name__, ep) module = ep.load() if not inspect.ismodule(module): + log.debug('%s is not a module, skipping...' % module) continue return module raise PecanExtensionMissing(