[PEAK] The path of model and storage

Phillip J. Eby pje at telecommunity.com
Wed Jul 28 01:53:31 EDT 2004

Here's a rough plan for the first stages of how I want to move towards 
implementing "workspace-based" object mappings in peak.model and storage, 
in the form of an list of "stories" or requirements to be 
implemented.  I've tried to order them approximately in the order I would 
write the tests to develop them in a test-driven way.  As you'll see, this 
list is quit concrete up to a point, and then fizzles out into general 
thoughts and directions that hopefully will be clearer by the time I get 
that far.  :)

For the most part, this list assumes that a workspace is all 
in-memory.  That is, a "document" persistence style.  However, all of the 
functionality described is prerequisite for implementing fact-base 
persistence as well, and should be factorable as such.  You will also see 
that the base functionality contemplated here is rather more sophisticated 
than what is currently provided via DM's, since it encompasses lookups by 
various kinds of keys, deletion, detection of whether an instance exists, 
and so on.

Workspace Access

* Create a workspace for a given abstract model, expressed as a module.

* Access classes, functions, etc. from the model module as attributes of 
the workspace

* Classes accessed as attributes have the workspace as their parent component

* Modules accessed as attributes are replaced by components

* Module components have the workspace as their parent

* Module components allow access to contained classes as attributes

* Module components allow access to contained modules as attributes

* The contained classes or modules have the module component as their parent

* Classes retrieved from a workspace or any module component thereof, have 
a 'find()' classmethod that iterates over all created instances of that 
class or any of its subclasses (within the corresponding workspace)

* The 'find()' classmethod, if given keyword arguments named for the 
features of the type, yields only instances whose feature values are equal 
to those supplied as arguments.

* The workspace offers a 'delete()' method which may be used to dispose of 
an instance of a workspace-specific class.  Once deleted, the instance is 
no longer returned by 'find()'.

* Mapped classes can find their mapped superclasses.  That is, if the 
mapped module contains:

    class A:  pass

    class B(A): pass

then 'workspace.B' must be able to gain access to 'workspace.A'.  This must 
be true even for classes in nested modules, e.g.:

    import a_mod
    class B(a_mod.A): pass

* An error occurs if a model class is accessed via a workspace, and any of 
the model class' base classes (apart from 'object', 'model.Element', etc.) 
are not accessible via some attribute path from the workspace.

Model Keys

* Mutable peak.model types allow the definition of keys, where a key is an 
ordered set of zero or more features, each of which must have an 
'upperBound' of 1 (i.e. multivalued features can't be part of a key), and 
whose values must be hashable and comparable.  (Note: a key with zero 
features means the class is effectively a singleton within a given workspace

* Keys can have an estimated multiplicity, which may be 1 or higher.  (1 
means unique, values higher than 1 are an estimate which may be used to 
drive caching rules)

* The keys for a type include keys defined by the type's superclasses.

* A type can return the current keys for an instance, in the form of a list 
of '(type,key)' tuples, where 'type' is the type where the key was defined, 
and each 'key' is a tuple ((f1,v1),(f2,v2),...) of '(feature_name,value)' 

* If a feature has no current value and no default value, keys which 
include the feature are not included in the list of "current keys"

* Changing an instance's features such that any of its current "unique" 
keys become non-unique within the workspace, should cause an error and a 
rejection of the change.  (Probable implementation: use 
'self.__class__.find()' against each current unique key involving the 
feature, and verify that using the new value yields self or an empty list.)

* Classes retrieved from a workspace or any module component thereof, have 
a 'get()' classmethod that returns a single instance of the type (or 
subtype) or None.  The instance must match the supplied keyword arugments, 
if any, following the same rules as 'find()', except that supplied values 
must be hashable.

* 'get()' should raise an error if the supplied keywords do not cover 
enough features to ensure that at least one unique key (i.e., a key with 
multiplicity of 1) can be constructed from the supplied arguments.  (Note 
that if the type is a per-workspace singleton, 'get()' with no arguments is 
perfectly valid for that type and should return the singleton instance.)

* 'get()' should return 'None' if no current matching instance exists (i.e. 
if 'find()' would yield no instances, given the same arguments).

Workspace SPI

* Cache objects or lists thereof by key

* Clear all cache entries touched during a transaction

* Track instances modified during a transaction, and whether they've been saved

Directions for Enhancement

* 'find()' and 'get()' should allow other criteria besides value equality

* query language

* time-to-live caching support, including clear after each txn. (Note that 
some kind of cleanup is needed, because w/out Persistent, circular 
references in the cache will be retained indefinitely.)

* save mementos?

* locking

* event hooks (fire when object(s) change)

More information about the PEAK mailing list