[Subversion] / PEAK / src / peak / model / features.py  

View of /PEAK/src/peak/model/features.py

Parent Directory | Revision Log
Revision: 963 - (download) (as text)
Fri Apr 4 21:23:31 2003 UTC (21 years ago) by pje
File size: 13518 byte(s)
Got rid of deprecated 'peak.model' classes: Model, Package, Reference, etc.
"""Basic implementation of a domain metamodel (minus enumerations)

    This module implements base classes for "Features" in the sense of
    the "Service-Element-Feature" pattern.  By subclassing from them,
    you get a wide variety of services automatically provided, ranging
    from automatic generation of getter/setter/mutator methods,
    metadata such as ordered lists of features provided by a class,
    well-defined hookpoints for "event" trapping, persistence support,
    and more.
"""

from peak.api import *
from interfaces import *
from method_exporter import MethodExporter
from peak.util.hashcmp import HashAndCompare


__all__ = [
    'StructuralFeature',  'Collection', 'Sequence',
    'DerivedFeature', 'structField', 'Attribute',
]




















class FeatureClass(HashAndCompare,MethodExporter):

    """Method-exporting Property (metaclass for StructuralFeature)
    
        This metaclass adds property support to 'MethodExporter' by adding
        '__get__', '__set__', and '__delete__' methods, which are delegated
        to the method templates for the 'get', 'set' and 'unset' verbs.

        In other words, if you define a feature 'foo', following standard
        naming patterns for its 'set', 'get' and 'unset' verbs, and 'bar' is
        an Element whose class includes the 'foo' feature, then 'bar.foo = 1'
        is equivalent to 'bar.setFoo(1)'.  Similarly, referencing 'bar.foo' by
        itself is equivalent to 'bar.getFoo()', and 'del bar.foo' is equivalent
        to 'bar.unsetFoo()'.

        Please see the 'peak.model.method_exporter.MethodExporter' class
        documentation for more detail on how method templates are defined,
        the use of naming conventions, verbs, template variants, etc."""

    __metaclass__ = binding.Activator   # metaclasses can't be components


    def __get__(self, ob, typ=None):

        """Get the feature's value by delegating to 'ob.getX()'"""

        if ob is None: return self
        return self.get(ob)


    def __set__(self, ob, val):
        """Set the feature's value by delegating to 'ob.setX()'"""

        if self.isChangeable:
            self.set(ob,val)
        else:
            raise AttributeError("Unchangeable feature",self.attrName)




    def __delete__(self, ob):
        """Delete the feature's value by delegating to 'ob.unsetX()'"""

        if self.isChangeable:
            self.unset(ob)
        else:
            raise AttributeError("Unchangeable feature",self.attrName)


    def typeObject(self,d,a):
        """The actual type referred to by 'referencedType'

            Since a feature's 'referencedType' can be either a string or
            a type, the actual type object is cached in the 'typeObject'
            attribute.  If you need to get the type of feature 'aFeature',
            just refer to 'aFeature.typeObject'.  This will of course fail
            if the 'referencedType' attribute is invalid.
        """
        rt = self.referencedType
        if isinstance(rt,str):
            return binding.lookupComponent(rt,self)
        return rt

    typeObject = binding.Once(typeObject)
    fromString = binding.bindTo('typeObject/mdl_fromString')
    fromFields = binding.bindTo('typeObject/mdl_fromFields')
    normalize  = binding.bindTo('typeObject/mdl_normalize')

    sortPosn   = None

    def _hashAndCompare(self,d,a):

        """Features hash and compare based on position, name, and identity

        Specifically, a feature is hashed and compared as though it were
        a tuple of its 'sortPosn', '__name__', and 'id()'."""

        return self.sortPosn, self.__name__, id(self)
        
    _hashAndCompare = binding.Once(_hashAndCompare)

    isMany     = binding.Once(lambda s,d,a: s.upperBound<>1)
    isRequired = binding.Once(lambda s,d,a: s.lowerBound >0)

    isChangeable = binding.Once(
        lambda s,d,a: not s.isDerived,
        doc = "Feature is changeable; defaults to 'True' if not 'isDerived'"
    )

    implAttr   = binding.Once(
        lambda s,d,a: (s.useSlot and '_f_'+s.attrName or s.attrName),
        doc = "The underlying (private) attribute implementing this feature"
    )

    def isReference(self,d,a):
        """Does the feature refer to a non-primitive/non-struct type?"""
        from datatypes import TCKind
        return self.typeObject.mdl_typeCode.unaliased().kind==TCKind.tk_objref

    isReference = binding.Once(isReference)

    def _defaultValue(self,d,a):
        try:
            return self.defaultValue
        except AttributeError:
            return self.typeObject.mdl_defaultValue

    _defaultValue = binding.Once(_defaultValue)

    _bindFuncs = binding.Once(
        lambda s,d,a:
            s.getParentComponent()._getBindingFuncs(s.implAttr,s.useSlot)
    )

    _doGet = binding.Once(lambda s,d,a: s._bindFuncs[0])
    _doSet = binding.Once(lambda s,d,a: s._bindFuncs[1])
    _doDel = binding.Once(lambda s,d,a: s._bindFuncs[2])

    singularName = binding.bindTo('./attrName')



    rawTypeCode = binding.bindTo('typeObject/mdl_typeCode')
    typeKind    = binding.bindTo('typeCode/kind')
    typeCode    = binding.Once(lambda s,d,a: s.rawTypeCode.unaliased() )






































class StructuralFeature(object):

    __metaclass__ = FeatureClass

    __class_implements__ = IFeature, IFeatureSPI

    isDerived     = False
    isComposite   = False
    isOrdered     = False

    useSlot       = False

    lowerBound    = 0
    upperBound    = None    # None means unbounded upper end

    referencedEnd  = None
    referencedType = None

    newVerbs = Items(
        get     = 'get%(initCap)s',
        set     = 'set%(initCap)s',
        unset   = 'unset%(initCap)s',
        add     = 'add%(singularName.initCap)s',
        remove  = 'remove%(singularName.initCap)s',
        replace = 'replace%(singularName.initCap)s',
        insertBefore = 'insert%(singularName.initCap)sBefore',
    )














    def get(f):

        if f.isDerived:

            def get(feature,element):
                raise NotImplementedError

        elif f.isMany:

            def get(feature,element):
                try:
                    return feature._doGet(element)
                except AttributeError:
                    return []
            
        else:

            def get(feature,element):
                try:
                    return feature._doGet(element)
                except AttributeError:
                    value = feature._defaultValue
                    if value is NOT_GIVEN:
                        raise AttributeError,feature.attrName
                    return value
            
        return get


    get.isTemplate = True











    def set(f):

        if not f.isChangeable:
            set = None

        elif f.isMany:
        
            def set(feature, element, val):

                feature.unset(element)
                add = feature._notifyLink

                for v in val:
                    add(element,v)

        else:
        
            def set(feature, element, val):
                feature.unset(element)
                feature._notifyLink(element,val)

        return set

    set.isTemplate = True

















    def unset(f):

        if not f.isChangeable:
            unset = None

        elif f.isMany:

            def unset(feature, element):

                d = feature.get(element)

                items = zip(range(len(d)),d)
                items.reverse()

                remove = feature._notifyUnlink

                # remove items in reverse order, to simplify deletion and
                # to preserve any invariant that was relevant for addition
                # order...
        
                for posn,item in items:
                    remove(element,item,posn)
            
                feature._doDel(element)

        else:

            def unset(feature, element):
                try:
                    item = feature._doGet(element)
                except AttributeError:
                    pass
                else:
                    feature._notifyUnlink(element,item)

        return unset


    unset.isTemplate = True


    def replace(feature, element, oldItem, newItem):

        d = feature.get(element)

        if oldItem in d:
            p = d.index(oldItem)
            feature._notifyUnlink(element,oldItem,p)
            feature._notifyLink(element,newItem,p)

        else:
            raise ValueError("Can't replace missing item", oldItem)

    replace.installIf = lambda f,m: f.isChangeable and f.isMany



    def add(f):
        # Hardwire straight to _notifyLink(feature,element,item,posn=None)
        return f.methodTemplates['_notifyLink'](f)

    add.installIf  = lambda f,m: f.isChangeable and f.isMany
    add.isTemplate = True


    def remove(f):
        # Hardwire straight to _notifyUnlink(feature,element,item,posn=None)
        return f.methodTemplates['_notifyUnlink'](f)

    remove.installIf  = lambda f,m: f.isChangeable and f.isMany
    remove.isTemplate = True











    def _notifyLink(f):

        _link = f.methodTemplates['_link'](f)
        refEnd = f.referencedEnd

        if not f.isChangeable:
            _notifyLink = None

        elif refEnd:

            def _notifyLink(feature, element, item, posn=None):
                item     = _link(feature,element,item,posn)
                otherEnd = getattr(item.__class__, refEnd)
                otherEnd._link(item,element)

        else:
            # Return the _link method "in line"; _notify isn't needed
            _notifyLink = _link

        return _notifyLink


    _notifyLink.verb       = '_notifyLink'
    _notifyLink.isTemplate = True

















    def _notifyUnlink(f):

        _unlink = f.methodTemplates['_unlink'](f)
        refEnd = f.referencedEnd

        if not f.isChangeable:
            _notifyUnlink = None

        elif refEnd:

            def _notifyUnlink(feature, element, item, posn=None):
                _unlink(feature,element,item,posn)
                otherEnd = getattr(item.__class__, refEnd)
                otherEnd._unlink(item,element)

        else:
            # Return the _link method "in line"; _notify isn't needed
            _notifyUnlink = _unlink

        return _notifyUnlink


    _notifyUnlink.verb       = '_notifyUnlink'
    _notifyUnlink.isTemplate = True

















    def _link(f):

        if not f.isChangeable:
            _link = None

        elif f.isMany:

            def _link(feature,element,item,posn=None):

                ub = feature.upperBound
                d=feature.get(element)

                if ub and len(d)>=ub:
                    raise ValueError("Too many items")

                item = feature.normalize(item)
                feature._onLink(element,item,posn)

                feature._doSet(element,d)

                if posn is None:
                    d.append(item)
                else:
                    d.insert(posn,item)
                return item

        else:

            def _link(feature,element,item,posn=None):

                item = feature.normalize(item)
                feature._onLink(element,item,posn)
                feature._doSet(element,item)
                return item

        return _link

    _link.verb       = '_link'
    _link.isTemplate = True


    def _unlink(f):

        if not f.isChangeable:
            _unlink = None

        elif f.isMany:

            def _unlink(feature,element,item,posn=None):

                feature._onUnlink(element,item,posn)
                d=feature.get(element)
                feature._doSet(element,d)

                if posn is None:
                    d.remove(item)
                else:
                    del d[posn]

        else:

            def _unlink(feature,element,item,posn=None):
                feature._onUnlink(element,item,posn)
                feature._doDel(element)

        return _unlink


    _unlink.verb       = '_unlink'
    _unlink.isTemplate = True



    def _onLink(feature,element,item,posn):
        pass


    def _onUnlink(feature,element,item,posn):
        pass



    def _setup(feature,element,value):

        if feature.isChangeable:
            return feature.set(element,value)

        doLink = feature._onLink
        normalize = feature.normalize
        
        if feature.isMany:
            p = 0
            value = tuple(map(normalize,value))

            for v in value:
                doLink(element,value,p)
                p+=1

        else:
            doLink(element,normalize(value),0)

        feature._doSet(element,value)





















    def insertBefore(feature, element, oldItem, newItem):

        d = feature.get(element)

        if oldItem in d:
            feature._notifyLink(element,newItem,d.index(oldItem))
        else:
            raise ValueError("Can't insert before missing item", oldItem)

    insertBefore.installIf = lambda f,m: (
        f.isOrdered and f.isMany and f.isChangeable
    )





























class Collection(StructuralFeature):
    pass



class Attribute(StructuralFeature):

    upperBound = 1



class structField(StructuralFeature):

    """An unchangeable attribute; used for immutables"""

    upperBound = 1

    isChangeable = binding.classAttr( binding.Constant(None, False) )



class DerivedFeature(StructuralFeature):

    isDerived = True



class Sequence(StructuralFeature):

    isOrdered = True













cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help