summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephan Richter <stephan.richter@gmail.com>2014-02-05 22:18:35 -0500
committerStephan Richter <stephan.richter@gmail.com>2014-02-05 22:20:15 -0500
commit5a5625825c7cd05f02b5a19f70408cc66f6ef153 (patch)
treea6cc6b2b12515d2b634e6fb3f6c9159f9491c6e3
parent2429314dadbab3261f82fdec372dbf99e77a24db (diff)
downloadzope-component-5a5625825c7cd05f02b5a19f70408cc66f6ef153.tar.gz
Implemented ability to specify adapter and utility names in Python. Use
the ``@zope.component.named(name)`` decorator to specify the name. All tox environments pass and coverage is at 100%.
-rw-r--r--CHANGES.rst5
-rw-r--r--docs/zcml.rst43
-rw-r--r--setup.py4
-rw-r--r--src/zope/component/__init__.py1
-rw-r--r--src/zope/component/_declaration.py5
-rw-r--r--src/zope/component/testfiles/components.py10
-rw-r--r--src/zope/component/tests/test_zcml.py55
-rw-r--r--src/zope/component/zcml.py12
8 files changed, 113 insertions, 22 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index ac7e022..6cdba47 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,7 +1,7 @@
CHANGES
*******
-4.1.1 (unreleased)
+4.2.0 (unreleased)
==================
- Updated ``boostrap.py`` to version 2.2.
@@ -9,6 +9,9 @@ CHANGES
- Reset the cached ``adapter_hooks`` at
``zope.testing.cleanup.cleanUp`` time (LP1100501).
+- Implemented ability to specify adapter and utility names in Python. Use
+ the ``@zope.component.named(name)`` decorator to specify the name.
+
4.1.0 (2013-02-28)
==================
diff --git a/docs/zcml.rst b/docs/zcml.rst
index 330d353..cb94284 100644
--- a/docs/zcml.rst
+++ b/docs/zcml.rst
@@ -69,9 +69,9 @@ the <adapter /> directive:
.. doctest::
+ >>> import zope.component
>>> from zope.component.tests.examples import clearZCML
>>> clearZCML()
- >>> import zope.component
>>> zope.component.queryAdapter(Content(), IApp, 'test') is None
True
@@ -143,13 +143,13 @@ Of course, if no factory is provided at all, we will get an error:
ValueError: No factory specified
-Declaring ``for`` and ``provides`` in Python
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Declaring ``for``, ``provides`` and ``name`` in Python
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The <adapter /> directive can figure out from the in-line Python
-declaration (using ``zope.component.adapts()`` or
-``zope.component.adapter()`` as well as ``zope.interface.implements``)
-what the adapter should be registered for and what it provides:
+The <adapter /> directive can figure out from the in-line Python declaration
+(using ``zope.component.adapts()`` or ``zope.component.adapter()``,
+``zope.interface.implements`` as well as ``zope.component.named``) what the
+adapter should be registered for and what it provides:
.. doctest::
@@ -193,6 +193,14 @@ ZCML can't figure out what it should provide either:
ZopeXMLConfigurationError: File "<string>", line 4.2-7.8
TypeError: Missing 'provides' attribute
+Let's now register an adapter that has a name specified in Python:
+
+ >>> runSnippet('''
+ ... <adapter factory="zope.component.testfiles.components.Comp4" />''')
+
+ >>> zope.component.getAdapter(Content(), IApp, 'app').__class__
+ <class 'zope.component.testfiles.components.Comp4'>
+
A not so common edge case is registering adapters directly for
classes, not for interfaces. For example:
@@ -1037,6 +1045,27 @@ We can repeat the same drill for utility factories:
ZopeXMLConfigurationError: File "<string>", line 4.2-4.59
TypeError: Missing 'provides' attribute
+Declaring ``name`` in Python
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Let's now register a utility that has a name specified in Python:
+
+ >>> runSnippet('''
+ ... <utility component="zope.component.testfiles.components.comp4" />''')
+
+ >>> from zope.component.testfiles.components import comp4
+ >>> zope.component.getUtility(IApp, name='app') is comp4
+ True
+
+ >>> runSnippet('''
+ ... <utility factory="zope.component.testfiles.components.Comp4" />''')
+
+ >>> zope.component.getUtility(IApp, name='app') is comp4
+ False
+ >>> zope.component.getUtility(IApp, name='app').__class__
+ <class 'zope.component.testfiles.components.Comp4'>
+
+
Protected utilities
~~~~~~~~~~~~~~~~~~~
diff --git a/setup.py b/setup.py
index 000dcee..a7a8b6a 100644
--- a/setup.py
+++ b/setup.py
@@ -70,7 +70,7 @@ def read(*rnames):
setup(
name='zope.component',
- version='4.1.1.dev0',
+ version='4.2.0.dev0',
url='http://pypi.python.org/pypi/zope.component',
license='ZPL 2.1',
description='Zope Component Architecture',
@@ -105,7 +105,7 @@ setup(
tests_require = TESTS_REQUIRE,
test_suite='__main__.alltests',
install_requires=['setuptools',
- 'zope.interface>=3.8.0',
+ 'zope.interface>=4.1.0',
'zope.event',
],
include_package_data = True,
diff --git a/src/zope/component/__init__.py b/src/zope/component/__init__.py
index 28e0808..2b13302 100644
--- a/src/zope/component/__init__.py
+++ b/src/zope/component/__init__.py
@@ -16,6 +16,7 @@
from zope.interface import Interface
from zope.interface import implementedBy
from zope.interface import moduleProvides
+from zope.interface import named
from zope.interface import providedBy
from zope.component.interfaces import ComponentLookupError
diff --git a/src/zope/component/_declaration.py b/src/zope/component/_declaration.py
index b434d75..65a62d3 100644
--- a/src/zope/component/_declaration.py
+++ b/src/zope/component/_declaration.py
@@ -15,7 +15,7 @@
"""
import sys
-from zope.component._compat import CLASS_TYPES
+from zope.component._compat import CLASS_TYPES, _BLANK
class adapter(object):
@@ -46,6 +46,9 @@ def adapts(*interfaces):
def adaptedBy(ob):
return getattr(ob, '__component_adapts__', None)
+def getName(ob):
+ return getattr(ob, '__component_name__', _BLANK)
+
class _adapts_descr(object):
def __init__(self, interfaces):
self.interfaces = interfaces
diff --git a/src/zope/component/testfiles/components.py b/src/zope/component/testfiles/components.py
index efbd2fb..d9dd5c2 100644
--- a/src/zope/component/testfiles/components.py
+++ b/src/zope/component/testfiles/components.py
@@ -17,6 +17,7 @@ from zope.interface import Interface
from zope.interface import Attribute
from zope.interface import implementer
from zope.component import adapter
+from zope.component import named
class IAppb(Interface):
a = Attribute('test attribute')
@@ -40,7 +41,6 @@ class Content(object):
@adapter(IContent)
@implementer(IApp)
class Comp(object):
- pass
def __init__(self, *args):
# Ignore arguments passed to constructor
@@ -57,6 +57,14 @@ class Comp3(object):
def __init__(self, context):
self.context = context
+@adapter(IContent)
+@implementer(IApp)
+@named('app')
+class Comp4(object):
+ def __init__(self, context=None):
+ self.context = context
+
comp = Comp()
+comp4 = Comp4()
content = Content()
diff --git a/src/zope/component/tests/test_zcml.py b/src/zope/component/tests/test_zcml.py
index f64e822..22cd56a 100644
--- a/src/zope/component/tests/test_zcml.py
+++ b/src/zope/component/tests/test_zcml.py
@@ -83,7 +83,7 @@ class Test_adapter(unittest.TestCase):
def _callFUT(self, *args, **kw):
from zope.component.zcml import adapter
return adapter(*args, **kw)
-
+
def test_empty_factory(self):
from zope.interface import Interface
from zope.component.zcml import ComponentConfigurationError
@@ -92,7 +92,7 @@ class Test_adapter(unittest.TestCase):
_cfg_ctx = _makeConfigContext()
self.assertRaises(ComponentConfigurationError,
self._callFUT, _cfg_ctx, [], [Interface], IFoo)
-
+
def test_multiple_factory_multiple_for_(self):
from zope.interface import Interface
from zope.component.zcml import ComponentConfigurationError
@@ -116,7 +116,27 @@ class Test_adapter(unittest.TestCase):
self.context = context
_cfg_ctx = _makeConfigContext()
self.assertRaises(TypeError, self._callFUT, _cfg_ctx, [_Factory])
-
+
+ def test_no_name(self):
+ from zope.interface import Interface
+ class IFoo(Interface):
+ pass
+ class IBar(Interface):
+ pass
+ from zope.component import adapter, named
+ from zope.interface import implementer
+ @adapter(IFoo)
+ @implementer(IBar)
+ @named('bar')
+ class _Factory(object):
+ def __init__(self, context):
+ self.context = context
+ _cfg_ctx = _makeConfigContext()
+ self._callFUT(_cfg_ctx, [_Factory])
+ # Register the adapter
+ action =_cfg_ctx._actions[0][1]
+ self.assertEqual(action['args'][4], 'bar')
+
def test_no_for__factory_adapts_no_provides_factory_not_implements(self):
from zope.interface import Interface
from zope.component._declaration import adapter
@@ -126,7 +146,7 @@ class Test_adapter(unittest.TestCase):
self.context = context
_cfg_ctx = _makeConfigContext()
self.assertRaises(TypeError, self._callFUT, _cfg_ctx, [_Factory])
-
+
def test_multiple_factory_single_for__w_name(self):
from zope.interface import Interface
from zope.component.interface import provideInterface
@@ -164,7 +184,7 @@ class Test_adapter(unittest.TestCase):
self.assertEqual(action['callable'], provideInterface)
self.assertEqual(action['discriminator'], None)
self.assertEqual(action['args'], ('', Interface))
-
+
@skipIfNoSecurity
def test_single_factory_single_for_w_permission(self):
from zope.interface import Interface
@@ -194,7 +214,7 @@ class Test_adapter(unittest.TestCase):
self.assertEqual(action['args'][3], IFoo)
self.assertEqual(action['args'][4], '')
self.assertEqual(action['args'][5], 'TESTING')
-
+
@skipIfNoSecurity
def test_single_factory_single_for_w_locate_no_permission(self):
from zope.interface import Interface
@@ -223,7 +243,7 @@ class Test_adapter(unittest.TestCase):
self.assertEqual(action['args'][3], IFoo)
self.assertEqual(action['args'][4], '')
self.assertEqual(action['args'][5], 'TESTING')
-
+
@skipIfNoSecurity
def test_single_factory_single_for_w_trusted_no_permission(self):
from zope.interface import Interface
@@ -251,7 +271,7 @@ class Test_adapter(unittest.TestCase):
self.assertEqual(action['args'][3], IFoo)
self.assertEqual(action['args'][4], '')
self.assertEqual(action['args'][5], 'TESTING')
-
+
def test_no_for__no_provides_factory_adapts_factory_implements(self):
from zope.interface import Interface
from zope.interface import implementer
@@ -610,7 +630,7 @@ class Test_utility(unittest.TestCase):
self.assertEqual(action['discriminator'], None)
self.assertEqual(action['args'], ('', IFoo))
- def test_w_component_w_provides_w_naem(self):
+ def test_w_component_w_provides_w_name(self):
from zope.interface import Interface
from zope.component.interface import provideInterface
from zope.component.zcml import handler
@@ -638,6 +658,23 @@ class Test_utility(unittest.TestCase):
self.assertEqual(action['discriminator'], None)
self.assertEqual(action['args'], ('', IFoo))
+ def test_w_component_wo_provides_wo_name(self):
+ from zope.interface import Interface, implementer, named
+ from zope.component.zcml import handler
+ class IFoo(Interface):
+ pass
+ @implementer(IFoo)
+ @named('foo')
+ class Foo(object):
+ pass
+ foo = Foo()
+ _cfg_ctx = _makeConfigContext()
+ self._callFUT(_cfg_ctx, component=foo)
+ action =_cfg_ctx._actions[0][1]
+ self.assertEqual(action['args'][1], foo)
+ self.assertEqual(action['args'][2], IFoo)
+ self.assertEqual(action['args'][3], 'foo')
+
def test_w_component_wo_provides_component_provides(self):
from zope.interface import Interface
from zope.interface import directlyProvides
diff --git a/src/zope/component/zcml.py b/src/zope/component/zcml.py
index d760ce1..37a1f32 100644
--- a/src/zope/component/zcml.py
+++ b/src/zope/component/zcml.py
@@ -26,7 +26,7 @@ from zope.interface import providedBy
from zope.schema import TextLine
from zope.component._api import getSiteManager
-from zope.component._declaration import adaptedBy
+from zope.component._declaration import adaptedBy, getName
from zope.component.interface import provideInterface
from zope.component._compat import _BLANK
@@ -182,6 +182,10 @@ def adapter(_context, factory, provides=None, for_=None, permission=None,
if provides is None:
raise TypeError("Missing 'provides' attribute")
+ if name == '':
+ if len(factory) == 1:
+ name = getName(factory[0])
+
# Generate a single factory from multiple factories:
factories = factory
if len(factories) == 1:
@@ -380,6 +384,12 @@ def utility(_context, provides=None, component=None, factory=None,
else:
raise TypeError("Missing 'provides' attribute")
+ if name == '':
+ if factory:
+ name = getName(factory)
+ else:
+ name = getName(component)
+
if permission is not None:
component = proxify(component, provides=provides, permission=permission)