| 1 | # -*- test-case-name: twisted.python.test.test_components -*- |
|---|
| 2 | # Copyright (c) Twisted Matrix Laboratories. |
|---|
| 3 | # See LICENSE for details. |
|---|
| 4 | |
|---|
| 5 | |
|---|
| 6 | """ |
|---|
| 7 | Component architecture for Twisted, based on Zope3 components. |
|---|
| 8 | |
|---|
| 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.:: |
|---|
| 11 | |
|---|
| 12 | from zope.interface import Interface, implements |
|---|
| 13 | |
|---|
| 14 | class IFoo(Interface): |
|---|
| 15 | pass |
|---|
| 16 | |
|---|
| 17 | class Foo: |
|---|
| 18 | implements(IFoo) |
|---|
| 19 | |
|---|
| 20 | print IFoo.implementedBy(Foo) # True |
|---|
| 21 | print IFoo.providedBy(Foo()) # True |
|---|
| 22 | |
|---|
| 23 | L{twisted.python.components.registerAdapter} from this module may be used to |
|---|
| 24 | add to Twisted's global adapter registry. |
|---|
| 25 | |
|---|
| 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 |
|---|
| 28 | interface. |
|---|
| 29 | """ |
|---|
| 30 | |
|---|
| 31 | # zope3 imports |
|---|
| 32 | from zope.interface import interface, declarations |
|---|
| 33 | from zope.interface.adapter import AdapterRegistry |
|---|
| 34 | |
|---|
| 35 | # twisted imports |
|---|
| 36 | from twisted.python import reflect |
|---|
| 37 | from twisted.persisted import styles |
|---|
| 38 | |
|---|
| 39 | |
|---|
| 40 | |
|---|
| 41 | # Twisted's global adapter registry |
|---|
| 42 | globalRegistry = AdapterRegistry() |
|---|
| 43 | |
|---|
| 44 | # Attribute that registerAdapter looks at. Is this supposed to be public? |
|---|
| 45 | ALLOW_DUPLICATES = 0 |
|---|
| 46 | |
|---|
| 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): |
|---|
| 52 | """ |
|---|
| 53 | Return the adapter factory for the given parameters in the given |
|---|
| 54 | registry, or None if there is not one. |
|---|
| 55 | """ |
|---|
| 56 | return registry.get(required).selfImplied.get(provided, {}).get('') |
|---|
| 57 | else: |
|---|
| 58 | def _registered(registry, required, provided): |
|---|
| 59 | """ |
|---|
| 60 | Return the adapter factory for the given parameters in the given |
|---|
| 61 | registry, or None if there is not one. |
|---|
| 62 | """ |
|---|
| 63 | return registry.registered([required], provided) |
|---|
| 64 | |
|---|
| 65 | |
|---|
| 66 | def registerAdapter(adapterFactory, origInterface, *interfaceClasses): |
|---|
| 67 | """Register an adapter class. |
|---|
| 68 | |
|---|
| 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 |
|---|
| 72 | 'origInterface'. |
|---|
| 73 | """ |
|---|
| 74 | self = globalRegistry |
|---|
| 75 | assert interfaceClasses, "You need to pass an Interface" |
|---|
| 76 | global ALLOW_DUPLICATES |
|---|
| 77 | |
|---|
| 78 | # deal with class->interface adapters: |
|---|
| 79 | if not isinstance(origInterface, interface.InterfaceClass): |
|---|
| 80 | origInterface = declarations.implementedBy(origInterface) |
|---|
| 81 | |
|---|
| 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) |
|---|
| 88 | |
|---|
| 89 | |
|---|
| 90 | def getAdapterFactory(fromInterface, toInterface, default): |
|---|
| 91 | """Return registered adapter for a given class and interface. |
|---|
| 92 | |
|---|
| 93 | Note that is tied to the *Twisted* global registry, and will |
|---|
| 94 | thus not find adapters registered elsewhere. |
|---|
| 95 | """ |
|---|
| 96 | self = globalRegistry |
|---|
| 97 | if not isinstance(fromInterface, interface.InterfaceClass): |
|---|
| 98 | fromInterface = declarations.implementedBy(fromInterface) |
|---|
| 99 | factory = self.lookup1(fromInterface, toInterface) |
|---|
| 100 | if factory is None: |
|---|
| 101 | factory = default |
|---|
| 102 | return factory |
|---|
| 103 | |
|---|
| 104 | |
|---|
| 105 | def _addHook(registry): |
|---|
| 106 | """ |
|---|
| 107 | Add an adapter hook which will attempt to look up adapters in the given |
|---|
| 108 | registry. |
|---|
| 109 | |
|---|
| 110 | @type registry: L{zope.interface.adapter.AdapterRegistry} |
|---|
| 111 | |
|---|
| 112 | @return: The hook which was added, for later use with L{_removeHook}. |
|---|
| 113 | """ |
|---|
| 114 | lookup = registry.lookup1 |
|---|
| 115 | def _hook(iface, ob): |
|---|
| 116 | factory = lookup(declarations.providedBy(ob), iface) |
|---|
| 117 | if factory is None: |
|---|
| 118 | return None |
|---|
| 119 | else: |
|---|
| 120 | return factory(ob) |
|---|
| 121 | interface.adapter_hooks.append(_hook) |
|---|
| 122 | return _hook |
|---|
| 123 | |
|---|
| 124 | |
|---|
| 125 | def _removeHook(hook): |
|---|
| 126 | """ |
|---|
| 127 | Remove a previously added adapter hook. |
|---|
| 128 | |
|---|
| 129 | @param hook: An object previously returned by a call to L{_addHook}. This |
|---|
| 130 | will be removed from the list of adapter hooks. |
|---|
| 131 | """ |
|---|
| 132 | interface.adapter_hooks.remove(hook) |
|---|
| 133 | |
|---|
| 134 | # add global adapter lookup hook for our newly created registry |
|---|
| 135 | _addHook(globalRegistry) |
|---|
| 136 | |
|---|
| 137 | |
|---|
| 138 | def getRegistry(): |
|---|
| 139 | """Returns the Twisted global |
|---|
| 140 | C{zope.interface.adapter.AdapterRegistry} instance. |
|---|
| 141 | """ |
|---|
| 142 | return globalRegistry |
|---|
| 143 | |
|---|
| 144 | # FIXME: deprecate attribute somehow? |
|---|
| 145 | CannotAdapt = TypeError |
|---|
| 146 | |
|---|
| 147 | class Adapter: |
|---|
| 148 | """I am the default implementation of an Adapter for some interface. |
|---|
| 149 | |
|---|
| 150 | This docstring contains a limerick, by popular demand:: |
|---|
| 151 | |
|---|
| 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* |
|---|
| 157 | |
|---|
| 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. |
|---|
| 162 | """ |
|---|
| 163 | |
|---|
| 164 | # These attributes are used with Componentized. |
|---|
| 165 | |
|---|
| 166 | temporaryAdapter = 0 |
|---|
| 167 | multiComponent = 1 |
|---|
| 168 | |
|---|
| 169 | def __init__(self, original): |
|---|
| 170 | """Set my 'original' attribute to be the object I am adapting. |
|---|
| 171 | """ |
|---|
| 172 | self.original = original |
|---|
| 173 | |
|---|
| 174 | def __conform__(self, interface): |
|---|
| 175 | """ |
|---|
| 176 | I forward __conform__ to self.original if it has it, otherwise I |
|---|
| 177 | simply return None. |
|---|
| 178 | """ |
|---|
| 179 | if hasattr(self.original, "__conform__"): |
|---|
| 180 | return self.original.__conform__(interface) |
|---|
| 181 | return None |
|---|
| 182 | |
|---|
| 183 | def isuper(self, iface, adapter): |
|---|
| 184 | """ |
|---|
| 185 | Forward isuper to self.original |
|---|
| 186 | """ |
|---|
| 187 | return self.original.isuper(iface, adapter) |
|---|
| 188 | |
|---|
| 189 | |
|---|
| 190 | class Componentized(styles.Versioned): |
|---|
| 191 | """I am a mixin to allow you to be adapted in various ways persistently. |
|---|
| 192 | |
|---|
| 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. |
|---|
| 197 | |
|---|
| 198 | Many other classes and utilities listed here are present in Zope3; this one |
|---|
| 199 | is specific to Twisted. |
|---|
| 200 | """ |
|---|
| 201 | |
|---|
| 202 | persistenceVersion = 1 |
|---|
| 203 | |
|---|
| 204 | def __init__(self): |
|---|
| 205 | self._adapterCache = {} |
|---|
| 206 | |
|---|
| 207 | def locateAdapterClass(self, klass, interfaceClass, default): |
|---|
| 208 | return getAdapterFactory(klass, interfaceClass, default) |
|---|
| 209 | |
|---|
| 210 | def setAdapter(self, interfaceClass, adapterClass): |
|---|
| 211 | self.setComponent(interfaceClass, adapterClass(self)) |
|---|
| 212 | |
|---|
| 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. |
|---|
| 216 | |
|---|
| 217 | @return: The adapter instantiated. |
|---|
| 218 | """ |
|---|
| 219 | adapt = adapterClass(self) |
|---|
| 220 | self.addComponent(adapt, ignoreClass) |
|---|
| 221 | return adapt |
|---|
| 222 | |
|---|
| 223 | def setComponent(self, interfaceClass, component): |
|---|
| 224 | """ |
|---|
| 225 | """ |
|---|
| 226 | self._adapterCache[reflect.qual(interfaceClass)] = component |
|---|
| 227 | |
|---|
| 228 | def addComponent(self, component, ignoreClass=0): |
|---|
| 229 | """ |
|---|
| 230 | Add a component to me, for all appropriate interfaces. |
|---|
| 231 | |
|---|
| 232 | In order to determine which interfaces are appropriate, the component's |
|---|
| 233 | provided interfaces will be scanned. |
|---|
| 234 | |
|---|
| 235 | If the argument 'ignoreClass' is True, then all interfaces are |
|---|
| 236 | considered appropriate. |
|---|
| 237 | |
|---|
| 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 |
|---|
| 240 | getComponent. |
|---|
| 241 | |
|---|
| 242 | @return: the list of appropriate interfaces |
|---|
| 243 | """ |
|---|
| 244 | for iface in declarations.providedBy(component): |
|---|
| 245 | if (ignoreClass or |
|---|
| 246 | (self.locateAdapterClass(self.__class__, iface, None) |
|---|
| 247 | == component.__class__)): |
|---|
| 248 | self._adapterCache[reflect.qual(iface)] = component |
|---|
| 249 | |
|---|
| 250 | def unsetComponent(self, interfaceClass): |
|---|
| 251 | """Remove my component specified by the given interface class.""" |
|---|
| 252 | del self._adapterCache[reflect.qual(interfaceClass)] |
|---|
| 253 | |
|---|
| 254 | def removeComponent(self, component): |
|---|
| 255 | """ |
|---|
| 256 | Remove the given component from me entirely, for all interfaces for which |
|---|
| 257 | it has been registered. |
|---|
| 258 | |
|---|
| 259 | @return: a list of the interfaces that were removed. |
|---|
| 260 | """ |
|---|
| 261 | l = [] |
|---|
| 262 | for k, v in self._adapterCache.items(): |
|---|
| 263 | if v is component: |
|---|
| 264 | del self._adapterCache[k] |
|---|
| 265 | l.append(reflect.namedObject(k)) |
|---|
| 266 | return l |
|---|
| 267 | |
|---|
| 268 | def getComponent(self, interface, default=None): |
|---|
| 269 | """Create or retrieve an adapter for the given interface. |
|---|
| 270 | |
|---|
| 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. |
|---|
| 274 | |
|---|
| 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. |
|---|
| 279 | |
|---|
| 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. |
|---|
| 283 | """ |
|---|
| 284 | k = reflect.qual(interface) |
|---|
| 285 | if self._adapterCache.has_key(k): |
|---|
| 286 | return self._adapterCache[k] |
|---|
| 287 | else: |
|---|
| 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) |
|---|
| 296 | if adapter is None: |
|---|
| 297 | return default |
|---|
| 298 | return adapter |
|---|
| 299 | |
|---|
| 300 | |
|---|
| 301 | def __conform__(self, interface): |
|---|
| 302 | return self.getComponent(interface) |
|---|
| 303 | |
|---|
| 304 | |
|---|
| 305 | class ReprableComponentized(Componentized): |
|---|
| 306 | def __init__(self): |
|---|
| 307 | Componentized.__init__(self) |
|---|
| 308 | |
|---|
| 309 | def __repr__(self): |
|---|
| 310 | from cStringIO import StringIO |
|---|
| 311 | from pprint import pprint |
|---|
| 312 | sio = StringIO() |
|---|
| 313 | pprint(self._adapterCache, sio) |
|---|
| 314 | return sio.getvalue() |
|---|
| 315 | |
|---|
| 316 | |
|---|
| 317 | |
|---|
| 318 | def proxyForInterface(iface, originalAttribute='original'): |
|---|
| 319 | """ |
|---|
| 320 | Create a class which proxies all method calls which adhere to an interface |
|---|
| 321 | to another provider of that interface. |
|---|
| 322 | |
|---|
| 323 | This function is intended for creating specialized proxies. The typical way |
|---|
| 324 | to use it is by subclassing the result:: |
|---|
| 325 | |
|---|
| 326 | class MySpecializedProxy(proxyForInterface(IFoo)): |
|---|
| 327 | def someInterfaceMethod(self, arg): |
|---|
| 328 | if arg == 3: |
|---|
| 329 | return 3 |
|---|
| 330 | return self.original.someInterfaceMethod(arg) |
|---|
| 331 | |
|---|
| 332 | @param iface: The Interface to which the resulting object will conform, and |
|---|
| 333 | which the wrapped object must provide. |
|---|
| 334 | |
|---|
| 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} |
|---|
| 338 | |
|---|
| 339 | @return: A class whose constructor takes the original object as its only |
|---|
| 340 | argument. Constructing the class creates the proxy. |
|---|
| 341 | """ |
|---|
| 342 | def __init__(self, original): |
|---|
| 343 | setattr(self, originalAttribute, original) |
|---|
| 344 | contents = {"__init__": __init__} |
|---|
| 345 | for name in iface: |
|---|
| 346 | contents[name] = _ProxyDescriptor(name, originalAttribute) |
|---|
| 347 | proxy = type("(Proxy for %s)" |
|---|
| 348 | % (reflect.qual(iface),), (object,), contents) |
|---|
| 349 | declarations.classImplements(proxy, iface) |
|---|
| 350 | return proxy |
|---|
| 351 | |
|---|
| 352 | |
|---|
| 353 | |
|---|
| 354 | class _ProxiedClassMethod(object): |
|---|
| 355 | """ |
|---|
| 356 | A proxied class method. |
|---|
| 357 | |
|---|
| 358 | @ivar methodName: the name of the method which this should invoke when |
|---|
| 359 | called. |
|---|
| 360 | @type methodName: C{str} |
|---|
| 361 | |
|---|
| 362 | @ivar originalAttribute: name of the attribute of the proxy where the |
|---|
| 363 | original object is stored. |
|---|
| 364 | @type orginalAttribute: C{str} |
|---|
| 365 | """ |
|---|
| 366 | def __init__(self, methodName, originalAttribute): |
|---|
| 367 | self.methodName = methodName |
|---|
| 368 | self.originalAttribute = originalAttribute |
|---|
| 369 | |
|---|
| 370 | |
|---|
| 371 | def __call__(self, oself, *args, **kw): |
|---|
| 372 | """ |
|---|
| 373 | Invoke the specified L{methodName} method of the C{original} attribute |
|---|
| 374 | for proxyForInterface. |
|---|
| 375 | |
|---|
| 376 | @param oself: an instance of a L{proxyForInterface} object. |
|---|
| 377 | |
|---|
| 378 | @return: the result of the underlying method. |
|---|
| 379 | """ |
|---|
| 380 | original = getattr(oself, self.originalAttribute) |
|---|
| 381 | actualMethod = getattr(original, self.methodName) |
|---|
| 382 | return actualMethod(*args, **kw) |
|---|
| 383 | |
|---|
| 384 | |
|---|
| 385 | |
|---|
| 386 | class _ProxyDescriptor(object): |
|---|
| 387 | """ |
|---|
| 388 | A descriptor which will proxy attribute access, mutation, and |
|---|
| 389 | deletion to the L{original} attribute of the object it is being accessed |
|---|
| 390 | from. |
|---|
| 391 | |
|---|
| 392 | @ivar attributeName: the name of the attribute which this descriptor will |
|---|
| 393 | retrieve from instances' C{original} attribute. |
|---|
| 394 | @type attributeName: C{str} |
|---|
| 395 | |
|---|
| 396 | @ivar originalAttribute: name of the attribute of the proxy where the |
|---|
| 397 | original object is stored. |
|---|
| 398 | @type orginalAttribute: C{str} |
|---|
| 399 | """ |
|---|
| 400 | def __init__(self, attributeName, originalAttribute): |
|---|
| 401 | self.attributeName = attributeName |
|---|
| 402 | self.originalAttribute = originalAttribute |
|---|
| 403 | |
|---|
| 404 | |
|---|
| 405 | def __get__(self, oself, type=None): |
|---|
| 406 | """ |
|---|
| 407 | Retrieve the C{self.attributeName} property from L{oself}. |
|---|
| 408 | """ |
|---|
| 409 | if oself is None: |
|---|
| 410 | return _ProxiedClassMethod(self.attributeName, |
|---|
| 411 | self.originalAttribute) |
|---|
| 412 | original = getattr(oself, self.originalAttribute) |
|---|
| 413 | return getattr(original, self.attributeName) |
|---|
| 414 | |
|---|
| 415 | |
|---|
| 416 | def __set__(self, oself, value): |
|---|
| 417 | """ |
|---|
| 418 | Set the C{self.attributeName} property of L{oself}. |
|---|
| 419 | """ |
|---|
| 420 | original = getattr(oself, self.originalAttribute) |
|---|
| 421 | setattr(original, self.attributeName, value) |
|---|
| 422 | |
|---|
| 423 | |
|---|
| 424 | def __delete__(self, oself): |
|---|
| 425 | """ |
|---|
| 426 | Delete the C{self.attributeName} property of L{oself}. |
|---|
| 427 | """ |
|---|
| 428 | original = getattr(oself, self.originalAttribute) |
|---|
| 429 | delattr(original, self.attributeName) |
|---|
| 430 | |
|---|
| 431 | |
|---|
| 432 | |
|---|
| 433 | __all__ = [ |
|---|
| 434 | # Sticking around: |
|---|
| 435 | "registerAdapter", "getAdapterFactory", |
|---|
| 436 | "Adapter", "Componentized", "ReprableComponentized", "getRegistry", |
|---|
| 437 | "proxyForInterface", |
|---|
| 438 | ] |
|---|