"""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 FunctionType |
|
from Interface import Standard |
|
from TW.Utils.Code import Function |
|
|
|
__all__ = [ |
|
'Base','App','Service','Specialist','DynamicBinding','StaticBinding', |
|
'StructuralFeature', 'Field', 'Collection', 'Reference', 'Sequence', |
|
'Classifier','PrimitiveType','Enumeration','DataType','Element', |
|
'bindTo', 'requireBinding', 'bindToNames', 'bindToParent', 'Specialist' |
|
] |
|
|
|
|
|
# We export the interfaces too, so people don't have to dig for them... |
|
|
|
__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 |
|
|
|
|
from weakref import ref, WeakValueDictionary |
|
|
class DynamicBinding(object): |
from peak.naming.names import toName, Syntax, CompoundName |
|
from peak.naming.interfaces import NameNotFoundException |
__metaclass__ = DynamicBindingMC |
from peak.util.EigenData import EigenRegistry |
|
|
|
|
|
|
|
|
class bindTo(Once): |
|
|
|
"""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 |
|
calling 'self.getService("path.to.service")' repeatedly. |
|
""" |
|
|
|
singleValue = 1 |
|
|
|
def __init__(self,targetName): |
|
self.targetNames = (targetName,) |
|
|
|
def computeValue(self, obj, instanceDict, attrName): |
|
|
|
instanceDict[attrName] = None # recursion guard |
|
|
|
parent = obj.getSEFparent() |
|
if parent is None: parent = obj |
|
|
|
obs = map(parent.getService, self.targetNames) |
|
|
|
for newOb in obs: |
|
if newOb is None: |
|
del instanceDict[attrName] |
|
raise NameError( |
|
"%s not found binding %s" % (self.targetName, attrName) |
|
) |
|
elif self.singleValue: |
|
return newOb |
|
|
|
return tuple(obs) |
|
|
|
|
|
|
|
class bindToNames(bindTo): |
|
|
|
"""Automatically look up and cache a sequence of services by name |
|
|
|
Usage:: |
|
|
|
class someClass(SEF.Service): |
|
|
|
thingINeed = SEF.bindToNames( |
|
"path.to.service1", "another.path", ... |
|
) |
|
|
|
'someClass' can then refer to 'self.thingINeed' to get a tuple of |
|
services instead of calling 'self.getService()' on a series of names. |
|
""" |
|
|
|
singleValue = 0 |
|
|
|
def __init__(self,*targetNames): |
|
self.targetNames = targetNames |
|
|
|
|
from Interface import Interface |
|
from peak.api import config, NOT_FOUND |
|
|
|
|
|
__all__ = [ |
|
'Component','AutoCreated','Provider','CachingProvider', |
|
'bindTo', 'requireBinding', 'bindToNames', 'bindToParent', 'bindToSelf', |
|
'getRootComponent', 'getParentComponent', 'lookupComponent', |
|
'acquireComponent', 'globalLookup' |
|
] |
|
|
|
|
|
InterfaceClass = Interface.__class__ |
|
|
|
|
|
|
|
|
|
|
|
|
class bindToParent(Once): |
|
|
|
"""Look up and cache a reference to the nth-level parent service |
def Provider(callable): |
|
return lambda foundIn, forObj: callable(forObj) |
|
|
Usage:: |
|
|
|
class someClass(SEF.Service): |
def CachingProvider(callable, weak=False): |
|
|
grandPa = SEF.bindToParent(2) |
def provider(foundIn, forObj): |
|
|
'someClass' can then refer to 'self.grandPa' instead of calling |
fid = id(foundIn) |
'self.getSEFparent().getSEFparent()'. |
utility = provider.cache.get(fid) |
|
|
Note that this binding creates a circular reference as soon as it |
if utility is None: |
is retrieved from an instance. The circular reference can be |
utility = provider.cache[fid] = callable(foundIn) |
broken by deleting the attribute (e.g. 'del self.grandPa'), but of |
|
course it will come back the next time you use the attribute. |
|
""" |
|
|
|
def __init__(self,level=1): |
return utility |
self.level = level |
|
|
|
def computeValue(self, obj, instDict, attrName): |
if weak: |
|
provider.cache = WeakValueDictionary() |
for step in range(self.level): |
else: |
newObj = obj.getSEFparent() |
provider.cache = {} |
if newObj is None: break |
|
obj = newObj |
|
|
|
return obj |
|
|
|
|
return provider |
|
|
|
|
|
|
|
|
|
|
|
|
class requireBinding(Once): |
|
|
|
"""Placeholder for a binding that should be (re)defined by a subclass""" |
|
|
|
def __init__(self,description=""): |
|
self.description = description |
|
|
|
def computeValue(self, obj, instanceDict, attrName): |
|
|
|
raise NameError("Class %s must define %s; %s" |
|
% (obj.__class__.__name__, attrName, self.description) |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
def acquireComponent(component, name): |
|
|
|
"""Acquire 'name' relative to 'component', w/fallback to globalLookup()""" |
|
|
class Base(object): |
target = component |
|
|
"""Base for all S-E-F classes""" |
while target is not None: |
|
|
__metaclasses__ = ( |
ob = getattr(target, name, NOT_FOUND) |
Meta.AssertInterfaces, Meta.ClassInit, Meta.ActiveDescriptors |
|
) |
|
|
|
__implements__ = ISEF |
if ob is not NOT_FOUND: |
_sefParent = None |
return ob |
|
|
def getService(self,name=None): |
target = getParentComponent(target) |
|
|
if name: |
|
if not isinstance(name,tuple): |
|
name = tuple(name.split('.')) |
|
|
|
if hasattr(self,name[0]): |
|
o = getattr(self,name[0]) |
|
if len(name)==1: |
|
return o |
|
else: |
else: |
return o.getService(name[1:]) |
return globalLookup(name, component) |
else: |
|
parent = self.getSEFparent() |
|
if parent is not None: |
|
return parent.getService(name) |
|
else: |
|
return self.getSEFparent() |
|
|
|
|
|
def _setSEFparent(self,parent): |
|
from weakref import ref |
|
self.getSEFparent = ref(parent) |
|
|
|
def getSEFparent(self): return None |
|
|
|
def _componentName(self): return self.__class__.__name__ |
|
|
|
_componentName = property(_componentName) |
|
|
|
class Service(DynamicBinding, Base): |
|
|
|
"""Instance (as opposed to class)""" |
|
|
|
__implements__ = IService |
|
|
|
|
|
class App(Service): |
|
|
|
"""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 StaticBinding(object): |
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ComponentNameSyntax = Syntax( |
|
direction = 1, |
|
separator = '/', |
|
) |
|
|
|
|
|
def ComponentName(nameStr): |
|
return CompoundName(nameStr, ComponentNameSyntax) |
|
|
|
|
class Specialist(Service): |
_getFirstPathComponent = dict( ( |
|
('', getRootComponent), |
|
('.', lambda x:x), |
|
('..', getParentComponent), |
|
) ).get |
|
|
"""Service for finding/creating objects of a specific type family |
|
|
|
Specialists are the high-level building blocks of TransWarp |
_getNextPathComponent = dict( ( |
applications, providing "well-known" locations for retrieving or |
('', lambda x:x), |
creating objects that play a given role in an application. They also |
('.', lambda x:x), |
serve as a focal point for operations that deal with objects of a |
('..', getParentComponent), |
particular type, but which deal with zero or more than one instance |
) ).get |
of that type (and thus can't be instance methods). |
|
|
|
Why not just use a class? Because a Specialist serves as a "placeful" |
|
implementation of storage and lifecycle management for its objects, |
|
while classes in the general sense are neither placeful nor provide |
|
storage/lifecycle management. |
|
|
|
This basic Specialist implementation supplies retrieval, creation, and |
|
caching support for Elements based on Records (see the |
|
TW.Database.DataModel package). It should be straightforward to create |
|
mixin classes, however, that implement other storage methodologies such |
|
as ZODB-backed BTrees, etc. |
|
|
|
Also, most application Specialists will probably add in a few |
|
application-specific methods, and Zope 3 applications will probably |
|
also want to define more specific interfaces than 'ISpecialist' so |
|
they can use views to create user interfaces for their Specialists. |
|
|
|
Configuring a Specialist |
|
|
|
Specialists are specified as subclasses of 'SEF.Specialist'. Here |
|
are a few of the attributes you can specify in subclasses: |
|
|
|
* 'isAbstract' -- if true, the Specialist will not create new |
|
items. The default is false. |
|
|
|
* 'elementClass' -- the class used for retrieved or created |
|
elements. Can be specified using 'SEF.bindTo' functions. |
|
|
|
* 'keyGenerator' -- an object with a 'next()' method, which will |
|
be called to generate the "next" key value for a newly created |
|
item, if a key is not supplied by the caller. |
|
|
|
* 'cache' -- A 'OnceClass' that implements the 'TW.Caching.ICache' |
|
interface, such as any of the 'TW.Caching' cache classes, or |
|
the 'TW.Database.Transactions.TransactionalCache' class. |
|
The default is 'TW.Caching.WeakCache'. |
|
|
|
Note that not all subclasses of Specialist may use or honor these |
|
attributes, since it is always possible to override the methods |
|
in this base class that use them. |
|
|
|
Record Management Support |
|
|
|
To support retrieving DataModel Records from RecordManagers, there |
|
are some additional attributes which you must specify: |
|
|
|
* 'recordManager' -- the RecordManager which records will be |
|
retrieved from; can be specified using a 'SEF.bindTo' function. |
|
|
|
* 'requiredType' -- The name of the RecordType which records |
|
retrieved from 'recordManager' must have, to be considered |
|
valid for retrieval. |
|
|
|
* 'keyField' -- name of the record field which corresponds to the |
|
application key. |
|
|
|
* 'keyConstants' -- An optional sequence of '(key,value)' pairs |
|
which will be passed to 'recordManager.getItem()' along with |
|
the key field. This helps support multi-field keys, which is |
|
especially useful with CORE and WarpCORE "object names". |
|
|
|
Of course, if you are using a Specialist subclass/variant which |
|
doesn't use RecordManagers, then you needn't supply these |
|
class attributes. |
|
|
|
Polymorphism (Sub-Specialist) Support |
|
|
|
In many applications, there will be Specialists whose scope |
def lookupComponent(component, name): |
includes more-specialized variants of themselves. For example, a |
|
task management application might have two specialists, 'Tasks' |
|
and 'ToDos', where the to-do item is a more specialized kind of |
|
task. Semantically, the 'Tasks' specialist must also be able to |
|
retrieve items that are actually managed by the 'ToDos' specialist. |
|
|
|
The base 'Specialist' class includes support for this circumstance, |
"""Lookup 'name' relative to 'component' |
so long as the record management support is being used, the |
|
specialists are using the same RecordManager, and the RecordTypes |
|
are subclasses of each other. To activate this support, supply |
|
a 'subtypeSpecialists' class attribute which is a sequence of the |
|
specialists which specialize in direct subtypes. |
|
'SEF.bindToNames()' is an easy way to provide such a sequence, |
|
e.g.:: |
|
|
|
class Task(SEF.Element): |
'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()'. |
|
|
class ToDo(Task): |
Regardless of how the lookup is processed, a 'naming.NameNotFoundException' |
... |
will be raised if the name cannot be found. |
|
|
class Project(Task): |
Component Path Syntax |
... |
|
|
|
class Tasks(SEF.Specialist): |
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. |
|
|
elementClass = Task |
Paths beginning with anything other than '/', './', or '../' are |
subtypeSpecialists = SEF.bindToNames('ToDos', 'Projects') |
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) |
|
|
class ToDos(SEF.Specialist): |
parsedName = toName(name, ComponentName, 1) |
|
|
elementClass = ToDo |
# 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 |
|
|
class Projects(SEF.Specialist): |
parts = iter(parsedName) |
|
|
elementClass = Project |
attr = parts.next() # first part |
|
pc = _getFirstPathComponent(attr) |
|
|
... |
if pc: |
|
ob = pc(component) |
|
else: |
|
ob = acquireComponent(component,attr) |
|
|
The subtype specialists will be asked to retrieve an object |
resolved = [] |
whenever the more-general specialist does not have a suitable |
append = resolved.append |
item available in its cache. The first non-None item returned |
|
from one of the subtype specialists will be used. If no subtype |
|
specialist claims the item, the more-general specialist will |
|
use its own element class to wrap the retrieved record. |
|
""" |
|
|
|
__implements__ = ISpecialist |
try: |
|
for attr in parts: |
|
pc = _getNextPathComponent(attr) |
|
if pc: |
|
ob = pc(ob) |
|
else: |
|
ob = getattr(ob,attr) |
|
append(attr) |
|
|
isAbstract = 0 |
except AttributeError: |
|
|
elementClass = requireBinding( |
raise NameNotFoundException( |
"Class for elements managed by this specialist" |
resolvedName = ComponentName(resolved), |
|
remainingName = ComponentName([attr] + [a for a in parts]), |
|
resolvedObj = ob |
) |
) |
|
|
keyGenerator = requireBinding("Object with a 'next()' method") |
return ob |
|
|
recordManager = requireBinding("RecordManager to get records from") |
|
requiredType = requireBinding("RecordType name that records must have") |
|
keyField = requireBinding("Name of record key field") |
|
keyConstants = () # key/value pairs to be passed to recordType |
|
|
|
subtypeSpecialists = () # specialists to check for subtypes |
|
|
|
from TW.Caching import WeakCache as cache |
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 |
|
|
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, key, default=NOT_GIVEN): |
|
|
|
"""Retrieve element designated by 'key'; raise KeyError if not found""" |
|
|
|
item = self.cache.get(key, NOT_GIVEN) |
|
|
|
if item is NOT_GIVEN: |
|
item = self._retrieveItem(key) |
|
self.cache[key] = item |
|
|
|
if item is NOT_FOUND: |
|
|
|
if default is NOT_GIVEN: |
|
raise KeyError,key |
|
|
|
item = default |
|
|
|
return item |
|
|
|
|
|
def getItem(self, key, default=None): |
|
|
|
"""Retrieve element designated by 'key', or 'default' if not found""" |
def computeValue(self, obj, instanceDict, attrName): |
|
|
return self.__getitem__(key,default) |
names = self.targetNames |
|
obs = map(obj.lookupComponent, names) |
|
|
|
for name,newOb in zip(names, obs): |
|
|
def newItem(self, key=None): |
if newOb is NOT_FOUND: |
|
|
"""Create element with key 'key' (or a new key if 'key' is None)""" |
del instanceDict[attrName] |
|
raise NameNotFoundError(attrName, resolvedName = name) |
|
|
if self.isAbstract: |
if self.singleValue: |
raise TypeError("Abstract specialists can't create new items") |
|
|
|
if key is None: |
if self.weak: |
key = self.keyGenerator.next() |
return ref(newOb) |
|
else: |
|
return newOb |
|
|
item = self.cache[key] = self._newItem(key) |
if self.weak: |
return item |
obs = map(ref,obs) |
|
|
|
return tuple(obs) |
|
|
def _retrieveItem(self,key): |
|
|
|
"""The heavy lifting for retrieval; redefine for non-RM subclasses""" |
|
|
|
record = self.recordManager.getItem( |
|
self.keyConstants + ((self.keyField,key),) |
|
) |
|
|
|
# record doesn't exist or is wrong type, ditch it... |
|
|
|
if not record.hasType(self.requiredType): |
|
|
|
if not record.exists(): |
|
record.invalidate() |
|
|
|
return None |
|
|
|
|
|
# Check sub-specialists so that we get most-specific type |
|
|
|
for sub in self.subtypeSpecialists: |
|
|
|
item = sub.getItem(record[sub.keyField]) |
|
|
|
if item is not None: |
|
return item |
|
|
|
|
|
return self._wrapElement(record) |
|
|
|
|
|
def _wrapElement(self,record): |
|
|
|
"""Wrap 'record' in an Element""" |
|
|
|
element = self.elementClass() |
class bindToNames(bindTo): |
element._fData = record |
|
element._setSEFparent(self) |
|
|
|
return element |
"""Automatically look up and cache a sequence of services by name |
|
|
def _newItem(self,key): |
Usage:: |
|
|
"""The heavy lifting for creation; redefine for non-RM subclasses""" |
class someClass(binding.AutoCreated): |
|
|
record = self.recordManager.getItem( |
thingINeed = binding.bindToNames( |
self.keyConstants + ((self.keyField,key),) |
"path/to/service", "another/path", ... |
) |
) |
|
|
if record.exists(): |
'someClass' can then refer to 'self.thingINeed' to get a tuple of |
raise KeyError(key, "key already exists") |
services instead of calling 'self.lookupComponent()' on a series of |
|
names. As with 'bindTo()', a 'weak' keyword argument can be set to |
record.addType(self.requiredType) |
indicate that the sequence should consist of weak references to the |
|
named objects. |
item = self._wrapElement(record) |
""" |
self.cache[key] = item |
|
|
|
return item |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FeatureMC(Meta.ActiveDescriptor, type): |
|
|
|
"""Metaclass for Features that export methods to their Element |
singleValue = False |
|
|
Example usage:: |
def __init__(self, *targetNames, **kw): |
|
self.targetNames = targetNames |
|
self.weak = kw.get('weak') |
|
self._provides = kw.get('provides') |
|
|
class aFeatureBase(SEF.Feature): |
|
|
|
hasFoo = 0 |
|
|
|
def foo(self, anArg, someParam): |
|
feature = type(self).__feature__ |
|
print feature.hasFoo |
|
|
|
# 'namingConvention' makes foo a method template |
|
foo.namingConvention = 'foo%(initcap)s' |
|
|
|
# 'installIf' determines whether template will be installed, |
|
# based on subclass metadata |
|
foo.installIf = lambda feature,method: feature.hasFoo |
|
|
|
def bar(klass, something): |
|
# This will be a class method of the feature, |
|
# because it has no 'namingConvention'. |
|
... |
|
|
|
class anEl(SEF.Element): |
|
|
|
class myFeature(aFeatureBase): |
|
hasFoo = 1 |
|
|
|
|
|
el=anEl() |
|
el.fooMyFeature(1,2) |
|
el.__class__.myFeature.bar('spam') |
|
|
|
In the above example, class 'anEl' receives a 'fooMyFeature()' |
|
method from the 'myFeature' feature. The feature and its class |
|
methods/attributes are available via 'element.__class__.myFeature'. |
|
|
|
|
|
The Basics |
|
|
|
Features are immutable, singleton classes. They are never |
|
instantiated, and should never be modified after their creation. |
|
Any methods defined in a feature are either class methods of the |
|
feature class, or method templates for export to any Element class |
|
which the feature is installed in. |
|
|
|
Method templates automate the process of creating getters, setters, |
|
and similarly patterned methods. That is, instead of writing |
|
'setFoo()' and 'getFoo()' methods for each feature of an element, |
|
one need only define a 'foo' feature which inherits from a base |
|
feature class with 'set()' and 'get()' templates. |
|
|
|
Accessing Inner Methods |
class bindToParent(WeakBinding): |
|
|
... |
"""Look up and cache a weak ref to the nth-level parent component |
|
|
Dynamic Method Access |
Usage:: |
|
|
... |
class someClass(binding.AutoCreated): |
|
|
Defining Method Templates |
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): |
|
|
|
"""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): |
|
|
def __get__(self, ob, typ=None): |
raise NameError("Class %s must define %s; %s" |
|
% (obj.__class__.__name__, attrName, self.description) |
"""Get the feature's value by delegating to 'ob.getX()'""" |
|
|
|
if ob is None: |
|
return self |
|
|
|
return self.getMethod(ob,'get')() |
|
|
|
|
|
def __set__(self, ob, val): |
|
"""Set the feature's value by delegating to 'ob.setX()'""" |
|
return self.getMethod(ob,'set')(val) |
|
|
|
|
|
def __delete__(self, ob): |
|
"""Delete the feature's value by delegating to 'ob.delattrX()'""" |
|
return self.getMethod(ob,'delattr')() |
|
|
|
|
|
def getMethod(self, ob, verb): |
|
"""Look up a method dynamically""" |
|
return getattr(ob,self.methodNames[verb]) |
|
|
|
|
|
def activate(self,klass,attrName): |
|
|
|
"""Install the feature's getX/setX/etc. methods upon use in a class""" |
|
|
|
if attrName != self.__name__: |
|
raise TypeError( |
|
"Feature %s installed in %s as %s; must be named %s" % |
|
(self.__name__,klass,attrName,self.__name__) |
|
) |
) |
|
|
for verb,methodName in self.methodNames.items(): |
|
setattr(klass, methodName, getattr(self,verb)) |
|
|
|
|
|
|
|
|
|
def __init__(self, className, bases, classDict): |
|
|
|
"""Set up method templates, name mapping, etc.""" |
|
|
|
super(FeatureMC,self).__init__(className, bases, classDict) |
|
|
|
items = []; d={} |
|
|
|
for b in bases: |
|
items.extend(getattr(b,'methodTemplates',d).items()) |
|
|
|
items.reverse() |
|
mt = self.methodTemplates = dict(items) |
|
|
|
for methodName, method in classDict.items(): |
|
if not isinstance(method,FunctionType): continue |
|
|
|
nc = getattr(method,'namingConvention',None) |
|
if nc: |
|
mt[methodName]=method |
|
setattr(self,methodName,staticmethod(method)) |
|
else: |
|
setattr(self,methodName,classmethod(method)) |
|
|
|
names = { |
|
'name': className, 'upper': className.upper(), |
|
'lower': className.lower(), 'capital': className.capitalize(), |
|
'initCap': className[:1].upper()+className[1:] |
|
} |
|
|
|
mn = self.methodNames = {} |
|
for methodName,method in mt.items(): |
|
verb = getattr(method,'verb',methodName) |
|
installIf = getattr(method,'installIf',None) |
|
|
|
if installIf is None or installIf(self,method): |
|
mn[verb] = method.namingConvention % names |
|
f=Function(method) |
|
f.co_names[f.co_names.index('__feature__')] = className |
|
setattr(self,verb,staticmethod(f.func())) |
|
|
|
class StructuralFeature(Base): |
|
|
|
__metaclasses__ = FeatureMC, |
|
|
|
isRequired = 0 |
|
lowerBound = 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 |
|
defaultValue = None |
|
|
|
def get(self): |
|
feature = self.__class__.__feature__ |
|
return self.__dict__.get(feature.__name__, feature.defaultValue) |
|
|
|
get.namingConvention = 'get%(initCap)s' |
|
|
|
|
|
def set(self,val): |
|
feature = self.__class__.__feature__ |
|
self.__dict__[feature.__name__]=[val] |
|
|
|
set.namingConvention = 'set%(initCap)s' |
|
|
|
|
|
def delete(self): |
|
feature = self.__class__.__feature__ |
|
del self.__dict__[feature.__name__] |
|
|
|
delete.namingConvention = 'delete%(initCap)s' |
|
delete.verb = 'delattr' |
|
|
|
|
|
class Field(StructuralFeature): |
|
__class_implements__ = IValue |
|
upperBound = 1 |
|
|
|
class Collection(StructuralFeature): |
|
|
|
__class_implements__ = ICollection |
|
|
|
def get(self): |
|
feature = self.__class__.__feature__ |
|
return self.__dict__.get(feature.__name__, []) |
|
|
|
get.namingConvention = 'get%(initCap)s' |
|
|
|
|
|
def add(self,item): |
|
|
|
"""Add the item to the collection/relationship""" |
|
|
|
feature = self.__class__.__feature__ |
|
ub = feature.upperBound |
|
value = feature.__get__(self) |
|
|
|
if not ub or len(value)<ub: |
|
feature._notifyLink(self,item) |
|
feature._link(self,item) |
|
else: |
|
raise ValueError("Too many items") |
|
|
|
add.namingConvention = 'add%(initCap)s' |
|
|
|
|
|
def remove(self,item): |
class Component(object): |
"""Remove the item from the collection/relationship, if present""" |
|
feature = self.__class__.__feature__ |
|
feature._unlink(self,item) |
|
feature._notifyUnlink(self,item) |
|
|
|
remove.namingConvention = 'remove%(initCap)s' |
"""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 setParentComponent(self,parent): |
|
from weakref import ref |
|
self.getParentComponent = ref(parent) |
|
|
|
def getParentComponent(self): |
|
return None |
|
|
def replace(self,oldItem,newItem): |
def _componentName(self, dict, name): |
|
return self.__class__.__name__.split('.')[-1] |
|
|
feature = self.__class__.__feature__ |
_componentName = Once(_componentName) |
|
|
d = feature.__get__(self) |
__instance_provides__ = New(EigenRegistry) |
p = d.index(oldItem) |
|
|
|
if p!=-1: |
__class_provides__ = EigenRegistry() |
d[p]=newItem |
|
feature._notifyUnlink(self,oldItem) |
|
feature._notifyLink(self,newItem) |
|
else: |
|
raise ValueError(oldItem,"not found") |
|
|
|
replace.namingConvention = 'replace%(initCap)s' |
|
|
|
|
|
def delete(self): |
|
"""Unset the value of the feature (like __delattr__)""" |
|
|
|
feature = self.__class__.__feature__ |
|
referencedEnd = feature.referencedEnd |
|
|
|
d = feature.__get__(self) |
|
|
|
if referencedEnd: |
|
|
|
for item in d: |
|
otherEnd = getattr(item.__class__,referencedEnd) |
|
otherEnd._unlink(item,self) |
|
|
|
super(Collection,feature).delete(self) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def acquireUtility(self, iface, forObj=None, localLookup=True): |
|
|
|
if forObj is None: |
|
forObj=self |
|
|
|
if localLookup: |
|
|
def _notifyLink(self,element,item): |
provider = self.__instance_provides__.get(iface) |
|
|
referencedEnd = self.referencedEnd |
if provider is not None: |
|
return provider(self,forObj) |
|
|
if referencedEnd: |
attr = self.__class_provides__.get(iface) |
otherEnd = getattr(item.__class__,referencedEnd) |
|
otherEnd._link(item,element) |
|
|
|
|
if attr is not None: |
|
|
def _notifyUnlink(self,element,item): |
utility = getattr(self,attr) |
|
|
referencedEnd = self.referencedEnd |
if utility is not NOT_FOUND: |
|
return utility |
|
|
if referencedEnd: |
parent = self.getParentComponent() |
otherEnd = getattr(item.__class__,referencedEnd) |
|
otherEnd._unlink(item,element) |
|
|
|
|
if parent is None: |
|
parent = config.getLocal(self) |
|
|
def _link(self,element,item): |
return parent.acquireUtility(iface,forObj) |
d=self.__get__(element) |
|
d.append(item) |
|
self.__set__(element,d) |
|
|
|
def _unlink(self,element,item): |
|
d=self.__get__(element) |
|
d.remove(item) |
|
self.__set__(element,d) |
|
|
|
|
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): |
|
|
class Reference(Collection): |
if obj is None: |
|
return self |
|
|
__class_implements__ = IReference |
newOb = self(obj) |
|
|
upperBound = 1 |
obj.__dict__[newOb._componentName] = newOb |
|
return newOb |
|
|
def __call__(self): |
|
"""Return the value of the feature""" |
|
return self._getData(None) |
|
|
|
def _set(self,value): |
class AutoCreated(Component): |
self.clear() |
|
self._setData([value]) |
|
|
|
|
"""Component that auto-creates itself in instances of its containing class |
|
""" |
|
|
class Sequence(Collection): |
__metaclasses__ = AutoCreatable, |
|
|
__class_implements__ = ISequence |
def __init__(self, parent=None): |
|
|
isOrdered = 1 |
super(AutoCreated,self).__init__() |
|
|
def insertBefore(self,oldItem,newItem): |
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() |
|
|
|
|
|
|