[Subversion] / PEAK / src / peak / util / Struct.py  

View of /PEAK/src/peak/util/Struct.py

Parent Directory | Revision Log
Revision: 1076 - (download) (as text)
Sat May 3 13:21:20 2003 UTC (20 years, 11 months ago) by pje
File size: 8657 byte(s)
Normalized whitespace.
"""It's a tuple...  it's a dictionary...  it's super struct!"""

from types import StringTypes

__all__ = [
    'structType', 'struct', 'makeStructType'
]


































class structType(type):

    """Sets up __fieldmap__ and field properties"""

    classmethods = (
        'fromArgs', 'fromOther', 'fromString',
        'fromMapping', 'extractFromMapping',
    )

    def __new__(meta, name, bases, cdict):

        cdict = cdict.copy()
        cdict['__slots__']=[]

        for cm in meta.classmethods:
            if cm in cdict:
                cdict[cm] = classmethod(cdict[cm])

        if '__fields__' in cdict:
            cdict['__fields__'] = list(cdict['__fields__'])

        return super(structType,meta).__new__(meta, name, bases, cdict)

    def __init__(klass, name, bases, cdict):

        fields = getattr(klass,'__fields__', ())
        baseMap = getattr(super(klass,klass), '__fieldmap__', {})
        fzip = zip(fields, range(len(fields)))
        fieldMap = klass.__fieldmap__ = dict(fzip)

        # No change in schema? leave properties alone
        if baseMap==fieldMap: return

        for fieldName, fieldNum in fzip:
            if fieldName in cdict or baseMap.get(fieldName)==fieldNum:
                # don't override any explicitly supplied properties
                # or inherited ones based on the same field number
                continue

            setattr(klass, fieldName, makeFieldProperty(fieldName, fieldNum))

    def addField(klass, fieldName):

        """Add field 'fieldName' to an existing struct class"""

        fm = klass.__fieldmap__

        if fieldName not in fm:

            fm[fieldName] = fieldNum = len(fm)
            setattr(klass, fieldName, makeFieldProperty(fieldName,fieldNum))
            klass.__fields__.append(fieldName)






























class struct(tuple):

    """Typed, immutable, multi-field object w/sequence and mapping interfaces

    Usage::

        class myRecord(struct):
            __fields__ = 'first', 'second', 'third'

        # the following will now all produce identical objects
        # and they'll all compare equal to the tuple (1,2,3):

        r = myRecord([1,2,3])
        r = myRecord(first=1, second=2, third=3)
        r = myRecord({'first':1, 'second':2, 'third':3})
        r = myRecord.fromMapping({'first':1, 'second':2, 'third':3})
        r = myRecord.extractFromMapping(
            {'first':1, 'second':2, 'third':3, 'blue':'lagoon'}
        )
        r = myRecord.fromMapping( myRecord([1,2,3]) )

        # the following will all print the same thing for any 'r' above:

        print r
        print (r.first, r.second, r.third)
        print (r[0], r[1], r[2])
        print (r['first'], r['second'], r['third'])

    If you want to define your own properties in place of the automagically
    generated ones, just include them in your class.  Your defined properties
    will be inherited by subclasses, as long as the field of that name is at
    the same position in the record.  If a subclass changes the field order,
    the inherited property will be overridden by a generated one, unless the
    subclass supplies a replacement as part of the class dictionary.

    Note: if you define custom properties, they only determine the *attributes*
    of the instance.  All other behaviors including string representation,
    iteration, item retrieval, etc., will be unaffected.  It's probably best
    to redefine the 'fromArgs' classmethod to manage the initial construction
    of the fields instead."""

    __metaclass__ = structType

    __fields__ = __converters__ = __defaults__ = ()


    def __new__(klass, *__args, **__kw):

        if len(__args)==1 and not __kw:

            arg = __args[0]

            if isinstance(arg,StringTypes):
                return klass.fromString(arg)

            elif isinstance(arg,dict):
                return klass.fromMapping(arg)

            return klass.fromOther(arg)


        elif __kw and not __args:
            return klass.fromMapping(__kw)

        return klass.fromArgs(*__args, **__kw)


    def fromString(klass, arg):

        """Override this classmethod to enable parsing from a string"""

        raise NotImplementedError










    def fromArgs(klass, *__args, **__kw):

        """Create from arguments

            By default, this classmethod is where all the other creation
            methods "call down" to, so that you can do any validation or
            conversions.  The default implementation just calls
            'tuple.__new__' on the '*__args' tuple.  You should override
            this with a classmethod that takes the arguments you want, in
            the same order as your '__fields__' definition, supplying
            defaults if desired.

            The default version of this method will accept input sequences
            with more items than there are fields to fill.  The extra data
            isn't lost, it's just unavailable except via sequence methods.
            If you want different behavior, such as truncating the sequence
            or raising an exception, you'll need to override this method.
        """

        if __kw:
            raise TypeError("Invalid keyword arguments for " + klass.__name__)

        return tuple.__new__(klass, __args)


















    def fromOther(klass, arg):

        """Create from a single argument

            You can define a classmethod here, to be used in place of
            'tuple.__new__' when the struct is being created from a single
            argument that is not a dictionary, keywords, or a string.

            The default simply hands the argument through to the
            'fromArgs()' method, where it will be treated as being the
            first field of the struct.
        """
        return klass.fromArgs(arg)


    def fromMapping(klass, arg):

        """Create a struct from a mapping

            This method checks that the mapping doesn't contain any field names
            the struct won't accept.  This prevents silent unintentional loss
            of information during conversions.  If you want extra data in the
            mapping to be ignored, you should use 'extractFromMapping' instead.

            Note that although this method will raise 'ValueError' for fields
            that would be dropped, it uses a default value of 'None' for any
            fields which are missing from the mapping.  If you want a stricter
            policy, you'll need to override this.
        """

        fm = klass.__fieldmap__; nfm = fm.copy(); nfm.update(arg)

        if len(nfm)>len(fm):
            raise ValueError(
                ("Mapping contains keys which are not fields of %s"
                 % klass.__name__), arg
            )

        return klass.fromArgs(*tuple(map(arg.get, klass.__fields__)))


    def extractFromMapping(klass, arg):
        """Fast extraction from a mapping; ignores undefined fields"""
        return klass.fromArgs(*tuple(map(arg.get, klass.__fields__)))


    def __getitem__(self, key):

        if isinstance(key,StringTypes):

            # will raise KeyError for us if it's not found
            i = self.__fieldmap__[key]

            if i>=len(self):
                raise KeyError,key
            else:
                key = i

        # this will raise IndexError instead of KeyError, which we want
        # if it was a number...
        return tuple.__getitem__(self,key)


    def get(self, key, default=None):
        try:
            return self[key]
        except (KeyError,IndexError):
            return default


    def copy(self):         return dict(self.items())
    def keys(self):         return list(self.__fields__[:len(self)])
    def iterkeys(self):     return iter(self.__fields__[:len(self)])
    def items(self):        return zip(self.__fields__,self)
    def iteritems(self):    return iter(self.items())
    def values(self):       return list(self)
    def itervalues(self):   return iter(self)





    __safe_for_unpickling__ = True

    def __reduce__(self):   return self.__class__, (tuple(self),)

    def __contains__(self, key):
        myLen = len(self)
        return self.__fieldmap__.get(key,myLen) < myLen

    has_key = __contains__



def makeFieldProperty(fieldName, fieldNum):

    def get(self):
        try:
            return tuple.__getitem__(self,fieldNum)
        except IndexError:
            return None

    return property(get)


def makeStructType(name, fields, baseType=struct, **kw):
    kw['__fields__'] = fields
    return structType(name or 'anonymous_struct', (baseType,), kw)
















cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help