summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2013-05-21 18:28:33 -0400
committerChris McDonough <chrism@plope.com>2013-05-21 18:28:33 -0400
commit1166fecd2ee051029ecbcccbaa3e3daae3526cf8 (patch)
tree5314c4fdab8a8deacd43c504f3a41af20e0d093a
parent91385e4c204532394d0cdee3ad61e0ff6cb51f0f (diff)
parentbea48e2f01070003a795aa12b8d8ba464fe15115 (diff)
downloadpyramid-1166fecd2ee051029ecbcccbaa3e3daae3526cf8.tar.gz
pyramid-1166fecd2ee051029ecbcccbaa3e3daae3526cf8.tar.bz2
pyramid-1166fecd2ee051029ecbcccbaa3e3daae3526cf8.zip
Merge branch 'feature-pep302'
-rw-r--r--CHANGES.txt5
-rw-r--r--pyramid/config/assets.py38
-rw-r--r--pyramid/interfaces.py44
-rw-r--r--pyramid/tests/test_config/test_assets.py96
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,