##############################################################################
#
# 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 ...