[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)'
pairs.
* 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