Skip to content

Commit

Permalink
Implemented ability to specify adapter and utility names in Python. Use
Browse files Browse the repository at this point in the history
the ``@zope.component.named(name)`` decorator to specify the name.

All tox environments pass and coverage is at 100%.
  • Loading branch information
strichter committed Feb 6, 2014
1 parent 2429314 commit 5a56258
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 22 deletions.
5 changes: 4 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
CHANGES
*******

4.1.1 (unreleased)
4.2.0 (unreleased)
==================

- Updated ``boostrap.py`` to version 2.2.

- 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)
==================
Expand Down
43 changes: 36 additions & 7 deletions docs/zcml.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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::

Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -105,7 +105,7 @@ def read(*rnames):
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,
Expand Down
1 change: 1 addition & 0 deletions src/zope/component/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/zope/component/_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"""
import sys

from zope.component._compat import CLASS_TYPES
from zope.component._compat import CLASS_TYPES, _BLANK

class adapter(object):

Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion src/zope/component/testfiles/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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')
Expand All @@ -40,7 +41,6 @@ class Content(object):
@adapter(IContent)
@implementer(IApp)
class Comp(object):
pass

def __init__(self, *args):
# Ignore arguments passed to constructor
Expand All @@ -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()
55 changes: 46 additions & 9 deletions src/zope/component/tests/test_zcml.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -92,7 +92,7 @@ class IFoo(Interface):
_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
Expand All @@ -116,7 +116,27 @@ def __init__(self, context):
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
Expand All @@ -126,7 +146,7 @@ def __init__(self, context):
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
Expand Down Expand Up @@ -164,7 +184,7 @@ class Bar(object):
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
Expand Down Expand Up @@ -194,7 +214,7 @@ class Foo(object):
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
Expand Down Expand Up @@ -223,7 +243,7 @@ class Foo(object):
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
Expand Down Expand Up @@ -251,7 +271,7 @@ class Foo(object):
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
Expand Down Expand Up @@ -610,7 +630,7 @@ class Foo(object):
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
Expand Down Expand Up @@ -638,6 +658,23 @@ class IFoo(Interface):
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
Expand Down
12 changes: 11 additions & 1 deletion src/zope/component/zcml.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 5a56258

Please sign in to comment.