View of /PEAK/src/peak/binding/components.txt
Parent Directory
| Revision Log
Revision:
2781 -
(
download)
Sun Nov 1 00:48:40 2015 UTC (8 years, 5 months ago) by
pje
File size: 10718 byte(s)
Fix component acquisition defaults and doc/test lookupComponent()
==================
Binding Components
==================
>>> from peak.api import *
---------------------------
The Component Hierarchy API
---------------------------
>>> root = config.makeRoot()
>>> c1 = binding.Component(root,"c1")
>>> c2 = binding.Component(root)
>>> c1a = binding.Component(c1)
Fundamental Operations
======================
There are two fundamental operations in the component hierarchy API. These
generic functions are used as a basis for all of the other operations, so to
use the rest of the hierarchy API for a given type, you need only define
methods for these two basic operations. Definitions already exist for
PEAK-supplied types and for modules, so you only need to add methods for
types you want to use, that aren't subclasses of an already supported type.
``getParentComponent(component)``
Return parent of `component`, or ``None`` if root or non-component, e.g.::
>>> binding.getParentComponent(c1a) is c1
True
This also works for module objects, and 'binding.ActiveClass' objects,
for which the containing module or package is returned::
>>> binding
<module 'peak.binding.api' from ...>
>>> binding.getParentComponent(binding)
<module 'peak.binding' from ...>
>>> binding.Component
<class 'peak.binding.components.Component'>
>>> binding.getParentComponent(binding.Component)
<module 'peak.binding.components' from ...>
This is a single-dispatch generic function, so you can add cases for
additional object types using 'binding.getParentComponent.when()' as a
decorator. For example::
>>> class MyComponent:
... def __init__(self,parent): self.my_parent = parent
>>> anOb = MyComponent(42)
>>> print binding.getParentComponent(anOb) # not a known type yet
None
>>> if binding.getParentComponent.when(MyComponent): # XXX @
... def get_for_myComponent(component):
... return component.my_parent
>>> binding.getParentComponent(anOb) # now it's known
42
``getComponentName(component)``
Return name of `component`, or ``None`` if unknown or a non-component::
>>> binding.getComponentName(c1)
'c1'
>>> print binding.getComponentName(c2)
None
>>> print binding.getComponentName(42)
None
This also works for module objects, and 'binding.ActiveClass' objects,
for which the module or class' '__name__' is returned::
>>> binding.getComponentName(protocols)
'protocols'
>>> binding.getComponentName(binding.Component)
'Component'
This is a single-dispatch generic function, so you can add cases for
additional object types using 'binding.getComponentName.when()' as a
decorator::
>>> class MyComponent:
... def __init__(self,name): self.my_name = name
>>> anOb = MyComponent("blue 42")
>>> print binding.getComponentName(anOb) # not a known type yet
None
>>> if binding.getComponentName.when(MyComponent): # XXX @
... def get_for_myComponent(component):
... return component.my_name
>>> binding.getComponentName(anOb) # now it's known
'blue 42'
So, any class for which the above two operations are defined can be used with
the remaining hierarchy APIs, below.
Convenience Operations
======================
These convenience APIs are shortcuts for common usage patterns when working
with a component hierarchy. They're defined in terms of the fundamental
operations above, so they'll work for any type you've defined the fundamental
operations for, and will degrade gracefully for other types.
``iterParents(component,max_depth=100)``
Iterate over the parents of `component`, beginning with `component` itself,
in hierarchy order up through the root component (i.e., the first component
whose parent is ``None``)::
>>> list(binding.iterParents(c1)) == [c1,root]
True
>>> list(binding.iterParents(c1a)) == [c1a,c1,root]
True
>>> list(binding.iterParents(root)) == [root]
True
>>> list(binding.iterParents(42)) == [42]
True
The `max_depth` parameter controls how deeply the hierarchy can be iterated
before a recursion error occurs. This is intended to prevent infinite
iteration if you accidentally create a circular hierarchy, e.g.::
>>> x = binding.Component()
>>> x.setParentComponent(x)
>>> list(binding.iterParents(x))
Traceback (most recent call last):
...
RuntimeError: ('maximum recursion limit exceeded', ...)
>>> del x
Currently, PEAK assumes that there is little reason to have a component
with 100 levels of parents; consider that if each component in such a
hierarchy has two children, that's 2^100 objects, which is more objects
than can be fit into a 64-bit computer's address space. :)
``hasParent(component,parent)``
Is `component` within the hierarchy of `parent`? This routine returns
truth if ``component is parent`` or if `parent` is one of the parents of
`component`::
>>> binding.hasParent(c1a,c1a)
True
>>> binding.hasParent(c1a,c1)
True
>>> binding.hasParent(c1a,root)
True
>>> binding.hasParent(c1a,c2)
False
``hasParent()`` is specially optimized for use with generic functions,
so that you can define generic function methods that apply only within
a particular component hierarchy.
``getRootComponent(component)``
Return the root component of the tree `component` belongs to. Basically
this returns the first parent of `component` whose parent is ``None``::
>>> binding.getRootComponent(c1) is root
True
>>> binding.getRootComponent(binding)
<module 'peak' from ...>
>>> binding.getRootComponent(binding.Component)
<module 'peak' from ...>
>>> binding.getRootComponent(42)
42
``getComponentPath(component,relativeTo=None)``
Return the ``binding.ComponentName`` that would traverse from `relativeTo`
to `component`::
>>> binding.getComponentPath(binding.Component)
ComponentName(['', 'binding', 'components', 'Component'])
>>> print binding.getComponentPath(binding.Component)
/binding/components/Component
If `relativeTo` is ``None`` or not supplied, the path returned is relative
to the root component of `component`. Note that if supplied, `relativeTo`
must be an ancestor (parent, parent's parent, etc.) of `component`::
>>> import peak
>>> binding.getComponentPath(binding.Component, peak)
ComponentName(['binding', 'components', 'Component'])
>>> print binding.getComponentPath(binding.Component, peak)
binding/components/Component
Finding Components
==================
``acquireComponent(component, name, default=NOT_GIVEN)``
If `component` has an attribute of `name`, return its value. Otherwise,
walk the ``iterParents()`` of `component` looking for `name`. If no parent
has the named attribute, it adapts the last parent found to a
``config.IConfigurationRoot`` and delegates the lookup via the
``nameNotFound()`` method of that interface. (The default implementation
does a `naming.lookup()`, which in turn will raise a ``NameNotFound`` exception
or return the provided `default`.)
::
>>> c1 = binding.Component(root, 'c1')
>>> c2 = c1.c2 = binding.Component(c1, 'c2')
>>> binding.acquireComponent(c2, 'c2') is c2
True
>>> binding.acquireComponent(c2, 'c3')
Traceback (most recent call last):
...
NameNotFound: [remainingName=CompoundName(['c3']),resolvedObj=<...>]
>>> binding.acquireComponent(c2, 'c3', NOT_FOUND)
NOT_FOUND
``lookupComponent(component, name, default=NOT_GIVEN, adaptTo=None, creationName=None, suggestParent=True)``
Lookup `name` as a component key relative to `component`. If the key cannot be
found, an ``exceptions.NameNotFound`` error will be raised unless a `default`
other than ``NOT_GIVEN`` is provided.
`name` can be any object that implements or is adaptable to ``IComponentKey``.
Such objects include ``peak.naming`` names, interface objects, property
names, and any custom objects you may create that implement ``IComponentKey``.
Strings will be converted to a URL, or to a ``ComponentName`` if they have
no URL prefix.
``ComponentName`` names are ``/``-separated attribute paths::
>>> binding.lookupComponent(c1, 'c2') is c2
True
If the first attribute name isn't found, it's looked up in the hierarchy using
``acquireComponent()``::
>>> binding.lookupComponent(c2, 'c2') is c2
True
Which of course will fail if the name isn't found::
>>> binding.lookupComponent(c1, 'c3')
Traceback (most recent call last):
...
NameNotFound: [remainingName=CompoundName(['c3']),resolvedObj=<...>]
Or fall back to the default if one is given::
>>> binding.lookupComponent(c1, 'c3', 99)
99
This also works with multi-part names: the first part is acquired, but the
other parts must be attributes of the component acquired by the first part::
>>> c3 = c2.c3 = binding.Component(c2, 'c3')
>>> c1.x = 1
>>> c2.x = 2
>>> c3.x = 3
>>> binding.lookupComponent(c2, 'c2/x')
2
>>> binding.lookupComponent(c3, 'c2/x')
2
>>> binding.lookupComponent(c2, 'c3/x')
3
>>> binding.lookupComponent(c3, 'c3/x')
3
>>> binding.lookupComponent(c3, 'c1/x') # (c1 isn't an attribute of root)
Traceback (most recent call last):
...
NameNotFound: [remainingName=CompoundName(['c1']),resolvedObj=<...>]
>>> binding.lookupComponent(c3, 'c1/x', 99)
99
>>> binding.lookupComponent(c3, 'c2/y')
Traceback (most recent call last):
...
NameNotFound: [resolvedName=ComponentName([]),remainingName=ComponentName(['y']),resolvedObj=<...>]
>>> binding.lookupComponent(c3, 'c2/y', 99)
99
Paths can be explicitly relative, using ``.`` and ``..`` to refer to the
current component or its parent::
>>> binding.lookupComponent(c2, './x')
2
>>> binding.lookupComponent(c2, '../x')
1
They can also be root-relative, by starting with a ``/``::
>>> binding.lookupComponent(root, '/getParentComponent')
<bound method ConfigurationRoot.getParentComponent...>
XXX IComponentKey
Assembling Components
=====================
XXX notifyUponAssembly, suggestParentComponent, IAttachable