1 # -*- test-case-name: twisted.python.test.test_components -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
7 Component architecture for Twisted, based on Zope3 components.
9 Using the Zope3 API directly is strongly recommended. Everything
10 you need is in the top-level of the zope.interface package, e.g.::
12 from zope.interface import Interface, implements
14 class IFoo(Interface):
20 print IFoo.implementedBy(Foo) # True
21 print IFoo.providedBy(Foo()) # True
23 L{twisted.python.components.registerAdapter} from this module may be used to
24 add to Twisted's global adapter registry.
26 L{twisted.python.components.proxyForInterface} is a factory for classes
27 which allow access to only the parts of another class defined by a specified
32 from zope.interface import interface, declarations
33 from zope.interface.adapter import AdapterRegistry
36 from twisted.python import reflect
37 from twisted.persisted import styles
41 # Twisted's global adapter registry
42 globalRegistry = AdapterRegistry()
44 # Attribute that registerAdapter looks at. Is this supposed to be public?
47 # Define a function to find the registered adapter factory, using either a
48 # version of Zope Interface which has the `registered' method or an older
49 # version which does not.
50 if getattr(AdapterRegistry, 'registered', None) is None:
51 def _registered(registry, required, provided):
53 Return the adapter factory for the given parameters in the given
54 registry, or None if there is not one.
56 return registry.get(required).selfImplied.get(provided, {}).get('')
58 def _registered(registry, required, provided):
60 Return the adapter factory for the given parameters in the given
61 registry, or None if there is not one.
63 return registry.registered([required], provided)
66 def registerAdapter(adapterFactory, origInterface, *interfaceClasses):
67 """Register an adapter class.
69 An adapter class is expected to implement the given interface, by
70 adapting instances implementing 'origInterface'. An adapter class's
71 __init__ method should accept one parameter, an instance implementing
75 assert interfaceClasses, "You need to pass an Interface"
76 global ALLOW_DUPLICATES
78 # deal with class->interface adapters:
79 if not isinstance(origInterface, interface.InterfaceClass):
80 origInterface = declarations.implementedBy(origInterface)
82 for interfaceClass in interfaceClasses:
83 factory = _registered(self, origInterface, interfaceClass)
84 if factory is not None and not ALLOW_DUPLICATES:
85 raise ValueError("an adapter (%s) was already registered." % (factory, ))
86 for interfaceClass in interfaceClasses:
87 self.register([origInterface], interfaceClass, '', adapterFactory)
90 def getAdapterFactory(fromInterface, toInterface, default):
91 """Return registered adapter for a given class and interface.
93 Note that is tied to the *Twisted* global registry, and will
94 thus not find adapters registered elsewhere.
97 if not isinstance(fromInterface, interface.InterfaceClass):
98 fromInterface = declarations.implementedBy(fromInterface)
99 factory = self.lookup1(fromInterface, toInterface)
105 def _addHook(registry):
107 Add an adapter hook which will attempt to look up adapters in the given
110 @type registry: L{zope.interface.adapter.AdapterRegistry}
112 @return: The hook which was added, for later use with L{_removeHook}.
114 lookup = registry.lookup1
115 def _hook(iface, ob):
116 factory = lookup(declarations.providedBy(ob), iface)
121 interface.adapter_hooks.append(_hook)
125 def _removeHook(hook):
127 Remove a previously added adapter hook.
129 @param hook: An object previously returned by a call to L{_addHook}. This
130 will be removed from the list of adapter hooks.
132 interface.adapter_hooks.remove(hook)
134 # add global adapter lookup hook for our newly created registry
135 _addHook(globalRegistry)
139 """Returns the Twisted global
140 C{zope.interface.adapter.AdapterRegistry} instance.
142 return globalRegistry
144 # FIXME: deprecate attribute somehow?
145 CannotAdapt = TypeError
148 """I am the default implementation of an Adapter for some interface.
150 This docstring contains a limerick, by popular demand::
152 Subclassing made Zope and TR
153 much harder to work with by far.
154 So before you inherit,
155 be sure to declare it
156 Adapter, not PyObject*
158 @cvar temporaryAdapter: If this is True, the adapter will not be
159 persisted on the Componentized.
160 @cvar multiComponent: If this adapter is persistent, should it be
161 automatically registered for all appropriate interfaces.
164 # These attributes are used with Componentized.
169 def __init__(self, original):
170 """Set my 'original' attribute to be the object I am adapting.
172 self.original = original
174 def __conform__(self, interface):
176 I forward __conform__ to self.original if it has it, otherwise I
179 if hasattr(self.original, "__conform__"):
180 return self.original.__conform__(interface)
183 def isuper(self, iface, adapter):
185 Forward isuper to self.original
187 return self.original.isuper(iface, adapter)
190 class Componentized(styles.Versioned):
191 """I am a mixin to allow you to be adapted in various ways persistently.
193 I define a list of persistent adapters. This is to allow adapter classes
194 to store system-specific state, and initialized on demand. The
195 getComponent method implements this. You must also register adapters for
196 this class for the interfaces that you wish to pass to getComponent.
198 Many other classes and utilities listed here are present in Zope3; this one
199 is specific to Twisted.
202 persistenceVersion = 1
205 self._adapterCache = {}
207 def locateAdapterClass(self, klass, interfaceClass, default):
208 return getAdapterFactory(klass, interfaceClass, default)
210 def setAdapter(self, interfaceClass, adapterClass):
211 self.setComponent(interfaceClass, adapterClass(self))
213 def addAdapter(self, adapterClass, ignoreClass=0):
214 """Utility method that calls addComponent. I take an adapter class and
215 instantiate it with myself as the first argument.
217 @return: The adapter instantiated.
219 adapt = adapterClass(self)
220 self.addComponent(adapt, ignoreClass)
223 def setComponent(self, interfaceClass, component):
226 self._adapterCache[reflect.qual(interfaceClass)] = component
228 def addComponent(self, component, ignoreClass=0):
230 Add a component to me, for all appropriate interfaces.
232 In order to determine which interfaces are appropriate, the component's
233 provided interfaces will be scanned.
235 If the argument 'ignoreClass' is True, then all interfaces are
236 considered appropriate.
238 Otherwise, an 'appropriate' interface is one for which its class has
239 been registered as an adapter for my class according to the rules of
242 @return: the list of appropriate interfaces
244 for iface in declarations.providedBy(component):
246 (self.locateAdapterClass(self.__class__, iface, None)
247 == component.__class__)):
248 self._adapterCache[reflect.qual(iface)] = component
250 def unsetComponent(self, interfaceClass):
251 """Remove my component specified by the given interface class."""
252 del self._adapterCache[reflect.qual(interfaceClass)]
254 def removeComponent(self, component):
256 Remove the given component from me entirely, for all interfaces for which
257 it has been registered.
259 @return: a list of the interfaces that were removed.
262 for k, v in self._adapterCache.items():
264 del self._adapterCache[k]
265 l.append(reflect.namedObject(k))
268 def getComponent(self, interface, default=None):
269 """Create or retrieve an adapter for the given interface.
271 If such an adapter has already been created, retrieve it from the cache
272 that this instance keeps of all its adapters. Adapters created through
273 this mechanism may safely store system-specific state.
275 If you want to register an adapter that will be created through
276 getComponent, but you don't require (or don't want) your adapter to be
277 cached and kept alive for the lifetime of this Componentized object,
278 set the attribute 'temporaryAdapter' to True on your adapter class.
280 If you want to automatically register an adapter for all appropriate
281 interfaces (with addComponent), set the attribute 'multiComponent' to
282 True on your adapter class.
284 k = reflect.qual(interface)
285 if self._adapterCache.has_key(k):
286 return self._adapterCache[k]
288 adapter = interface.__adapt__(self)
289 if adapter is not None and not (
290 hasattr(adapter, "temporaryAdapter") and
291 adapter.temporaryAdapter):
292 self._adapterCache[k] = adapter
293 if (hasattr(adapter, "multiComponent") and
294 adapter.multiComponent):
295 self.addComponent(adapter)
301 def __conform__(self, interface):
302 return self.getComponent(interface)
305 class ReprableComponentized(Componentized):
307 Componentized.__init__(self)
310 from cStringIO import StringIO
311 from pprint import pprint
313 pprint(self._adapterCache, sio)
314 return sio.getvalue()
318 def proxyForInterface(iface, originalAttribute='original'):
320 Create a class which proxies all method calls which adhere to an interface
321 to another provider of that interface.
323 This function is intended for creating specialized proxies. The typical way
324 to use it is by subclassing the result::
326 class MySpecializedProxy(proxyForInterface(IFoo)):
327 def someInterfaceMethod(self, arg):
330 return self.original.someInterfaceMethod(arg)
332 @param iface: The Interface to which the resulting object will conform, and
333 which the wrapped object must provide.
335 @param originalAttribute: name of the attribute used to save the original
336 object in the resulting class. Default to C{original}.
337 @type originalAttribute: C{str}
339 @return: A class whose constructor takes the original object as its only
340 argument. Constructing the class creates the proxy.
342 def __init__(self, original):
343 setattr(self, originalAttribute, original)
344 contents = {"__init__": __init__}
346 contents[name] = _ProxyDescriptor(name, originalAttribute)
347 proxy = type("(Proxy for %s)"
348 % (reflect.qual(iface),), (object,), contents)
349 declarations.classImplements(proxy, iface)
354 class _ProxiedClassMethod(object):
356 A proxied class method.
358 @ivar methodName: the name of the method which this should invoke when
360 @type methodName: C{str}
362 @ivar originalAttribute: name of the attribute of the proxy where the
363 original object is stored.
364 @type orginalAttribute: C{str}
366 def __init__(self, methodName, originalAttribute):
367 self.methodName = methodName
368 self.originalAttribute = originalAttribute
371 def __call__(self, oself, *args, **kw):
373 Invoke the specified L{methodName} method of the C{original} attribute
374 for proxyForInterface.
376 @param oself: an instance of a L{proxyForInterface} object.
378 @return: the result of the underlying method.
380 original = getattr(oself, self.originalAttribute)
381 actualMethod = getattr(original, self.methodName)
382 return actualMethod(*args, **kw)
386 class _ProxyDescriptor(object):
388 A descriptor which will proxy attribute access, mutation, and
389 deletion to the L{original} attribute of the object it is being accessed
392 @ivar attributeName: the name of the attribute which this descriptor will
393 retrieve from instances' C{original} attribute.
394 @type attributeName: C{str}
396 @ivar originalAttribute: name of the attribute of the proxy where the
397 original object is stored.
398 @type orginalAttribute: C{str}
400 def __init__(self, attributeName, originalAttribute):
401 self.attributeName = attributeName
402 self.originalAttribute = originalAttribute
405 def __get__(self, oself, type=None):
407 Retrieve the C{self.attributeName} property from L{oself}.
410 return _ProxiedClassMethod(self.attributeName,
411 self.originalAttribute)
412 original = getattr(oself, self.originalAttribute)
413 return getattr(original, self.attributeName)
416 def __set__(self, oself, value):
418 Set the C{self.attributeName} property of L{oself}.
420 original = getattr(oself, self.originalAttribute)
421 setattr(original, self.attributeName, value)
424 def __delete__(self, oself):
426 Delete the C{self.attributeName} property of L{oself}.
428 original = getattr(oself, self.originalAttribute)
429 delattr(original, self.attributeName)
435 "registerAdapter", "getAdapterFactory",
436 "Adapter", "Componentized", "ReprableComponentized", "getRegistry",