diff options
| author | Tres Seaver <tseaver@palladion.com> | 2012-03-26 20:52:07 +0000 |
|---|---|---|
| committer | Tres Seaver <tseaver@palladion.com> | 2012-03-26 20:52:07 +0000 |
| commit | 2fd652d0d095b79e2e6365b00c739f589bf631a5 (patch) | |
| tree | 1e00682f8755ed72f07981ebd22282d53b9cbc01 /src/zope/interface/declarations.py | |
| parent | 9a67b97df396ec8e9414e81c2660e496bf2f2ffa (diff) | |
| parent | 1722b56edbca94adaa2e461215e940d785b07d3c (diff) | |
| download | zope-interface-2fd652d0d095b79e2e6365b00c739f589bf631a5.tar.gz | |
New SVN branch for merging from LP.
Diffstat (limited to 'src/zope/interface/declarations.py')
| -rw-r--r-- | src/zope/interface/declarations.py | 1395 |
1 files changed, 1395 insertions, 0 deletions
diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py new file mode 100644 index 0000000..bafe399 --- /dev/null +++ b/src/zope/interface/declarations.py @@ -0,0 +1,1395 @@ +############################################################################## +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +############################################################################## +"""Implementation of interface declarations + +There are three flavors of declarations: + + - Declarations are used to simply name declared interfaces. + + - ImplementsDeclarations are used to express the interfaces that a + class implements (that instances of the class provides). + + Implements specifications support inheriting interfaces. + + - ProvidesDeclarations are used to express interfaces directly + provided by objects. + +""" +__docformat__ = 'restructuredtext' + +import sys +import weakref +from zope.interface.interface import InterfaceClass, Specification +from zope.interface.interface import SpecificationBase +from types import ModuleType, MethodType, FunctionType +from zope.interface.advice import addClassAdvisor + +# Registry of class-implementation specifications +BuiltinImplementationSpecifications = {} + +class Declaration(Specification): + """Interface declarations""" + + def __init__(self, *interfaces): + Specification.__init__(self, _normalizeargs(interfaces)) + + def changed(self, originally_changed): + Specification.changed(self, originally_changed) + try: + del self._v_attrs + except AttributeError: + pass + + def __contains__(self, interface): + """Test whether an interface is in the specification + + for example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration(I2, I3) + >>> spec = Declaration(I4, spec) + >>> int(I1 in spec) + 0 + >>> int(I2 in spec) + 1 + >>> int(I3 in spec) + 1 + >>> int(I4 in spec) + 1 + """ + return self.extends(interface) and interface in self.interfaces() + + def __iter__(self): + """Return an iterator for the interfaces in the specification + + for example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration(I2, I3) + >>> spec = Declaration(I4, spec) + >>> i = iter(spec) + >>> [x.getName() for x in i] + ['I4', 'I2', 'I3'] + >>> list(i) + [] + """ + return self.interfaces() + + def flattened(self): + """Return an iterator of all included and extended interfaces + + for example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration(I2, I3) + >>> spec = Declaration(I4, spec) + >>> i = spec.flattened() + >>> [x.getName() for x in i] + ['I4', 'I2', 'I1', 'I3', 'Interface'] + >>> list(i) + [] + + """ + return iter(self.__iro__) + + def __sub__(self, other): + """Remove interfaces from a specification + + Examples: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration() + >>> [iface.getName() for iface in spec] + [] + >>> spec -= I1 + >>> [iface.getName() for iface in spec] + [] + >>> spec -= Declaration(I1, I2) + >>> [iface.getName() for iface in spec] + [] + >>> spec = Declaration(I2, I4) + >>> [iface.getName() for iface in spec] + ['I2', 'I4'] + >>> [iface.getName() for iface in spec - I4] + ['I2'] + >>> [iface.getName() for iface in spec - I1] + ['I4'] + >>> [iface.getName() for iface + ... in spec - Declaration(I3, I4)] + ['I2'] + + """ + + return Declaration( + *[i for i in self.interfaces() + if not [j for j in other.interfaces() + if i.extends(j, 0)] + ] + ) + + def __add__(self, other): + """Add two specifications or a specification and an interface + + Examples: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration() + >>> [iface.getName() for iface in spec] + [] + >>> [iface.getName() for iface in spec+I1] + ['I1'] + >>> [iface.getName() for iface in I1+spec] + ['I1'] + >>> spec2 = spec + >>> spec += I1 + >>> [iface.getName() for iface in spec] + ['I1'] + >>> [iface.getName() for iface in spec2] + [] + >>> spec2 += Declaration(I3, I4) + >>> [iface.getName() for iface in spec2] + ['I3', 'I4'] + >>> [iface.getName() for iface in spec+spec2] + ['I1', 'I3', 'I4'] + >>> [iface.getName() for iface in spec2+spec] + ['I3', 'I4', 'I1'] + + """ + + seen = {} + result = [] + for i in self.interfaces(): + if i not in seen: + seen[i] = 1 + result.append(i) + for i in other.interfaces(): + if i not in seen: + seen[i] = 1 + result.append(i) + + return Declaration(*result) + + __radd__ = __add__ + + +############################################################################## +# +# Implementation specifications +# +# These specify interfaces implemented by instances of classes + +class Implements(Declaration): + + # class whose specification should be used as additional base + inherit = None + + # interfaces actually declared for a class + declared = () + + __name__ = '?' + + def __repr__(self): + return '<implementedBy %s>' % (self.__name__) + + def __reduce__(self): + return implementedBy, (self.inherit, ) + +def implementedByFallback(cls): + """Return the interfaces implemented for a class' instances + + The value returned is an IDeclaration. + + for example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> class C1(object): + ... implements(I2) + >>> class C2(C1): + ... implements(I3) + >>> [i.getName() for i in implementedBy(C2)] + ['I3', 'I2'] + + Really, any object should be able to receive a successful answer, even + an instance: + + >>> class Callable(object): + ... def __call__(self): + ... return self + + >>> implementedBy(Callable()) + <implementedBy zope.interface.declarations.?> + + Note that the name of the spec ends with a '?', because the `Callable` + instance does not have a `__name__` attribute. + """ + # This also manages storage of implementation specifications + + try: + spec = cls.__dict__.get('__implemented__') + except AttributeError: + + # we can't get the class dict. This is probably due to a + # security proxy. If this is the case, then probably no + # descriptor was installed for the class. + + # We don't want to depend directly on zope.security in + # zope.interface, but we'll try to make reasonable + # accommodations in an indirect way. + + # We'll check to see if there's an implements: + + spec = getattr(cls, '__implemented__', None) + if spec is None: + # There's no spec stred in the class. Maybe its a builtin: + spec = BuiltinImplementationSpecifications.get(cls) + if spec is not None: + return spec + return _empty + + if spec.__class__ == Implements: + # we defaulted to _empty or there was a spec. Good enough. + # Return it. + return spec + + # TODO: need old style __implements__ compatibility? + # Hm, there's an __implemented__, but it's not a spec. Must be + # an old-style declaration. Just compute a spec for it + return Declaration(*_normalizeargs((spec, ))) + + if isinstance(spec, Implements): + return spec + + if spec is None: + spec = BuiltinImplementationSpecifications.get(cls) + if spec is not None: + return spec + + # TODO: need old style __implements__ compatibility? + if spec is not None: + # old-style __implemented__ = foo declaration + spec = (spec, ) # tuplefy, as it might be just an int + spec = Implements(*_normalizeargs(spec)) + spec.inherit = None # old-style implies no inherit + del cls.__implemented__ # get rid of the old-style declaration + else: + try: + bases = cls.__bases__ + except AttributeError: + if not callable(cls): + raise TypeError("ImplementedBy called for non-factory", cls) + bases = () + + spec = Implements(*[implementedBy(c) for c in bases]) + spec.inherit = cls + + spec.__name__ = (getattr(cls, '__module__', '?') or '?') + \ + '.' + (getattr(cls, '__name__', '?') or '?') + + try: + cls.__implemented__ = spec + if not hasattr(cls, '__providedBy__'): + cls.__providedBy__ = objectSpecificationDescriptor + + if (isinstance(cls, DescriptorAwareMetaClasses) + and + '__provides__' not in cls.__dict__): + # Make sure we get a __provides__ descriptor + cls.__provides__ = ClassProvides( + cls, + getattr(cls, '__class__', type(cls)), + ) + + except TypeError: + if not isinstance(cls, type): + raise TypeError("ImplementedBy called for non-type", cls) + BuiltinImplementationSpecifications[cls] = spec + + return spec + +implementedBy = implementedByFallback + +def classImplementsOnly(cls, *interfaces): + """Declare the only interfaces implemented by instances of a class + + The arguments after the class are one or more interfaces or interface + specifications (``IDeclaration`` objects). + + The interfaces given (including the interfaces in the specifications) + replace any previous declarations. + + Consider the following example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(Interface): pass + ... + >>> class A(object): + ... implements(I3) + >>> class B(object): + ... implements(I4) + >>> class C(A, B): + ... pass + >>> classImplementsOnly(C, I1, I2) + >>> [i.getName() for i in implementedBy(C)] + ['I1', 'I2'] + + Instances of ``C`` provide only ``I1``, ``I2``, and regardless of + whatever interfaces instances of ``A`` and ``B`` implement. + """ + spec = implementedBy(cls) + spec.declared = () + spec.inherit = None + classImplements(cls, *interfaces) + +def classImplements(cls, *interfaces): + """Declare additional interfaces implemented for instances of a class + + The arguments after the class are one or more interfaces or + interface specifications (``IDeclaration`` objects). + + The interfaces given (including the interfaces in the specifications) + are added to any interfaces previously declared. + + Consider the following example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(Interface): pass + ... + >>> class I5(Interface): pass + ... + >>> class A(object): + ... implements(I3) + >>> class B(object): + ... implements(I4) + >>> class C(A, B): + ... pass + >>> classImplements(C, I1, I2) + >>> [i.getName() for i in implementedBy(C)] + ['I1', 'I2', 'I3', 'I4'] + >>> classImplements(C, I5) + >>> [i.getName() for i in implementedBy(C)] + ['I1', 'I2', 'I5', 'I3', 'I4'] + + Instances of ``C`` provide ``I1``, ``I2``, ``I5``, and whatever + interfaces instances of ``A`` and ``B`` provide. + """ + + spec = implementedBy(cls) + spec.declared += tuple(_normalizeargs(interfaces)) + + # compute the bases + bases = [] + seen = {} + for b in spec.declared: + if b not in seen: + seen[b] = 1 + bases.append(b) + + if spec.inherit is not None: + + for c in spec.inherit.__bases__: + b = implementedBy(c) + if b not in seen: + seen[b] = 1 + bases.append(b) + + spec.__bases__ = tuple(bases) + +def _implements_advice(cls): + interfaces, classImplements = cls.__dict__['__implements_advice_data__'] + del cls.__implements_advice_data__ + classImplements(cls, *interfaces) + return cls + + +class implementer: + + def __init__(self, *interfaces): + self.interfaces = interfaces + + def __call__(self, ob): + if isinstance(ob, DescriptorAwareMetaClasses): + classImplements(ob, *self.interfaces) + return ob + + spec = Implements(*self.interfaces) + try: + ob.__implemented__ = spec + except AttributeError: + raise TypeError("Can't declare implements", ob) + return ob + +class implementer_only: + + def __init__(self, *interfaces): + self.interfaces = interfaces + + def __call__(self, ob): + if isinstance(ob, (FunctionType, MethodType)): + # XXX Does this decorator make sense for anything but classes? + # I don't think so. There can be no inheritance of interfaces + # on a method pr function.... + raise ValueError('The implementer_only decorator is not ' + 'supported for methods or functions.') + else: + # Assume it's a class: + classImplementsOnly(ob, *self.interfaces) + return ob + +def _implements(name, interfaces, classImplements): + frame = sys._getframe(2) + locals = frame.f_locals + + # Try to make sure we were called from a class def. In 2.2.0 we can't + # check for __module__ since it doesn't seem to be added to the locals + # until later on. + if (locals is frame.f_globals) or ( + ('__module__' not in locals) and sys.version_info[:3] > (2, 2, 0)): + raise TypeError(name+" can be used only from a class definition.") + + if '__implements_advice_data__' in locals: + raise TypeError(name+" can be used only once in a class definition.") + + locals['__implements_advice_data__'] = interfaces, classImplements + addClassAdvisor(_implements_advice, depth=3) + +def implements(*interfaces): + """Declare interfaces implemented by instances of a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface + specifications (IDeclaration objects). + + The interfaces given (including the interfaces in the + specifications) are added to any interfaces previously + declared. + + Previous declarations include declarations for base classes + unless implementsOnly was used. + + This function is provided for convenience. It provides a more + convenient way to call classImplements. For example:: + + implements(I1) + + is equivalent to calling:: + + classImplements(C, I1) + + after the class has been created. + + Consider the following example:: + + + >>> from zope.interface import Interface + >>> class IA1(Interface): pass + ... + >>> class IA2(Interface): pass + ... + >>> class IB(Interface): pass + ... + >>> class IC(Interface): pass + ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) + + >>> class C(A, B): + ... implements(IC) + + >>> ob = C() + >>> int(IA1 in providedBy(ob)) + 1 + >>> int(IA2 in providedBy(ob)) + 1 + >>> int(IB in providedBy(ob)) + 1 + >>> int(IC in providedBy(ob)) + 1 + + Instances of ``C`` implement ``I1``, ``I2``, and whatever interfaces + instances of ``A`` and ``B`` implement. + + """ + _implements("implements", interfaces, classImplements) + +def implementsOnly(*interfaces): + """Declare the only interfaces implemented by instances of a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface + specifications (IDeclaration objects). + + Previous declarations including declarations for base classes + are overridden. + + This function is provided for convenience. It provides a more + convenient way to call classImplementsOnly. For example:: + + implementsOnly(I1) + + is equivalent to calling:: + + classImplementsOnly(I1) + + after the class has been created. + + Consider the following example:: + + >>> from zope.interface import Interface + >>> class IA1(Interface): pass + ... + >>> class IA2(Interface): pass + ... + >>> class IB(Interface): pass + ... + >>> class IC(Interface): pass + ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) + + >>> class C(A, B): + ... implementsOnly(IC) + + >>> ob = C() + >>> int(IA1 in providedBy(ob)) + 0 + >>> int(IA2 in providedBy(ob)) + 0 + >>> int(IB in providedBy(ob)) + 0 + >>> int(IC in providedBy(ob)) + 1 + + + Instances of ``C`` implement ``IC``, regardless of what + instances of ``A`` and ``B`` implement. + + """ + _implements("implementsOnly", interfaces, classImplementsOnly) + +############################################################################## +# +# Instance declarations + +class Provides(Declaration): # Really named ProvidesClass + """Implement __provides__, the instance-specific specification + + When an object is pickled, we pickle the interfaces that it implements. + """ + + def __init__(self, cls, *interfaces): + self.__args = (cls, ) + interfaces + self._cls = cls + Declaration.__init__(self, *(interfaces + (implementedBy(cls), ))) + + def __reduce__(self): + return Provides, self.__args + + __module__ = 'zope.interface' + + def __get__(self, inst, cls): + """Make sure that a class __provides__ doesn't leak to an instance + + For example: + + >>> from zope.interface import Interface + >>> class IFooFactory(Interface): pass + ... + + >>> class C(object): + ... pass + + >>> C.__provides__ = ProvidesClass(C, IFooFactory) + >>> [i.getName() for i in C.__provides__] + ['IFooFactory'] + >>> getattr(C(), '__provides__', 0) + 0 + + """ + if inst is None and cls is self._cls: + # We were accessed through a class, so we are the class' + # provides spec. Just return this object, but only if we are + # being called on the same class that we were defined for: + return self + + raise AttributeError('__provides__') + +ProvidesClass = Provides + +# Registry of instance declarations +# This is a memory optimization to allow objects to share specifications. +InstanceDeclarations = weakref.WeakValueDictionary() + +def Provides(*interfaces): + """Cache instance declarations + + Instance declarations are shared among instances that have the same + declaration. The declarations are cached in a weak value dictionary. + + (Note that, in the examples below, we are going to make assertions about + the size of the weakvalue dictionary. For the assertions to be + meaningful, we need to force garbage collection to make sure garbage + objects are, indeed, removed from the system. Depending on how Python + is run, we may need to make multiple calls to be sure. We provide a + collect function to help with this: + + >>> import gc + >>> def collect(): + ... for i in range(4): + ... gc.collect() + + ) + + >>> collect() + >>> before = len(InstanceDeclarations) + + >>> class C(object): + ... pass + + >>> from zope.interface import Interface + >>> class I(Interface): + ... pass + + >>> c1 = C() + >>> c2 = C() + + >>> len(InstanceDeclarations) == before + 1 + + >>> directlyProvides(c1, I) + >>> len(InstanceDeclarations) == before + 1 + 1 + + >>> directlyProvides(c2, I) + >>> len(InstanceDeclarations) == before + 1 + 1 + + >>> del c1 + >>> collect() + >>> len(InstanceDeclarations) == before + 1 + 1 + + >>> del c2 + >>> collect() + >>> len(InstanceDeclarations) == before + 1 + """ + + spec = InstanceDeclarations.get(interfaces) + if spec is None: + spec = ProvidesClass(*interfaces) + InstanceDeclarations[interfaces] = spec + + return spec +Provides.__safe_for_unpickling__ = True + +try: + from types import ClassType + DescriptorAwareMetaClasses = ClassType, type +except ImportError: # Python 3 + DescriptorAwareMetaClasses = (type,) + +def directlyProvides(object, *interfaces): + """Declare interfaces declared directly for an object + + The arguments after the object are one or more interfaces or interface + specifications (``IDeclaration`` objects). + + The interfaces given (including the interfaces in the specifications) + replace interfaces previously declared for the object. + + Consider the following example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + >>> class IA1(Interface): pass + ... + >>> class IA2(Interface): pass + ... + >>> class IB(Interface): pass + ... + >>> class IC(Interface): pass + ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) + + >>> class C(A, B): + ... implements(IC) + + >>> ob = C() + >>> directlyProvides(ob, I1, I2) + >>> int(I1 in providedBy(ob)) + 1 + >>> int(I2 in providedBy(ob)) + 1 + >>> int(IA1 in providedBy(ob)) + 1 + >>> int(IA2 in providedBy(ob)) + 1 + >>> int(IB in providedBy(ob)) + 1 + >>> int(IC in providedBy(ob)) + 1 + + The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces + instances have been declared for instances of ``C``. + + To remove directly provided interfaces, use ``directlyProvidedBy`` and + subtract the unwanted interfaces. For example: + + >>> directlyProvides(ob, directlyProvidedBy(ob)-I2) + >>> int(I1 in providedBy(ob)) + 1 + >>> int(I2 in providedBy(ob)) + 0 + + removes I2 from the interfaces directly provided by ``ob``. The object, + ``ob`` no longer directly provides ``I2``, although it might still + provide ``I2`` if it's class implements ``I2``. + + To add directly provided interfaces, use ``directlyProvidedBy`` and + include additional interfaces. For example: + + >>> int(I2 in providedBy(ob)) + 0 + >>> directlyProvides(ob, directlyProvidedBy(ob), I2) + + adds ``I2`` to the interfaces directly provided by ob:: + + >>> int(I2 in providedBy(ob)) + 1 + + """ + + # We need to avoid setting this attribute on meta classes that + # don't support descriptors. + # We can do away with this check when we get rid of the old EC + cls = getattr(object, '__class__', None) + if cls is not None and getattr(cls, '__class__', None) is cls: + # It's a meta class (well, at least it it could be an extension class) + if not isinstance(object, DescriptorAwareMetaClasses): + raise TypeError("Attempt to make an interface declaration on a " + "non-descriptor-aware class") + + interfaces = _normalizeargs(interfaces) + if cls is None: + cls = type(object) + + issub = False + for damc in DescriptorAwareMetaClasses: + if issubclass(cls, damc): + issub = True + break + if issub: + # we have a class or type. We'll use a special descriptor + # that provides some extra caching + object.__provides__ = ClassProvides(object, cls, *interfaces) + else: + object.__provides__ = Provides(cls, *interfaces) + + +def alsoProvides(object, *interfaces): + """Declare interfaces declared directly for an object + + The arguments after the object are one or more interfaces or interface + specifications (``IDeclaration`` objects). + + The interfaces given (including the interfaces in the specifications) are + added to the interfaces previously declared for the object. + + Consider the following example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + >>> class IA1(Interface): pass + ... + >>> class IA2(Interface): pass + ... + >>> class IB(Interface): pass + ... + >>> class IC(Interface): pass + ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) + + >>> class C(A, B): + ... implements(IC) + + >>> ob = C() + >>> directlyProvides(ob, I1) + >>> int(I1 in providedBy(ob)) + 1 + >>> int(I2 in providedBy(ob)) + 0 + >>> int(IA1 in providedBy(ob)) + 1 + >>> int(IA2 in providedBy(ob)) + 1 + >>> int(IB in providedBy(ob)) + 1 + >>> int(IC in providedBy(ob)) + 1 + + >>> alsoProvides(ob, I2) + >>> int(I1 in providedBy(ob)) + 1 + >>> int(I2 in providedBy(ob)) + 1 + >>> int(IA1 in providedBy(ob)) + 1 + >>> int(IA2 in providedBy(ob)) + 1 + >>> int(IB in providedBy(ob)) + 1 + >>> int(IC in providedBy(ob)) + 1 + + The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces + instances have been declared for instances of ``C``. Notice that the + alsoProvides just extends the provided interfaces. + """ + directlyProvides(object, directlyProvidedBy(object), *interfaces) + +def noLongerProvides(object, interface): + """ + This removes a directly provided interface from an object. + Consider the following two interfaces: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + + ``I1`` is provided through the class, ``I2`` is directly provided + by the object: + + >>> class C(object): + ... implements(I1) + >>> c = C() + >>> alsoProvides(c, I2) + >>> I2.providedBy(c) + True + + Remove I2 from c again: + + >>> noLongerProvides(c, I2) + >>> I2.providedBy(c) + False + + Removing an interface that is provided through the class is not possible: + + >>> noLongerProvides(c, I1) + Traceback (most recent call last): + ... + ValueError: Can only remove directly provided interfaces. + + """ + directlyProvides(object, directlyProvidedBy(object)-interface) + if interface.providedBy(object): + raise ValueError("Can only remove directly provided interfaces.") + +class ClassProvidesBasePy(object): + + def __get__(self, inst, cls): + if cls is self._cls: + # We only work if called on the class we were defined for + + if inst is None: + # We were accessed through a class, so we are the class' + # provides spec. Just return this object as is: + return self + + return self._implements + + raise AttributeError('__provides__') + +ClassProvidesBase = ClassProvidesBasePy + +# Try to get C base: +try: + import _zope_interface_coptimizations +except ImportError: + pass +else: + from _zope_interface_coptimizations import ClassProvidesBase + + +class ClassProvides(Declaration, ClassProvidesBase): + """Special descriptor for class __provides__ + + The descriptor caches the implementedBy info, so that + we can get declarations for objects without instance-specific + interfaces a bit quicker. + + For example: + + >>> from zope.interface import Interface + >>> class IFooFactory(Interface): + ... pass + >>> class IFoo(Interface): + ... pass + >>> class C(object): + ... implements(IFoo) + ... classProvides(IFooFactory) + >>> [i.getName() for i in C.__provides__] + ['IFooFactory'] + + >>> [i.getName() for i in C().__provides__] + ['IFoo'] + """ + + def __init__(self, cls, metacls, *interfaces): + self._cls = cls + self._implements = implementedBy(cls) + self.__args = (cls, metacls, ) + interfaces + Declaration.__init__(self, *(interfaces + (implementedBy(metacls), ))) + + def __reduce__(self): + return self.__class__, self.__args + + # Copy base-class method for speed + __get__ = ClassProvidesBase.__get__ + +def directlyProvidedBy(object): + """Return the interfaces directly provided by the given object + + The value returned is an ``IDeclaration``. + """ + provides = getattr(object, "__provides__", None) + if (provides is None # no spec + or + # We might have gotten the implements spec, as an + # optimization. If so, it's like having only one base, that we + # lop off to exclude class-supplied declarations: + isinstance(provides, Implements) + ): + return _empty + + # Strip off the class part of the spec: + return Declaration(provides.__bases__[:-1]) + +def classProvides(*interfaces): + """Declare interfaces provided directly by a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface specifications + (``IDeclaration`` objects). + + The given interfaces (including the interfaces in the specifications) + are used to create the class's direct-object interface specification. + An error will be raised if the module class has an direct interface + specification. In other words, it is an error to call this function more + than once in a class definition. + + Note that the given interfaces have nothing to do with the interfaces + implemented by instances of the class. + + This function is provided for convenience. It provides a more convenient + way to call directlyProvides for a class. For example:: + + classProvides(I1) + + is equivalent to calling:: + + directlyProvides(theclass, I1) + + after the class has been created. + + For example: + + >>> from zope.interface import Interface + >>> class IFoo(Interface): pass + ... + >>> class IFooFactory(Interface): pass + ... + >>> class C(object): + ... implements(IFoo) + ... classProvides(IFooFactory) + >>> [i.getName() for i in C.__providedBy__] + ['IFooFactory'] + >>> [i.getName() for i in C().__providedBy__] + ['IFoo'] + + if equivalent to: + + >>> from zope.interface import Interface + >>> class IFoo(Interface): pass + ... + >>> class IFooFactory(Interface): pass + ... + >>> class C(object): + ... implements(IFoo) + >>> directlyProvides(C, IFooFactory) + >>> [i.getName() for i in C.__providedBy__] + ['IFooFactory'] + >>> [i.getName() for i in C().__providedBy__] + ['IFoo'] + + """ + frame = sys._getframe(1) + locals = frame.f_locals + + # Try to make sure we were called from a class def + if (locals is frame.f_globals) or ('__module__' not in locals): + raise TypeError("classProvides can be used only from a class definition.") + + if '__provides__' in locals: + raise TypeError( + "classProvides can only be used once in a class definition.") + + locals["__provides__"] = _normalizeargs(interfaces) + + addClassAdvisor(_classProvides_advice, depth=2) + +def _classProvides_advice(cls): + interfaces = cls.__dict__['__provides__'] + del cls.__provides__ + directlyProvides(cls, *interfaces) + return cls + +class provider: + """Class decorator version of classProvides""" + + def __init__(self, *interfaces): + self.interfaces = interfaces + + def __call__(self, ob): + directlyProvides(ob, *self.interfaces) + return ob + +def moduleProvides(*interfaces): + """Declare interfaces provided by a module + + This function is used in a module definition. + + The arguments are one or more interfaces or interface specifications + (``IDeclaration`` objects). + + The given interfaces (including the interfaces in the specifications) are + used to create the module's direct-object interface specification. An + error will be raised if the module already has an interface specification. + In other words, it is an error to call this function more than once in a + module definition. + + This function is provided for convenience. It provides a more convenient + way to call directlyProvides. For example:: + + moduleImplements(I1) + + is equivalent to:: + + directlyProvides(sys.modules[__name__], I1) + """ + frame = sys._getframe(1) + locals = frame.f_locals + + # Try to make sure we were called from a class def + if (locals is not frame.f_globals) or ('__name__' not in locals): + raise TypeError( + "moduleProvides can only be used from a module definition.") + + if '__provides__' in locals: + raise TypeError( + "moduleProvides can only be used once in a module definition.") + + locals["__provides__"] = Provides(ModuleType, + *_normalizeargs(interfaces)) + +############################################################################## +# +# Declaration querying support + +def ObjectSpecification(direct, cls): + """Provide object specifications + + These combine information for the object and for it's classes. + + For example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + >>> class I3(Interface): pass + ... + >>> class I31(I3): pass + ... + >>> class I4(Interface): pass + ... + >>> class I5(Interface): pass + ... + >>> class A(object): + ... implements(I1) + >>> class B(object): __implemented__ = I2 + ... + >>> class C(A, B): + ... implements(I31) + >>> c = C() + >>> directlyProvides(c, I4) + >>> [i.getName() for i in providedBy(c)] + ['I4', 'I31', 'I1', 'I2'] + >>> [i.getName() for i in providedBy(c).flattened()] + ['I4', 'I31', 'I3', 'I1', 'I2', 'Interface'] + >>> int(I1 in providedBy(c)) + 1 + >>> int(I3 in providedBy(c)) + 0 + >>> int(providedBy(c).extends(I3)) + 1 + >>> int(providedBy(c).extends(I31)) + 1 + >>> int(providedBy(c).extends(I5)) + 0 + >>> class COnly(A, B): + ... implementsOnly(I31) + >>> class D(COnly): + ... implements(I5) + >>> c = D() + >>> directlyProvides(c, I4) + >>> [i.getName() for i in providedBy(c)] + ['I4', 'I5', 'I31'] + >>> [i.getName() for i in providedBy(c).flattened()] + ['I4', 'I5', 'I31', 'I3', 'Interface'] + >>> int(I1 in providedBy(c)) + 0 + >>> int(I3 in providedBy(c)) + 0 + >>> int(providedBy(c).extends(I3)) + 1 + >>> int(providedBy(c).extends(I1)) + 0 + >>> int(providedBy(c).extends(I31)) + 1 + >>> int(providedBy(c).extends(I5)) + 1 + """ + + return Provides(cls, direct) + +def getObjectSpecification(ob): + + provides = getattr(ob, '__provides__', None) + if provides is not None: + if isinstance(provides, SpecificationBase): + return provides + + try: + cls = ob.__class__ + except AttributeError: + # We can't get the class, so just consider provides + return _empty + + return implementedBy(cls) + +def providedBy(ob): + + # Here we have either a special object, an old-style declaration + # or a descriptor + + # Try to get __providedBy__ + try: + r = ob.__providedBy__ + except AttributeError: + # Not set yet. Fall back to lower-level thing that computes it + return getObjectSpecification(ob) + + try: + # We might have gotten a descriptor from an instance of a + # class (like an ExtensionClass) that doesn't support + # descriptors. We'll make sure we got one by trying to get + # the only attribute, which all specs have. + r.extends + + except AttributeError: + + # The object's class doesn't understand descriptors. + # Sigh. We need to get an object descriptor, but we have to be + # careful. We want to use the instance's __provides__, if + # there is one, but only if it didn't come from the class. + + try: + r = ob.__provides__ + except AttributeError: + # No __provides__, so just fall back to implementedBy + return implementedBy(ob.__class__) + + # We need to make sure we got the __provides__ from the + # instance. We'll do this by making sure we don't get the same + # thing from the class: + + try: + cp = ob.__class__.__provides__ + except AttributeError: + # The ob doesn't have a class or the class has no + # provides, assume we're done: + return r + + if r is cp: + # Oops, we got the provides from the class. This means + # the object doesn't have it's own. We should use implementedBy + return implementedBy(ob.__class__) + + return r + +class ObjectSpecificationDescriptorPy(object): + """Implement the `__providedBy__` attribute + + The `__providedBy__` attribute computes the interfaces peovided by + an object. + """ + + def __get__(self, inst, cls): + """Get an object specification for an object + + For example: + + >>> from zope.interface import Interface + >>> class IFoo(Interface): pass + ... + >>> class IFooFactory(Interface): pass + ... + >>> class C(object): + ... implements(IFoo) + ... classProvides(IFooFactory) + >>> [i.getName() for i in C.__providedBy__] + ['IFooFactory'] + >>> [i.getName() for i in C().__providedBy__] + ['IFoo'] + + """ + + # Get an ObjectSpecification bound to either an instance or a class, + # depending on how we were accessed. + + if inst is None: + return getObjectSpecification(cls) + + provides = getattr(inst, '__provides__', None) + if provides is not None: + return provides + + return implementedBy(cls) + +ObjectSpecificationDescriptor = ObjectSpecificationDescriptorPy + +############################################################################## + +def _normalizeargs(sequence, output = None): + """Normalize declaration arguments + + Normalization arguments might contain Declarions, tuples, or single + interfaces. + + Anything but individial interfaces or implements specs will be expanded. + """ + if output is None: + output = [] + + cls = sequence.__class__ + if InterfaceClass in cls.__mro__ or Implements in cls.__mro__: + output.append(sequence) + else: + for v in sequence: + _normalizeargs(v, output) + + return output + +_empty = Declaration() + +try: + import _zope_interface_coptimizations +except ImportError: + pass +else: + from _zope_interface_coptimizations import implementedBy, providedBy + from _zope_interface_coptimizations import getObjectSpecification + from _zope_interface_coptimizations import ObjectSpecificationDescriptor + +objectSpecificationDescriptor = ObjectSpecificationDescriptor() |
