diff options
| author | Chris McDonough <chrism@plope.com> | 2013-05-21 18:28:33 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2013-05-21 18:28:33 -0400 |
| commit | 1166fecd2ee051029ecbcccbaa3e3daae3526cf8 (patch) | |
| tree | 5314c4fdab8a8deacd43c504f3a41af20e0d093a | |
| parent | 91385e4c204532394d0cdee3ad61e0ff6cb51f0f (diff) | |
| parent | bea48e2f01070003a795aa12b8d8ba464fe15115 (diff) | |
| download | pyramid-1166fecd2ee051029ecbcccbaa3e3daae3526cf8.tar.gz pyramid-1166fecd2ee051029ecbcccbaa3e3daae3526cf8.tar.bz2 pyramid-1166fecd2ee051029ecbcccbaa3e3daae3526cf8.zip | |
Merge branch 'feature-pep302'
| -rw-r--r-- | CHANGES.txt | 5 | ||||
| -rw-r--r-- | pyramid/config/assets.py | 38 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 44 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_assets.py | 96 |
4 files changed, 171 insertions, 12 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 468fe1ed1..ceaeb3519 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,11 @@ next release Features -------- +- Make the ``pyramid.config.assets.PackageOverrides`` object implement the + API for ``__loader__`` objects specified in PEP 302. Proxies to the + ``__loader__`` set by the importer, if present; otherwise, raises + ``NotImplementedError``. + - ``ACLAuthorizationPolicy`` supports ``__acl__`` as a callable. This removes the ambiguity between the potential ``AttributeError`` that would be raised on the ``context`` when the property was not defined and the diff --git a/pyramid/config/assets.py b/pyramid/config/assets.py index 5d4682349..0616e6cda 100644 --- a/pyramid/config/assets.py +++ b/pyramid/config/assets.py @@ -81,14 +81,12 @@ class OverrideProvider(pkg_resources.DefaultProvider): self, resource_name) @implementer(IPackageOverrides) -class PackageOverrides: +class PackageOverrides(object): # pkg_resources arg in kw args below for testing def __init__(self, package, pkg_resources=pkg_resources): - if hasattr(package, '__loader__') and not isinstance(package.__loader__, - self.__class__): - raise TypeError('Package %s already has a non-%s __loader__ ' - '(probably a module in a zipped egg)' % - (package, self.__class__)) + loader = self._real_loader = getattr(package, '__loader__', None) + if isinstance(loader, self.__class__): + self._real_loader = None # We register ourselves as a __loader__ *only* to support the # setuptools _find_adapter adapter lookup; this class doesn't # actually support the PEP 302 loader "API". This is @@ -150,7 +148,33 @@ class PackageOverrides: for package, rname in self.search_path(resource_name): if pkg_resources.resource_exists(package, rname): return pkg_resources.resource_listdir(package, rname) - + + @property + def real_loader(self): + if self._real_loader is None: + raise NotImplementedError() + return self._real_loader + + def get_data(self, path): + """ See IPEP302Loader. + """ + return self.real_loader.get_data(path) + + def is_package(self, fullname): + """ See IPEP302Loader. + """ + return self.real_loader.is_package(fullname) + + def get_code(self, fullname): + """ See IPEP302Loader. + """ + return self.real_loader.get_code(fullname) + + def get_source(self, fullname): + """ See IPEP302Loader. + """ + return self.real_loader.get_source(fullname) + class DirectoryOverride: def __init__(self, path, package, prefix): diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 4fb4d615c..a57f61ddb 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -793,7 +793,49 @@ deprecated( 'See the "What\'s new In Pyramid 1.3" document for more details.' ) -class IPackageOverrides(Interface): +class IPEP302Loader(Interface): + """ See http://www.python.org/dev/peps/pep-0302/#id30. + """ + def get_data(path): + """ Retrieve data for and arbitrary "files" from storage backend. + + Raise IOError for not found. + + Data is returned as bytes. + """ + + def is_package(fullname): + """ Return True if the module specified by 'fullname' is a package. + """ + + def get_code(fullname): + """ Return the code object for the module identified by 'fullname'. + + Return 'None' if it's a built-in or extension module. + + If the loader doesn't have the code object but it does have the source + code, return the compiled source code. + + Raise ImportError if the module can't be found by the importer at all. + """ + + def get_source(fullname): + """ Return the source code for the module identified by 'fullname'. + + Return a string, using newline characters for line endings, or None + if the source is not available. + + Raise ImportError if the module can't be found by the importer at all. + """ + + def get_filename(fullname): + """ Return the value of '__file__' if the named module was loaded. + + If the module is not found, raise ImportError. + """ + + +class IPackageOverrides(IPEP302Loader): """ Utility for pkg_resources overrides """ # VH_ROOT_KEY is an interface; its imported from other packages (e.g. diff --git a/pyramid/tests/test_config/test_assets.py b/pyramid/tests/test_config/test_assets.py index 5fe02c358..345e7f8d6 100644 --- a/pyramid/tests/test_config/test_assets.py +++ b/pyramid/tests/test_config/test_assets.py @@ -314,16 +314,40 @@ class TestPackageOverrides(unittest.TestCase): from pyramid.config.assets import PackageOverrides return PackageOverrides - def _makeOne(self, package, pkg_resources=None): + def _makeOne(self, package=None, pkg_resources=None): + if package is None: + package = DummyPackage('package') klass = self._getTargetClass() if pkg_resources is None: pkg_resources = DummyPkgResources() return klass(package, pkg_resources=pkg_resources) + def test_class_conforms_to_IPackageOverrides(self): + from zope.interface.verify import verifyClass + from pyramid.interfaces import IPackageOverrides + verifyClass(IPackageOverrides, self._getTargetClass()) + + def test_instance_conforms_to_IPackageOverrides(self): + from zope.interface.verify import verifyObject + from pyramid.interfaces import IPackageOverrides + verifyObject(IPackageOverrides, self._makeOne()) + + def test_class_conforms_to_IPEP302Loader(self): + from zope.interface.verify import verifyClass + from pyramid.interfaces import IPEP302Loader + verifyClass(IPEP302Loader, self._getTargetClass()) + + def test_instance_conforms_to_IPEP302Loader(self): + from zope.interface.verify import verifyObject + from pyramid.interfaces import IPEP302Loader + verifyObject(IPEP302Loader, self._makeOne()) + def test_ctor_package_already_has_loader_of_different_type(self): package = DummyPackage('package') - package.__loader__ = True - self.assertRaises(TypeError, self._makeOne, package) + loader = package.__loader__ = DummyLoader() + po = self._makeOne(package) + self.assertTrue(package.__loader__ is po) + self.assertTrue(po.real_loader is loader) def test_ctor_package_already_has_loader_of_same_type(self): package = DummyPackage('package') @@ -502,6 +526,55 @@ class TestPackageOverrides(unittest.TestCase): po.overrides= overrides self.assertEqual(po.listdir('whatever'), None) + # PEP 302 __loader__ extensions: use the "real" __loader__, if present. + def test_get_data_pkg_has_no___loader__(self): + package = DummyPackage('package') + po = self._makeOne(package) + self.assertRaises(NotImplementedError, po.get_data, 'whatever') + + def test_get_data_pkg_has___loader__(self): + package = DummyPackage('package') + loader = package.__loader__ = DummyLoader() + po = self._makeOne(package) + self.assertEqual(po.get_data('whatever'), b'DEADBEEF') + self.assertEqual(loader._got_data, 'whatever') + + def test_is_package_pkg_has_no___loader__(self): + package = DummyPackage('package') + po = self._makeOne(package) + self.assertRaises(NotImplementedError, po.is_package, 'whatever') + + def test_is_package_pkg_has___loader__(self): + package = DummyPackage('package') + loader = package.__loader__ = DummyLoader() + po = self._makeOne(package) + self.assertTrue(po.is_package('whatever')) + self.assertEqual(loader._is_package, 'whatever') + + def test_get_code_pkg_has_no___loader__(self): + package = DummyPackage('package') + po = self._makeOne(package) + self.assertRaises(NotImplementedError, po.get_code, 'whatever') + + def test_get_code_pkg_has___loader__(self): + package = DummyPackage('package') + loader = package.__loader__ = DummyLoader() + po = self._makeOne(package) + self.assertEqual(po.get_code('whatever'), b'DEADBEEF') + self.assertEqual(loader._got_code, 'whatever') + + def test_get_source_pkg_has_no___loader__(self): + package = DummyPackage('package') + po = self._makeOne(package) + self.assertRaises(NotImplementedError, po.get_source, 'whatever') + + def test_get_source_pkg_has___loader__(self): + package = DummyPackage('package') + loader = package.__loader__ = DummyLoader() + po = self._makeOne(package) + self.assertEqual(po.get_source('whatever'), 'def foo():\n pass') + self.assertEqual(loader._got_source, 'whatever') + class TestDirectoryOverride(unittest.TestCase): def _getTargetClass(self): from pyramid.config.assets import DirectoryOverride @@ -570,10 +643,25 @@ class DummyPkgResources: def register_loader_type(self, typ, inst): self.registered.append((typ, inst)) - + class DummyPackage: def __init__(self, name): self.__name__ = name + +class DummyLoader: + _got_data = _is_package = None + def get_data(self, path): + self._got_data = path + return b'DEADBEEF' + def is_package(self, fullname): + self._is_package = fullname + return True + def get_code(self, fullname): + self._got_code = fullname + return b'DEADBEEF' + def get_source(self, fullname): + self._got_source = fullname + return 'def foo():\n pass' class DummyUnderOverride: def __call__(self, package, path, override_package, override_prefix, |
