"""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 types import ModuleType |
from peak.naming.interfaces import NameNotFoundException |
from peak.naming.names import toName, AbstractName, COMPOUND_KIND, IName |
from peak.util.EigenData import EigenRegistry |
from peak.naming.syntax import PathSyntax |
|
from peak.util.EigenData import AlreadyRead |
from Interface import Interface |
from peak.config.interfaces import IConfigKey, IPropertyMap, \ |
from peak.api import config, NOT_FOUND |
IConfigurationRoot, NullConfigRoot |
|
from peak.config.registries import ImmutableConfig |
|
from peak.util.imports import importString |
|
|
|
|
__all__ = [ |
__all__ = [ |
'Component','AutoCreated','Provider','CachingProvider', |
'Base', 'Component', 'whenAssembled', 'Obtain', 'Require', 'Delegate', |
'bindTo', 'requireBinding', 'bindToNames', 'bindToParent', 'bindToSelf', |
'bindTo', 'requireBinding', 'bindSequence', 'bindToParent', 'bindToSelf', |
'getRootComponent', 'getParentComponent', 'lookupComponent', |
'getRootComponent', 'getParentComponent', 'lookupComponent', |
'acquireComponent', 'globalLookup' |
'acquireComponent', 'notifyUponAssembly', 'PluginsFor', 'PluginKeys', |
|
'bindToUtilities', 'bindToProperty', 'Constant', 'delegateTo', |
|
'getComponentName', 'getComponentPath', 'Acquire', 'ComponentName', |
] |
] |
|
|
|
from _once import BaseDescriptor |
|
|
InterfaceClass = Interface.__class__ |
class _proxy(BaseDescriptor): |
|
|
|
def __init__(self,attrName): |
|
self.attrName = attrName |
|
|
|
def usageError(self): |
|
raise AttributeError, self.attrName |
|
|
|
def computeValue(self,ob,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 '*') |
|
|
def Provider(callable): |
c = getParentComponent(c) |
return lambda foundIn, forObj: callable(forObj) |
|
|
|
|
if c is None: |
|
break |
|
|
|
path.reverse() |
|
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(value, **kw): |
|
"""DEPRECATED: Use 'Make(lambda: value)' instead""" |
|
return Make(lambda: value, **kw) |
|
|
|
|
|
class ModuleAsNode(object): |
|
|
|
protocols.advise( |
|
instancesProvide=[IBindingNode], |
|
asAdapterForTypes=[ModuleType], |
|
) |
|
|
|
def __init__(self,ob,protocol): |
|
self.module = ob |
|
|
|
def getParentComponent(self): |
|
m = '.'.join(self.module.__name__.split('.')[:-1]) |
|
if m: return importString(m) |
|
return None |
|
|
|
def getComponentName(self): |
|
return self.module.__name__.split('.')[-1] |
|
|
|
|
|
# XXX it's not clear if we really need the below, since |
|
# XXX they are not currently used with an adaptation |
|
|
|
def _getConfigData(self, forObj, configKey): |
|
return NOT_FOUND |
|
|
|
def notifyUponAssembly(self,child): |
|
child.uponAssembly() |
|
|
|
|
|
|
|
|
try: |
try: |
gpc = component.getParentComponent |
gpc = component.getParentComponent |
|
|
except AttributeError: |
except AttributeError: |
pass |
|
|
component = adapt(component,IBindingNode,None) |
|
|
|
if component is not None: |
|
return component.getParentComponent() |
|
|
else: |
else: |
return gpc() |
return gpc() |
|
|
|
|
|
def getComponentName(component): |
|
|
|
"""Return name of 'component', or 'None' if root or non-component""" |
|
|
|
try: |
|
gcn = component.getComponentName |
|
|
|
except AttributeError: |
|
|
|
component = adapt(component,IBindingNode,None) |
|
|
|
if component is not None: |
|
return component.getComponentName() |
|
|
|
else: |
|
return gcn() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def getRootComponent(component): |
def getRootComponent(component): |
|
|
"""Return the root component of the tree 'component' belongs to""" |
"""Return the root component of the tree 'component' belongs to""" |
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): |
|
|
|
"""Call 'child.uponAssembly()' as soon as 'parent' knows all its parents""" |
|
|
|
try: |
|
nua = parent.notifyUponAssembly |
|
|
|
except AttributeError: |
|
|
|
parent = getParentComponent(parent) |
|
|
return InitialContext(RELATIVE_TO_COMPONENT=component).lookup(name) |
if parent is None: |
|
child.uponAssembly() |
|
else: |
|
notifyUponAssembly(parent,child) |
|
|
|
else: |
|
nua(child) |
|
|
|
|
|
|
|
|
def acquireComponent(component, name): |
def acquireComponent(component, name): |
|
|
"""Acquire 'name' relative to 'component', w/fallback to globalLookup()""" |
"""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.""" |
|
|
target = component |
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 |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = '/', |
|
) |
|
|
|
protocols.advise( |
|
instancesProvide=[IComponentKey] |
|
) |
|
|
|
|
|
|
|
|
ComponentNameSyntax = Syntax( |
|
direction = 1, |
|
separator = '/', |
|
|
def findComponent(self, component, default=NOT_GIVEN): |
|
|
|
if not self: # empty name refers to self |
|
return component |
|
|
|
parts = iter(self) |
|
attr = parts.next() # first part |
|
pc = _getFirstPathComponent(attr) |
|
|
|
|
|
if pc: ob = pc(component) |
|
else: ob = acquireComponent(component, attr) |
|
|
|
resolved = [] |
|
append = resolved.append |
|
|
|
try: |
|
for attr in parts: |
|
pc = _getNextPathComponent(attr) |
|
if pc: ob = pc(ob) |
|
else: ob = getattr(ob,attr) |
|
append(attr) |
|
|
|
except AttributeError: |
|
|
|
if default is not NOT_GIVEN: |
|
return default |
|
|
|
raise exceptions.NameNotFound( |
|
resolvedName = ComponentName(resolved), |
|
remainingName = ComponentName([attr] + [a for a in parts]), |
|
resolvedObj = ob |
) |
) |
|
|
|
return ob |
|
|
|
|
|
|
|
|
def ComponentName(nameStr): |
|
return CompoundName(nameStr, ComponentNameSyntax) |
|
|
|
|
|
_getFirstPathComponent = dict( ( |
_getFirstPathComponent = dict( ( |
) ).get |
) ).get |
|
|
|
|
|
def lookupComponent(component, name, default=NOT_GIVEN, adaptTo=None, |
|
creationName=None, suggestParent=True): |
|
|
|
"""Lookup 'name' as a component key relative to 'component' |
|
|
|
'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. If the key cannot be found, an 'exceptions.NameNotFound' |
|
error will be raised unless a 'default' other than 'NOT_GIVEN' is provided. |
|
""" |
|
|
|
result = adapt(name, IComponentKey).findComponent( component, default ) |
|
|
|
if adaptTo is not None: |
|
result = adapt(result,adaptTo) |
|
|
|
if suggestParent: |
|
suggestParentComponent(component,creationName,result) |
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
# Declare that strings should be converted to names (with a default class |
|
# of ComponentName), in order to use them as component keys |
|
# |
|
protocols.declareAdapter( |
|
lambda ob, proto: toName(ob, ComponentName, 1), |
|
provides = [IComponentKey], |
|
forTypes = [str, unicode], |
|
) |
|
|
|
|
|
class ConfigFinder(object): |
|
|
|
"""Look up utilities or properties""" |
|
|
|
__slots__ = 'ob' |
|
|
|
protocols.advise( |
|
instancesProvide = [IComponentKey], |
|
asAdapterForProtocols = [IConfigKey] |
|
) |
|
|
def lookupComponent(component, name): |
def __init__(self, ob, proto): |
|
self.ob = ob |
|
|
"""Lookup 'name' relative to 'component' |
def findComponent(self, component, default=NOT_GIVEN): |
|
return config.lookup(component, self.ob, default) |
|
|
'name' can be any name acceptable to the 'peak.naming' package, or an |
def __repr__(self): |
Interface object. Strings and 'CompoundName' names will be interpreted |
return repr(self.ob) |
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: |
|
raise NameNotFoundException(name, resolvedObj = component) |
|
|
|
parsedName = toName(name, ComponentName, 1) |
|
|
|
# URL's and composite names must be handled globally |
|
|
|
if not parsedName.isCompound: |
|
return globalLookup(name, component) |
|
|
|
if not parsedName: |
|
# empty name refers to self |
|
return component |
|
|
|
parts = iter(parsedName) |
|
|
|
attr = parts.next() # first part |
class PluginKeys(object): |
pc = _getFirstPathComponent(attr) |
"""Component key that finds the keys of plugins matching a given key |
|
|
if pc: |
Usage:: |
ob = pc(component) |
|
else: |
|
ob = acquireComponent(component,attr) |
|
|
|
resolved = [] |
# get a sorted list of the keys to all 'foo.bar' plugins |
append = resolved.append |
pluginNames = binding.Obtain( binding.PluginKeys('foo.bar') ) |
|
|
try: |
# get an unsorted list of the keys to all 'foo.bar' plugins |
for attr in parts: |
pluginNames = binding.Obtain( |
pc = _getNextPathComponent(attr) |
binding.PluginKeys('foo.bar', sortBy=None) |
if pc: |
) |
ob = pc(ob) |
|
else: |
|
ob = getattr(ob,attr) |
|
append(attr) |
|
|
|
except AttributeError: |
'sortBy' is either a false value or a callable that will be applied to |
|
each key to get a value for sorting purposes. If set to a false value, |
|
the keys will be in the same order as yielded by 'config.iterKeys()'. |
|
'sortBy' defaults to 'str', which means the keys will be sorted based |
|
on their string form. |
|
""" |
|
|
raise NameNotFoundException( |
protocols.advise( |
resolvedName = ComponentName(resolved), |
instancesProvide = [IComponentKey], |
remainingName = ComponentName([attr] + [a for a in parts]), |
|
resolvedObj = ob |
|
) |
) |
|
|
return ob |
def __init__(self, configKey, sortBy=str): |
|
self.configKey = adapt(configKey, IConfigKey) |
|
self.sortBy = sortBy |
|
|
class bindTo(Once): |
|
|
|
"""Automatically look up and cache a relevant component |
def findComponent(self, component, default=NOT_GIVEN): |
|
|
Usage:: |
keys = config.iterKeys(component, self.configKey) |
|
|
class someClass(binding.Component): |
if self.sortBy: |
|
sortBy = self.sortBy |
|
keys = [(sortBy(k),k) for k in keys] |
|
keys.sort() |
|
return [k for (sortedBy,k) in keys] |
|
|
thingINeed = binding.bindTo("path/to/service") |
return list(keys) |
|
|
getOtherThing = binding.bindTo("some/thing", weak=True) |
class PluginsFor(PluginKeys): |
|
|
'someClass' can then refer to 'self.thingINeed' instead of |
"""Component key that finds plugins matching a configuration key |
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 |
Usage:: |
|
|
def __init__(self,targetName,weak=False,provides=None): |
# get a list of 'my.plugins.X' plugins, sorted by property name |
|
myPlugins = binding.Obtain( binding.PluginsFor('my.plugins') ) |
|
|
self.targetNames = (targetName,) |
# get an unsorted list of all 'foo.bar' plugins |
self.weak = weak |
myPlugins = binding.Obtain( |
self._provides=provides |
binding.PluginsFor('foo.bar', sortKeys=False) |
|
) |
|
|
|
This key type works similarly to 'PluginKeys()', except that it returns the |
|
plugins themselves, rather than their configuration keys. |
|
|
|
'sortBy' is either a false value or a callable that will be applied to |
|
each plugin's key to get a value for sorting purposes. If set to a false |
|
value, plugins will be in the same order as their keys are yielded by |
|
'config.iterKeys()'. 'sortBy' defaults to 'str', which means the plugins |
|
will be sorted based on the string form of the keys used to retrieve them. |
|
""" |
|
|
|
def findComponent(self, component, default=NOT_GIVEN): |
|
keys = super(PluginsFor,self).findComponent(component) |
|
return [adapt(k,IComponentKey).findComponent(component) for k in keys] |
|
|
|
|
|
|
|
|
|
|
|
|
def computeValue(self, obj, instanceDict, attrName): |
|
|
|
names = self.targetNames |
class Obtain(Attribute): |
obs = map(obj.lookupComponent, names) |
"""'Obtain(componentKey,[default=value])' - finds/caches a needed component |
|
|
for name,newOb in zip(names, obs): |
Usage examples:: |
|
|
if newOb is NOT_FOUND: |
class someClass(binding.Component): |
|
|
del instanceDict[attrName] |
thingINeed = binding.Obtain("path/to/service") |
raise NameNotFoundError(attrName, resolvedName = name) |
otherThing = binding.Obtain(IOtherThing) |
|
aProperty = binding.Obtain(PropertyName('some.prop'), default=42) |
|
|
|
'someClass' instances can then refer to their attributes, such as |
|
'self.thingINeed', instead of repeatedly calling |
|
'self.lookupComponent(someKey)'. |
|
|
|
The initial argument to the 'Obtain' constructor must be adaptable to |
|
'binding.IComponentKey'. If a 'default' keyword argument is supplied, |
|
it will be used as the default in case the specified component key is not |
|
found. |
|
|
|
XXX need to document IComponentKey translations somewhere... probably |
|
w/IComponentKey""" |
|
|
|
default = NOT_GIVEN |
|
targetName = None |
|
|
|
def __init__(self,targetName,**kw): |
|
self.targetName = adapt(targetName, IComponentKey) |
|
super(Obtain,self).__init__(**kw) |
|
|
if self.singleValue: |
def computeValue(self, obj, instanceDict, attrName): |
|
return self.targetName.findComponent(obj, self.default) |
|
|
if self.weak: |
def __repr__(self): |
return ref(newOb) |
if self.__doc__: |
|
return "binding.Obtain(%r):\n\n%s" % (self.targetName,self.__doc__) |
else: |
else: |
return newOb |
return "binding.Obtain(%r)" % (self.targetName,) |
|
|
if self.weak: |
bindTo = Obtain # XXX DEPRECATED |
obs = map(ref,obs) |
|
|
|
return tuple(obs) |
def bindSequence(*targetNames, **kw): |
|
"""DEPRECATED: use binding.Obtain([key1,key2,...])""" |
|
return Obtain(targetNames, **kw) |
|
|
|
|
|
class SequenceFinder(object): |
|
|
|
"""Look up sequences of component keys""" |
|
|
|
__slots__ = 'ob' |
|
|
|
protocols.advise( |
|
instancesProvide = [IComponentKey], |
|
asAdapterForProtocols = [protocols.sequenceOf(IComponentKey)] |
|
) |
|
|
|
def __init__(self, ob, proto): |
|
self.ob = ob |
|
|
|
def findComponent(self, component, default=NOT_GIVEN): |
|
return tuple([ob.findComponent(component, default) for ob in self.ob]) |
|
|
|
|
|
def whenAssembled(func, **kw): |
|
"""DEPRECATED: use 'Make(func, uponAssembly=True)'""" |
|
kw['uponAssembly'] = True |
|
return Make(func, **kw) |
|
|
|
|
|
|
|
|
|
|
|
|
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 |
class Delegate(Make): |
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 |
"""Delegate attribute to the same attribute of another object |
|
|
def __init__(self, *targetNames, **kw): |
Usage:: |
self.targetNames = targetNames |
|
self.weak = kw.get('weak') |
|
self._provides = kw.get('provides') |
|
|
|
|
class PasswordFile(binding.Component): |
|
shadow = binding.Obtain('config:etc.shadow/') |
|
checkPwd = changePwd = binding.Delegate('shadow') |
|
|
|
The above is equivalent to this longer version:: |
|
|
|
class PasswordFile(binding.Component): |
|
shadow = binding.Obtain('config:etc.shadow/') |
|
checkPwd = binding.Obtain('shadow/checkPwd') |
|
changePwd = binding.Obtain('shadow/changePwd') |
|
|
|
Because 'Delegate' 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 'Obtain()'.""" |
|
|
|
delegateAttr = None |
|
|
|
def __init__(self, delegateAttr, **kw): |
|
def delegate(s,d,a): |
|
return getattr(getattr(s,delegateAttr),a) |
|
super(Delegate,self).__init__(delegate,delegateAttr=delegateAttr,**kw) |
|
|
|
def __repr__(self): |
|
if self.__doc__: |
|
return "binding.Delegate(%r):\n\n%s" % ( |
|
self.delegateAttr,self.__doc__ |
|
) |
|
else: |
|
return "binding.Delegate(%r)" % (self.delegateAttr,) |
|
|
|
|
|
delegateTo = Delegate # XXX DEPRECATED; backward compat. |
|
|
|
|
|
|
|
def Acquire(key, **kw): |
|
"""DEPRECATED: use Obtain(key, offerAs=[key])""" |
|
key = adapt(key,IConfigKey) |
|
kw['offerAs'] = [key] # XXX should check that kwarg wasn't supplied |
|
return Obtain(key,**kw) |
|
|
|
def bindToParent(level=1, **kw): |
|
"""DEPRECATED: use binding.Obtain('..')""" |
|
return Obtain('/'.join(['..']*level), **kw) |
|
|
|
def bindToSelf(**kw): |
|
"""DEPRECATED: use binding.Obtain('.')""" |
|
return Obtain('.', **kw) |
|
|
|
def bindToProperty(propName, default=NOT_GIVEN, **kw): |
|
"""DEPRECATED: use binding.Obtain(PropertyName(propName))""" |
|
kw['default'] = default |
|
return binding.Obtain(PropertyName(propName), **kw) |
|
|
|
|
|
|
|
|
|
|
|
|
class bindToParent(WeakBinding): |
|
|
|
"""Look up and cache a weak ref to the nth-level parent component |
|
|
|
Usage:: |
|
|
|
class someClass(binding.AutoCreated): |
|
|
|
grandPa = binding.bindToParent(2) |
|
|
|
'someClass' can then refer to 'self.grandPa' instead of calling |
|
'self.getParentComponent().getParentComponent()'. |
|
""" |
|
|
|
def __init__(self,level=1,provides=None): |
|
self.level = level |
|
self._provides = provides |
|
|
|
def computeValue(self, obj, instDict, attrName): |
|
|
|
for step in range(self.level): |
|
newObj = obj.getParentComponent() |
|
if newObj is None: break |
|
obj = newObj |
|
|
|
return obj |
|
|
|
|
|
def bindToSelf(provides=None): |
|
|
|
"""Weak reference to the 'self' object |
|
|
|
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 |
|
various interfaces in the absence of an object to delegate to. The object |
|
can refer to 'self.delegateForInterfaceX.someMethod()', and have |
|
'delegateForInterfaceX' be a 'bindToSelf()' by default.""" |
|
|
|
return bindToParent(0,provides) |
|
|
|
|
|
class requireBinding(Once): |
class Require(Attribute): |
|
|
"""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): |
description = '' |
self.description = description |
|
self._provides = provides |
|
|
|
def computeValue(self, obj, instanceDict, attrName): |
def __init__(self, description="", **kw): |
|
kw['description'] = description |
|
super(Require,self).__init__(**kw) |
|
|
|
|
|
def computeValue(self, obj, instanceDict, attrName): |
raise NameError("Class %s must define %s; %s" |
raise NameError("Class %s must define %s; %s" |
% (obj.__class__.__name__, attrName, self.description) |
% (obj.__class__.__name__, attrName, self.description) |
) |
) |
|
|
|
def __repr__(self): |
|
if self.__doc__: |
|
return "binding.Require(%r):\n\n%s" % ( |
|
self.description,self.__doc__ |
|
) |
|
else: |
|
return "binding.Require(%r)" % (self.description,) |
|
|
|
requireBinding = Require # XXX DEPRECATED |
|
|
|
|
|
|
|
def bindToUtilities(iface, **kw): |
|
"""DEPRECATED: bind list of all 'iface' utilities above the component""" |
|
|
|
return Make(lambda self: list(config.iterValues(self,iface)), **kw) |
|
|
|
|
|
|
|
|
|
|
|
|
|
class _Base(object): |
|
|
|
"""Basic attribute management and "active class" support""" |
|
|
|
__metaclass__ = ActiveClass |
|
|
|
protocols.advise( |
|
instancesProvide = [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 |
|
|
|
|
|
|
|
|
|
def _getBindingFuncs(klass, attr, useSlot=False): |
|
if useSlot: |
|
d = getattr(klass,attr) |
|
else: |
|
d = _proxy(attr) |
|
return d.__get__, d.__set__, d.__delete__ |
|
|
|
_getBindingFuncs = classmethod(_getBindingFuncs) |
|
|
|
|
|
def _delBinding(self, attr, useSlot=False): |
|
|
|
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] |
|
|
|
def _hasBinding(self,attr,useSlot=False): |
|
|
|
if useSlot: |
|
return hasattr(self,attr) |
|
else: |
|
return attr in self.__dict__ |
|
|
|
|
|
def _bindingChanging(self,attr,newval,isSlot=False): |
|
pass |
|
|
|
|
|
def _postGet(self,attr,value,isSlot=False): |
|
return value |
|
|
|
|
class Component(object): |
class Component(_Base): |
|
|
"""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__ = ( |
protocols.advise( |
meta.AssertInterfaces, meta.ActiveDescriptors |
classProvides = [IComponentFactory], |
|
instancesProvide = [IComponent] |
|
) |
|
|
|
|
|
def __init__(self, parentComponent=NOT_GIVEN, componentName=None, **kw): |
|
|
|
# Set up keywords first, so state is sensible |
|
if kw: |
|
|
|
klass = self.__class__ |
|
|
|
for k,v in kw.iteritems(): |
|
if hasattr(klass,k): |
|
setattr(self,k,v) |
|
else: |
|
raise TypeError( |
|
"%s constructor has no keyword argument %s" % |
|
(klass, k) |
) |
) |
|
|
# use the global lookupComponent function as a method |
# 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 |
lookupComponent = lookupComponent |
|
|
def setParentComponent(self,parent): |
|
from weakref import ref |
|
self.getParentComponent = ref(parent) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fromZConfig(klass, section): |
|
|
|
"""Classmethod: Create an instance from a ZConfig 'section'""" |
|
|
|
# ZConfig uses unicode for keys and defaults unsupplied values to None |
|
data = dict([(str(k),v) for k,v in section.__dict__.items() |
|
if v is not None]) |
|
|
|
if not hasattr(klass,'_name') and '_name' in data: |
|
del data['_name'] |
|
|
|
if not hasattr(klass,'_matcher') and '_matcher' in data: |
|
del data['_matcher'] |
|
|
|
return klass(**data) |
|
|
|
fromZConfig = classmethod(fromZConfig) |
|
|
|
|
|
def setParentComponent(self, parentComponent, componentName=None, |
|
suggest=False): |
|
|
|
pc = self.__parentSetting |
|
|
|
if pc is NOT_GIVEN: |
|
self.__parentSetting = parentComponent |
|
self.__componentName = componentName |
|
self.__parentComponent # lock and invoke assembly events |
|
return |
|
|
|
elif suggest: |
|
return |
|
|
|
raise AlreadyRead( |
|
"Component %r already has parent %r; tried to set %r" |
|
% (self,pc,parentComponent) |
|
) |
|
|
|
__parentSetting = NOT_GIVEN |
|
__componentName = None |
|
|
|
def __parentComponent(self,d,a): |
|
|
|
parent = self.__parentSetting |
|
if parent is NOT_GIVEN: |
|
parent = self.__parentSetting = None |
|
|
|
d[a] = parent |
|
if parent is None: |
|
self.uponAssembly() |
|
elif (self.__class__.__attrsToBeAssembled__ |
|
or self._getBinding('__objectsToBeAssembled__')): |
|
notifyUponAssembly(parent,self) |
|
|
|
return parent |
|
|
|
__parentComponent = Make(__parentComponent, suggestParent=False) |
|
|
|
|
def getParentComponent(self): |
def getParentComponent(self): |
return None |
return self.__parentComponent |
|
|
def _componentName(self, dict, name): |
def getComponentName(self): |
return self.__class__.__name__.split('.')[-1] |
return self.__componentName |
|
|
_componentName = Once(_componentName) |
__instance_offers__ = Make( |
|
'peak.config.config_components:PropertyMap', offerAs=[IPropertyMap] |
|
) |
|
|
__instance_provides__ = New(EigenRegistry) |
|
|
|
__class_provides__ = EigenRegistry() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _configKeysMatching(self, configKey): |
|
|
|
"""Iterable over defined keys that match 'configKey' |
|
|
def acquireUtility(self, iface, forObj=None, localLookup=True): |
A key 'k' in the map is considered to "match" 'configKey' if any of |
|
'k.parentKeys()' are listed as keys in 'configKey.registrationKeys()'. |
|
You must not change the configuration map while iterating over the |
|
keys. Also, keep in mind that only explicitly-registered keys are |
|
returned; for instance, load-on-demand rules will only show up as |
|
wildcard keys.""" |
|
|
if forObj is None: |
maps = [self.__class__.__class_offers__] |
forObj=self |
attr = self._getBinding('__instance_offers__') |
|
|
if localLookup: |
if attr: |
|
maps.append(attr) |
|
|
provider = self.__instance_provides__.get(iface) |
yielded = {} |
|
|
if provider is not None: |
for cMap in maps: |
return provider(self,forObj) |
for key in cMap._configKeysMatching(configKey): |
|
if key in yielded: |
|
continue |
|
yield key |
|
yielded[key] = 1 |
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _getConfigData(self, forObj, configKey): |
|
|
|
attr = self._getBinding('__instance_offers__') |
|
|
|
if attr: |
|
value = attr.getValueFor(forObj, configKey) |
|
|
class AutoCreatable(type): |
if value is not NOT_FOUND: |
|
return value |
|
|
"""Metaclass for components which auto-create when used""" |
attr = self.__class__.__class_offers__.lookup(configKey) |
|
|
def __get__(self, obj, typ=None): |
if attr: |
|
return getattr(self, attr, NOT_FOUND) |
|
|
if obj is None: |
return NOT_FOUND |
return self |
|
|
|
newOb = self(obj) |
|
|
|
obj.__dict__[newOb._componentName] = newOb |
def registerProvider(self, configKey, provider): |
return newOb |
self.__instance_offers__.registerProvider(configKey, provider) |
|
|
|
|
class AutoCreated(Component): |
def notifyUponAssembly(self,child): |
|
|
"""Component that auto-creates itself in instances of its containing class |
tba = self.__objectsToBeAssembled__ |
""" |
|
|
if tba is None: |
|
child.uponAssembly() # assembly has already occurred |
|
else: |
|
tba.append(child) # save reference to child for callback |
|
|
|
if (len(tba)==1 and self.__parentSetting is not NOT_GIVEN |
|
and len(tba)==1 and not self.__class__.__attrsToBeAssembled__ |
|
): |
|
# Make sure our parent calls us, since we need to call a |
|
# child now, but would not have been registered ourselves. |
|
notifyUponAssembly(self.getParentComponent(),self) |
|
|
|
|
|
|
|
|
|
def uponAssembly(self): |
|
"""Don't override this unless you can handle the reentrancy issues!""" |
|
tba = self.__objectsToBeAssembled__ |
|
|
|
if tba is None: |
|
return |
|
|
__metaclasses__ = AutoCreatable, |
self.__objectsToBeAssembled__ = None |
|
|
def __init__(self, parent=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 |
|
|
super(AutoCreated,self).__init__() |
__objectsToBeAssembled__ = Make(list) |
|
|
if parent is not None: |
|
self.setParentComponent(parent) |
|
|
|
|
def __attrsToBeAssembled__(klass,d,a): |
|
aa = {} |
|
map(aa.update, getInheritedRegistries(klass, '__attrsToBeAssembled__')) |
|
|
|
for attrName, descr in klass.__class_descriptors__.items(): |
|
notify = getattr(descr,'uponAssembly',False) |
|
if notify: aa[attrName] = True |
|
|
|
return aa |
|
|
|
__attrsToBeAssembled__ = classAttr(Make(__attrsToBeAssembled__)) |
|
|
|
|
|
def __class_offers__(klass,d,a): |
|
|
|
return ImmutableConfig( |
|
baseMaps = getInheritedRegistries(klass, '__class_offers__'), |
|
items = [(adapt(key,IConfigKey), attrName) |
|
for attrName, descr in klass.__class_descriptors__.items() |
|
for key in getattr(descr,'offerAs',()) |
|
] |
|
) |
|
|
|
|
|
__class_offers__ = classAttr(Make(__class_offers__)) |
|
|
|
|
|
|
|
|
|
|
modules.setupModule() |
Base = Component # XXX backward compatibility; deprecated |
|
|
|
|
|
|