diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-06-29 02:49:30 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-06-29 02:49:30 +0000 |
| commit | 2869fc2f7f8fa9a4230d66859f232fe2e764103f (patch) | |
| tree | f947f4ac308595f115966b47eaea2594697c00c3 | |
| parent | e1622d3f023e1fe5c6a580668ec3f5ecab9c0a37 (diff) | |
| download | pyramid-2869fc2f7f8fa9a4230d66859f232fe2e764103f.tar.gz pyramid-2869fc2f7f8fa9a4230d66859f232fe2e764103f.tar.bz2 pyramid-2869fc2f7f8fa9a4230d66859f232fe2e764103f.zip | |
Merge pkg_resource_overrides branch.
| -rw-r--r-- | repoze/bfg/includes/meta.zcml | 6 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 3 | ||||
| -rw-r--r-- | repoze/bfg/resource.py | 106 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_resource.py | 237 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_templating.py | 3 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 107 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 68 |
7 files changed, 524 insertions, 6 deletions
diff --git a/repoze/bfg/includes/meta.zcml b/repoze/bfg/includes/meta.zcml index 4acdfe937..ba0fc50cc 100644 --- a/repoze/bfg/includes/meta.zcml +++ b/repoze/bfg/includes/meta.zcml @@ -34,6 +34,12 @@ handler="repoze.bfg.zcml.route" /> + <meta:directive + name="resource" + schema="repoze.bfg.zcml.IResourceDirective" + handler="repoze.bfg.zcml.resource" + /> + </meta:directives> </configure> diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 432c645a4..779c0ef03 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -231,6 +231,9 @@ class IRequestFactories(Interface): """ Marker utility interface representing a dictionary of request factory descriptions""" +class IPackageOverrides(Interface): + """ Utility for pkg_resources overrides """ + # VH_ROOT_KEY is an interface; its imported from other packages (e.g. # traversalwrapper) VH_ROOT_KEY = 'HTTP_X_VHM_ROOT' diff --git a/repoze/bfg/resource.py b/repoze/bfg/resource.py new file mode 100644 index 000000000..00e30170d --- /dev/null +++ b/repoze/bfg/resource.py @@ -0,0 +1,106 @@ +import pkg_resources +from zope.component import queryUtility +from zope.interface import implements + +from repoze.bfg.interfaces import IPackageOverrides + +class OverrideProvider(pkg_resources.DefaultProvider): + def __init__(self, module): + pkg_resources.DefaultProvider.__init__(self, module) + self.module_name = module.__name__ + + def _get_overrides(self): + overrides = queryUtility(IPackageOverrides, self.module_name) + return overrides + + def get_resource_filename(self, manager, resource_name): + """ Return a true filesystem path for resource_name, + co-ordinating the extraction with manager, if the resource + must be unpacked to the filesystem. + """ + overrides = self._get_overrides() + if overrides is not None: + filename = overrides.get_filename(resource_name) + if filename is not None: + return filename + return pkg_resources.DefaultProvider.get_resource_filename( + self, manager, resource_name) + + def get_resource_stream(self, manager, resource_name): + """ Return a readable file-like object for resource_name.""" + overrides = self._get_overrides() + if overrides is not None: + stream = overrides.get_stream(resource_name) + if stream is not None: + return stream + return pkg_resources.DefaultProvider.get_resource_stream( + self, manager, resource_name) + + def get_resource_string(self, manager, resource_name): + """ Return a string containing the contents of resource_name.""" + overrides = self._get_overrides() + if overrides is not None: + string = overrides.get_string(resource_name) + if string is not None: + return string + return pkg_resources.DefaultProvider.get_resource_string( + self, manager, resource_name) + +class PackageOverrides: + implements(IPackageOverrides) + def __init__(self, overridden_package): + self.overrides = [] + self.overridden_package = overridden_package + + def insert(self, path, package, prefix): + if path.endswith('/'): + override = DirectoryOverride(path, package, prefix) + else: + override = FileOverride(path, package, prefix) + self.overrides.insert(0, override) + return override + + def search_path(self, resource_name): + for override in self.overrides: + o = override(resource_name) + if o is not None: + package, name = o + yield package, name + + def get_filename(self, resource_name): + for package, rname in self.search_path(resource_name): + if pkg_resources.resource_exists(package, rname): + return pkg_resources.resource_filename(package, rname) + + def get_stream(self, resource_name): + for package, rname in self.search_path(resource_name): + if pkg_resources.resource_exists(package, rname): + return pkg_resources.resource_stream(package, rname) + + def get_string(self, resource_name): + for package, rname in self.search_path(resource_name): + if pkg_resources.resource_exists(package, rname): + return pkg_resources.resource_string(package, rname) + +class DirectoryOverride: + def __init__(self, path, package, prefix): + self.path = path + self.package = package + self.prefix = prefix + self.pathlen = len(self.path) + + def __call__(self, resource_name): + if resource_name.startswith(self.path): + name = '%s%s' % (self.prefix, resource_name[self.pathlen:]) + return self.package, name + +class FileOverride: + def __init__(self, path, package, prefix): + self.path = path + self.package = package + self.prefix = prefix + + def __call__(self, resource_name): + if resource_name == self.path: + return self.package, self.prefix + diff --git a/repoze/bfg/tests/test_resource.py b/repoze/bfg/tests/test_resource.py new file mode 100644 index 000000000..176a07049 --- /dev/null +++ b/repoze/bfg/tests/test_resource.py @@ -0,0 +1,237 @@ +import unittest +from repoze.bfg.testing import cleanUp + +class TestOverrideProvider(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _getTargetClass(self): + from repoze.bfg.resource import OverrideProvider + return OverrideProvider + + def _makeOne(self, module): + klass = self._getTargetClass() + return klass(module) + + def _registerOverrides(self, overrides, name='repoze.bfg.tests'): + from repoze.bfg.interfaces import IPackageOverrides + from zope.component import getSiteManager + sm = getSiteManager() + sm.registerUtility(overrides, IPackageOverrides, name=name) + + def test_get_resource_filename_no_overrides(self): + import os + resource_name = 'test_resource.py' + import repoze.bfg.tests + provider = self._makeOne(repoze.bfg.tests) + here = os.path.dirname(os.path.abspath(__file__)) + expected = os.path.join(here, resource_name) + result = provider.get_resource_filename(None, resource_name) + self.assertEqual(result, expected) + + def test_get_resource_stream_no_overrides(self): + import os + resource_name = 'test_resource.py' + import repoze.bfg.tests + provider = self._makeOne(repoze.bfg.tests) + here = os.path.dirname(os.path.abspath(__file__)) + expected = open(os.path.join(here, resource_name)).read() + result = provider.get_resource_stream(None, resource_name) + self.assertEqual(result.read(), expected) + + def test_get_resource_string_no_overrides(self): + import os + resource_name = 'test_resource.py' + import repoze.bfg.tests + provider = self._makeOne(repoze.bfg.tests) + here = os.path.dirname(os.path.abspath(__file__)) + expected = open(os.path.join(here, resource_name)).read() + result = provider.get_resource_string(None, resource_name) + self.assertEqual(result, expected) + + def test_get_resource_filename_override_returns_None(self): + overrides = DummyOverrides(None) + self._registerOverrides(overrides) + import os + resource_name = 'test_resource.py' + import repoze.bfg.tests + provider = self._makeOne(repoze.bfg.tests) + here = os.path.dirname(os.path.abspath(__file__)) + expected = os.path.join(here, resource_name) + result = provider.get_resource_filename(None, resource_name) + self.assertEqual(result, expected) + + def test_get_resource_stream_override_returns_None(self): + overrides = DummyOverrides(None) + self._registerOverrides(overrides) + import os + resource_name = 'test_resource.py' + import repoze.bfg.tests + provider = self._makeOne(repoze.bfg.tests) + here = os.path.dirname(os.path.abspath(__file__)) + expected = os.path.join(here, resource_name) + result = provider.get_resource_filename(None, resource_name) + self.assertEqual(result, expected) + + def test_get_resource_string_override_returns_None(self): + overrides = DummyOverrides(None) + self._registerOverrides(overrides) + import os + resource_name = 'test_resource.py' + import repoze.bfg.tests + provider = self._makeOne(repoze.bfg.tests) + here = os.path.dirname(os.path.abspath(__file__)) + expected = os.path.join(here, resource_name) + result = provider.get_resource_filename(None, resource_name) + self.assertEqual(result, expected) + + def test_get_resource_filename_override_returns_value(self): + overrides = DummyOverrides('value') + import repoze.bfg.tests + self._registerOverrides(overrides) + provider = self._makeOne(repoze.bfg.tests) + result = provider.get_resource_filename(None, 'test_resource.py') + self.assertEqual(result, 'value') + + def test_get_resource_stream_override_returns_value(self): + overrides = DummyOverrides('value') + import repoze.bfg.tests + self._registerOverrides(overrides) + provider = self._makeOne(repoze.bfg.tests) + result = provider.get_resource_stream(None, 'test_resource.py') + self.assertEqual(result, 'value') + + def test_get_resource_string_override_returns_value(self): + overrides = DummyOverrides('value') + import repoze.bfg.tests + self._registerOverrides(overrides) + provider = self._makeOne(repoze.bfg.tests) + result = provider.get_resource_string(None, 'test_resource.py') + self.assertEqual(result, 'value') + +class TestPackageOverrides(unittest.TestCase): + def _getTargetClass(self): + from repoze.bfg.resource import PackageOverrides + return PackageOverrides + + def _makeOne(self, package): + klass = self._getTargetClass() + return klass(package) + + def test_insert_directory(self): + from repoze.bfg.resource import DirectoryOverride + po = self._makeOne('package') + po.overrides= [None] + po.insert('foo/', 'package', 'bar/') + self.assertEqual(len(po.overrides), 2) + override = po.overrides[0] + self.assertEqual(override.__class__, DirectoryOverride) + + def test_insert_file(self): + from repoze.bfg.resource import FileOverride + po = self._makeOne('package') + po.overrides= [None] + po.insert('foo.pt', 'package', 'bar.pt') + self.assertEqual(len(po.overrides), 2) + override = po.overrides[0] + self.assertEqual(override.__class__, FileOverride) + + def test_search_path(self): + overrides = [ DummyOverride(None), DummyOverride(('package', 'name'))] + po = self._makeOne('package') + po.overrides= overrides + self.assertEqual(list(po.search_path('whatever')), + [('package', 'name')]) + + def test_get_filename(self): + import os + overrides = [ DummyOverride(None), DummyOverride( + ('repoze.bfg.tests', 'test_resource.py'))] + po = self._makeOne('package') + po.overrides= overrides + here = os.path.dirname(os.path.abspath(__file__)) + expected = os.path.join(here, 'test_resource.py') + self.assertEqual(po.get_filename('whatever'), expected) + + def test_get_stream(self): + import os + overrides = [ DummyOverride(None), DummyOverride( + ('repoze.bfg.tests', 'test_resource.py'))] + po = self._makeOne('package') + po.overrides= overrides + here = os.path.dirname(os.path.abspath(__file__)) + expected = open(os.path.join(here, 'test_resource.py')).read() + self.assertEqual(po.get_stream('whatever').read(), expected) + + def test_get_string(self): + import os + overrides = [ DummyOverride(None), DummyOverride( + ('repoze.bfg.tests', 'test_resource.py'))] + po = self._makeOne('package') + po.overrides= overrides + here = os.path.dirname(os.path.abspath(__file__)) + expected = open(os.path.join(here, 'test_resource.py')).read() + self.assertEqual(po.get_string('whatever'), expected) + + +class TestDirectoryOverride(unittest.TestCase): + def _getTargetClass(self): + from repoze.bfg.resource import DirectoryOverride + return DirectoryOverride + + def _makeOne(self, path, package, prefix): + klass = self._getTargetClass() + return klass(path, package, prefix) + + def test_it_match(self): + o = self._makeOne('foo/', 'package', 'bar/') + result = o('foo/something.pt') + self.assertEqual(result, ('package', 'bar/something.pt')) + + def test_it_no_match(self): + o = self._makeOne('foo/', 'package', 'bar/') + result = o('baz/notfound.pt') + self.assertEqual(result, None) + + +class TestFileOverride(unittest.TestCase): + def _getTargetClass(self): + from repoze.bfg.resource import FileOverride + return FileOverride + + def _makeOne(self, path, package, prefix): + klass = self._getTargetClass() + return klass(path, package, prefix) + + def test_it_match(self): + o = self._makeOne('foo.pt', 'package', 'bar.pt') + result = o('foo.pt') + self.assertEqual(result, ('package', 'bar.pt')) + + def test_it_no_match(self): + o = self._makeOne('foo.pt', 'package', 'bar.pt') + result = o('notfound.pt') + self.assertEqual(result, None) + + + +class DummyOverride: + def __init__(self, result): + self.result = result + + def __call__(self, resource_name): + return self.result + + +class DummyOverrides: + def __init__(self, result): + self.result = result + + def get_filename(self, resource_name): + return self.result + + get_stream = get_string = get_filename + diff --git a/repoze/bfg/tests/test_templating.py b/repoze/bfg/tests/test_templating.py index 57c6d0866..3ae0483d9 100644 --- a/repoze/bfg/tests/test_templating.py +++ b/repoze/bfg/tests/test_templating.py @@ -54,7 +54,6 @@ class TestRendererFromCache(unittest.TestCase): def test_relpath_alreadyregistered(self): from repoze.bfg.interfaces import ITemplateRenderer - import os from repoze.bfg.tests import test_templating module_name = test_templating.__name__ relpath = 'test_templating.py' @@ -65,7 +64,7 @@ class TestRendererFromCache(unittest.TestCase): self.failUnless(result is renderer) def test_relpath_notyetregistered(self): - from repoze.bfg.interfaces import ITemplateRenderer + from repoze.bfg import resource import os from repoze.bfg.tests import test_templating module_name = test_templating.__name__ diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index 1a0ee3c3f..875628f5f 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -804,8 +804,6 @@ class TestRouteDirective(unittest.TestCase): self.assertEqual(route_args, ('name', 'path', None,)) def test_without_view(self): - from zope.component import getUtility - from repoze.bfg.interfaces import IRequestFactories from repoze.bfg.zcml import connect_route context = DummyContext() @@ -827,8 +825,6 @@ class TestRouteDirective(unittest.TestCase): self.assertEqual(route_args, ('name','path', None)) def test_with_request_type(self): - from zope.component import getUtility - from repoze.bfg.interfaces import IRequestFactories from repoze.bfg.zcml import connect_route context = DummyContext() @@ -849,6 +845,94 @@ class TestRouteDirective(unittest.TestCase): self.assertEqual(route_discriminator[3], 'GET') self.assertEqual(route_args, ('name','path', None)) +class TestResourceDirective(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, *arg, **kw): + from repoze.bfg.zcml import resource + return resource(*arg, **kw) + + def test_samename(self): + from zope.configuration.exceptions import ConfigurationError + context = DummyContext() + self.assertRaises(ConfigurationError, self._callFUT, context, 'a', 'a') + + def test_override_directory_with_file(self): + from zope.configuration.exceptions import ConfigurationError + context = DummyContext() + self.assertRaises(ConfigurationError, self._callFUT, context, 'a/', 'a') + + def test_override_file_with_directory(self): + from zope.configuration.exceptions import ConfigurationError + context = DummyContext() + self.assertRaises(ConfigurationError, self._callFUT, context, 'a', 'a/') + + def test_no_colons(self): + from repoze.bfg.zcml import _override + context = DummyContext() + self._callFUT(context, 'a', 'b') + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + self.assertEqual(action['callable'], _override) + self.assertEqual(action['discriminator'], None) + self.assertEqual(action['args'], + ('IDummy', '', 'IDummy', '')) + + def test_with_colons(self): + from repoze.bfg.zcml import _override + context = DummyContext() + self._callFUT(context, 'a:foo.pt', 'b:foo.pt') + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + self.assertEqual(action['callable'], _override) + self.assertEqual(action['discriminator'], None) + self.assertEqual(action['args'], + ('IDummy', 'foo.pt', 'IDummy', 'foo.pt')) + +class Test_OverrideFunction(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, *arg, **kw): + from repoze.bfg.zcml import _override + return _override(*arg, **kw) + + def _registerOverrides(self, overrides, package_name): + from repoze.bfg.interfaces import IPackageOverrides + from zope.component import getSiteManager + sm = getSiteManager() + sm.registerUtility(overrides, IPackageOverrides, name=package_name) + + def test_overrides_not_yet_registered(self): + from repoze.bfg.resource import OverrideProvider + from zope.component import queryUtility + from repoze.bfg.interfaces import IPackageOverrides + resources = DummyPackageResources() + self._callFUT('package', 'path', 'opackage', 'oprefix', + PackageOverrides=DummyOverrides, pkg_resources=resources) + overrides = queryUtility(IPackageOverrides, name='package') + self.assertEqual(overrides.package, 'package') + self.assertEqual(overrides.inserted, [('path', 'opackage', 'oprefix')]) + self.assertEqual(len(resources.registered), 1) + resource = resources.registered[0] + self.assertEqual(resource[0], type(None)) + self.assertEqual(resource[1], OverrideProvider) + + def test_overrides_already_registered(self): + overrides = DummyOverrides('package') + self._registerOverrides(overrides, 'package') + self._callFUT('package', 'path', 'opackage', 'oprefix') + self.assertEqual(overrides.inserted, [('path', 'opackage', 'oprefix')]) + class TestZCMLConfigure(unittest.TestCase): i = 0 def _callFUT(self, path, package): @@ -1244,3 +1328,18 @@ class DummyRequest: def get_response(self, app): return app +class DummyOverrides: + def __init__(self, package): + self.package = package + self.inserted = [] + + def insert(self, path, package, prefix): + self.inserted.append((path, package, prefix)) + +class DummyPackageResources: + def __init__(self): + self.registered = [] + + def register_loader_type(self, typ, provider): + self.registered.append((typ, provider)) + diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index 786301c45..38db4ead7 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -1,5 +1,6 @@ import inspect import types +import pkg_resources from zope.configuration import xmlconfig @@ -27,6 +28,10 @@ from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IUnauthorizedAppFactory from repoze.bfg.interfaces import ILogger from repoze.bfg.interfaces import IRequestFactories +from repoze.bfg.interfaces import IPackageOverrides + +from repoze.bfg.resource import OverrideProvider +from repoze.bfg.resource import PackageOverrides from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES from repoze.bfg.request import named_request_factories @@ -155,6 +160,69 @@ def scan(_context, package, martian=martian): martian.grok_dotted_name(package.__name__, grokker=module_grokker, context=_context, exclude_filter=exclude) +class IResourceDirective(Interface): + """ + Directive for specifying that one package may override resources from + another package. + """ + to_override = TextLine( + title=u"Override spec", + description=u'The spec of the resource to override.', + required=True) + override_with = TextLine( + title=u"With spec", + description=u"The spec of the resource providing the override.", + required=True) + +def _override(package, path, override_package, override_prefix, + PackageOverrides=PackageOverrides, pkg_resources=pkg_resources): + # PackageOverrides and pkg_resources kw args for tests + sm = getSiteManager() + override = queryUtility(IPackageOverrides, name=package) + if override is None: + override = PackageOverrides(package) + sm.registerUtility(override, IPackageOverrides, name=package) + # register_loader_type will be called too many times if there + # is more than one overridden package; that's OK, as our + # mutation is idempotent + pkg_resources.register_loader_type(type(None), OverrideProvider) + override.insert(path, override_package, override_prefix) + +def resource(context, to_override, override_with): + if to_override == override_with: + raise ConfigurationError('You cannot override a resource with itself') + + if to_override.endswith('/'): + if not override_with.endswith('/'): + raise ConfigurationError( + 'A directory cannot be overridden with a file (put a slash ' + 'at the end of override_with if necessary)') + + if override_with.endswith('/'): + if not to_override.endswith('/'): + raise ConfigurationError( + 'A file cannot be overridden with a directory (put a slash ' + 'at the end of to_override if necessary)') + + package = to_override + path = '' + if ':' in to_override: + package, path = to_override.split(':', 1) + + override_package = override_with + override_prefix = '' + if ':' in override_with: + override_package, override_prefix = override_with.split(':', 1) + + package = context.resolve(package).__name__ + override_package = context.resolve(package).__name__ + + context.action( + discriminator = None, + callable = _override, + args = (package, path, override_package, override_prefix), + ) + class IRouteDirective(Interface): """ The interface for the ``route`` ZCML directive """ |
