Source code for nti.zodb.persistentproperty

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Classes for making :class:`property` objects (actually, general descriptors)
more convenient for working with in :class:`persistent.Persistent` objects.

"""
__docformat__ = "restructuredtext en"

from persistent import Persistent


[docs] class PropertyHoldingPersistent(object): """ Base class mixin for a property that, when installed in a :class:`PersistentPropertyHolder`, can be used to hold another persistent object. This property object takes all responsibility for changing persistent state (of the instance it is installed in) if needed. """
def _get_or_make_cache(cls): # Needs to be its own property, not inherited cache = cls.__dict__.get('_v_persistentpropertyholder_cache') if cache is None: cache = {} # Walk through the inheritance tree, *from the root down*, collecting # descriptors. Order matters. for _cls in reversed(cls.mro()): for k, v in _cls.__dict__.items(): if isinstance(v, PropertyHoldingPersistent): cache[k] = v setattr(cls, '_v_persistentpropertyholder_cache', cache) return cache
[docs] class PersistentPropertyHolder(Persistent): """ Lets you assign to a property without necessarily changing the ``_p_status`` of this object. In a subclass of :class:`persistent.Persistent`, the ``__setattr__`` method sets ``_p_changed`` to True when called with a ``name`` argument that does not start with ``_p_`` (properties of the persistent object itself) or ``_v_`` (volatile properties). This makes it hard to use with conflict-reducing objects like :class:`nti.zodb.minmax.NumericMaximum`: instead of being able to define a descriptor to access and mutate them directly, you must remember to go through their API, and replacing existing simple attributes (a plain number) with a property doesn't actually reduce conflicts until all callers have been updated to use the API. This superclass fixes that problem. When :meth:`__setattr__` is called, it checks to see if the underlying attribute is actually a descriptor extending :class:`PropertyHoldingPersistent`, and if so, delegates directly to that object. That object is responsible for managing the persistent state of that instance. .. caution:: When you subclass this, you should not modify the type after the first instance is constructed by adding new :class:`PropertyHoldingPersistent` instances. As an implementation note, the ``__new__`` method caches the properties that are ``PropertyHoldingPersistent``. Adding new ones will bypass the cache (and make the instance modified) but otherwise still behave correctly. Replacing one with a different type of property or deleting the property altogether may not function correctly. """ def __new__(cls, *args, **kwargs): # We do this is __new__ and avoid a metaclass so that subclasses can # still choose their own metaclass. # Sadly, some extension classes (notably Acquisition.Implicit) # do not call super.__new__, so we cannat count on this being done # here. _get_or_make_cache(cls) return super(PersistentPropertyHolder, cls).__new__(cls, *args, **kwargs) def __setattr__(self, name, value): descriptor = _get_or_make_cache(type(self)).get(name) if descriptor is not None: descriptor.__set__(self, value) else: super().__setattr__(name, value)