Persistent objects

Description

This document tells how to save objects to Plone/Zope database. Persistent objects are automatically read and written from ZODB database in Plone and they appear as normal Python objects in your code. This document clarifies some of special properties, like with containers, when you deal with persistent objects programmatically.

Introduction

Q: How do I save() object in Plone

A: You don’t

Plone does this automatically for you. You just assign the file data as an attribute of some persistent object. When the HTTP request completes, Zope transaction manager will automatically update all changed persistent objects to the database. There is no “save” as such in Zope world - it all is transparent to the developer. If the transaction fails in any point, no data is being written and you do not need to worry about the partial data being written to the database.

  • Changed objects will be automatically saved (if they are attached to the traversing graph)
  • Save will not occur if an exception is raised

If your data class inherits from higher level Plone base classes (all go up to persistent.Persitent class). persistency is handled transparently for you. Plone also handles transaction automatically for each HTTP request. Unless you wish to do manual transactions there is no need to call transaction.commit().

If you want to do your own persistent classes please read the following

Lists and dictionaries

If you modify objects inside persistent lists and dictionaries, the change is not automatically reflected to the parent container.

PersistentList vs. normal Python list

All items in normal Python list are stored as one write and loaded on one write. PersistentList is slower, but allows individual objects picked from the list without loading the whole list.

For more information, see

Persistent, modifications, __setattr__ and transactions

When Persitent object is modified, via attribute set or __setattr__() call, the current transaction is converted to a write transaction. Write transactions are usually undoable (visible on Zope’s Undo tab).

If you are using Python property mutator and even if it does not write to the object it still will trigger the object rewrite.

More info

Up-to-date reads

Normally, ZODB only assures that objects read are consistent, but not necessarily up to date. Checking whether an object is up to date is important when information read from one object is used to update another.

The following will force the object to use the most up-to-date version in the transaction:

self._p_jar.readCurrent(ob)

A conflict error will be raised if the version of ob read by the transaction isn’t current when the transaction is committed.

Note

ZODB versions older than 3.10.0b5 do not support this feature.

More information

Accessing broken objects

ZODB is object database. By default, it cannot load object from the database if the code (Python class) is not present.

You can still access data in the objects by creating Python code “stubs” which fake the non-existing classes in the run-time environment.

More info

Fixing damaged objects

If your BTrees have been damaged, you can use dm.historical tool to inspect the object history and rewind it to a working state.

See also

Volatile references

Volatile attributes are attributes on persistent objects which never get stored. ZODB assumes variable is volatile if it has _v_ prefix.

Volatiles are useful when framework expects the object to conform certain interface, like form frameworks. However, your persistent object edited by form cannot have persistent attributes for all variables the form expects to see.

Example:

from persistent import Persistent
from zope.annotation import IAnnotations

class VolatileContext(object):
    """ Mix-in class to provide context variable to persistent classes which is not persistent.

    Some subsystems (e.g. forms) expect objects to have a reference to parent/site/whatever.
    However, it might not be a wise idea to have circular persistent references.

    This helper class creates a context property which is volatile (never persistent),
    but can be still set on the object after creation or after database load.
    """

    def _set_context(self, context):
        self._v_context = context

    def _get_context(self):
        return self._v_context

class MobileBehaviorStorage(VolatileContext, Persistent):
    """Set mobile specific field properties on the context object and return the context object itself.#

    This allows to use attribute storage with schema input validation.
    """

    mobileFolderListing = FieldPropertyDelegate(IMobileBehavior["mobileFolderListing"])


KEY = "mobile"

def manufacture_mobile_behavior(context):

    annotations = IAnnotations(context)
    if not KEY in annotations:
        annotations[KEY] = MobileBehaviorStorage()

    object = annotations[KEY]

    # Set volatile context
    object.context = context

    return object

Correct use of volatile variables in functions

WRONG:

if hasattr(self, '_v_image'):
    return self._v_image

RIGHT:

marker = []
value = getattr(self, "_v_image", marker)
if value is not marker:
    return value

RIGHT:

try:
    return self._v_image
except AttributeError:

WRONG:

self._v_image=expensive_calculation()
return self._v_image

RIGHT:

image=expensive_calculation()
self._v_image=image
return image

For more information, see

Measuring persistent object sizes

Get the size of the pickled object in the database.

Something like:

pickle, serial = obj._p_jar._storage.load(obj._p_oid, obj._p_jar._version)

See also