[PEAK] Making Data Managers easier to use

Phillip J. Eby pje at telecommunity.com
Fri May 20 22:23:09 EDT 2005

After thinking some more about Erik Rose's questions and suggestions, I've 
decided to go ahead and implement parts of the planned peak.schema 
interface for the current peak.storage DM classes.  Specifically, here's 
what I've added and am now checking in to CVS:

     Return the object if found, otherwise return the default.  Unlike 
``dm[oid]``, this returns a *non-lazy* object (i.e., it will never return a 
ghost).  It should not be used when setting up inter-object references -- 
keep using ``dm[oid]`` for that, so that you don't cause your entire 
database to load at once!  But for "client" code that accesses DM's, you 
will usually want to use this method instead.  Note that to be able to use 
this method, you *must* modify the DM's ``_load()`` method to raise 
``storage.InvalidKeyError`` when it cannot find the requested object ID.  I 
had to use a new exception to avoid unintentionally trapping errors other 
than the item being not found.  Also note that using ``get()`` *will* cause 
a database access if the requested object is not in cache, or does not 
exist (which implies that it won't be in cache).

     Returns true if the given object or oid is present in the DB.  If ob 
is an instance of a ``Persistent`` subclass, then the return value 
indicates whether the object "belongs" to this DM.  Otherwise, the object 
is considered an ID, and the return value indicates whether ``dm.get(oid)`` 
would return a non-None value.  (Note: this method is implemented in terms 
of ``get()``, so note that using it may result in a database access.  You 
must also update your ``_load()`` method as you would for using ``get()``.)

     Add the already-created object to the DM.  The object must not already 
be owned by another DM.  (If it has already been added to the DM, this 
method is a no-op.)  You do not need to do anything special to use this 
method, but you *should* add a ``_check(ob)`` method to verify that `ob` is 
of a suitable type for storing in the DM.  Note that this method is now 
preferred to ``newItem()``, which is consequently deprecated.

     Remove a previously-added object.  The object will no longer be owned 
by the DM, and future attempts to retrieve the object will fail.  (i.e. 
``ob in dm`` will be false, and ``dm.get(ob._p_oid)`` will return 
None).  The object is not actually deleted from the underlying database 
until ``dm.flush()`` is called, or the transaction commits.  You must 
implement a ``_delete_oids(oidList)`` method in order to use this 
method.  The ``_delete_oids()`` method should delete each of the supplied 
oids from the database, in the order given.  It is possible that some of 
the supplied oids may not exist in the underlying database, and this should 
*not* result in an error; it may be that the object was added and deleted 
without ever having been stored, and that is a perfectly valid use case.

     This is not actually implemented; in fact it raises 
``NotImplementedError``.  But if you want to do something like the 
``getAll()`` methods on the bulletins example DM's (which I'm changing to 
use ``__iter__``, by the way), then you should override it.  Your method 
should look something like this::

         def __iter__(self):
             for state in anIterableOfStates():
                 oid = state['whatever_field_the_oid_is_in']
                 if oid not in self.to_delete:
                     yield self.preloadState(oid, state)

     Your ``__iter__`` method should *not* yield items that have been 
deleted, but it *should* yield items that have been added to the DM even if 
they have not yet been added to the underlying database.  If you can't do 
this in a more efficient way, your method can always just start with a 
``self.flush()`` call.  This will flush all pending adds, deletes, and 
modifications to the underlying database, so you can then just query that 
database and get on with it, without worrying about uncommitted adds or 

Whew.  I think that about covers it.  Hopefully, this will address a fairly 
long list of DM quirks that have been somewhat annoying to a lot of people 
for some time now.  Interestingly, it also makes several chunks of the 
IntroToPeak tutorial unnecessary.  The things that those sections say are 
still correct and should still work, mind you.  It's just that you don't 
need to write your own ``__contains__`` or ``get()`` or ``remove()`` 
methods any more, and the quirky nature of ``newItem()`` can be now be 
avoided as well.

The implementation passes the existing tests, and a few new ones as well, 
but there remains the possibility that I've nonetheless introduced a bug 
somewhere, so be sure to test thoroughly before upgrading a production 
application to the latest CVS version -- not that any of you would do that, 
right?  :)  And remember, that applies whether you actually use the new 
features or not.

More information about the PEAK mailing list