Learn more  » Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

edgify / persistent   python

Repository URL to install this package:

/ persistence.py

##############################################################################
#
# Copyright (c) 2011 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
import struct

from zope.interface import implementer
from persistent import interfaces
from persistent.interfaces import SERIAL_TYPE
from persistent.timestamp import TimeStamp
from persistent.timestamp import _ZERO
from persistent._compat import copy_reg
from persistent._compat import intern
from persistent._compat import use_c_impl

__all__ = [
    'Persistent',
    'PersistentPy',
]

# We use implementation details of PickleCachePy
# pylint:disable=protected-access
# we have lots of not-quite-correct continuation indentation.
# TODO: Fix that in a whitespace-only commit.
# pylint:disable=bad-continuation
# There are a few places we need to work with exact types.
# pylint:disable=unidiomatic-typecheck

_INITIAL_SERIAL = _ZERO


# Bitwise flags
_CHANGED = 0x0001
_STICKY = 0x0002

_OGA = object.__getattribute__
_OSA = object.__setattr__
_ODA = object.__delattr__

# These names can be used from a ghost without causing it to be
# activated. These are standardized with the C implementation
SPECIAL_NAMES = ('__class__',
                 '__del__',
                 '__dict__',
                 '__of__',
                 '__setstate__',)

# And this is an implementation detail of this class; it holds
# the standard names plus the slot names, allowing for just one
# check in __getattribute__
_SPECIAL_NAMES = set(SPECIAL_NAMES)

# __ring is for use by PickleCachePy and is opaque to us.
_SLOTS = ('__jar', '__oid', '__serial', '__flags', '__size', '__ring',)
_SPECIAL_NAMES.update([intern('_Persistent' + x) for x in _SLOTS])


# Represent 8-byte OIDs as hex integer, just like
# ZODB does.
_OID_STRUCT = struct.Struct('>Q')
_OID_UNPACK = _OID_STRUCT.unpack



@use_c_impl
@implementer(interfaces.IPersistent)
class Persistent(object):
    """ Pure Python implmentation of Persistent base class
    """
    __slots__ = _SLOTS

    def __new__(cls, *args, **kw):
        inst = super(Persistent, cls).__new__(cls)
        # We bypass the __setattr__ implementation of this object
        # at __new__ time, just like the C implementation does. This
        # makes us compatible with subclasses that want to access
        # properties like _p_changed in their setattr implementation
        _OSA(inst, '_Persistent__jar', None)
        _OSA(inst, '_Persistent__oid', None)
        _OSA(inst, '_Persistent__serial', None)
        _OSA(inst, '_Persistent__flags', None)
        _OSA(inst, '_Persistent__size', 0)
        _OSA(inst, '_Persistent__ring', None)
        return inst

    # _p_jar:  see IPersistent.
    def _get_jar(self):
        return _OGA(self, '_Persistent__jar')

    def _set_jar(self, value):
        jar = _OGA(self, '_Persistent__jar')
        if self._p_is_in_cache(jar) and value is not None and jar != value:
            # The C implementation only forbids changing the jar
            # if we're already in a cache. Match its error message
            raise ValueError('can not change _p_jar of cached object')

        if _OGA(self, '_Persistent__jar') != value:
            _OSA(self, '_Persistent__jar', value)
            _OSA(self, '_Persistent__flags', 0)

    def _del_jar(self):
        jar = _OGA(self, '_Persistent__jar')
        if jar is not None:
            if self._p_is_in_cache(jar):
                raise ValueError("can't delete _p_jar of cached object")
            _OSA(self, '_Persistent__jar', None)
            _OSA(self, '_Persistent__flags', None)

    _p_jar = property(_get_jar, _set_jar, _del_jar)

    # _p_oid:  see IPersistent.
    def _get_oid(self):
        return _OGA(self, '_Persistent__oid')

    def _set_oid(self, value):
        if value == _OGA(self, '_Persistent__oid'):
            return
        # The C implementation allows *any* value to be
        # used as the _p_oid.
        #if value is not None:
        #    if not isinstance(value, OID_TYPE):
        #        raise ValueError('Invalid OID type: %s' % value)
        # The C implementation only forbids changing the OID
        # if we're in a cache, regardless of what the current
        # value or jar is
        if self._p_is_in_cache():
            # match the C error message
            raise ValueError('can not change _p_oid of cached object')
        _OSA(self, '_Persistent__oid', value)

    def _del_oid(self):
        jar = _OGA(self, '_Persistent__jar')
        oid = _OGA(self, '_Persistent__oid')
        if jar is not None:
            if oid and jar._cache.get(oid):
                raise ValueError('Cannot delete _p_oid of cached object')
        _OSA(self, '_Persistent__oid', None)

    _p_oid = property(_get_oid, _set_oid, _del_oid)

    # _p_serial:  see IPersistent.
    def _get_serial(self):
        serial = _OGA(self, '_Persistent__serial')
        if serial is not None:
            return serial
        return _INITIAL_SERIAL

    def _set_serial(self, value):
        if not isinstance(value, SERIAL_TYPE):
            raise ValueError('Invalid SERIAL type: %s' % value)
        if len(value) != 8:
            raise ValueError('SERIAL must be 8 octets')
        _OSA(self, '_Persistent__serial', value)

    def _del_serial(self):
        _OSA(self, '_Persistent__serial', None)

    _p_serial = property(_get_serial, _set_serial, _del_serial)

    # _p_changed:  see IPersistent.
    def _get_changed(self):
        if _OGA(self, '_Persistent__jar') is None:
            return False
        flags = _OGA(self, '_Persistent__flags')
        if flags is None: # ghost
            return None
        return bool(flags & _CHANGED)

    def _set_changed(self, value):
        if _OGA(self, '_Persistent__flags') is None:
            if value:
                self._p_activate()
                self._p_set_changed_flag(value)
        else:
            if value is None: # -> ghost
                self._p_deactivate()
            else:
                self._p_set_changed_flag(value)

    def _del_changed(self):
        self._p_invalidate()

    _p_changed = property(_get_changed, _set_changed, _del_changed)

    # _p_mtime
    def _get_mtime(self):
        # The C implementation automatically unghostifies the object
        # when _p_mtime is accessed.
        self._p_activate()
        self._p_accessed()
        serial = _OGA(self, '_Persistent__serial')
        return TimeStamp(serial).timeTime() if serial is not None else None

    _p_mtime = property(_get_mtime)

    # _p_state
    def _get_state(self):
        # Note the use of OGA and caching to avoid recursive calls to __getattribute__:
        # __getattribute__ calls _p_accessed calls cache.mru() calls _p_state
        if _OGA(self, '_Persistent__jar') is None:
            return interfaces.UPTODATE
        flags = _OGA(self, '_Persistent__flags')
        if flags is None:
            return interfaces.GHOST
        if flags & _CHANGED:
            result = interfaces.CHANGED
        else:
            result = interfaces.UPTODATE
        if flags & _STICKY:
            return interfaces.STICKY
        return result

    _p_state = property(_get_state)

    # _p_estimated_size:  XXX don't want to reserve the space?
    def _get_estimated_size(self):
        return _OGA(self, '_Persistent__size') * 64

    def _set_estimated_size(self, value):
        if isinstance(value, int):
            if value < 0:
                raise ValueError('_p_estimated_size must not be negative')
            _OSA(self, '_Persistent__size', _estimated_size_in_24_bits(value))
        else:
            raise TypeError("_p_estimated_size must be an integer")

    def _del_estimated_size(self):
        _OSA(self, '_Persistent__size', 0)

    _p_estimated_size = property(
        _get_estimated_size, _set_estimated_size, _del_estimated_size)

    # The '_p_sticky' property is not (yet) part of the API:  for now,
    # it exists to simplify debugging and testing assertions.
    def _get_sticky(self):
        flags = _OGA(self, '_Persistent__flags')
        if flags is None:
            return False
        return bool(flags & _STICKY)
    def _set_sticky(self, value):
        flags = _OGA(self, '_Persistent__flags')
        if flags is None:
            raise ValueError('Ghost')
        if value:
            flags |= _STICKY
        else:
            flags &= ~_STICKY
        _OSA(self, '_Persistent__flags', flags)
    _p_sticky = property(_get_sticky, _set_sticky)

    # The '_p_status' property is not (yet) part of the API:  for now,
    # it exists to simplify debugging and testing assertions.
    def _get_status(self):
        if _OGA(self, '_Persistent__jar') is None:
            return 'unsaved'
        flags = _OGA(self, '_Persistent__flags')
        if flags is None:
            return 'ghost'
        if flags & _STICKY:
            return 'sticky'
        if flags & _CHANGED:
            return 'changed'
        return 'saved'

    _p_status = property(_get_status)

    # Methods from IPersistent.
    def __getattribute__(self, name):
        """ See IPersistent.
        """
        oga = _OGA
        if (not name.startswith('_p_') and
            name not in _SPECIAL_NAMES):
            if oga(self, '_Persistent__flags') is None:
                oga(self, '_p_activate')()
            oga(self, '_p_accessed')()
        return oga(self, name)

    def __setattr__(self, name, value):
        special_name = (name in _SPECIAL_NAMES or
                        name.startswith('_p_'))
        volatile = name.startswith('_v_')
        if not special_name:
            if _OGA(self, '_Persistent__flags') is None:
                _OGA(self, '_p_activate')()
            if not volatile:
                _OGA(self, '_p_accessed')()
        _OSA(self, name, value)
        if (_OGA(self, '_Persistent__jar') is not None and
            _OGA(self, '_Persistent__oid') is not None and
            not special_name and
            not volatile):
            before = _OGA(self, '_Persistent__flags')
            after = before | _CHANGED
            if before != after:
                _OSA(self, '_Persistent__flags', after)
                _OGA(self, '_p_register')()

    def __delattr__(self, name):
        special_name = (name in _SPECIAL_NAMES or
                        name.startswith('_p_'))
        if not special_name:
            if _OGA(self, '_Persistent__flags') is None:
                _OGA(self, '_p_activate')()
            _OGA(self, '_p_accessed')()
            before = _OGA(self, '_Persistent__flags')
            after = before | _CHANGED
            if before != after:
                _OSA(self, '_Persistent__flags', after)
                if (_OGA(self, '_Persistent__jar') is not None and
                    _OGA(self, '_Persistent__oid') is not None):
                    _OGA(self, '_p_register')()
        _ODA(self, name)

    def _slotnames(self, _v_exclude=True):
        slotnames = copy_reg._slotnames(type(self))
        return [x for x in slotnames
                   if not x.startswith('_p_') and
                      not (x.startswith('_v_') and _v_exclude) and
                      not x.startswith('_Persistent__') and
                      x not in _SLOTS]

    def __getstate__(self):
        """ See IPersistent.
        """
        idict = getattr(self, '__dict__', None)
        slotnames = self._slotnames()
        if idict is not None:
            # TODO: Convert to a dictionary comprehension, avoid the intermediate
            # list.
            # pylint:disable=consider-using-dict-comprehension
            d = dict([x for x in idict.items()
                         if not x[0].startswith('_p_') and
                            not x[0].startswith('_v_')])
        else:
            d = None
Loading ...