"""Basic binding tools""" |
"""Basic binding tools""" |
|
|
from once import Once, New, WeakBinding |
from __future__ import generators |
import meta, modules |
from peak.api import * |
|
|
from weakref import ref, WeakValueDictionary |
from once import * |
|
from interfaces import * |
from peak.naming.names import toName, Syntax, CompoundName |
from weakref import WeakValueDictionary, ref |
from peak.naming.interfaces import NameNotFoundException |
from types import ModuleType |
from peak.util.EigenData import EigenRegistry |
from peak.naming.names import toName, AbstractName, COMPOUND_KIND |
|
from peak.naming.syntax import PathSyntax |
from Interface import Interface |
from peak.util.EigenData import EigenCell, AlreadyRead, EigenRegistry |
from peak.api import config, NOT_FOUND |
from peak.config.interfaces import IConfigKey, IPropertyMap, \ |
|
IConfigurationRoot, NullConfigRoot |
|
from peak.util.imports import importString |
|
from peak.interface import adapt |
|
from warnings import warn, warn_explicit |
|
|
|
class ComponentSetupWarning(UserWarning): |
|
"""Large iterator passed to suggestParentComponent""" |
|
|
__all__ = [ |
__all__ = [ |
'Component','AutoCreated','Provider','CachingProvider', |
'Base', 'Component', 'ComponentSetupWarning', |
'bindTo', 'requireBinding', 'bindToNames', 'bindToParent', 'bindToSelf', |
'bindTo', 'requireBinding', 'bindSequence', 'bindToParent', 'bindToSelf', |
'getRootComponent', 'getParentComponent', 'lookupComponent', |
'getRootComponent', 'getParentComponent', 'lookupComponent', |
'acquireComponent', 'globalLookup' |
'acquireComponent', 'suggestParentComponent', 'notifyUponAssembly', |
|
'bindToUtilities', 'bindToProperty', 'Constant', 'delegateTo', |
|
'getComponentName', 'getComponentPath', 'Acquire', 'ComponentName', |
] |
] |
|
|
|
|
InterfaceClass = Interface.__class__ |
class _proxy(Once): |
|
|
|
|
|
def __init__(self,attrName): |
|
self.attrName = attrName |
|
|
|
def usageError(self): |
|
raise AttributeError, self.attrName |
|
|
|
def computeValue(self,d,a): raise AttributeError, a |
|
|
|
def getComponentPath(component, relativeTo=None): |
|
|
|
"""Get 'ComponentName' that would traverse from 'relativeTo' to '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'.""" |
|
|
|
path = []; root=None |
|
|
|
if relativeTo is None: |
|
root = getRootComponent(component) |
|
|
|
c = component |
|
|
|
while 1: |
|
|
|
if c is root: |
|
path.append(''); break |
|
|
|
elif c is relativeTo: |
|
break |
|
|
|
path.append(getComponentName(c) or '*') |
|
|
|
c = getParentComponent(c) |
|
|
|
if c is None: |
|
break |
|
|
def Provider(callable): |
path.reverse() |
return lambda foundIn, forObj: callable(forObj) |
return ComponentName(path) |
|
|
|
|
def CachingProvider(callable, weak=False): |
|
|
|
def provider(foundIn, forObj): |
|
|
|
fid = id(foundIn) |
|
utility = provider.cache.get(fid) |
|
|
|
if utility is None: |
|
utility = provider.cache[fid] = callable(foundIn) |
|
|
|
return utility |
|
|
|
if weak: |
|
provider.cache = WeakValueDictionary() |
|
else: |
|
provider.cache = {} |
|
|
|
return provider |
|
|
|
|
|
|
|
|
|
|
def Constant(provides, value, doc=None): |
|
"""Supply a constant as a property or utility""" |
|
return Once(lambda s,d,a: value, provides=provides, doc=doc) |
|
|
|
|
|
def getParentComponent(component): |
|
|
|
"""Return parent of 'component', or 'None' if root or non-component""" |
|
|
|
try: |
|
gpc = component.__class__.getParentComponent |
|
|
|
except AttributeError: |
|
|
|
if isinstance(component,ModuleType): |
|
m = '.'.join(component.__name__.split('.')[:-1]) |
|
if m: return importString(m) |
|
|
|
else: |
|
return gpc(component) |
|
|
|
|
|
def getComponentName(component): |
|
|
|
"""Return name of 'component', or 'None' if root or non-component""" |
|
|
|
try: |
|
gcn = component.__class__.getComponentName |
|
|
|
except AttributeError: |
|
|
|
if isinstance(component,ModuleType): |
|
return component.__name__.split('.')[-1] |
|
|
|
else: |
|
return gcn(component) |
|
|
def getParentComponent(component): |
|
|
|
"""Return parent of 'component', or 'None' if root or non-component""" |
|
|
|
try: |
|
gpc = component.getParentComponent |
|
except AttributeError: |
|
pass |
|
else: |
|
return gpc() |
|
|
|
|
|
def getRootComponent(component): |
def getRootComponent(component): |
return component |
return component |
|
|
|
|
def globalLookup(name, component=None): |
|
|
|
"""Lookup 'name' in global 'InitialContext', w/'component' in environ""" |
|
|
|
from peak.naming.api import InitialContext |
def notifyUponAssembly(parent,child): |
|
|
return InitialContext(RELATIVE_TO_COMPONENT=component).lookup(name) |
"""Call 'child.uponAssembly()' as soon as 'parent' knows all its parents""" |
|
|
|
try: |
|
nua = parent.__class__.notifyUponAssembly |
|
|
|
except AttributeError: |
|
|
|
parent = getParentComponent(parent) |
|
|
|
if parent is None: |
|
child.uponAssembly() |
|
else: |
|
notifyUponAssembly(parent,child) |
|
|
|
else: |
|
nua(parent,child) |
|
|
|
|
|
|
|
|
|
|
|
|
def acquireComponent(component, name): |
|
|
|
"""Acquire 'name' relative to 'component', w/fallback to globalLookup()""" |
|
|
|
target = component |
def acquireComponent(component, name, creationName=None): |
|
|
|
"""Acquire 'name' relative to 'component', w/fallback to naming.lookup() |
|
|
|
'name' is looked for as an attribute of 'component'. If not found, |
|
the component's parent will be searched, and so on until the root component |
|
is reached. If 'name' is still not found, and the root component |
|
implements 'config.IConfigurationRoot', the name will be looked up in the |
|
default naming context, if any. Otherwise, a NameNotFound error will be |
|
raised.""" |
|
|
|
prev = target = component |
|
|
while target is not None: |
while target is not None: |
|
|
if ob is not NOT_FOUND: |
if ob is not NOT_FOUND: |
return ob |
return ob |
|
|
|
prev = target |
target = getParentComponent(target) |
target = getParentComponent(target) |
|
|
else: |
else: |
return globalLookup(name, component) |
return adapt( |
|
prev, IConfigurationRoot, NullConfigRoot |
|
).nameNotFound( |
|
prev, name, component, creationName |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
class ComponentName(AbstractName): |
|
|
|
"""Path between components |
|
|
|
Component Path Syntax |
|
|
|
Paths are '"/"' separated attribute names. Path segments of '"."' and |
|
'".."' mean the same as they do in URLs. A leading '"/"' (or a |
|
compound name beginning with an empty path segment), will be treated |
|
as an "absolute path" relative to the component's root component. |
|
|
|
Paths beginning with anything other than '"/"', '"./"', or '"../"' are |
|
acquired, which means that the first path segment will be looked |
|
up using 'acquireComponent()' before processing the rest of the path. |
|
(See 'acquireComponent()' for more details.) If you do not want |
|
a name to be acquired, simply prefix it with './' so it is relative |
|
to the starting object. |
|
|
|
All path segments after the first are interpreted as attribute names |
|
to be looked up, beginning at the component referenced by the first |
|
path segment. '.' and '..' are interpreted the same as for the first |
|
path segment. |
|
""" |
|
|
|
nameKind = COMPOUND_KIND |
|
|
|
syntax = PathSyntax( |
|
direction = 1, |
|
separator = '/', |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
ComponentNameSyntax = Syntax( |
|
direction = 1, |
|
separator = '/', |
|
) |
|
|
|
|
|
def ComponentName(nameStr): |
|
return CompoundName(nameStr, ComponentNameSyntax) |
|
|
|
|
|
_getFirstPathComponent = dict( ( |
_getFirstPathComponent = dict( ( |
|
|
|
|
|
|
def lookupComponent(component, name): |
|
|
|
"""Lookup 'name' relative to 'component' |
|
|
|
'name' can be any name acceptable to the 'peak.naming' package, or an |
|
Interface object. Strings and 'CompoundName' names will be interpreted |
|
as paths relative to the starting component. An empty name will return |
|
the starting component. Interfaces will be lookedup using |
|
'component.acquireUtility()'. All other kinds of names, including URL |
|
strings and 'CompositeName' instances, will be looked up using |
|
'binding.globalLookup()'. |
|
|
|
Regardless of how the lookup is processed, a 'naming.NameNotFoundException' |
|
will be raised if the name cannot be found. |
|
|
|
Component Path Syntax |
|
|
|
Paths are '/'-separated attribute names. Path segments of '.' and |
|
'..' mean the same as they do in URLs. A leading '/' (or a |
|
CompoundName beginning with an empty path segment), will be treated |
|
as an "absolute path" relative to the component's root component. |
|
|
|
Paths beginning with anything other than '/', './', or '../' are |
|
acquired, which means that the first path segment will be looked |
|
up using 'acquireComponent()' before processing the rest of the path. |
|
(See 'acquireComponent()' for more details.) If you do not want |
|
a name to be acquired, simply prefix it with './' so it is relative |
|
to the starting object. |
|
|
|
All path segments after the first are interpreted as attribute names |
|
to be looked up, beginning at the component referenced by the first |
|
path segment. '.' and '..' are interpreted the same as for the first |
|
path segment.""" |
|
|
|
if isinstance(name, InterfaceClass): |
|
utility = component.acquireUtility(name) |
|
if utility is None: |
def lookupComponent(component, name, default=NOT_GIVEN, creationName=None): |
raise NameNotFoundException(name, resolvedObj = component) |
|
|
"""Lookup 'name' relative to 'component' |
|
|
|
'name' can be any name acceptable to the 'peak.naming' package, or an |
|
Interface object. Strings and compound names will be interpreted |
|
as paths relative to the starting component. (See the 'ComponentName' |
|
class for details of path interpretation.) An empty name will return |
|
the starting component. Interfaces and Properties will be looked up using |
|
'config.findUtility(component, name)'. All other kinds of names, |
|
including URL strings and 'CompositeName' instances, will be looked up |
|
using 'naming.lookup()'. |
|
|
|
Regardless of how the lookup is processed, an 'exceptions.NameNotFound' |
|
error will be raised if the name cannot be found.""" |
|
|
|
|
|
if IConfigKey.isImplementedBy(name): |
|
return config.findUtility(component, name, default) |
|
|
parsedName = toName(name, ComponentName, 1) |
parsedName = toName(name, ComponentName, 1) |
|
|
|
if not parsedName.nameKind == COMPOUND_KIND: |
# URL's and composite names must be handled globally |
# URL's and composite names must be handled globally |
|
try: |
|
return naming.lookup(component, name, |
|
creationParent=component, creationName=creationName |
|
) |
|
except exceptions.NameNotFound: |
|
if default is NOT_GIVEN: |
|
raise |
|
return default |
|
|
if not parsedName.isCompound: |
if not parsedName: # empty name refers to self |
return globalLookup(name, component) |
|
|
|
if not parsedName: |
|
# empty name refers to self |
|
return component |
return component |
|
|
parts = iter(parsedName) |
parts = iter(parsedName) |
|
|
attr = parts.next() # first part |
attr = parts.next() # first part |
pc = _getFirstPathComponent(attr) |
pc = _getFirstPathComponent(attr) |
|
|
if pc: |
|
ob = pc(component) |
if pc: ob = pc(component) |
else: |
else: ob = acquireComponent(component, attr, creationName) |
ob = acquireComponent(component,attr) |
|
|
|
resolved = [] |
resolved = [] |
append = resolved.append |
append = resolved.append |
try: |
try: |
for attr in parts: |
for attr in parts: |
pc = _getNextPathComponent(attr) |
pc = _getNextPathComponent(attr) |
if pc: |
if pc: ob = pc(ob) |
ob = pc(ob) |
else: ob = getattr(ob,attr) |
else: |
|
ob = getattr(ob,attr) |
|
append(attr) |
append(attr) |
|
|
except AttributeError: |
except AttributeError: |
|
|
raise NameNotFoundException( |
if default is not NOT_GIVEN: |
|
return default |
|
|
|
raise exceptions.NameNotFound( |
resolvedName = ComponentName(resolved), |
resolvedName = ComponentName(resolved), |
remainingName = ComponentName([attr] + [a for a in parts]), |
remainingName = ComponentName([attr] + [a for a in parts]), |
resolvedObj = ob |
resolvedObj = ob |
|
|
return ob |
return ob |
|
|
class bindTo(Once): |
|
|
|
"""Automatically look up and cache a relevant component |
|
|
|
Usage:: |
|
|
|
class someClass(binding.Component): |
|
|
|
thingINeed = binding.bindTo("path/to/service") |
|
|
|
getOtherThing = binding.bindTo("some/thing", weak=True) |
|
|
|
'someClass' can then refer to 'self.thingINeed' instead of |
|
calling 'self.lookupComponent("path/to/service")' repeatedly. |
|
It can also do 'self.getOtherThing()' to get '"some/thing"'. |
|
(The 'weak' argument, if true, means to bind to a weak reference.) |
|
""" |
|
|
|
singleValue = True |
|
|
|
def __init__(self,targetName,weak=False,provides=None): |
|
|
|
self.targetNames = (targetName,) |
|
self.weak = weak |
|
self._provides=provides |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class bindTo(Once): |
|
|
|
"""Automatically look up and cache a relevant component |
|
|
|
Usage:: |
|
|
|
class someClass(binding.Component): |
|
|
|
thingINeed = binding.bindTo("path/to/service") |
|
|
|
'someClass' can then refer to 'self.thingINeed' instead of |
|
calling 'self.lookupComponent("path/to/service")' repeatedly. |
|
""" |
|
|
|
singleValue = True |
|
|
|
def __init__(self,targetName,provides=None,doc=None, |
|
activateUponAssembly=False): |
|
|
|
self.targetNames = (targetName,) |
|
self.declareAsProviderOf = provides |
|
self.__doc__ = doc or ("binding.bindTo(%r)" % targetName) |
|
self.activateUponAssembly = activateUponAssembly |
|
|
def computeValue(self, obj, instanceDict, attrName): |
def computeValue(self, obj, instanceDict, attrName): |
|
|
names = self.targetNames |
names = self.targetNames |
obs = map(obj.lookupComponent, names) |
obs = [lookupComponent(obj,n,attrName) for n in names] |
|
|
for name,newOb in zip(names, obs): |
for name,newOb in zip(names, obs): |
|
|
if newOb is NOT_FOUND: |
if newOb is NOT_FOUND: |
|
|
del instanceDict[attrName] |
del instanceDict[attrName] |
raise NameNotFoundError(attrName, resolvedName = name) |
raise exceptions.NameNotFound(attrName, resolvedName = name) |
|
|
if self.singleValue: |
if self.singleValue: |
|
|
if self.weak: |
|
return ref(newOb) |
|
else: |
|
return newOb |
return newOb |
|
|
if self.weak: |
|
obs = map(ref,obs) |
|
|
|
return tuple(obs) |
return tuple(obs) |
|
|
|
class bindSequence(bindTo): |
|
|
|
"""Automatically look up and cache a sequence of services by name |
|
|
|
Usage:: |
|
|
|
class someClass(binding.AutoCreated): |
|
|
|
thingINeed = binding.bindSequence( |
|
"path/to/service", "another/path", ... |
|
) |
|
|
|
'someClass' can then refer to 'self.thingINeed' to get a tuple of |
|
services instead of calling 'self.lookupComponent()' on a series of |
|
names. |
|
""" |
|
|
|
singleValue = False |
|
|
|
def __init__(self, *targetNames, **kw): |
|
self.targetNames = targetNames |
|
self.declareAsProviderOf = kw.get('provides') |
|
self.__doc__ = kw.get('doc',("binding.bindSequence%s" % `targetNames`)) |
|
self.activateUponAssembly = kw.get('activateUponAssembly') |
|
|
|
|
|
|
|
|
|
|
|
|
class bindToNames(bindTo): |
|
|
|
"""Automatically look up and cache a sequence of services by name |
|
|
|
Usage:: |
|
|
|
class someClass(binding.AutoCreated): |
|
|
|
thingINeed = binding.bindToNames( |
|
"path/to/service", "another/path", ... |
|
) |
|
|
|
'someClass' can then refer to 'self.thingINeed' to get a tuple of |
|
services instead of calling 'self.lookupComponent()' on a series of |
|
names. As with 'bindTo()', a 'weak' keyword argument can be set to |
|
indicate that the sequence should consist of weak references to the |
|
named objects. |
|
""" |
|
|
|
singleValue = False |
|
|
|
def __init__(self, *targetNames, **kw): |
def suggestParentComponent(parent,name,child): |
self.targetNames = targetNames |
|
self.weak = kw.get('weak') |
|
self._provides = kw.get('provides') |
|
|
|
|
"""Suggest to 'child' that it has 'parent' and 'name' |
|
|
|
If 'child' does not support 'IComponent' and is a non-string, reiterable |
|
container, all of its elements that support 'IComponent' will be given |
|
a suggestion to use 'parent' and 'name' as well. Note that this |
|
means it would not be a good idea to use this on, say, a 10,000 element |
|
list or dictionary (especially if the objects in it aren't components), |
|
because this function has to check all of them.""" |
|
|
|
ob = adapt(child,IComponent,None) |
|
|
|
if ob is not None: |
|
# Tell it directly |
|
ob.setParentComponent(parent,name,suggest=True) |
|
|
|
elif not isinstance(child,(str,unicode)): |
|
|
|
# Check for a sequence of components |
|
|
|
try: |
|
i = iter(child) |
|
except TypeError: |
|
return |
|
|
|
if i is not child: # avoid non-reiterables |
|
ct = 0 |
|
for ob in i: |
|
ob = adapt(ob,IComponent,None) |
|
if ob is not None: |
|
ob.setParentComponent(parent,name,suggest=True) |
|
else: |
|
ct += 1 |
|
if ct==100: |
|
warn( |
|
("Large iterator for %s; if it will never" |
|
" contain components, this is wasteful" % name), |
|
ComponentSetupWarning, 3 |
|
) |
|
|
|
def delegateTo(delegateAttr, name=None, provides=None, doc=None): |
|
|
|
"""Delegate attribute to the same attribute of another object |
|
|
|
Usage:: |
|
|
|
class PasswordFile(binding.Component): |
|
shadow = binding.bindTo('config:etc.shadow/') |
|
checkPwd = changePwd = binding.delegateTo('shadow') |
|
|
|
The above is equivalent to this longer version:: |
|
|
|
class PasswordFile(binding.Component): |
|
shadow = binding.bindTo('config:etc.shadow/') |
|
checkPwd = binding.bindTo('shadow/checkPwd') |
|
changePwd = binding.bindTo('shadow/changePwd') |
|
|
|
Because 'delegateTo' uses the attribute name being looked up, you do not |
|
need to create a separate binding for each attribute that is delegated, |
|
as you do when using 'bindTo()'.""" |
|
|
|
return Once( |
|
lambda s,d,a: getattr(getattr(s,delegateAttr),a), name, provides, doc |
|
) |
|
|
|
def Acquire(key,doc=None,activateUponAssembly=False): |
|
"""Provide a utility or property, but look it up if not supplied |
|
|
|
'key' must be a configuration key (e.g. an Interface or a PropertyName). |
|
If the attribute defined by this binding is not set, it will be looked up |
|
by finding the appropriate utility or property. The attribute will also |
|
be registered as a source of that utility or property for child components. |
|
This allows you to easily override the configuration of the utility or |
|
property within a particular component subtree, simply by setting the |
|
attribute (e.g. via a constructor keyword).""" |
|
|
|
if not IConfigKey.isImplementedBy(key): |
|
raise exceptions.InvalidName("Not a configuration key:", key) |
|
|
|
return bindTo(key,key,doc,activateUponAssembly=activateUponAssembly) |
|
|
class bindToParent(WeakBinding): |
def bindToParent(level=1, name=None, provides=None, doc=None): |
|
|
"""Look up and cache a weak ref to the nth-level parent component |
"""Look up and cache a reference to the nth-level parent component |
|
|
Usage:: |
Usage:: |
|
|
'self.getParentComponent().getParentComponent()'. |
'self.getParentComponent().getParentComponent()'. |
""" |
""" |
|
|
def __init__(self,level=1,provides=None): |
def computeValue(obj, instDict, attrName): |
self.level = level |
|
self._provides = provides |
|
|
|
def computeValue(self, obj, instDict, attrName): |
for step in range(level): |
|
newObj = getParentComponent(obj) |
for step in range(self.level): |
|
newObj = obj.getParentComponent() |
|
if newObj is None: break |
if newObj is None: break |
obj = newObj |
obj = newObj |
|
|
return obj |
return obj |
|
|
|
return Once(computeValue, name=name, provides=provides, doc=doc) |
|
|
|
|
def bindToSelf(provides=None): |
def bindToSelf(name=None, provides=None, doc=None): |
|
|
"""Weak reference to the 'self' object |
"""Cached reference to the 'self' object |
|
|
This is just a shortcut for 'bindToParent(0)', and does pretty much what |
This is just a shortcut for 'bindToParent(0)', and does pretty much what |
you'd expect. It's handy for objects that provide default support for |
you'd expect. It's handy for objects that provide default support for |
can refer to 'self.delegateForInterfaceX.someMethod()', and have |
can refer to 'self.delegateForInterfaceX.someMethod()', and have |
'delegateForInterfaceX' be a 'bindToSelf()' by default.""" |
'delegateForInterfaceX' be a 'bindToSelf()' by default.""" |
|
|
return bindToParent(0,provides) |
return bindToParent(0,name,provides,doc) |
|
|
|
|
|
|
|
|
class requireBinding(Once): |
class requireBinding(Once): |
|
|
"""Placeholder for a binding that should be (re)defined by a subclass""" |
"""Placeholder for a binding that should be (re)defined by a subclass""" |
|
|
def __init__(self,description="",provides=None): |
def __init__(self,description="",name=None,provides=None,doc=None): |
self.description = description |
self.description = description |
self._provides = provides |
self.declareAsProviderOf = provides |
|
self.__doc__ = doc or ("binding.requireBinding: %s" % description) |
|
self.attrName = self.__name__ = name |
|
|
def computeValue(self, obj, instanceDict, attrName): |
def computeValue(self, obj, instanceDict, attrName): |
|
|
) |
) |
|
|
|
|
|
def bindToUtilities(iface,provides=None,doc=None,activateUponAssembly=False): |
|
|
|
"""Binding to a list of all 'iface' utilities above the component""" |
|
|
|
return Once(lambda s,d,a: list(config.findUtilities(s,iface)), |
|
provides=provides, doc=doc, activateUponAssembly=activateUponAssembly |
|
) |
|
|
|
|
|
def bindToProperty(propName, default=NOT_GIVEN, provides=None, doc=None, |
|
activateUponAssembly=False): |
|
|
|
"""Binding to property 'propName', defaulting to 'default' if not found |
|
|
|
If 'default' is not supplied, failure to locate the named property |
|
will result in a 'config.PropertyNotFound' exception. |
|
""" |
|
|
|
propName = PropertyName(propName) |
|
|
|
return Once(lambda s,d,a: config.getProperty(s,propName,default), |
|
provides=provides, doc=doc, activateUponAssembly = activateUponAssembly |
|
) |
|
|
|
class _Base(object): |
|
|
|
"""Basic attribute management and "active class" support""" |
|
|
|
__metaclass__ = ActiveClass |
|
__implements__ = IBindableAttrs |
|
|
|
def _setBinding(self, attr, value, useSlot=False): |
|
|
|
self._bindingChanging(attr,value,useSlot) |
|
|
|
if useSlot: |
|
getattr(self.__class__,attr).__set__(self,value) |
|
|
|
else: |
|
self.__dict__[attr] = value |
|
|
|
|
|
def _getBinding(self, attr, default=None, useSlot=False): |
|
|
|
if useSlot: |
|
val = getattr(self,attr,default) |
|
|
|
else: |
|
val = self.__dict__.get(attr,default) |
|
|
|
if val is not default: |
|
|
|
val = self._postGet(attr,val,useSlot) |
|
|
|
if val is NOT_FOUND: |
|
return default |
|
|
|
return val |
|
|
|
|
|
|
class Component(object): |
|
|
|
"""Thing that can be composed into a component tree, w/binding & lookups""" |
|
|
|
__metaclasses__ = ( |
|
meta.AssertInterfaces, meta.ActiveDescriptors |
|
) |
|
|
|
# use the global lookupComponent function as a method |
|
|
|
lookupComponent = lookupComponent |
def _getBindingFuncs(klass, attr, useSlot=False): |
|
if useSlot: |
|
d = getattr(klass,attr) |
|
else: |
|
d = _proxy(attr) |
|
return d.__get__, d.__set__, d.__delete__ |
|
|
def setParentComponent(self,parent): |
_getBindingFuncs = classmethod(_getBindingFuncs) |
from weakref import ref |
|
self.getParentComponent = ref(parent) |
|
|
|
def getParentComponent(self): |
|
return None |
|
|
|
def _componentName(self, dict, name): |
def _delBinding(self, attr, useSlot=False): |
return self.__class__.__name__.split('.')[-1] |
|
|
self._bindingChanging(attr, NOT_FOUND, useSlot) |
|
|
|
if useSlot: |
|
d = getattr(self.__class__,attr).__delete__ |
|
|
|
try: |
|
d(self) |
|
except AttributeError: |
|
pass |
|
|
|
elif attr in self.__dict__: |
|
del self.__dict__[attr] |
|
|
_componentName = Once(_componentName) |
def _hasBinding(self,attr,useSlot=False): |
|
|
__instance_provides__ = New(EigenRegistry) |
if useSlot: |
|
return hasattr(self,attr) |
|
else: |
|
return attr in self.__dict__ |
|
|
__class_provides__ = EigenRegistry() |
|
|
|
|
def _bindingChanging(self,attr,newval,isSlot=False): |
|
pass |
|
|
|
|
|
def _postGet(self,attr,value,isSlot=False): |
|
return value |
|
|
|
|
|
class Component(_Base): |
|
|
|
"""Thing that can be composed into a component tree, w/binding & lookups""" |
|
|
|
__class_implements__ = IComponentFactory |
|
__implements__ = IComponent |
|
|
|
|
|
def __init__(self, parentComponent=NOT_GIVEN, componentName=None, **kw): |
|
# Set up keywords first, so state is sensible |
|
if kw: |
|
klass = self.__class__ |
|
suggest = []; add = suggest.append; sPC = suggestParentComponent |
|
|
|
for kv in kw.iteritems(): |
|
k,v = kv |
|
if hasattr(klass,k): |
|
add(kv); setattr(self,k,v) |
|
else: |
|
raise TypeError( |
|
"%s constructor has no keyword argument %s" % |
|
(klass, k) |
|
) |
|
|
|
# Suggest parents only after our attrs are stable |
|
for k,v in suggest: |
|
sPC(self,k,v) |
|
|
|
# set our parent component and possibly invoke assembly events |
|
if parentComponent is not NOT_GIVEN or componentName is not None: |
|
self.setParentComponent(parentComponent,componentName) |
|
|
|
lookupComponent = lookupComponent |
|
|
|
|
|
def fromZConfig(klass, section): |
|
"""Classmethod: Create an instance from a ZConfig 'section'""" |
|
return klass(**section.__dict__) |
|
|
def acquireUtility(self, iface, forObj=None, localLookup=True): |
fromZConfig = classmethod(fromZConfig) |
|
|
if forObj is None: |
def setParentComponent(self, parentComponent, componentName=None, |
forObj=self |
suggest=False): |
|
|
if localLookup: |
pc = self.__parentSetting |
|
|
provider = self.__instance_provides__.get(iface) |
if pc is NOT_GIVEN: |
|
self.__parentSetting = parentComponent |
|
self.__componentName = componentName |
|
self.__parentComponent # lock and invoke assembly events |
|
return |
|
|
if provider is not None: |
elif suggest: |
return provider(self,forObj) |
return |
|
|
attr = self.__class_provides__.get(iface) |
raise AlreadyRead( |
|
"Component %r already has parent %r; tried to set %r" |
|
% (self,pc,parentComponent) |
|
) |
|
|
if attr is not None: |
__parentSetting = NOT_GIVEN #New(EigenCell) |
|
__componentName = None |
|
|
utility = getattr(self,attr) |
|
|
|
if utility is not NOT_FOUND: |
def __parentComponent(self,d,a): |
return utility |
|
|
|
parent = self.getParentComponent() |
parent = self.__parentSetting |
|
if parent is NOT_GIVEN: |
|
parent = self.__parentSetting = None |
|
|
|
d[a] = parent |
if parent is None: |
if parent is None: |
parent = config.getLocal(self) |
self.uponAssembly() |
|
elif (self.__class__.__attrsToBeAssembled__ |
|
or self._getBinding('__objectsToBeAssembled__')): |
|
notifyUponAssembly(parent,self) |
|
|
return parent.acquireUtility(iface,forObj) |
return parent |
|
|
|
__parentComponent = Once(__parentComponent) |
|
|
def registerProvider(self, ifaces, provider): |
|
self.__instance_provides__.register(ifaces, provider) |
|
|
|
|
def getParentComponent(self): |
|
return self.__parentComponent |
|
|
|
def getComponentName(self): |
|
return self.__componentName |
|
|
|
__instance_provides__ = New( |
|
'peak.config.config_components:PropertyMap', provides=IPropertyMap |
|
) |
|
|
|
|
|
def _getConfigData(self, forObj, configKey): |
|
|
|
attr = self._getBinding('__instance_provides__') |
|
|
|
if attr: |
|
value = attr.getValueFor(forObj, configKey) |
|
|
|
if value is not NOT_FOUND: |
|
return value |
|
|
|
attr = self.__class__.__class_provides__.get(configKey) |
|
|
class AutoCreatable(type): |
if attr: |
|
return getattr(self, attr, NOT_FOUND) |
|
|
"""Metaclass for components which auto-create when used""" |
return NOT_FOUND |
|
|
|
|
|
def registerProvider(self, configKeys, provider): |
|
self.__instance_provides__.registerProvider(configKeys, provider) |
|
|
def __get__(self, obj, typ=None): |
|
|
|
if obj is None: |
|
return self |
|
|
|
newOb = self(obj) |
|
|
|
obj.__dict__[newOb._componentName] = newOb |
|
return newOb |
|
|
|
|
|
class AutoCreated(Component): |
|
|
|
"""Component that auto-creates itself in instances of its containing class |
|
""" |
|
|
|
__metaclasses__ = AutoCreatable, |
|
|
|
def __init__(self, parent=None): |
|
|
|
super(AutoCreated,self).__init__() |
def notifyUponAssembly(self,child): |
|
|
if parent is not None: |
tba = self.__objectsToBeAssembled__ |
self.setParentComponent(parent) |
|
|
|
|
if tba is None: |
|
child.uponAssembly() # assembly has already occurred |
|
else: |
|
tba.append(child) # save reference to child for callback |
|
|
|
|
|
def uponAssembly(self): |
|
|
|
tba = self.__objectsToBeAssembled__ |
|
|
|
if tba is None: |
|
return |
|
|
|
self.__objectsToBeAssembled__ = None |
|
|
|
try: |
|
while tba: |
|
ob = tba.pop() |
|
try: |
|
ob.uponAssembly() |
|
except: |
|
tba.append(ob) |
|
raise |
|
|
|
for attr in self.__class__.__attrsToBeAssembled__: |
|
getattr(self,attr) |
|
|
|
except: |
|
self.__objectsToBeAssembled__ = tba |
|
raise |
|
|
|
|
|
|
|
|
|
|
|
|
modules.setupModule() |
|
|
|
|
__objectsToBeAssembled__ = New(list) |
|
|
|
def __attrsToBeAssembled__(klass,d,a): |
|
aa = {} |
|
map(aa.update, getInheritedRegistries(klass, '__attrsToBeAssembled__')) |
|
|
|
for attrName, descr in klass.__class_descriptors__.items(): |
|
notify = getattr(descr,'activateUponAssembly',False) |
|
if notify: aa[attrName] = True |
|
|
|
return aa |
|
|
|
__attrsToBeAssembled__ = classAttr(Once(__attrsToBeAssembled__)) |
|
|
|
|
|
def __class_provides__(klass,d,a): |
|
|
|
cp = EigenRegistry() |
|
map(cp.update, getInheritedRegistries(klass, '__class_provides__')) |
|
|
|
for attrName, descr in klass.__class_descriptors__.items(): |
|
provides = getattr(descr,'declareAsProviderOf',None) |
|
if provides is not None: |
|
cp.register(provides, attrName) |
|
|
|
return cp |
|
|
|
__class_provides__ = classAttr(Once(__class_provides__)) |
|
|
|
|
|
|
|
|
|
|
|
Base = Component # XXX backward compatibility; deprecated |
|
|
|
|
|
|