summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-06-29 02:49:30 +0000
committerChris McDonough <chrism@agendaless.com>2009-06-29 02:49:30 +0000
commit2869fc2f7f8fa9a4230d66859f232fe2e764103f (patch)
treef947f4ac308595f115966b47eaea2594697c00c3
parente1622d3f023e1fe5c6a580668ec3f5ecab9c0a37 (diff)
downloadpyramid-2869fc2f7f8fa9a4230d66859f232fe2e764103f.tar.gz
pyramid-2869fc2f7f8fa9a4230d66859f232fe2e764103f.tar.bz2
pyramid-2869fc2f7f8fa9a4230d66859f232fe2e764103f.zip
Merge pkg_resource_overrides branch.
-rw-r--r--repoze/bfg/includes/meta.zcml6
-rw-r--r--repoze/bfg/interfaces.py3
-rw-r--r--repoze/bfg/resource.py106
-rw-r--r--repoze/bfg/tests/test_resource.py237
-rw-r--r--repoze/bfg/tests/test_templating.py3
-rw-r--r--repoze/bfg/tests/test_zcml.py107
-rw-r--r--repoze/bfg/zcml.py68
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
"""