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

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

Parent Directory | Revision Log
Revision: 1582 - (download) (as text)
Sat Jan 3 00:36:27 2004 UTC (20 years, 3 months ago) by pje
File size: 8157 byte(s)
"Global" services defined by '[Component Factories]' sections now live in
the closest "service area" to the component that requests them.  A "service
area" is a parent component that implements 'config.IServiceArea', such as
a configuration root returned by 'config.makeRoot()'.  Applications loaded
by the 'peak runIni' command are now created in their own service area,
which means that settings in the .ini file being run will apply to services
the application uses.  (Because the application will have its own,
application-specific service instances, and they will use the configuration
loaded into the service area.)

For more information, see CHANGES.txt, and especially:

http://www.eby-sarna.com/pipermail/peak/2004-January/001087.html
"""Objects that can be written, unless they've been read

Dynamic configuration can be a wonderful thing.  It can also be a nightmare,
if you have configuration values that depend on other configuration values,
and you are using lots of components that are reading and writing those
values.  If you change a value after it has already been used by another
component, how can you be sure what objects were configured in what way?

Different languages and systems approach this issue differently.  In Java,
it's common to have settings which can be set only once.  In Python, one
may use immutables.  Zope 3X has a configuration conflict detection algorithm.
But all of these approaches depend on either a write-once approach or an
atomic configuration phase.

PEAK takes a "quantum physics" approach to immutability instead: treat
configuration data as an opaque box, whose contents you can change at will.
However, once somebody looks in the box, the result is fixed and can no
longer be changed.  You can think of it as a kind of "lazy immutability".

The EigenData module supplies an 'EigenCell' class for storing single
values, and an 'EigenDict' class to be used in place of dictionaries.
Modifying an 'EigenCell' or 'EigenDict' once it has been read results in
an 'AlreadyRead' exception.  Also available: 'CollapsedCell', which is an
empty 'EigenCell' that has already been read and is therefore immutable.

FYI: the module and classes are named after eigenstates in quantum physics;
"one of a finite number of states that a quantum system can be in".  The
famous "Schrodinger's Cat" thought experiment is an analogy for eigenstates:
once the box is opened, its contents are forced to collapse to a single
eigenstate.  'SchrodingDict' and 'SchrodingCell', however, are much more
awkward to say and type!"""

__all__ = [
    'AlreadyRead', 'EigenCell', 'CollapsedCell', 'EigenDict',
]

from protocols import Interface

class AlreadyRead(Exception):
    """Key or value has already been read"""

class EigenCell(object):

    """Hold a value which can be changed as long as it has not been examined

    An EigenCell is used to hold a configuration value that should not be
    changed once it is used.  The value may be 'set()' or 'unset()' at will
    as long as the 'exists()' or 'get()' methods have not been called.  As
    soon as the cell's value has been read or tested for existence, the value
    is locked and an 'AlreadyRead' exception will be thrown if you attempt to
    'set()' or 'unset()' the value after that point.  (Note: 'unset()' is a
    no-op if the value is empty, whether the cell is locked or not, because
    it does not change the value's nonexistence.)"""

    __slots__ = ['value','locked']

    def __init__(self):
        """Create an (empty and unread) EigenCell"""
        self.locked = False


    def get(self, setdefault=None):
        """Return the cell's value, or raise AttributeError if empty

        A function may be supplied to set a default value, if the cell has not
        yet been read and no value is currently assigned.  The return value
        of the function will be used.  For example::

            aCell.get(lambda: someObj())

        will set the value of 'aCell' to 'someObj()' if 'aCell' has not been
        read and has no value currently assigned.  Notice that you must pass
        a function, not a value.  The simplest way to do this is with 'lambda'
        as shown above."""

        if not self.locked and not hasattr(self,'value') and setdefault:
            self.value = setdefault()

        self.locked = True
        return self.value


    def exists(self):
        """Return true if the cell contains a value."""
        self.locked = True
        return hasattr(self,'value')

    def set(self,value):
        """Set the cell's value, or raise AlreadyRead"""
        if self.locked: raise AlreadyRead
        self.value = value

    def unset(self):
        """Remove the cell's value (reset to non-existence)"""
        if hasattr(self,'value'):
            if self.locked: raise AlreadyRead
            del self.value


























class EigenDict(object):

    """Mapping whose entries can be changed as long as they are unread

    An EigenDict is used to hold a set of configuration values that should not
    be changed once they are read.  It offers essentially the same interface
    as a standard Python dictionary, with the following twists:

     * Operations on individual entries are delegated to EigenCell objects,
       so a given entry cannot be written to or deleted once it has been
       read, or its existence inspected by 'has_key()', 'get()', etc.

     * Operations on the dictionary as a whole, such as 'keys()', 'values()',
       'copy()', '__cmp__()', '__repr__()', etc., will lock the entire
       dictionary and no further modifications will be allowed.

     * '__len__()' and 'popitem()' methods are not available.

     * '.copy()' returns a standard Python dictionary, not an EigenDict

    """

    def __init__(self, dict=None):
        self.data = {}
        if dict is not None: self.update(dict)

    def keys(self):
        self.lock(); return self.data.keys()

    def items(self):
        self.lock(); return [(k,v.get()) for (k,v) in self.data.items()]

    def values(self):
        self.lock(); return [v.get() for v in self.data.values()]

    def copy(self):
        return dict(self.items())




    def iteritems(self):
        return iter(self.items())

    def iterkeys(self):
        return iter(self.keys())

    def itervalues(self):
        return iter(self.values())


    def update(self, dict):
        sc = self._setCell
        for k, v in dict.items():
            sc(k).set(v)

    def clear(self):
        for v in self.data.values():
            v.unset()


    def __repr__(self):
        return `self.copy()`

    def __cmp__(self, dict):
        return cmp(self.copy(), dict.copy())

    def __iter__(self):
        return self.iterkeys()













    locked = False

    def lock(self):
        """Lock the dictionary, ensuring that its contents cannot change

            This method locks all the dictionary's cells, and sets a flag
            to ensure that no new cells will be created.  It also discards
            empty cells, for the convenience of routines that operate on
            the dictionary as a whole, like 'keys()' and 'values()'.
        """

        if self.locked:
            return

        self.locked = True
        data = self.data

        for k,v in data.items():
            if not v.exists():
                del data[k]


    def _setCell(self, key):
        """Return a new or existing EigenCell for 'key'

            If there is no EigenCell stored under 'key', return a new
            EigenCell and store it.  If the dictionary is locked, return
            an empty, locked EigenCell instead of creating a new one.  This
            ensures that no new entries can be created if locked."""

        cell = self.data.get(key)

        if cell is None:

            if self.locked:
                return CollapsedCell

            cell = self.data[key] = EigenCell()

        return cell

    def __getitem__(self, key):

        cell = self._setCell(key)

        if cell.exists():
            return cell.get()

        raise KeyError, key


    def get(self, key, failobj=None, factory=None):

        cell = self._setCell(key)

        try:
            return cell.get(factory)
        except AttributeError:
            return failobj


    def setdefault(self, key, failobj=None):
        if not self.data.has_key(key):
            self[key] = failobj
        return self[key]


    def has_key(self, key):
        return self._setCell(key).exists()

    def __contains__(self, key):
        return self._setCell(key).exists()

    def __setitem__(self, key, item):
        self._setCell(key).set(item)

    def __delitem__(self, key):
        self._setCell(key).unset()




# CollapsedCell is an empty (but read and locked) EigenCell

CollapsedCell = EigenCell()
CollapsedCell.exists()  # force locking






































cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help