[Zope3-checkins] CVS: Zope3/src/zodb/code - __init__.py:1.1.2.1 class.py:1.1.2.1 function.py:1.1.2.1 interfaces.py:1.1.2.1 module.py:1.1.2.1 patch.py:1.1.2.1
Jim Fulton
jim@zope.com
Mon, 23 Dec 2002 14:30:49 -0500
Update of /cvs-repository/Zope3/src/zodb/code
In directory cvs.zope.org:/tmp/cvs-serv19908/zodb/code
Added Files:
Tag: NameGeddon-branch
__init__.py class.py function.py interfaces.py module.py
patch.py
Log Message:
Initial renaming before debugging
=== Added File Zope3/src/zodb/code/__init__.py ===
#
# This file is necessary to make this directory a package.
=== Added File Zope3/src/zodb/code/class.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Persistent Classes."""
from persistence import Persistent, PersistentMetaClass
from persistence.persistence import UPTODATE, CHANGED, STICKY, GHOST
from persistence.interfaces import IPersistent
from zodb.code.function import PersistentFunction
import new
from types import FunctionType as function
import time
# XXX There is a lot of magic here to give classes and instances
# separate sets of attributes. This code should be documented, as it
# it quite delicate, and it should be move to a separate module.
__metaclass__ = type
class ExtClassDescr:
"""Maintains seperate class and instance descriptors for an attribute.
This allows a class to provide methods and attributes without
intefering with normal use of instances. The class and its
instances can each have methods with the same name.
This does interfere with introspection on the class.
"""
def __init__(self, name, instdescr):
self.name = name
self.instdescr = instdescr
def __get__(self, obj, cls):
if obj is None:
return self.clsget(cls)
else:
return self.instdescr.__get__(obj, cls)
def __set__(self, obj, val):
if obj is None:
self.clsset(val)
else:
if self.instdescr is None:
raise AttributeError, self.name
return self.instdescr.__set__(obj, val)
def __delete__(self, obj):
if self.instdescr is None:
raise AttributeError, self.name
return self.instdescr.__delete__(obj)
# subclass should override
def clsget(self, cls):
pass
def clsset(self, val):
pass
def clsdelete(self):
pass
class MethodMixin:
def __init__(self, name, descr, func):
super(MethodMixin, self).__init__(name, descr)
self.func = func
def clsget(self, cls):
def f(*args, **kwargs):
try:
return self.func(cls, *args, **kwargs)
except TypeError:
print `self.func`, `cls`, `args`, `kwargs`
raise
return f
class DataMixin:
def __init__(self, name, descr, val):
super(DataMixin, self).__init__(name, descr)
self.val = val
def clsget(self, cls):
return self.val
def clsset(self, val):
self.val = val
def clsdelete(self):
del self.val
class ExtClassObject:
_missing = object()
def __init__(self, name, instdescr):
self.name = name
self.instdescr = instdescr
def __get__(self, obj, cls):
if obj is None:
return self.clsget(cls)
else:
return self.instdescr.__get__(obj, cls)
def __set__(self, obj, cls):
if obj is None:
return self.clsset(cls)
else:
if self.instdescr is None:
raise AttributeError, self.name
return self.instdescr.__set__(obj, cls)
def __delete__(self, obj, cls):
if obj is None:
return self.clsdelete(cls)
else:
if self.instdescr is None:
raise AttributeError, self.name
return self.instdescr.__delete__(obj, cls)
class ExtClassMethodDescr(MethodMixin, ExtClassDescr):
pass
class ExtClassDataDescr(DataMixin, ExtClassDescr):
pass
# The next three classes conspire to make a PersistentFunction
# behave like a method when found in a class's __dict__.
class PersistentMethod:
"""Make PersistentFunctions into methods."""
def __init__(self, klass, inst, func):
self.im_class = klass
self.im_self = inst
self.im_func = func
def __repr__(self):
if self.im_self is None:
kind = "unbound"
else:
kind = "bound"
return ("<persistent %s method %s.%s of %s>"
% (kind, self.im_class.__name__, self.im_func.__name__,
self.im_self))
def __call__(self, *args, **kwargs):
if self.im_self is None:
if not isinstance(args[0], self.im_class):
raise TypeError("unbound method %s() must be called "
"with %s instance as first argument ("
"got %s instead)" % (self.im_func.__name__,
self.im_class.__name__,
type(args[0]).__name__))
else:
return self.im_func(self.im_self, *args, **kwargs)
class PersistentDescriptor:
def __init__(self, objclass, func):
self.__name__ = func.__name__
self.__doc__ = func.__doc__
self.__objclass__ = objclass
self._func = func
# Delegate __getstate__ and __setstate__ to the persistent func.
# The patch module will use these methods to update persistent
# methods in place.
self.__getstate__ = func.__getstate__
self.__setstate__ = func.__setstate__
def __repr__(self):
return "<descriptor %s.%s>" % (self.__objclass__.__name__,
self.__name__)
def __get__(self, object, klass=None):
if object is None:
return PersistentMethod(klass or self.__objclass__, None,
self._func)
else:
return PersistentMethod(klass or self.__objclass__, object,
self._func)
# XXX is missing necessary for findattr?
# None might be sufficient
_missing = object()
def findattr(cls, attr, default):
"""Walk the mro of cls to find attr."""
for c in cls.__mro__:
o = c.__dict__.get(attr, _missing)
if o is not _missing:
return o
return default
class PersistentClassMetaClass(PersistentMetaClass):
# an attempt to make persistent classes look just like other
# persistent objects by providing class attributes and methods
# that behave like the persistence machinery.
# the chief limitation of this approach is that class.attr won't
# always behave the way it does for normal classes
__implements__ = IPersistent
_pc_init = False
def __new__(meta, name, bases, dict):
cls = super(PersistentClassMetaClass, meta).__new__(
meta, name, bases, dict)
# helper functions
def extend_attr(attr, v):
prev = findattr(cls, attr, None)
setattr(cls, attr, ExtClassDataDescr(attr, prev, v))
def extend_meth(attr, m):
prev = findattr(cls, attr, None)
setattr(cls, attr, ExtClassMethodDescr(attr, prev, m))
extend_attr("_p_oid", None)
extend_attr("_p_jar", None)
extend_attr("_p_state", UPTODATE)
extend_meth("_p_activate", meta._p_activate)
extend_meth("_p_deactivate", meta._p_activate)
extend_meth("__getstate__", meta.__getstate__)
extend_meth("__setstate__", meta.__setstate__)
extend_attr("__implements__", meta.__implements__)
for k, v in dict.items():
if isinstance(v, PersistentFunction):
setattr(cls, k, PersistentDescriptor(cls, v))
cls._pc_init = True
return cls
def fixup(cls, mod):
for k, v in cls.__dict__.items():
if isinstance(v, function):
setattr(cls, k, PersistentFunction(v, mod))
def __getattribute__(cls, name):
# XXX I'm not sure I understand this code any more.
super_meth = super(PersistentClassMetaClass, cls).__getattribute__
# If we are initializing the class, don't trying to check variables
# like _p_state, since they may not be initialized.
if not super_meth("_pc_init"):
return super_meth(name)
if (name[0] == "_" and
not (name.startswith("_p_") or name.startswith("_pc_") or
name == "__dict__")):
if cls._p_state == GHOST:
cls._p_activate()
cls._p_atime = int(time.time() % 86400)
return super_meth(name)
def __setattr__(cls, attr, val):
if not attr.startswith("_pc_") and cls._pc_init:
descr = cls.__dict__.get(attr)
if descr is not None:
set = getattr(descr, "__set__", None)
if set is not None:
set(None, val)
return
super(PersistentClassMetaClass, cls).__setattr__(attr, val)
def __delattr__(cls, attr):
if attr.startswith('_p_'):
if attr == "_p_changed":
# this means something special
pass
else:
return
super(PersistentClassMetaClass, cls).__delattr__(attr)
def __getstate__(cls):
dict = {}
for k, v in cls.__dict__.items():
if hasattr(v, '_p_oid'):
dict[k] = v
return dict
def __setstate__(cls, dict):
for k, v in dict.items():
setattr(cls, k, v)
def _p_deactivate(cls):
# do nothing but mark the state change for now
cls._p_state = GHOST
def _p_activate(cls):
if cls._p_state is None:
dm = cls._p_jar
if dm is not None:
# reactivate
cls._p_state = UPTODATE
# Methods below here are not wrapped to be class-only attributes.
# They are available as methods of classes using this metaclass.
def __getnewargs__(cls):
return cls.__name__, cls.__bases__, {}
def _p_newstate(cls, acls):
# Update a class's __dict__ in place. Must use setattr and
# delattr because __dict__ is a read-only proxy.
# XXX This doesn't handle __methods__ correctly.
def getkeys(cls):
L = [n for n in cls.__dict__.keys()
if not (n.startswith("__") and n.endswith("__"))]
d = {}
for elt in L:
d[elt] = True
return d
oldnames = getkeys(cls)
newnames = getkeys(acls)
for name in oldnames:
if not name in newnames:
delattr(cls, name)
for name in newnames:
setattr(cls, name, acls.__dict__[name])
=== Added File Zope3/src/zodb/code/function.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Persistent functions."""
import dis
import new
import sys
# in 2.3, this will be spelled new.function and new.code
from types import FunctionType as function, CodeType as code
from persistence import Persistent
_STORE_GLOBAL = chr(dis.opname.index("STORE_GLOBAL"))
def has_side_effect(func):
# will find this as an opcode or oparg
return _STORE_GLOBAL in func.func_code.co_code
class CodeWrapper:
"""Package a code object so that it can be pickled."""
nested = 0
def __init__(self, co):
consts = co.co_consts
nested = [(i, c) for i, c in zip(range(len(consts)), consts)
if isinstance(c, code)]
if nested:
self.nested = 1
L = list(consts)
for i, c in nested:
L[i] = CodeWrapper(c)
consts = tuple(L)
# args stores the arguments to new.code in order
self.args = [co.co_argcount,
co.co_nlocals,
co.co_stacksize,
co.co_flags,
co.co_code,
consts,
co.co_names,
co.co_varnames,
co.co_filename,
co.co_name,
co.co_firstlineno,
co.co_lnotab,
co.co_freevars,
co.co_cellvars]
def ascode(self):
if self.nested:
L = list(self.args[5])
for i, elt in zip(range(len(L)), L):
if isinstance(elt, CodeWrapper):
L[i] = elt.ascode()
self.args[5] = tuple(L)
return new.code(*self.args)
def get_code_args(co):
"""Return args from code object suitable for passing to constructor."""
class PersistentFunction(Persistent):
def __init__(self, func, module):
# Use _pf_ as the prefix to minimize the possibility that
# these attribute names will conflict with function attributes
# found in user code. It would have been nice to use _p_
# since it's already an reserved attribute prefix, but the
# base persistent getattr function does not unghostify an
# object on refences to _p_ attributes.
self._pf_func = func
self._v_side_effect = has_side_effect(func)
self._pf_module = module
self._pf_code = {}
self._fixup_contained()
def __repr__(self):
return "<PersistentFunction %s.%s>" % (self._pf_module.__name__,
self._pf_func.func_name)
def _fixup_contained(self):
# The function object may contain other function objects as a
# default value for an argument. These functions are
# converted to persistent objects, but are not updated in
# place when the containing module is changed.
new = {}
defaults = self._pf_func.func_defaults
if defaults is None:
return
for i in range(len(defaults)):
obj = defaults[i]
if isinstance(obj, function):
new[i] = PersistentFunction(obj, self._pf_module)
if new:
new_defs = list(defaults)
for i, pf in new.items():
new_defs[i] = pf
self._pf_func.func_defaults = tuple(new_defs)
# We need attribute hooks to handle access to _pf_ attributes in a
# special way. All other attributes should be looked up on
# _pf_func.
def __getattr__(self, attr):
# If it wasn't found in __dict__, then it must be a function
# attribute.
return getattr(self._pf_func, attr)
def __setattr__(self, attr, value):
if not self._p_setattr(attr, value):
# the persistence machinery didn't handle this attribute,
# it must be ours
if attr.startswith('_pf_'):
self.__dict__[attr] = value
if attr == "_pf_func":
self._v_side_effect = has_side_effect(self._pf_func)
else:
setattr(self._pf_func, attr, value)
def __delattr__(self, attr):
if not self._p_delattr(attr):
# the persistence machinery didn't handle this attribute,
# it must be ours
if attr.startswith('_pf_'):
del self.__dict__[attr]
else:
delattr(self._pf_func, attr)
def __call__(self, *args, **kwargs):
# We must make sure that _module is loaded when func is
# executed because the function may reference a global
# variable and that global variable must be in the module's
# __dict__. We can't use a PersistentDict because the
# interpreter requires that globals be a real dict.
self._pf_module._p_activate()
# XXX What if the function module is deactivated while the
# function is executing? It seems like we need to expose
# refcounts at the Python level to guarantee that this will
# work.
try:
return self._pf_func(*args, **kwargs)
finally:
# If the func has a side-effect, the module must be marked
# as changed. We use the conservative approximation that
# any function with a STORE_GLOBAL opcode has a
# side-effect, regardless of whether a a particular call
# of the function actually executes STORE_GLOBAL.
# XXX Is this sufficient?
if self._v_side_effect:
self._pf_module._p_changed = 1
def __getstate__(self):
# If func_dict is empty, store None to avoid creating a dict
# unnecessarily when the function is unpickled
# XXX new.function doesn't accept a closure
func = self._pf_func
func_state = func.func_defaults, func.func_dict or None
# Store the code separately from the function
code = func.func_code
# The code object is can only be reused in an interpreter
# running the same version of Python and with the same
# __debug__ value. Store code in a dict keyed by these two values.
key = sys.version_info, __debug__
if key not in self._pf_code:
self._pf_code[key] = CodeWrapper(code)
return func_state, self._pf_code, self._pf_module
def __setstate__(self, (func, code, mod)):
self._pf_code = code
self._pf_module = mod
# recreate the code object
code = None
key = sys.version_info, __debug__
cowrap = self._pf_code.get(key, None)
if cowrap is None:
assert False, "not implemented yet"
else:
code = cowrap.ascode()
func_defaults, func_dict = func
func = new.function(code, mod.__dict__, func_defaults)
if func_dict:
func.func_dict.update(func_dict)
self._pf_func = func
self._v_side_effect = has_side_effect(func)
=== Added File Zope3/src/zodb/code/interfaces.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
from zope.interface import Interface
class IPersistentModuleImportRegistry(Interface):
def findModule(name):
"""Return module registered under name or None."""
class IPersistentModuleUpdateRegistry(IPersistentModuleImportRegistry):
def setModule(name, module):
"""Register module under name.
Raises ValueError if module is already registered.
"""
def delModule(name):
"""Unregister module registered under name.
Raises KeyError in module is not registered.
"""
try:
from zope.interface import Interface
from zope.interface.element import Attribute
except ImportError:
class Interface:
pass
def Attribute(x):
return x
class IPersistentModuleManager(Interface):
def new(name, source):
"""Create and register a new named module from source."""
def update(src):
"""Update the source of the existing module."""
def remove():
"""Unregister the module and forget about it."""
name = Attribute("Absolute module name")
source = Attribute("Module source string")
=== Added File Zope3/src/zodb/code/module.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Persistent Module."""
__metaclass__ = type
import __builtin__
# in 2.3, this will be spelled new.function
from types import FunctionType as function
import sys
from persistence import Persistent
from persistence.persistence import GHOST
from zodb.code.class import PersistentClassMetaClass
from zodb.code.function import PersistentFunction
from zodb.code.interfaces import IPersistentModuleManager
from zodb.code.interfaces \
import IPersistentModuleImportRegistry, IPersistentModuleUpdateRegistry
from zodb.code.patch import NameFinder, convert
from transaction import get_transaction
# builtins are explicitly assigned when a module is unpickled
import __builtin__
# Modules aren't picklable by default, but we'd like them to be
# pickled just like classes (by name).
import copy_reg
def _pickle_module(mod):
return mod.__name__
def _unpickle_module(modname):
mod = __import__(modname)
if "." in modname:
parts = modname.split(".")[1:]
for part in parts:
mod = getattr(mod, part)
return mod
copy_reg.pickle(type(copy_reg), _pickle_module, _unpickle_module)
# XXX Is this comment still relevant?
#
# There seems to be something seriously wrong with a module pickle
# that contains objects pickled via save_global(). These objects are
# pickled using references to the module. It appears that unpickling the
# object in the module causes the persistence machinery to fail.
#
# My suspicion is that the assignment to po_state before trying to
# load the state confuses things. The first call to setstate attempts
# to reference an attribute of the module. That getattr() fails because
# the module is not a ghost, but does have any empty dict. Since
# that getattr() fails, its state can't be unpickled.
#
# Not sure what to do about this.
class PersistentModule(Persistent):
def __init__(self, name):
self.__name__ = name
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.__name__)
# XXX need getattr &c. hooks to update _p_changed?
# XXX what about code that modifies __dict__ directly?
# XXX one example is a function that rebinds a global
def __getstate__(self):
d = self.__dict__.copy()
try:
del d["__builtins__"]
except KeyError:
pass
return d
def __setstate__(self, state):
state["__builtins__"] = __builtin__
self.__dict__.update(state)
class PersistentPackage(PersistentModule):
# XXX Is it okay that these packages don't have __path__?
# A PersistentPackage can exist in a registry without a manager.
# It only gets a manager if someone creates an __init__ module for
# the package.
def __init__(self, name):
self.__name__ = name
__persistent_module_registry__ = "__persistent_module_registry__"
class PersistentModuleManager(Persistent):
__implements__ = IPersistentModuleManager
def __init__(self, registry):
self._registry = registry
self._module = None
self.name = None
self.source = None
def new(self, name, source):
if self._module is not None:
raise ValueError, "module already exists"
if "." in name:
parent = self._new_package(name)
else:
parent = None
self._module = PersistentModule(name)
try:
self._registry.setModule(name, self._module)
except ValueError, err:
self._module = None
raise
self.name = name
self.update(source)
if parent is not None:
modname = name.split(".")[-1]
setattr(parent, modname, self._module)
def update(self, source):
self._module._p_changed = True
moddict = self._module.__dict__
old_names = NameFinder(self._module)
moddict[__persistent_module_registry__] = self._registry
exec source in moddict
del moddict[__persistent_module_registry__]
new_names = NameFinder(self._module)
replacements = new_names.replacements(old_names)
convert(self._module, replacements)
self.source = source
def remove(self, source):
self._registry.delModule(self._module.__name__)
self._module = None
def _fixup(self, new, old, module):
# Update persistent objects in place, and
# convert new functions to persistent functions
# XXX should convert classes, too
for k, v in new.items():
if isinstance(v, function):
v = new[k] = PersistentFunction(v, module)
elif isinstance(v.__class__, PersistentClassMetaClass):
v.__class__.fixup(module)
# XXX need to check for classes that are not persistent!
old_v = old.get(k)
if old_v is not None:
# XXX the type test below works for functions, but may
# not work for classes or other objects
if (isinstance(old_v, Persistent)
and type(old_v) == type(v)):
state = v.__getstate__()
old_v.__setstate__(state)
new[k] = old_v
def _new_package(self, name):
parent = self._get_parent(name)
modname = name.split(".")[-1]
if modname == "__init__":
self._module = parent
return None
else:
self._module = PersistentModule(name)
return parent
def _get_parent(self, name):
# If a module is being created in a package, automatically
# create parent packages that do no already exist.
parts = name.split(".")[:-1]
parent = None
for i in range(len(parts)):
if parts[i] == "__init__":
raise ValueError, "__init__ can not be a package"
pname = ".".join(parts[:i+1])
package = self._registry.findModule(pname)
if package is None:
package = PersistentPackage(pname)
self._registry.setModule(pname, package)
if parent is not None:
setattr(parent, parts[i], package)
elif not isinstance(package, PersistentPackage):
raise ValueError, "%s is module" % pname
parent = package
return parent
class PersistentModuleImporter:
"""An import hook that loads persistent modules.
The importer cooperates with other objects to make sure imports of
persistent modules work correctly. The default importer depends
on finding a persistent module registry in the globals passed to
__import__(). It looks for the name __persistent_module_registry__.
A PersistentModuleManager places its registry in the globals used
to exec module source.
It is important that the registry be activated before it is used
to handle imports. If a ghost registry is used for importing, a
circular import occurs. The second import occurs when the
machinery searches for the class of the registry. It will re-use
the registry and fail, because the registry will be marked as
changed but not yet have its stated loaded. XXX There ought to be
a way to deal with this.
"""
def __init__(self):
self._saved_import = None
def install(self):
self._saved_import = __builtin__.__import__
__builtin__.__import__ = self.__import__
def uninstall(self):
__builtin__.__import__ = self._saved_import
def _import(self, registry, name, parent, fromlist):
mod = None
if parent is not None:
fullname = "%s.%s" % (parent, name)
mod = registry.findModule(fullname)
if mod is None:
parent = None
if mod is None: # no parent or didn't find in parent
mod = registry.findModule(name)
if mod is None:
return None
if fromlist:
if isinstance(mod, PersistentPackage):
self._import_fromlist(registry, mod, fromlist)
return mod
else:
i = name.find(".")
if i == -1:
return mod
name = name[:i]
if parent:
name = "%s.%s" % (parent, name)
top = registry.findModule(name)
assert top is not None, "No package for module %s" % name
return top
def _import_fromlist(self, registry, mod, fromlist):
for name in fromlist:
if not hasattr(mod, name):
fullname = "%s.%s" % (mod.__name__, name)
self._import(registry, fullname, None, [])
def __import__(self, name, globals={}, locals={}, fromlist=[]):
registry = globals.get(__persistent_module_registry__)
if registry is not None:
mod = self._import(registry, name, self._get_parent(globals),
fromlist)
if mod is not None:
return mod
return self._saved_import(name, globals, locals, fromlist)
def _get_parent(self, globals):
name = globals.get("__name__")
if name is None or "." not in name:
return None
i = name.rfind(".")
return name[:i]
class PersistentModuleRegistry(Persistent):
__implements__ = (IPersistentModuleImportRegistry,
IPersistentModuleUpdateRegistry)
def __init__(self):
self.__modules = {}
def findModule(self, name):
assert self._p_state != GHOST
return self.__modules.get(name)
def setModule(self, name, module):
if name in self.__modules:
raise ValueError, name
self._p_changed = True
self.__modules[name] = module
def delModule(self, name):
self._p_changed = True
del self.__modules[name]
=== Added File Zope3/src/zodb/code/patch.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Patch references to auto-persistent objects in a module.
When a persistent module is compiled, all classes and functions should
be converted to persistent classes and functions. When a module is
updated, it is compiled and its persistent functions and classes are
updated in place so that clients of the module see the update.
The specific semantics of the convert and update-in-place operations
are still being determined. Here are some rough notes:
- Classes and functions are not converted in place. New objects are
created to replace the builtin functions and classes.
- Every function object is converted to a PersistentFunction.
- Every class is converted to a new class that is created by calling
the PersistentClassMetaClass with the name, bases, and dict of the
class being converted.
- The conversion operation must preserve object identity. If an
object created by a def or class statement is referenced elsewhere
in the module, all references must be replaced with references to
the converted object.
Implementation notes:
The conversion operation is implemented using a pickler. It wasn't
possible to use the copy module, because it isn't possible to extend
the copy module in a safe way. The copy module depends on module globals.
What semantics do we want for update-in-place in the presence of aliases?
Semantics based on per-namespace updates don't work in the presence of
aliases. If an update changes an alias, then the old binding will be
updated with the state of the new binding.
Semantics based on containing namespaces seem to work. The outermost
namespace that contains a name is updated in place. Aliases are
simple rebinding operations that do not update in place.
The containment approach seems to have a problem with bound methods,
where an instance can stash a copy of a bound method created via an
alias. When the class is updated, the alias changes, but the bound
method isn't. Then the bound method can invoke an old method on a new
object, which may not be legal. It might sufficient to outlaw this case.
XXX Open issues
Can we handle metaclasses within this framework? That is, what if an
object's type is not type, but a subclass of type.
How do we handle things like staticmethods? We'd like the code to be
able to use them, but Python doesn't expose an introspection on them.
What if the same object is bound to two different names in the same
namespace? Example:
x = lambda: 1
y = x
If the module is updated to:
x = lambda: 1
y = lambda: 2
What are the desired semantics?
"""
__metaclass__ = type
from copy_reg import dispatch_table
from cStringIO import StringIO
import pickle
from types import *
from zodb.code.class import PersistentClassMetaClass, PersistentDescriptor
from zodb.code.function import PersistentFunction
from persistence import Persistent
class Wrapper:
"""Implement pickling reduce protocol for update-able object.
The Pickler creates a Wrapper instance and uses it as the reduce
function. The Unpickler calls the instance to recreate the
object.
"""
__safe_for_unpickling__ = True
def __init__(self, obj, module, replace=None):
self._obj = obj
self._module = module
self._replace = replace
def __call__(self, *args):
new = self.unwrap(*args)
if self._replace is not None:
# XXX Hack: Use _p_newstatefor persistent classes, because
# a persistent class's persistent state is a fairly limited
# subset of the dict and we really want to replace everything.
if hasattr(self._replace, "_p_newstate"):
self._replace._p_newstate(new)
else:
self._replace.__setstate__(new.__getstate__())
return self._replace
else:
return new
class FunctionWrapper(Wrapper):
def unwrap(self, defaults, dict):
self._obj.func_defaults = defaults
self._obj.func_dict.update(dict)
return PersistentFunction(self._obj, self._module)
class TypeWrapper(Wrapper):
def unwrap(self, bases, dict):
# XXX Add Persistent to the list of bases so that type (the
# base class of PersistentClassMetaClass) will create the
# correct C layout.
# We must maintain a linearizable MRO when adding Persistent
# to list of bases. In particular, object is in Persistent's
# __bases__ to Persistent must occur before object in the
# new class's __bases__.
if not Persistent in bases:
if object in bases:
L = list(bases)
i = L.index(object)
newbases = bases[:i] + (Persistent,) + bases[i:]
else:
newbases = bases + (Persistent,)
return PersistentClassMetaClass(self._obj.__name__, newbases, dict)
class Pickler(pickle.Pickler):
dispatch = {}
dispatch.update(pickle.Pickler.dispatch)
def __init__(self, file, module, memo, replacements):
pickle.Pickler.__init__(self, file, bin=True)
self._pmemo = memo
self._module = module
self._repl = replacements
self._builtins = module.__builtins__
def wrap(self, wrapperclass, object):
return wrapperclass(object, self._module, self._repl.get(id(object)))
def persistent_id(self, object, force=False):
if isinstance(object, Wrapper) or object is self._builtins or force:
oid = id(object)
self._pmemo[oid] = object
return oid
else:
return None
def save_type(self, atype):
if atype.__module__ == "__builtin__":
self.save_global(atype)
else:
self.save_reduce(self.wrap(TypeWrapper, atype),
(atype.__bases__, atype.__dict__))
dispatch[TypeType] = save_type
dispatch[ClassType] = save_type
def save_function(self, func):
self.save_reduce(self.wrap(FunctionWrapper, func),
(func.func_defaults, func.func_dict))
dispatch[FunctionType] = save_function
# New-style classes don't have real dicts. They have dictproxies.
# There's no official way to spell the dictproxy type, so we have
# to get it by using type() on an example.
dispatch[type(Wrapper.__dict__)] = pickle.Pickler.save_dict
def save(self, object, ignore=None):
# Override the save() implementation from pickle.py, because
# we don't ever want to invoke __reduce__() on builtin types
# that aren't picklable. Instead, we'd like to pickle all of
# those objects using the persistent_id() mechanism. There's
# no need to cover every type with this pickler, because it
# isn't being used for persistent just to create a copy.
# The ignored parameter is for compatible with Python 2.2,
# which has the old inst_persistent_id feature.
pid = self.persistent_id(object)
if pid is not None:
self.save_pers(pid)
return
d = id(object)
t = type(object)
if (t is TupleType) and (len(object) == 0):
if self.bin:
self.save_empty_tuple(object)
else:
self.save_tuple(object)
return
if d in self.memo:
self.write(self.get(self.memo[d][0]))
return
try:
f = self.dispatch[t]
except KeyError:
try:
issc = issubclass(t, TypeType)
except TypeError: # t is not a class
issc = 0
if issc:
self.save_global(object)
return
try:
reduce = dispatch_table[t]
except KeyError:
self.save_pers(self.persistent_id(object, True))
return
else:
tup = reduce(object)
if type(tup) is StringType:
self.save_global(object, tup)
return
if type(tup) is not TupleType:
raise pickle.PicklingError("Value returned by %s must be a "
"tuple" % reduce)
l = len(tup)
if (l != 2) and (l != 3):
raise pickle.PicklingError("tuple returned by %s must "
"contain only two or three "
"elements" % reduce)
callable = tup[0]
arg_tup = tup[1]
if l > 2:
state = tup[2]
else:
state = None
if type(arg_tup) is not TupleType and arg_tup is not None:
raise pickle.PicklingError("Second element of tuple "
"returned by %s must be a "
"tuple" % reduce)
self.save_reduce(callable, arg_tup, state)
memo_len = len(self.memo)
self.write(self.put(memo_len))
self.memo[d] = (memo_len, object)
return
f(self, object)
class Unpickler(pickle.Unpickler):
def __init__(self, file, pmemo):
pickle.Unpickler.__init__(self, file)
self._pmemo = pmemo
def persistent_load(self, oid):
return self._pmemo[oid]
class NameFinder:
"""Find a canonical name for each update-able object."""
# XXX should we try to handle descriptors? If it looks like a
# descriptor, try calling it and passing the class object?
classTypes = {
TypeType: True,
ClassType: True,
PersistentClassMetaClass: True,
}
types = {
FunctionType: True,
PersistentFunction: True,
PersistentDescriptor: True,
}
types.update(classTypes)
def __init__(self, module):
self._names = {} # map object ids to (canonical name, obj) pairs
self.walkModule(module)
def names(self):
return [n for n, o in self._names.itervalues()]
def _walk(self, obj, name, fmt):
classes = []
for k, v in obj.__dict__.items():
aType = type(v)
anId = id(v)
if aType in self.types and not anId in self._names:
self._names[anId] = fmt % (name, k), v
if aType in self.classTypes:
classes.append((v, k))
for _klass, _name in classes:
self.walkClass(_klass, fmt % (name, _name))
def walkModule(self, mod):
self._walk(mod, "", "%s%s")
def walkClass(self, klass, name):
self._walk(klass, name, "%s.%s")
def replacements(self, aFinder):
"""Return a dictionary of replacements.
self and aFinder are two NameFinder instances. Return a dict
of all the objects in the two that share the same name. The
keys are the ids in self and the values are the objects in
aFinder.
"""
temp = {}
result = {}
for anId, (name, obj) in self._names.iteritems():
temp[name] = anId
for anId, (name, obj) in aFinder._names.iteritems():
if name in temp:
result[temp[name]] = obj
return result
def convert(module, replacements):
"""Convert object to persistent objects in module.
Use replacements dictionary to determine which objects to update
in place.
"""
f = StringIO()
memo = {}
p = Pickler(f, module, memo, replacements)
moddict = module.__dict__
p.dump(moddict)
f.reset()
u = Unpickler(f, memo)
newdict = u.load()
module.__dict__.clear()
module.__dict__.update(newdict)
if __name__ == "__main__":
pass