"""Basic, in-memory implementation of the Service-Element-Feature pattern""" |
"""Basic binding tools""" |
|
|
from TW.API import * |
from once import Once, New, WeakBinding |
from TW.SEF.Interfaces import * |
import meta, modules |
from TW.SEF.Interfaces import __all__ as allInterfaces |
|
|
|
from types import TupleType |
from weakref import ref, WeakValueDictionary |
from Interface import Standard |
|
|
from peak.naming.names import toName, Syntax, CompoundName |
|
from peak.naming.interfaces import NameNotFoundException |
|
from peak.util.EigenData import EigenRegistry |
|
|
|
from Interface import Interface |
|
from peak.api import config, NOT_FOUND |
|
|
|
|
__all__ = [ |
__all__ = [ |
'Base','App','Service','TypeService','DynamicBinding','StaticBinding', |
'Component','AutoCreated','Provider','CachingProvider', |
'StructuralFeature', 'Field', 'Collection', 'Reference', 'Sequence', |
'bindTo', 'requireBinding', 'bindToNames', 'bindToParent', 'bindToSelf', |
'Classifier','PrimitiveType','Enumeration','DataType','Element', |
'getRootComponent', 'getParentComponent', 'lookupComponent', |
'bindTo', 'requireBinding', |
'acquireComponent', 'globalLookup' |
] |
] |
|
|
|
|
# We export the interfaces too, so people don't have to dig for them... |
InterfaceClass = Interface.__class__ |
|
|
__all__ += allInterfaces |
|
|
|
|
|
|
|
class DynamicBindingMC(Meta.AssertInterfaces): |
|
|
|
def __get__(self, obj, typ=None): |
|
if obj is None: return self |
|
newOb = self() |
|
newOb._setSEFparent(obj) |
|
obj.__dict__[newOb._componentName] = newOb |
|
return newOb |
|
|
|
|
|
class DynamicBinding(object): |
|
|
|
__metaclass__ = DynamicBindingMC |
|
|
|
|
|
|
|
|
|
class bindTo(object): |
|
|
|
"""Automatically look up and cache a relevant service |
|
|
|
Usage:: |
|
|
|
class someClass(SEF.Service): |
|
|
|
thingINeed = SEF.bindTo("path.to.service") |
|
|
|
'someClass' can then refer to 'self.thingINeed' instead of |
def Provider(callable): |
calling 'self.getService("path.to.service")' repeatedly. |
return lambda foundIn, forObj: callable(forObj) |
""" |
|
|
|
__implements__ = INamedDescriptor |
|
|
|
|
def CachingProvider(callable, weak=False): |
|
|
def __init__(self,targetName): |
def provider(foundIn, forObj): |
self.targetName = targetName |
|
|
|
|
fid = id(foundIn) |
|
utility = provider.cache.get(fid) |
|
|
def copyWithName(self,newName): |
if utility is None: |
from copy import copy |
utility = provider.cache[fid] = callable(foundIn) |
newOb = copy(self) |
|
newOb._componentName = newName |
|
|
|
|
return utility |
|
|
|
if weak: |
|
provider.cache = WeakValueDictionary() |
|
else: |
|
provider.cache = {} |
|
|
|
return provider |
|
|
|
|
|
|
|
|
|
|
|
|
def __get__(self, obj, typ=None): |
|
|
|
if obj is None: return self |
|
|
|
d = obj.__dict__ |
|
n = self._componentName |
|
t = self.targetName |
|
|
|
if not n: |
|
raise TypeError( |
|
"%s used in type which does not support NamedDescriptors" |
|
% self |
|
) |
|
d[n] = None |
|
|
|
newOb = obj.getSEFparent() |
|
if newOb is None: newOb = obj |
|
newOb = newOb.getService(t) |
|
|
|
if newOb is None: |
|
del d[n] |
|
raise NameError("%s not found binding %s" % (t, n)) |
|
|
|
d[n] = newOb |
|
return newOb |
|
|
|
|
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): |
|
|
|
"""Return the root component of the tree 'component' belongs to""" |
|
|
|
next = component |
|
|
|
while next is not None: |
|
component = next |
|
next = getParentComponent(component) |
|
|
|
return component |
|
|
|
|
|
def globalLookup(name, component=None): |
|
|
|
"""Lookup 'name' in global 'InitialContext', w/'component' in environ""" |
|
|
|
from peak.naming.api import InitialContext |
|
|
|
return InitialContext(RELATIVE_TO_COMPONENT=component).lookup(name) |
|
|
|
|
class requireBinding(bindTo): |
|
|
|
"""Placeholder for a binding that should be (re)defined by a subclass""" |
|
|
|
def __init__(self,description=""): |
|
self.description = description |
|
|
|
def __get__(self, obj, typ=None): |
|
|
|
if obj is None: return self |
|
|
|
raise NameError("Class %s must define %s; %s" |
|
% (obj.__class__.__name__, self._componentName, self.description) |
|
|
def acquireComponent(component, name): |
|
|
|
"""Acquire 'name' relative to 'component', w/fallback to globalLookup()""" |
|
|
|
target = component |
|
|
|
while target is not None: |
|
|
|
ob = getattr(target, name, NOT_FOUND) |
|
|
|
if ob is not NOT_FOUND: |
|
return ob |
|
|
|
target = getParentComponent(target) |
|
|
|
else: |
|
return globalLookup(name, component) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ComponentNameSyntax = Syntax( |
|
direction = 1, |
|
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(component, name): |
|
|
|
"""Lookup 'name' relative to 'component' |
|
|
class Base(object): |
'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()'. |
|
|
"""Base for all S-E-F classes""" |
Regardless of how the lookup is processed, a 'naming.NameNotFoundException' |
|
will be raised if the name cannot be found. |
|
|
__metaclasses__ = Meta.AssertInterfaces, Meta.ClassInit |
Component Path Syntax |
__implements__ = ISEF |
|
_sefParent = None |
|
|
|
def getService(self,name=None): |
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. |
|
|
if name: |
Paths beginning with anything other than '/', './', or '../' are |
if not isinstance(name,TupleType): |
acquired, which means that the first path segment will be looked |
name = tuple(name.split('.')) |
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. |
|
|
if hasattr(self,name[0]): |
All path segments after the first are interpreted as attribute names |
o = getattr(self,name[0]) |
to be looked up, beginning at the component referenced by the first |
if len(name)==1: |
path segment. '.' and '..' are interpreted the same as for the first |
return o |
path segment.""" |
else: |
|
return o.getService(name[1:]) |
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 |
|
pc = _getFirstPathComponent(attr) |
|
|
|
if pc: |
|
ob = pc(component) |
else: |
else: |
parent = self.getSEFparent() |
ob = acquireComponent(component,attr) |
if parent is not None: |
|
return parent.getService(name) |
resolved = [] |
|
append = resolved.append |
|
|
|
try: |
|
for attr in parts: |
|
pc = _getNextPathComponent(attr) |
|
if pc: |
|
ob = pc(ob) |
else: |
else: |
return self.getSEFparent() |
ob = getattr(ob,attr) |
|
append(attr) |
|
|
|
except AttributeError: |
|
|
def _setSEFparent(self,parent): |
raise NameNotFoundException( |
from weakref import ref |
resolvedName = ComponentName(resolved), |
self.getSEFparent = ref(parent) |
remainingName = ComponentName([attr] + [a for a in parts]), |
|
resolvedObj = ob |
|
) |
|
|
def getSEFparent(self): |
return ob |
return None |
|
|
|
def _componentName(self): |
class bindTo(Once): |
return self.__class__.__name__ |
|
|
|
_componentName = property(_componentName) |
"""Automatically look up and cache a relevant component |
|
|
|
Usage:: |
|
|
def __class_init__(thisClass, newClass, next): |
class someClass(binding.Component): |
|
|
if __proceed__ is not None: |
thingINeed = binding.bindTo("path/to/service") |
__proceed__(thisClass, newClass, next) |
|
else: |
|
next().__class_init__(newClass,next) |
|
|
|
for k,v in newClass.__dict__.items(): |
getOtherThing = binding.bindTo("some/thing", weak=True) |
if hasattr(v,'copyWithName') and isNamedDescriptor(v): |
|
setattr(newClass, k, v.copyWithName(k)) |
|
|
|
|
'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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
def computeValue(self, obj, instanceDict, attrName): |
|
|
|
names = self.targetNames |
|
obs = map(obj.lookupComponent, names) |
|
|
|
for name,newOb in zip(names, obs): |
|
|
|
if newOb is NOT_FOUND: |
|
|
|
del instanceDict[attrName] |
|
raise NameNotFoundError(attrName, resolvedName = name) |
|
|
|
if self.singleValue: |
|
|
|
if self.weak: |
|
return ref(newOb) |
|
else: |
|
return newOb |
|
|
|
if self.weak: |
|
obs = map(ref,obs) |
|
|
|
return tuple(obs) |
|
|
|
|
|
|
class App(Base): |
|
|
|
"""Application class""" |
|
|
|
def newElement(self,elementType,*args,**kw): |
|
element = apply(getattr(self,elementType),args,kw) # XXX won't do dotted names |
|
element._fData = {} |
|
element._setSEFparent(self) |
|
return element |
|
|
|
|
|
class Service(DynamicBinding, Base): |
|
|
|
"""Instance (as opposed to class)""" |
|
|
|
__implements__ = IService |
|
|
|
|
|
class TypeService(Service): |
|
|
|
"""Service that supports a (possibly abstract) class""" |
|
|
|
__implements__ = ITypeService |
|
|
|
|
|
class StaticBinding(object): |
|
pass |
|
|
|
|
|
|
|
|
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): |
|
self.targetNames = targetNames |
|
self.weak = kw.get('weak') |
|
self._provides = kw.get('provides') |
|
|
|
|
|
|
|
|
class StructuralFeature(DynamicBinding, Base): |
|
|
|
# XXX lowerBound = Eval("isRequired and 1 or 0") |
|
# XXX lowerBound.copyIntoSubclasses = 1 |
|
|
|
isRequired = 0 # XXX SubclassDefault(0) |
|
|
|
upperBound = None # None means unbounded upper end |
|
|
|
isOrdered = 0 |
|
isChangeable = 1 # default is to be changeable |
|
|
|
referencedEnd = None # and without an 'other end' |
|
referencedType = None |
|
|
|
def getElement(self): |
|
return self.getSEFparent() |
|
|
|
def getService(self,name=None): |
|
return self.getSEFparent().getService(name) |
|
|
|
def getReferencedType(self): |
|
return self.getService(self.referencedType) |
|
|
|
def _getData(self,default=()): |
|
return self.getSEFparent()._fData.get(self._componentName,default) |
|
|
|
def _setData(self,value): |
|
self.getSEFparent()._fData[self._componentName]=value |
|
|
|
def _delData(self): |
|
del self.getSEFparent()._fData[self._componentName] |
|
|
|
def _hasData(self): |
class bindToParent(WeakBinding): |
return self.getSEFparent()._fData.has_key(self._componentName) |
|
|
|
|
"""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()'. |
|
""" |
|
|
class Field(StructuralFeature): |
def __init__(self,level=1,provides=None): |
|
self.level = level |
|
self._provides = provides |
|
|
__implements__ = IValue |
def computeValue(self, obj, instDict, attrName): |
|
|
upperBound = 1 |
for step in range(self.level): |
|
newObj = obj.getParentComponent() |
|
if newObj is None: break |
|
obj = newObj |
|
|
def __call__(self): |
return obj |
"""Return the value of the feature""" |
|
return self._getData(None) |
|
|
|
def values(self): |
|
"""Return the value(s) of the feature as a sequence, even if it's a single value""" |
|
v=self._getData(NOT_FOUND) |
|
if v is NOT_FOUND: return () |
|
return v, |
|
|
|
def clear(self): |
def bindToSelf(provides=None): |
"""Unset the value of the feature (like __delattr__)""" |
|
if self._hasData(): |
|
self._delData() |
|
|
|
def set(self,value): |
"""Weak reference to the 'self' object |
"""Set the value of the feature to value""" |
|
if self.isChangeable: |
|
self._set(value) |
|
else: |
|
raise TypeError,("Read-only field %s" % self._componentName) |
|
|
|
def _set(self,value): |
This is just a shortcut for 'bindToParent(0)', and does pretty much what |
self.clear() |
you'd expect. It's handy for objects that provide default support for |
self._setData(value) |
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): |
|
|
|
"""Placeholder for a binding that should be (re)defined by a subclass""" |
|
|
|
def __init__(self,description="",provides=None): |
|
self.description = description |
|
self._provides = provides |
|
|
|
def computeValue(self, obj, instanceDict, attrName): |
|
|
|
raise NameError("Class %s must define %s; %s" |
|
% (obj.__class__.__name__, attrName, self.description) |
|
) |
|
|
|
|
|
|
class Collection(StructuralFeature): |
|
|
|
__implements__ = ICollection |
|
|
|
|
|
def set(self,value): |
|
"""Set the value of the feature to value""" |
|
self._set(value) |
|
|
|
|
|
def addItem(self,item): |
|
"""Add the item to the collection/relationship, reject if multiplicity exceeded""" |
|
|
|
ub = self.upperBound |
|
|
|
if not ub or len(self)<ub: |
|
self._notifyLink(item) |
|
self._link(item) |
|
else: |
|
raise ValueError |
|
|
|
|
|
def removeItem(self,item): |
|
"""Remove the item from the collection/relationship, if present""" |
|
self._unlink(item) |
|
self._notifyUnlink(item) |
|
|
|
|
|
def replaceItem(self,oldItem,newItem): |
|
d = self._getData([]) |
|
p = d.index(oldItem) |
|
if p!=-1: |
|
d[p]=newItem |
|
self._setData(d) |
|
self._notifyUnlink(oldItem) |
|
self._notifyLink(newItem) |
|
else: |
|
raise ValueError # XXX |
|
|
|
|
|
|
|
def __call__(self): |
|
"""Return the value of the feature""" |
|
return self._getData() |
|
|
|
|
|
def values(self): |
|
"""Return the value(s) of the feature as a sequence, even if it's a single value""" |
|
return self._getData() |
|
|
|
|
|
def clear(self): |
|
"""Unset the value of the feature (like __delattr__)""" |
|
|
|
referencedEnd = self.referencedEnd |
|
|
|
d = self._getData() |
|
|
|
if referencedEnd: |
|
element = self.getElement() |
|
for item in d: |
|
otherEnd = getattr(item,referencedEnd) |
|
otherEnd._unlink(element) |
|
|
|
if d: |
|
self._delData() |
|
|
|
|
|
def __len__(self): |
|
return len(self._getData()) |
|
|
|
def isEmpty(self): |
|
return len(self._getData())==0 |
|
|
|
def isReferenced(self,item): |
|
return item in self._getData() |
|
|
|
|
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 _notifyLink(self,item): |
def setParentComponent(self,parent): |
referencedEnd = self.referencedEnd |
from weakref import ref |
if referencedEnd: |
self.getParentComponent = ref(parent) |
otherEnd = getattr(item,referencedEnd) |
|
otherEnd._link(self.getElement()) |
|
|
|
def _notifyUnlink(self,item): |
def getParentComponent(self): |
referencedEnd = self.referencedEnd |
return None |
if referencedEnd: |
|
otherEnd = getattr(item,referencedEnd) |
|
otherEnd._unlink(self.getElement()) |
|
|
|
|
def _componentName(self, dict, name): |
|
return self.__class__.__name__.split('.')[-1] |
|
|
def _set(self,value): |
_componentName = Once(_componentName) |
self.clear() |
|
self._setData(value) |
|
|
|
|
__instance_provides__ = New(EigenRegistry) |
|
|
def _link(self,element): |
__class_provides__ = EigenRegistry() |
d=self._getData([]) |
|
d.append(element) |
|
self._setData(d) |
|
|
|
def _unlink(self,element): |
|
d=self._getData([]) |
|
d.remove(element) |
|
self._setData(d) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def acquireUtility(self, iface, forObj=None, localLookup=True): |
|
|
class Reference(Collection): |
if forObj is None: |
|
forObj=self |
|
|
__implements__ = IReference |
if localLookup: |
|
|
upperBound = 1 |
provider = self.__instance_provides__.get(iface) |
|
|
def __call__(self): |
if provider is not None: |
"""Return the value of the feature""" |
return provider(self,forObj) |
return self._getData(None) |
|
|
|
def _set(self,value): |
attr = self.__class_provides__.get(iface) |
self.clear() |
|
self._setData([value]) |
|
|
|
|
if attr is not None: |
|
|
class Sequence(Collection): |
utility = getattr(self,attr) |
|
|
__implements__ = ISequence |
if utility is not NOT_FOUND: |
|
return utility |
|
|
isOrdered = 1 |
parent = self.getParentComponent() |
|
|
def insertBefore(self,oldItem,newItem): |
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) |
|
|
d = self._getData([]) |
|
|
|
ub = self.upperBound |
|
if ub and len(d)>=ub: |
|
raise ValueError # XXX |
|
|
|
i = -1 |
|
if d: i = d.index(element) |
|
|
|
if i!=-1: |
|
d.insert(i,newItem) |
|
self._setData(d) |
|
self._notifyLink(newItem) |
|
else: |
|
raise ValueError # XXX |
|
|
|
|
|
|
|
class Classifier(StaticBinding, Base): |
|
"""Basis for all flavors""" |
|
|
|
class PrimitiveType(Classifier): |
|
"""A primitive type (e.g. Boolean, String, etc.)""" |
|
|
|
class Enumeration(DynamicBinding, Classifier): |
|
"""An enumerated type""" |
|
|
|
class DataType(Classifier): |
|
"""A complex datatype""" |
|
|
|
class Element(DataType): |
|
"""An element in its own right""" |
|
__implements__ = IElement |
|
|
|
|
|
setupModule() |
modules.setupModule() |
|
|
|
|
|
|