"""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 |
from peak.naming.interfaces import NameNotFoundException |
|
from peak.util.EigenData import EigenRegistry |
from peak.naming.names import toName, Syntax, Name |
|
from peak.util.EigenData import EigenRegistry, EigenCell |
from Interface import Interface |
from peak.config.interfaces import IConfigKey, IPropertyMap |
from peak.api import config, NOT_FOUND |
from peak.util.imports import importString |
|
|
|
|
__all__ = [ |
__all__ = [ |
'Component','AutoCreated','Provider','CachingProvider', |
'Base', 'Component','AutoCreated', 'AutoCreatable', |
'bindTo', 'requireBinding', 'bindToNames', 'bindToParent', 'bindToSelf', |
'bindTo', 'requireBinding', 'bindSequence', 'bindToParent', 'bindToSelf', |
'getRootComponent', 'getParentComponent', 'lookupComponent', |
'getRootComponent', 'getParentComponent', 'lookupComponent', |
'acquireComponent', 'globalLookup' |
'acquireComponent', 'globalLookup', |
|
'bindToUtilities', 'bindToProperty', 'Constant', |
|
'getComponentName', 'getComponentPath', 'Acquire', 'ComponentName', |
] |
] |
|
|
|
|
InterfaceClass = Interface.__class__ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def Provider(callable): |
def getComponentPath(component, relativeTo=None): |
return lambda foundIn, forObj: callable(forObj) |
|
|
|
|
"""Get 'ComponentName' that would traverse from 'relativeTo' to 'component' |
|
|
def CachingProvider(callable, weak=False): |
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'.""" |
|
|
def provider(foundIn, forObj): |
path = []; root=None |
|
|
fid = id(foundIn) |
if relativeTo is None: |
utility = provider.cache.get(fid) |
root = getRootComponent(component) |
|
|
if utility is None: |
c = component |
utility = provider.cache[fid] = callable(foundIn) |
|
|
|
return utility |
while 1: |
|
|
if weak: |
if c is root: |
provider.cache = WeakValueDictionary() |
path.append(''); break |
else: |
|
provider.cache = {} |
|
|
|
return provider |
elif c is relativeTo: |
|
break |
|
|
|
path.append(getComponentName(c) or '*') |
|
|
|
c = getParentComponent(c) |
|
|
|
if c is None: |
|
break |
|
|
|
path.reverse() |
|
return ComponentName(path) |
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
pass |
|
else: |
|
return gpc(component) |
|
|
|
|
def getParentComponent(component): |
def getComponentName(component): |
|
|
"""Return parent of 'component', or 'None' if root or non-component""" |
"""Return name of 'component', or 'None' if root or non-component""" |
|
|
try: |
try: |
gpc = component.getParentComponent |
gcn = component.__class__.getComponentName |
except AttributeError: |
except AttributeError: |
pass |
pass |
else: |
else: |
return gpc() |
return gcn(component) |
|
|
|
|
def getRootComponent(component): |
def getRootComponent(component): |
|
|
return component |
return component |
|
|
|
def globalLookup(name, component=None, targetName=None): |
|
|
def globalLookup(name, component=None): |
"""Lookup 'name' in global 'InitialContext', relative to 'component'""" |
|
|
"""Lookup 'name' in global 'InitialContext', w/'component' in environ""" |
|
|
|
from peak.naming.api import InitialContext |
|
|
|
return InitialContext(RELATIVE_TO_COMPONENT=component).lookup(name) |
|
|
|
|
|
|
return naming.lookup(name, component, |
|
creationParent=component, creationName=targetName |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
def acquireComponent(component, name): |
def acquireComponent(name, component=None, targetName=None): |
|
|
"""Acquire 'name' relative to 'component', w/fallback to globalLookup()""" |
"""Acquire 'name' relative to 'component', w/fallback to globalLookup()""" |
|
|
target = getParentComponent(target) |
target = getParentComponent(target) |
|
|
else: |
else: |
return globalLookup(name, component) |
return globalLookup(name, component, targetName) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ComponentName(Name): |
|
|
|
"""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. |
|
""" |
|
|
|
isCompound = 1 |
|
|
ComponentNameSyntax = Syntax( |
syntax = Syntax( |
direction = 1, |
direction = 1, |
separator = '/', |
separator = '/', |
) |
) |
|
|
|
|
def ComponentName(nameStr): |
|
return CompoundName(nameStr, ComponentNameSyntax) |
|
|
|
|
|
_getFirstPathComponent = dict( ( |
|
('', getRootComponent), |
|
('.', lambda x:x), |
|
('..', getParentComponent), |
|
) ).get |
|
|
|
|
|
_getNextPathComponent = dict( ( |
|
('', lambda x:x), |
|
('.', lambda x:x), |
|
('..', getParentComponent), |
|
) ).get |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def lookupComponent(name, component=None): |
|
|
|
"""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. An empty name will return |
|
the starting component. Interfaces and Properties will be looked up using |
|
'config.findUtility(name, component)'. 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, an 'exceptions.NameNotFound' |
|
error will be raised if the name cannot be found.""" |
|
|
|
return _lookupComponent(component, name) |
|
|
|
|
|
|
|
_getFirstPathComponent = dict( ( |
|
('', getRootComponent), |
|
('.', lambda x:x), |
|
('..', getParentComponent), |
|
) ).get |
|
|
|
|
|
_getNextPathComponent = dict( ( |
|
('', lambda x:x), |
|
('.', lambda x:x), |
|
('..', getParentComponent), |
|
) ).get |
|
|
|
|
|
|
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 |
def _lookupComponent(component, name, targetName=None): |
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): |
if IConfigKey.isImplementedBy(name): |
utility = component.acquireUtility(name) |
return config.findUtility(name, component) |
if utility is None: |
|
raise NameNotFoundException(name, resolvedObj = component) |
|
|
|
parsedName = toName(name, ComponentName, 1) |
parsedName = toName(name, ComponentName, 1) |
|
|
# URL's and composite names must be handled globally |
|
|
|
if not parsedName.isCompound: |
if not parsedName.isCompound: |
return globalLookup(name, component) |
# URL's and composite names must be handled globally |
|
return globalLookup(name, component, targetName) |
|
|
if not parsedName: |
if not parsedName: # empty name refers to self |
# 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: |
if pc: ob = pc(component) |
ob = pc(component) |
else: ob = acquireComponent(attr, component, targetName) |
else: |
|
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( |
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 |
|
|
thingINeed = binding.bindTo("path/to/service") |
thingINeed = binding.bindTo("path/to/service") |
|
|
getOtherThing = binding.bindTo("some/thing", weak=True) |
|
|
|
'someClass' can then refer to 'self.thingINeed' instead of |
'someClass' can then refer to 'self.thingINeed' instead of |
calling 'self.lookupComponent("path/to/service")' repeatedly. |
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 |
singleValue = True |
|
|
def __init__(self,targetName,weak=False,provides=None): |
|
|
def __init__(self,targetName,provides=None,doc=None): |
|
|
self.targetNames = (targetName,) |
self.targetNames = (targetName,) |
self.weak = weak |
|
self._provides=provides |
self._provides=provides |
|
self.__doc__ = doc or ("binding.bindTo(%r)" % targetName) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class bindToNames(bindTo): |
|
|
|
"""Automatically look up and cache a sequence of services by name |
"""Automatically look up and cache a sequence of services by name |
|
|
|
|
class someClass(binding.AutoCreated): |
class someClass(binding.AutoCreated): |
|
|
thingINeed = binding.bindToNames( |
thingINeed = binding.bindSequence( |
"path/to/service", "another/path", ... |
"path/to/service", "another/path", ... |
) |
) |
|
|
'someClass' can then refer to 'self.thingINeed' to get a tuple of |
'someClass' can then refer to 'self.thingINeed' to get a tuple of |
services instead of calling 'self.lookupComponent()' on a series of |
services instead of calling 'self.lookupComponent()' on a series of |
names. As with 'bindTo()', a 'weak' keyword argument can be set to |
names. |
indicate that the sequence should consist of weak references to the |
|
named objects. |
|
""" |
""" |
|
|
singleValue = False |
singleValue = False |
|
|
def __init__(self, *targetNames, **kw): |
def __init__(self, *targetNames, **kw): |
self.targetNames = targetNames |
self.targetNames = targetNames |
self.weak = kw.get('weak') |
|
self._provides = kw.get('provides') |
self._provides = kw.get('provides') |
|
self.__doc__ = kw.get('doc',("binding.bindSequence%s" % `targetNames`)) |
|
|
|
|
|
def Acquire(key,doc=None): |
|
|
|
if not IConfigKey.isImplementedBy(key): |
|
raise exceptions.InvalidName("Not a configuration key:", key) |
|
|
|
return bindTo(key,key,doc) |
|
|
|
|
|
|
|
|
|
|
|
|
|
def bindToParent(level=1, name=None, provides=None, doc=None): |
|
|
|
"""Look up and cache a reference to the nth-level parent component |
class bindToParent(WeakBinding): |
|
|
|
"""Look up and cache a weak ref 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(self.level): |
for step in range(level): |
newObj = obj.getParentComponent() |
newObj = getParentComponent(obj) |
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): |
|
|
|
"""Weak reference to the 'self' object |
def bindToSelf(name=None, provides=None, doc=None): |
|
|
|
"""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._provides = 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): |
|
|
|
"""Binding to a list of all 'iface' utilities above the component""" |
|
|
|
return Once(lambda s,d,a: list(config.findUtilities(iface,s)), |
|
provides=provides, doc=doc |
|
) |
|
|
|
|
|
def bindToProperty(propName, default=NOT_GIVEN, provides=None, doc=None): |
|
|
|
"""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(propName,s,default), |
|
provides=provides, doc=doc |
|
) |
|
|
|
|
|
class Base(object): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Component(object): |
|
|
|
"""Thing that can be composed into a component tree, w/binding & lookups""" |
"""Thing that can be composed into a component tree, w/binding & lookups""" |
|
|
__metaclasses__ = ( |
__class_implements__ = IBindingFactory |
meta.AssertInterfaces, meta.ActiveDescriptors |
__implements__ = IBindingAPI |
) |
__metaclass__ = ActiveClass |
|
|
# use the global lookupComponent function as a method |
def __init__(self, parentComponent=None, componentName=None, **kw): |
|
self.setParentComponent(parentComponent,componentName) |
|
if kw: |
|
self.__dict__.update(kw) |
|
|
|
lookupComponent = _lookupComponent |
|
|
|
def setParentComponent(self, parentComponent, componentName=None): |
|
self.__parentCell.set(parentComponent) |
|
self.__componentName = componentName |
|
|
lookupComponent = lookupComponent |
__componentName = None |
|
|
def setParentComponent(self,parent): |
|
from weakref import ref |
|
self.getParentComponent = ref(parent) |
|
|
|
def getParentComponent(self): |
def getParentComponent(self): |
return None |
return self.__parentCell.get() |
|
|
def _componentName(self, dict, name): |
|
return self.__class__.__name__.split('.')[-1] |
|
|
|
_componentName = Once(_componentName) |
|
|
|
__instance_provides__ = New(EigenRegistry) |
|
|
|
__class_provides__ = EigenRegistry() |
|
|
|
|
|
|
def getComponentName(self): |
|
return self.__componentName |
|
|
|
def __parentCell(s,d,a): |
|
cell = EigenCell() |
|
cell.set(None) |
|
s.getParentComponent = cell.get |
|
return cell |
|
|
|
__parentCell = Once(__parentCell) |
|
|
|
def _getConfigData(self, configKey, forObj): |
|
return NOT_FOUND |
|
|
|
def _hasBinding(self,attr): |
|
return attr in self.__dict__ |
|
|
|
def _getBinding(self,attr,default=None): |
|
return self.__dict__.get(attr,default) |
|
|
|
def _setBinding(self,attr,value): |
|
self.__dict__[attr]=value |
|
|
|
def _delBinding(self,attr): |
|
if attr in self.__dict__: |
|
del self.__dict__[attr] |
|
|
|
|
|
|
|
|
|
|
def acquireUtility(self, iface, forObj=None, localLookup=True): |
|
|
|
if forObj is None: |
|
forObj=self |
|
|
|
if localLookup: |
|
|
|
provider = self.__instance_provides__.get(iface) |
|
|
|
if provider is not None: |
|
return provider(self,forObj) |
|
|
|
attr = self.__class_provides__.get(iface) |
|
|
|
if attr is not None: |
|
|
|
utility = getattr(self,attr) |
|
|
|
if utility is not NOT_FOUND: |
|
return utility |
|
|
|
parent = self.getParentComponent() |
|
|
|
if parent is None: |
|
parent = config.getLocal(self) |
|
|
|
return parent.acquireUtility(iface,forObj) |
|
|
|
|
|
def registerProvider(self, ifaces, provider): |
|
self.__instance_provides__.register(ifaces, provider) |
|
|
|
|
|
|
|
|
|
|
|
|
|
class AutoCreatable(type): |
|
|
|
"""Metaclass for components which auto-create when used""" |
|
|
|
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__() |
|
|
|
if parent is not None: |
|
self.setParentComponent(parent) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Component(Base): |
|
|
|
"""An configurable implementation (i.e., solution-domain) component""" |
|
|
|
__implements__ = IComponent |
|
|
|
def __instance_provides__(self,d,a): |
|
from peak.config.config_components import PropertyMap |
|
pm=PropertyMap() |
|
pm.setParentComponent(self) |
|
return pm |
|
|
|
__instance_provides__ = Once(__instance_provides__, provides=IPropertyMap) |
|
__class_provides__ = EigenRegistry() |
|
|
|
|
modules.setupModule() |
def _getConfigData(self, configKey, forObj): |
|
|
|
attr = self._getBinding('__instance_provides__') |
|
|
|
if attr: |
|
value = attr.getValueFor(configKey, forObj) |
|
|
|
if value is not NOT_FOUND: |
|
return value |
|
|
|
attr = self.__class_provides__.get(configKey) |
|
|
|
if attr: |
|
return getattr(self, attr, NOT_FOUND) |
|
|
|
return NOT_FOUND |
|
|
|
|
|
def registerProvider(self, configKeys, provider): |
|
self.__instance_provides__.registerProvider(configKeys, provider) |
|
|
|
|
|
|
|
|
|
|
|
class AutoCreatable(OnceClass, ActiveClass): |
|
|
|
"""DEPRECATED Metaclass for components which auto-create when used""" |
|
|
|
def computeValue(self,owner,_d,_a): |
|
return self(owner,_a) |
|
|
|
|
|
|
|
class AutoCreated(Component): |
|
|
|
"""DEPRECATED Component that auto-creates itself in instances of its |
|
containing class |
|
""" |
|
|
|
__metaclass__ = AutoCreatable |
|
|
|
|
|
|