diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-11-25 17:54:40 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-11-25 17:54:40 +0000 |
| commit | 103efb5aae885a70590ad1f3c5807af9da8d1ab7 (patch) | |
| tree | 1b335d2d088b84544f8ef5bad713bc8922674153 | |
| parent | 2aa86c1a5227a675d32c4ede06e0c031ae3edfad (diff) | |
| download | pyramid-103efb5aae885a70590ad1f3c5807af9da8d1ab7.tar.gz pyramid-103efb5aae885a70590ad1f3c5807af9da8d1ab7.tar.bz2 pyramid-103efb5aae885a70590ad1f3c5807af9da8d1ab7.zip | |
- A dependency on the ``repoze.zcml`` package has been removed (its
functionality is replaced internally).
| -rw-r--r-- | CHANGES.txt | 18 | ||||
| -rw-r--r-- | repoze/bfg/includes/configure.zcml | 4 | ||||
| -rw-r--r-- | repoze/bfg/includes/meta.zcml | 18 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 273 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 230 | ||||
| -rw-r--r-- | setup.py | 2 |
6 files changed, 534 insertions, 11 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index c871769d7..ff30b2447 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -126,6 +126,10 @@ Internals - Instead of depending on the ``martian`` package to do code scanning, we now just use our own scanning routines. +- We now no longer have a dependency on ``repoze.zcml`` package; + instead, the ``repoze.bfg`` package includes implementations of the + ``adapter``, ``subscriber`` and ``utility`` directives. + Backwards Incompatibilites -------------------------- @@ -218,6 +222,15 @@ Backwards Incompatibilites add an attribute to your unit test request object named ``registry`` with the result. +- Because the ``repoze.bfg`` package includes implementations of the + ``adapter``, ``subscriber`` and ``utility`` ZCML directives, it is + now an error to have ``<include package="repoze.zcml" + file="meta.zcml"/>`` in the ZCML of a ``repoze.bfg`` application. A + ZCML conflict error will be raised if your ZCML does so. This + shouldn't be an issue for "normal" installations; it has always been + the responsibility of the ``repoze.bfg.includes`` ZCML to include + this file in the past; it now just doesn't. + Deprecations ------------ @@ -230,7 +243,10 @@ Deprecations Dependencies ------------ -- The dependency on the ``martian`` package has been removed (its +- A dependency on the ``martian`` package has been removed (its + functionality is replaced internally). + +- A dependency on the ``repoze.zcml`` package has been removed (its functionality is replaced internally). 1.1.1 (2009-11-21) diff --git a/repoze/bfg/includes/configure.zcml b/repoze/bfg/includes/configure.zcml index ffabca9a3..6a373fa5d 100644 --- a/repoze/bfg/includes/configure.zcml +++ b/repoze/bfg/includes/configure.zcml @@ -1,9 +1,5 @@ <configure xmlns="http://namespaces.repoze.org/bfg"> - <include package="repoze.zcml" file="meta.zcml" /> - - <!-- traversal adapters --> - <include file="meta.zcml" /> </configure> diff --git a/repoze/bfg/includes/meta.zcml b/repoze/bfg/includes/meta.zcml index 4c9d18873..feb44f5dd 100644 --- a/repoze/bfg/includes/meta.zcml +++ b/repoze/bfg/includes/meta.zcml @@ -76,6 +76,24 @@ handler="repoze.bfg.zcml.renderer" /> + <meta:directive + name="adapter" + schema="repoze.bfg.zcml.IAdapterDirective" + handler="repoze.bfg.zcml.adapter" + /> + + <meta:directive + name="subscriber" + schema="repoze.bfg.zcml.ISubscriberDirective" + handler="repoze.bfg.zcml.subscriber" + /> + + <meta:directive + name="utility" + schema="repoze.bfg.zcml.IUtilityDirective" + handler="repoze.bfg.zcml.utility" + /> + </meta:directives> </configure> diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index 34250bdde..2834a8cb1 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -6,6 +6,9 @@ import unittest from repoze.bfg import testing +from zope.interface import Interface +from zope.interface import implements + class TestViewDirective(unittest.TestCase): def setUp(self): testing.setUp() @@ -617,6 +620,269 @@ class TestZCMLScanDirective(unittest.TestCase): self.assertEqual(action['discriminator'], None) self.assertEqual(action['args'], (dummy_module, None)) + +class TestAdapterDirective(unittest.TestCase): + def setUp(self): + testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _callFUT(self, *arg, **kw): + from repoze.bfg.zcml import adapter + return adapter(*arg, **kw) + + def test_for_is_None_no_adaptedBy(self): + context = DummyContext() + factory = DummyFactory() + self.assertRaises(TypeError, self._callFUT, context, [factory], + provides=None, for_=None) + + def test_for_is_None_adaptedBy_still_None(self): + context = DummyContext() + factory = DummyFactory() + factory.__component_adapts__ = None + self.assertRaises(TypeError, self._callFUT, context, [factory], + provides=None, for_=None) + + def test_for_is_None_adaptedBy_set(self): + from repoze.bfg.zcml import handler + context = DummyContext() + factory = DummyFactory() + factory.__component_adapts__ = (IDummy,) + self._callFUT(context, [factory], provides=IFactory, for_=None) + self.assertEqual(len(context.actions), 1) + regadapt = context.actions[0] + self.assertEqual(regadapt['discriminator'], + ('adapter', (IDummy,), IFactory, '')) + self.assertEqual(regadapt['callable'], + handler) + self.assertEqual(regadapt['args'], + ('registerAdapter', factory, (IDummy,), IFactory, + '', None)) + + def test_provides_missing(self): + context = DummyContext() + factory = DummyFactory() + self.assertRaises(TypeError, self._callFUT, context, [factory], + provides=None, for_=(IDummy,)) + + def test_provides_obtained_via_implementedBy(self): + from repoze.bfg.zcml import handler + context = DummyContext() + self._callFUT(context, [DummyFactory], for_=(IDummy,)) + regadapt = context.actions[0] + self.assertEqual(regadapt['discriminator'], + ('adapter', (IDummy,), IFactory, '')) + self.assertEqual(regadapt['callable'], + handler) + self.assertEqual(regadapt['args'], + ('registerAdapter', DummyFactory, (IDummy,), IFactory, + '', None)) + + def test_multiple_factories_multiple_for(self): + context = DummyContext() + factory = DummyFactory() + self.assertRaises(ValueError, self._callFUT, context, + [factory, factory], + provides=IFactory, + for_=(IDummy, IDummy)) + + def test_no_factories_multiple_for(self): + context = DummyContext() + factory = DummyFactory() + self.assertRaises(ValueError, self._callFUT, context, + factory=[], + provides=IFactory, + for_=(IDummy, IDummy)) + + def test_rolled_up_factories(self): + from repoze.bfg.zcml import handler + context = DummyContext() + factory = DummyFactory() + self._callFUT(context, + [factory, factory], + provides=IFactory, + for_=(IDummy,)) + regadapt = context.actions[0] + self.assertEqual(regadapt['discriminator'], + ('adapter', (IDummy,), IFactory, '')) + self.assertEqual(regadapt['callable'], + handler) + self.assertEqual(len(regadapt['args']), 6) + + +class TestSubscriberDirective(unittest.TestCase): + def setUp(self): + testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _callFUT(self, *arg, **kw): + from repoze.bfg.zcml import subscriber + return subscriber(*arg, **kw) + + def test_no_factory_no_handler(self): + context = DummyContext() + self.assertRaises(TypeError, + self._callFUT, context, for_=None, factory=None, + handler=None, + provides=None) + + def test_handler_with_provides(self): + context = DummyContext() + self.assertRaises(TypeError, + self._callFUT, context, for_=None, factory=None, + handler=1, provides=1) + + def test_handler_and_factory(self): + context = DummyContext() + self.assertRaises(TypeError, + self._callFUT, context, for_=None, factory=1, + handler=1, provides=None) + + def test_no_provides_with_factory(self): + context = DummyContext() + self.assertRaises(TypeError, + self._callFUT, context, for_=None, factory=1, + handler=None, provides=None) + + def test_adapted_by_as_for_is_None(self): + context = DummyContext() + factory = DummyFactory() + factory.__component_adapts__ = None + self.assertRaises(TypeError, self._callFUT, context, for_=None, + factory=factory, handler=None, provides=IFactory) + + def test_register_with_factory(self): + from repoze.bfg.zcml import handler + context = DummyContext() + factory = DummyFactory() + self._callFUT(context, for_=(IDummy,), + factory=factory, handler=None, provides=IFactory) + self.assertEqual(len(context.actions), 1) + subadapt = context.actions[0] + self.assertEqual(subadapt['discriminator'], None) + self.assertEqual(subadapt['callable'], handler) + self.assertEqual(subadapt['args'], + ('registerSubscriptionAdapter', factory, + (IDummy,), IFactory, u'', None) ) + + def test_register_with_handler(self): + from repoze.bfg.zcml import handler + context = DummyContext() + factory = DummyFactory() + self._callFUT(context, for_=(IDummy,), + factory=None, handler=factory) + self.assertEqual(len(context.actions), 1) + subadapt = context.actions[0] + self.assertEqual(subadapt['discriminator'], None) + self.assertEqual(subadapt['callable'], handler) + self.assertEqual(subadapt['args'], + ('registerHandler', factory, + (IDummy,), u'', None) ) + +class TestUtilityDirective(unittest.TestCase): + def setUp(self): + testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _callFUT(self, *arg, **kw): + from repoze.bfg.zcml import utility + return utility(*arg, **kw) + + def test_factory_and_component(self): + context = DummyContext() + self.assertRaises(TypeError, self._callFUT, + context, factory=1, component=1) + + def test_missing_provides(self): + context = DummyContext() + self.assertRaises(TypeError, self._callFUT, context, provides=None) + + def test_provides_from_factory_implements(self): + from repoze.bfg.zcml import handler + context = DummyContext() + self._callFUT(context, factory=DummyFactory) + self.assertEqual(len(context.actions), 1) + utility = context.actions[0] + self.assertEqual(utility['discriminator'], ('utility', IFactory, '')) + self.assertEqual(utility['callable'], handler) + self.assertEqual(utility['args'], + ('registerUtility', None, IFactory, '')) + self.assertEqual(utility['kw'], + {'factory': DummyFactory}) + + def test_provides_from_component_provides(self): + from repoze.bfg.zcml import handler + context = DummyContext() + component = DummyFactory() + self._callFUT(context, component=component) + self.assertEqual(len(context.actions), 1) + utility = context.actions[0] + self.assertEqual(utility['discriminator'], ('utility', IFactory, '')) + self.assertEqual(utility['callable'], handler) + self.assertEqual(utility['args'], + ('registerUtility', component, IFactory, '')) + self.assertEqual(utility['kw'], {}) + +class TestLoadZCML(unittest.TestCase): + def setUp(self): + testing.setUp() + + def tearDown(self): + testing.tearDown() + + def test_it(self): + from zope.configuration import xmlconfig + import repoze.bfg.includes + xmlconfig.file('configure.zcml', package=repoze.bfg.includes) + +class TestHandler(unittest.TestCase): + def setUp(self): + testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _callFUT(self, methodName, *arg, **kw): + from repoze.bfg.zcml import handler + return handler(methodName, *arg, **kw) + + def test_it(self): + def foo(): + pass + from zope.interface import Interface + class IWhatever(Interface): + pass + self._callFUT('registerUtility', foo, IWhatever) + +class TestRolledUpFactory(unittest.TestCase): + def _callFUT(self, *factories): + from repoze.bfg.zcml import _rolledUpFactory + return _rolledUpFactory(factories) + + def test_it(self): + def foo(ob): + return ob + factory = self._callFUT(foo, foo) + result = factory(True) + self.assertEqual(result, True) + +class IDummy(Interface): + pass + +class IFactory(Interface): + pass + +class DummyFactory(object): + implements(IFactory) + def __call__(self): + return 1 + class DummyModule: __path__ = "foo" __name__ = "dummy" @@ -649,7 +915,8 @@ class DummyContext: self.actions.append( {'discriminator':discriminator, 'callable':callable, - 'args':args} + 'args':args, + 'kw':kw} ) def path(self, path): @@ -664,10 +931,6 @@ class Dummy: class DummyRoute: pass -from zope.interface import Interface -class IDummy(Interface): - pass - class DummyRequest: subpath = () def __init__(self, environ=None): diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index b97456668..91252ff53 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -1,9 +1,15 @@ +from zope.component import adaptedBy + from zope.configuration.exceptions import ConfigurationError +from zope.configuration.fields import GlobalInterface from zope.configuration.fields import GlobalObject +from zope.configuration.fields import Tokens from zope.configuration.config import ConfigurationMachine from zope.configuration import xmlconfig from zope.interface import Interface +from zope.interface import implementedBy +from zope.interface import providedBy from zope.schema import Bool from zope.schema import Int @@ -524,3 +530,227 @@ def zcml_configure(name, package): file_configure = zcml_configure # backwards compat (>0.8.1) +def handler(methodName, *args, **kwargs): + registry = get_current_registry() + method = getattr(registry, methodName) + method(*args, **kwargs) + +def adapter(_context, factory, provides=None, for_=None, name=''): + + if for_ is None: + if len(factory) == 1: + for_ = adaptedBy(factory[0]) + + if for_ is None: + raise TypeError("No for attribute was provided and can't " + "determine what the factory adapts.") + + for_ = tuple(for_) + + if provides is None: + if len(factory) == 1: + p = list(implementedBy(factory[0])) + if len(p) == 1: + provides = p[0] + + if provides is None: + raise TypeError("Missing 'provides' attribute") + + # Generate a single factory from multiple factories: + factories = factory + if len(factories) == 1: + factory = factories[0] + elif len(factories) < 1: + raise ValueError("No factory specified") + elif len(factories) > 1 and len(for_) != 1: + raise ValueError("Can't use multiple factories and multiple for") + else: + factory = _rolledUpFactory(factories) + + _context.action( + discriminator = ('adapter', for_, provides, name), + callable = handler, + args = ('registerAdapter', + factory, for_, provides, name, _context.info), + ) + +class IAdapterDirective(Interface): + """ + Register an adapter + """ + + factory = Tokens( + title=u"Adapter factory/factories", + description=(u"A list of factories (usually just one) that create" + " the adapter instance."), + required=True, + value_type=GlobalObject() + ) + + provides = GlobalInterface( + title=u"Interface the component provides", + description=(u"This attribute specifies the interface the adapter" + " instance must provide."), + required=False, + ) + + for_ = Tokens( + title=u"Specifications to be adapted", + description=u"This should be a list of interfaces or classes", + required=False, + value_type=GlobalObject( + missing_value=object(), + ), + ) + + name = TextLine( + title=u"Name", + description=(u"Adapters can have names.\n\n" + "This attribute allows you to specify the name for" + " this adapter."), + required=False, + ) + +_handler = handler +def subscriber(_context, for_=None, factory=None, handler=None, provides=None): + if factory is None: + if handler is None: + raise TypeError("No factory or handler provided") + if provides is not None: + raise TypeError("Cannot use handler with provides") + factory = handler + else: + if handler is not None: + raise TypeError("Cannot use handler with factory") + if provides is None: + raise TypeError( + "You must specify a provided interface when registering " + "a factory") + + if for_ is None: + for_ = adaptedBy(factory) + if for_ is None: + raise TypeError("No for attribute was provided and can't " + "determine what the factory (or handler) adapts.") + + for_ = tuple(for_) + + if handler is not None: + _context.action( + discriminator = None, + callable = _handler, + args = ('registerHandler', + handler, for_, u'', _context.info), + ) + else: + _context.action( + discriminator = None, + callable = _handler, + args = ('registerSubscriptionAdapter', + factory, for_, provides, u'', _context.info), + ) + +class ISubscriberDirective(Interface): + """ + Register a subscriber + """ + + factory = GlobalObject( + title=u"Subscriber factory", + description=u"A factory used to create the subscriber instance.", + required=False, + ) + + handler = GlobalObject( + title=u"Handler", + description=u"A callable object that handles events.", + required=False, + ) + + provides = GlobalInterface( + title=u"Interface the component provides", + description=(u"This attribute specifies the interface the adapter" + " instance must provide."), + required=False, + ) + + for_ = Tokens( + title=u"Interfaces or classes that this subscriber depends on", + description=u"This should be a list of interfaces or classes", + required=False, + value_type=GlobalObject( + missing_value = object(), + ), + ) + +def utility(_context, provides=None, component=None, factory=None, name=''): + if factory and component: + raise TypeError("Can't specify factory and component.") + + if provides is None: + if factory: + provides = list(implementedBy(factory)) + else: + provides = list(providedBy(component)) + if len(provides) == 1: + provides = provides[0] + else: + raise TypeError("Missing 'provides' attribute") + + if factory: + kw = dict(factory=factory) + else: + # older zope.component registries don't accept factory as a kwarg, + # so if we don't need it, we don't pass it + kw = {} + + _context.action( + discriminator = ('utility', provides, name), + callable = handler, + args = ('registerUtility', component, provides, name), + kw = kw, + ) + +class IUtilityDirective(Interface): + """Register a utility.""" + + component = GlobalObject( + title=u"Component to use", + description=(u"Python name of the implementation object. This" + " must identify an object in a module using the" + " full dotted name. If specified, the" + " ``factory`` field must be left blank."), + required=False, + ) + + factory = GlobalObject( + title=u"Factory", + description=(u"Python name of a factory which can create the" + " implementation object. This must identify an" + " object in a module using the full dotted name." + " If specified, the ``component`` field must" + " be left blank."), + required=False, + ) + + provides = GlobalInterface( + title=u"Provided interface", + description=u"Interface provided by the utility.", + required=False, + ) + + name = TextLine( + title=u"Name", + description=(u"Name of the registration. This is used by" + " application code when locating a utility."), + required=False, + ) + +def _rolledUpFactory(factories): + def factory(ob): + for f in factories: + ob = f(ob) + return ob + # Store the original factory for documentation + factory.factory = factories[0] + return factory @@ -36,8 +36,8 @@ install_requires=[ 'WebOb', 'zope.interface >= 3.5.1', # 3.5.0 comment: "allow to bootstrap on jython" 'zope.component >= 3.6.0', # independent of zope.hookable + 'zope.configuration', 'zope.deprecation', - 'repoze.zcml', 'repoze.lru', ] |
