zope.copy-3.5.0/0000755000175000017500000000000011143656274011652 5ustar nyonyozope.copy-3.5.0/src/0000755000175000017500000000000011143656274012441 5ustar nyonyozope.copy-3.5.0/src/zope/0000755000175000017500000000000011143656274013416 5ustar nyonyozope.copy-3.5.0/src/zope/copy/0000755000175000017500000000000011143656274014370 5ustar nyonyozope.copy-3.5.0/src/zope/copy/tests.py0000644000175000017500000000215411143656212016076 0ustar nyonyo############################################################################## # # Copyright (c) 2001, 2002 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. # ############################################################################## """Tests for the zope.copy package. $Id: tests.py 96251 2009-02-08 16:17:47Z nadako $ """ import unittest from zope.testing import doctest, module def setUp(test): module.setUp(test, 'zope.copy.doctest') def tearDown(test): module.tearDown(test) def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('README.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE), )) zope.copy-3.5.0/src/zope/copy/interfaces.py0000644000175000017500000000332511143656212017060 0ustar nyonyo############################################################################## # # Copyright (c) 2009 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. # ############################################################################## """ $Id: interfaces.py 96280 2009-02-08 22:27:11Z nadako $ """ import zope.interface class ResumeCopy(Exception): """Don't use the hook, resume the copy. This is a special exception, raised from the copy hook to signal copier that it should continue copying the object recursively. See ICopyHook.__call__ method documentation. """ class ICopyHook(zope.interface.Interface): """An adapter to an object that is being copied""" def __call__(toplevel, register): """Given the top-level object that is being copied, return the version of the adapted object that should be used in the new copy. Raising ResumeCopy means that you are foregoing the hook: the adapted object will continue to be recursively copied as usual. If you need to have a post-copy actions executed, register a callable with ``register``. This callable must take a single argument: a callable that, given an object from the original, returns the equivalent in the copy. See README.txt for more explanation. """ zope.copy-3.5.0/src/zope/copy/README.txt0000644000175000017500000002476511143656212016074 0ustar nyonyo============== Object copying ============== This package provides a pluggable way to copy persistent objects. It was once extracted from the zc.copy package to contain much less dependencies. In fact, we only depend on zope.interface to provide pluggability. The package provides a ``clone`` function that does the object cloning and the ``copy`` wrapper that sets __parent__ and __name__ attributes of object's copy to None. This is useful, when working with Zope's located objects (see zope.location package). The ``copy`` function actually calls the ``clone`` function so we'll use the first one in the examples below. We'll also look a bit at their differences in the end of this document. The ``clone`` function (and thus the ``copy`` function that wraps it) uses pickling to copy the object and all its subobjects recursively. As each object and subobject is pickled, the function tries to adapt it to ``zope.copy.interfaces.ICopyHook``. If a copy hook is found, the recursive copy is halted. The hook is called with two values: the main, top-level object that is being copied; and a callable that supports registering functions to be called after the copy is made. The copy hook should return the exact object or subobject that should be used at this point in the copy, or raise ``zope.copy.interfaces.ResumeCopy`` exception to resume copying the object or subobject recursively after all. Note that we use zope's component architecture provided by the ``zope.component`` package in this document, but the ``zope.copy`` package itself doesn't use or depend on it, so you can provide another adaptation mechanism as described in zope.interface's adapter documentation. Simple hooks ------------ First let's examine a simple use. A hook is to support the use case of resetting the state of data that should be changed in a copy -- for instance, a log, or freezing or versioning data. The canonical way to do this is by storing the changable data on a special sub-object of the object that is to be copied. We'll look at a simple case of a subobject that should be converted to None when it is copied -- the way that the zc.freeze copier hook works. Also see the zc.objectlog copier module for a similar example. So, here is a simple object that stores a boolean on a special object. >>> class Demo(object): ... _frozen = None ... def isFrozen(self): ... return self._frozen is not None ... def freeze(self): ... self._frozen = Data() ... >>> class Data(object): ... pass ... Here's what happens if we copy one of these objects without a copy hook. >>> original = Demo() >>> original.isFrozen() False >>> original.freeze() >>> original.isFrozen() True >>> import zope.copy >>> copy = zope.copy.copy(original) >>> copy is original False >>> copy.isFrozen() True Now let's make a super-simple copy hook that always returns None, no matter what the top-level object being copied is. We'll register it and make another copy. >>> import zope.component >>> import zope.interface >>> import zope.copy.interfaces >>> def _factory(obj, register): ... return None >>> @zope.component.adapter(Data) ... @zope.interface.implementer(zope.copy.interfaces.ICopyHook) ... def data_copyfactory(obj): ... return _factory ... >>> zope.component.provideAdapter(data_copyfactory) >>> copy2 = zope.copy.copy(original) >>> copy2 is original False >>> copy2.isFrozen() False Much better. Post-copy functions ------------------- Now, let's look at the registration function that the hook can use. It is useful for resetting objects within the new copy -- for instance, back references such as __parent__ pointers. This is used concretely in the zc.objectlog.copier module; we will come up with a similar but artificial example here. Imagine an object with a subobject that is "located" (i.e., zope.location) on the parent and should be replaced whenever the main object is copied. >>> import zope.location.location >>> class Subobject(zope.location.location.Location): ... def __init__(self): ... self.counter = 0 ... def __call__(self): ... res = self.counter ... self.counter += 1 ... return res ... >>> o = zope.location.location.Location() >>> s = Subobject() >>> o.subobject = s >>> zope.location.location.locate(s, o, 'subobject') >>> s.__parent__ is o True >>> o.subobject() 0 >>> o.subobject() 1 >>> o.subobject() 2 Without an ICopyHook, this will simply duplicate the subobject, with correct new pointers. >>> c = zope.copy.copy(o) >>> c.subobject.__parent__ is c True Note that the subobject has also copied state. >>> c.subobject() 3 >>> o.subobject() 3 Our goal will be to make the counters restart when they are copied. We'll do that with a copy hook. This copy hook is different: it provides an object to replace the old object, but then it needs to set it up further after the copy is made. This is accomplished by registering a callable, ``reparent`` here, that sets up the __parent__. The callable is passed a function that can translate something from the original object into the equivalent on the new object. We use this to find the new parent, so we can set it. >>> import zope.component >>> import zope.interface >>> import zope.copy.interfaces >>> @zope.component.adapter(Subobject) ... @zope.interface.implementer(zope.copy.interfaces.ICopyHook) ... def subobject_copyfactory(original): ... def factory(obj, register): ... obj = Subobject() ... def reparent(translate): ... obj.__parent__ = translate(original.__parent__) ... register(reparent) ... return obj ... return factory ... >>> zope.component.provideAdapter(subobject_copyfactory) Now when we copy, the new subobject will have the correct, revised __parent__, but will be otherwise reset (here, just the counter) >>> c = zope.copy.copy(o) >>> c.subobject.__parent__ is c True >>> c.subobject() 0 >>> o.subobject() 4 Resuming recursive copy ----------------------- One thing we didn't examine yet is the use of ResumeCopy exception in the copy hooks. For example, when copying located objects we don't want to copy referenced subobjects that are not located in the object that is being copied. Imagine, we have a content object that has an image object, referenced by the ``cover`` attribute, but located in an independent place. >>> root = zope.location.location.Location() >>> content = zope.location.location.Location() >>> zope.location.location.locate(content, root, 'content') >>> image = zope.location.location.Location() >>> zope.location.location.locate(image, root, 'image.jpg') >>> content.cover = image Without any hooks, the image object will be cloned as well: >>> new = zope.copy.copy(content) >>> new.cover is image False That's not what we'd expect though, so, let's provide a copy hook to deal with that. The copy hook for this case is provided by zope.location package, but we'll create one from scratch as we want to check out the usage of the ResumeCopy. >>> @zope.component.adapter(zope.location.interfaces.ILocation) ... @zope.interface.implementer(zope.copy.interfaces.ICopyHook) ... def location_copyfactory(obj): ... def factory(location, register): ... if not zope.location.location.inside(obj, location): ... return obj ... raise zope.copy.interfaces.ResumeCopy ... return factory ... >>> zope.component.provideAdapter(location_copyfactory) This hook returns objects as they are if they are not located inside object that's being copied, or raises ResumeCopy to signal that the recursive copy should be continued and used for the object. >>> new = zope.copy.copy(content) >>> new.cover is image True Much better :-) ``clone`` vs ``copy`` --------------------- As we stated before, there's two functions that is used for copying objects. The ``clone`` - that does the job, and its wrapper, ``copy`` that calls ``clone`` and then clears copy's __parent__ and __name__ attribute values. Let's create a location object with __name__ and __parent__ set. >>> root = zope.location.location.Location() >>> folder = zope.location.location.Location() >>> folder.__name__ = 'files' >>> folder.__parent__ = root The ``clone`` function will leave those attributes as is. Note that the referenced __parent__ won't be cloned, as we registered a hook for locations in the previous section. >>> folder_clone = zope.copy.clone(folder) >>> folder_clone.__parent__ is root True >>> folder_clone.__name__ == 'files' True However, the ``copy`` function will reset those attributes to None, as we will probably want to place our object into another container with another name. >>> folder_clone = zope.copy.copy(folder) >>> folder_clone.__parent__ is None True >>> folder_clone.__name__ is None True Notice, that if your object doesn't have __parent__ and __name__ attributes at all, or these attributes could'nt be got or set because of some protections (as with zope.security's proxies, for example), you still can use the ``copy`` function, because it works for objects that don't have those attributes. It won't set them if original object doesn't have them: >>> class Something(object): ... pass >>> s = Something() >>> s_copy = zope.copy.copy(s) >>> s_copy.__parent__ Traceback (most recent call last): ... AttributeError: ... >>> s_copy.__name__ Traceback (most recent call last): ... AttributeError: ... And it won't fail if original object has them but doesn't allow to set them. >>> root = object() >>> class Something(object): ... ... @apply ... def __name__(): ... def fget(self): ... return 'something' ... def fset(self, value): ... raise AttributeError ... return property(fget, fset) ... ... @apply ... def __parent__(): ... def fget(self): ... return root ... def fset(self, value): ... raise AttributeError ... return property(fget, fset) >>> s = Something() >>> s_copy = zope.copy.copy(s) >>> s_copy.__parent__ is root True >>> s_copy.__name__ == 'something' True zope.copy-3.5.0/src/zope/copy/__init__.py0000644000175000017500000000605511143656212016477 0ustar nyonyo############################################################################## # # Copyright (c) 2009 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. # ############################################################################## """ $Id: __init__.py 96250 2009-02-08 16:15:43Z nadako $ """ import tempfile import cPickle from zope.copy import interfaces def clone(obj): """Clone an object by pickling and unpickling it""" tmp = tempfile.TemporaryFile() persistent = CopyPersistent(obj) # Pickle the object to a temporary file pickler = cPickle.Pickler(tmp, 2) pickler.persistent_id = persistent.id pickler.dump(obj) # Now load it back tmp.seek(0) unpickler = cPickle.Unpickler(tmp) unpickler.persistent_load = persistent.load res = unpickler.load() # run the registered cleanups def convert(obj): return unpickler.memo[pickler.memo[id(obj)][0]] for call in persistent.registered: call(convert) return res def copy(obj): """Clone an object, clearing the __name__ and __parent__ attribute values of the copy.""" res = clone(obj) if getattr(res, '__parent__', None) is not None: try: res.__parent__ = None except AttributeError: pass if getattr(res, '__name__', None) is not None: try: res.__name__ = None except AttributeError: pass return res class CopyPersistent(object): """A helper class providing the persisntent_id and persistent_load functions for pickling and unpickling respectively. It uses the adaptation to ICopyHook to allow control over object copying. See README.txt for more information on that mechanism. """ def __init__(self, obj): self.toplevel = obj self.pids_by_id = {} self.others_by_pid = {} self.load = self.others_by_pid.get self.registered = [] def id(self, obj): hook = interfaces.ICopyHook(obj, None) if hook is not None: oid = id(obj) if oid in self.pids_by_id: return self.pids_by_id[oid] try: res = hook(self.toplevel, self.registered.append) except interfaces.ResumeCopy: pass else: pid = len(self.others_by_pid) # The following is needed to overcome a bug # in pickle.py. The pickle checks the boolean value # of the id, rather than whether it is None. pid += 1 self.pids_by_id[oid] = pid self.others_by_pid[pid] = res return pid return None zope.copy-3.5.0/src/zope/__init__.py0000644000175000017500000000031011143656212015511 0ustar nyonyo# this is a namespace package try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) zope.copy-3.5.0/src/zope.copy.egg-info/0000755000175000017500000000000011143656274016061 5ustar nyonyozope.copy-3.5.0/src/zope.copy.egg-info/PKG-INFO0000644000175000017500000003240211143656271017154 0ustar nyonyoMetadata-Version: 1.0 Name: zope.copy Version: 3.5.0 Summary: Pluggable object copying mechanism Home-page: http://pypi.python.org/pypi/zope.copy Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ============== Object copying ============== This package provides a pluggable way to copy persistent objects. It was once extracted from the zc.copy package to contain much less dependencies. In fact, we only depend on zope.interface to provide pluggability. The package provides a ``clone`` function that does the object cloning and the ``copy`` wrapper that sets __parent__ and __name__ attributes of object's copy to None. This is useful, when working with Zope's located objects (see zope.location package). The ``copy`` function actually calls the ``clone`` function so we'll use the first one in the examples below. We'll also look a bit at their differences in the end of this document. The ``clone`` function (and thus the ``copy`` function that wraps it) uses pickling to copy the object and all its subobjects recursively. As each object and subobject is pickled, the function tries to adapt it to ``zope.copy.interfaces.ICopyHook``. If a copy hook is found, the recursive copy is halted. The hook is called with two values: the main, top-level object that is being copied; and a callable that supports registering functions to be called after the copy is made. The copy hook should return the exact object or subobject that should be used at this point in the copy, or raise ``zope.copy.interfaces.ResumeCopy`` exception to resume copying the object or subobject recursively after all. Note that we use zope's component architecture provided by the ``zope.component`` package in this document, but the ``zope.copy`` package itself doesn't use or depend on it, so you can provide another adaptation mechanism as described in zope.interface's adapter documentation. Simple hooks ------------ First let's examine a simple use. A hook is to support the use case of resetting the state of data that should be changed in a copy -- for instance, a log, or freezing or versioning data. The canonical way to do this is by storing the changable data on a special sub-object of the object that is to be copied. We'll look at a simple case of a subobject that should be converted to None when it is copied -- the way that the zc.freeze copier hook works. Also see the zc.objectlog copier module for a similar example. So, here is a simple object that stores a boolean on a special object. >>> class Demo(object): ... _frozen = None ... def isFrozen(self): ... return self._frozen is not None ... def freeze(self): ... self._frozen = Data() ... >>> class Data(object): ... pass ... Here's what happens if we copy one of these objects without a copy hook. >>> original = Demo() >>> original.isFrozen() False >>> original.freeze() >>> original.isFrozen() True >>> import zope.copy >>> copy = zope.copy.copy(original) >>> copy is original False >>> copy.isFrozen() True Now let's make a super-simple copy hook that always returns None, no matter what the top-level object being copied is. We'll register it and make another copy. >>> import zope.component >>> import zope.interface >>> import zope.copy.interfaces >>> def _factory(obj, register): ... return None >>> @zope.component.adapter(Data) ... @zope.interface.implementer(zope.copy.interfaces.ICopyHook) ... def data_copyfactory(obj): ... return _factory ... >>> zope.component.provideAdapter(data_copyfactory) >>> copy2 = zope.copy.copy(original) >>> copy2 is original False >>> copy2.isFrozen() False Much better. Post-copy functions ------------------- Now, let's look at the registration function that the hook can use. It is useful for resetting objects within the new copy -- for instance, back references such as __parent__ pointers. This is used concretely in the zc.objectlog.copier module; we will come up with a similar but artificial example here. Imagine an object with a subobject that is "located" (i.e., zope.location) on the parent and should be replaced whenever the main object is copied. >>> import zope.location.location >>> class Subobject(zope.location.location.Location): ... def __init__(self): ... self.counter = 0 ... def __call__(self): ... res = self.counter ... self.counter += 1 ... return res ... >>> o = zope.location.location.Location() >>> s = Subobject() >>> o.subobject = s >>> zope.location.location.locate(s, o, 'subobject') >>> s.__parent__ is o True >>> o.subobject() 0 >>> o.subobject() 1 >>> o.subobject() 2 Without an ICopyHook, this will simply duplicate the subobject, with correct new pointers. >>> c = zope.copy.copy(o) >>> c.subobject.__parent__ is c True Note that the subobject has also copied state. >>> c.subobject() 3 >>> o.subobject() 3 Our goal will be to make the counters restart when they are copied. We'll do that with a copy hook. This copy hook is different: it provides an object to replace the old object, but then it needs to set it up further after the copy is made. This is accomplished by registering a callable, ``reparent`` here, that sets up the __parent__. The callable is passed a function that can translate something from the original object into the equivalent on the new object. We use this to find the new parent, so we can set it. >>> import zope.component >>> import zope.interface >>> import zope.copy.interfaces >>> @zope.component.adapter(Subobject) ... @zope.interface.implementer(zope.copy.interfaces.ICopyHook) ... def subobject_copyfactory(original): ... def factory(obj, register): ... obj = Subobject() ... def reparent(translate): ... obj.__parent__ = translate(original.__parent__) ... register(reparent) ... return obj ... return factory ... >>> zope.component.provideAdapter(subobject_copyfactory) Now when we copy, the new subobject will have the correct, revised __parent__, but will be otherwise reset (here, just the counter) >>> c = zope.copy.copy(o) >>> c.subobject.__parent__ is c True >>> c.subobject() 0 >>> o.subobject() 4 Resuming recursive copy ----------------------- One thing we didn't examine yet is the use of ResumeCopy exception in the copy hooks. For example, when copying located objects we don't want to copy referenced subobjects that are not located in the object that is being copied. Imagine, we have a content object that has an image object, referenced by the ``cover`` attribute, but located in an independent place. >>> root = zope.location.location.Location() >>> content = zope.location.location.Location() >>> zope.location.location.locate(content, root, 'content') >>> image = zope.location.location.Location() >>> zope.location.location.locate(image, root, 'image.jpg') >>> content.cover = image Without any hooks, the image object will be cloned as well: >>> new = zope.copy.copy(content) >>> new.cover is image False That's not what we'd expect though, so, let's provide a copy hook to deal with that. The copy hook for this case is provided by zope.location package, but we'll create one from scratch as we want to check out the usage of the ResumeCopy. >>> @zope.component.adapter(zope.location.interfaces.ILocation) ... @zope.interface.implementer(zope.copy.interfaces.ICopyHook) ... def location_copyfactory(obj): ... def factory(location, register): ... if not zope.location.location.inside(obj, location): ... return obj ... raise zope.copy.interfaces.ResumeCopy ... return factory ... >>> zope.component.provideAdapter(location_copyfactory) This hook returns objects as they are if they are not located inside object that's being copied, or raises ResumeCopy to signal that the recursive copy should be continued and used for the object. >>> new = zope.copy.copy(content) >>> new.cover is image True Much better :-) ``clone`` vs ``copy`` --------------------- As we stated before, there's two functions that is used for copying objects. The ``clone`` - that does the job, and its wrapper, ``copy`` that calls ``clone`` and then clears copy's __parent__ and __name__ attribute values. Let's create a location object with __name__ and __parent__ set. >>> root = zope.location.location.Location() >>> folder = zope.location.location.Location() >>> folder.__name__ = 'files' >>> folder.__parent__ = root The ``clone`` function will leave those attributes as is. Note that the referenced __parent__ won't be cloned, as we registered a hook for locations in the previous section. >>> folder_clone = zope.copy.clone(folder) >>> folder_clone.__parent__ is root True >>> folder_clone.__name__ == 'files' True However, the ``copy`` function will reset those attributes to None, as we will probably want to place our object into another container with another name. >>> folder_clone = zope.copy.copy(folder) >>> folder_clone.__parent__ is None True >>> folder_clone.__name__ is None True Notice, that if your object doesn't have __parent__ and __name__ attributes at all, or these attributes could'nt be got or set because of some protections (as with zope.security's proxies, for example), you still can use the ``copy`` function, because it works for objects that don't have those attributes. It won't set them if original object doesn't have them: >>> class Something(object): ... pass >>> s = Something() >>> s_copy = zope.copy.copy(s) >>> s_copy.__parent__ Traceback (most recent call last): ... AttributeError: ... >>> s_copy.__name__ Traceback (most recent call last): ... AttributeError: ... And it won't fail if original object has them but doesn't allow to set them. >>> root = object() >>> class Something(object): ... ... @apply ... def __name__(): ... def fget(self): ... return 'something' ... def fset(self, value): ... raise AttributeError ... return property(fget, fset) ... ... @apply ... def __parent__(): ... def fget(self): ... return root ... def fset(self, value): ... raise AttributeError ... return property(fget, fset) >>> s = Something() >>> s_copy = zope.copy.copy(s) >>> s_copy.__parent__ is root True >>> s_copy.__name__ == 'something' True ======= CHANGES ======= 3.5.0 (2009-02-09) ------------------ - Initial release. The functionality was extracted from ``zc.copy`` to provide a generic object copying mechanism with minimal dependencies. Keywords: zope3 copying cloning Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Zope3 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Database zope.copy-3.5.0/src/zope.copy.egg-info/dependency_links.txt0000644000175000017500000000000111143656271022124 0ustar nyonyo zope.copy-3.5.0/src/zope.copy.egg-info/SOURCES.txt0000644000175000017500000000067711143656271017754 0ustar nyonyoCHANGES.txt LICENSE.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.copy.egg-info/PKG-INFO src/zope.copy.egg-info/SOURCES.txt src/zope.copy.egg-info/dependency_links.txt src/zope.copy.egg-info/namespace_packages.txt src/zope.copy.egg-info/not-zip-safe src/zope.copy.egg-info/requires.txt src/zope.copy.egg-info/top_level.txt src/zope/copy/README.txt src/zope/copy/__init__.py src/zope/copy/interfaces.py src/zope/copy/tests.pyzope.copy-3.5.0/src/zope.copy.egg-info/top_level.txt0000644000175000017500000000000511143656271020603 0ustar nyonyozope zope.copy-3.5.0/src/zope.copy.egg-info/not-zip-safe0000644000175000017500000000000111143656241020301 0ustar nyonyo zope.copy-3.5.0/src/zope.copy.egg-info/namespace_packages.txt0000644000175000017500000000000511143656271022404 0ustar nyonyozope zope.copy-3.5.0/src/zope.copy.egg-info/requires.txt0000644000175000017500000000011311143656271020451 0ustar nyonyosetuptools zope.interface [test] zope.component zope.location zope.testingzope.copy-3.5.0/PKG-INFO0000644000175000017500000003240211143656274012750 0ustar nyonyoMetadata-Version: 1.0 Name: zope.copy Version: 3.5.0 Summary: Pluggable object copying mechanism Home-page: http://pypi.python.org/pypi/zope.copy Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ============== Object copying ============== This package provides a pluggable way to copy persistent objects. It was once extracted from the zc.copy package to contain much less dependencies. In fact, we only depend on zope.interface to provide pluggability. The package provides a ``clone`` function that does the object cloning and the ``copy`` wrapper that sets __parent__ and __name__ attributes of object's copy to None. This is useful, when working with Zope's located objects (see zope.location package). The ``copy`` function actually calls the ``clone`` function so we'll use the first one in the examples below. We'll also look a bit at their differences in the end of this document. The ``clone`` function (and thus the ``copy`` function that wraps it) uses pickling to copy the object and all its subobjects recursively. As each object and subobject is pickled, the function tries to adapt it to ``zope.copy.interfaces.ICopyHook``. If a copy hook is found, the recursive copy is halted. The hook is called with two values: the main, top-level object that is being copied; and a callable that supports registering functions to be called after the copy is made. The copy hook should return the exact object or subobject that should be used at this point in the copy, or raise ``zope.copy.interfaces.ResumeCopy`` exception to resume copying the object or subobject recursively after all. Note that we use zope's component architecture provided by the ``zope.component`` package in this document, but the ``zope.copy`` package itself doesn't use or depend on it, so you can provide another adaptation mechanism as described in zope.interface's adapter documentation. Simple hooks ------------ First let's examine a simple use. A hook is to support the use case of resetting the state of data that should be changed in a copy -- for instance, a log, or freezing or versioning data. The canonical way to do this is by storing the changable data on a special sub-object of the object that is to be copied. We'll look at a simple case of a subobject that should be converted to None when it is copied -- the way that the zc.freeze copier hook works. Also see the zc.objectlog copier module for a similar example. So, here is a simple object that stores a boolean on a special object. >>> class Demo(object): ... _frozen = None ... def isFrozen(self): ... return self._frozen is not None ... def freeze(self): ... self._frozen = Data() ... >>> class Data(object): ... pass ... Here's what happens if we copy one of these objects without a copy hook. >>> original = Demo() >>> original.isFrozen() False >>> original.freeze() >>> original.isFrozen() True >>> import zope.copy >>> copy = zope.copy.copy(original) >>> copy is original False >>> copy.isFrozen() True Now let's make a super-simple copy hook that always returns None, no matter what the top-level object being copied is. We'll register it and make another copy. >>> import zope.component >>> import zope.interface >>> import zope.copy.interfaces >>> def _factory(obj, register): ... return None >>> @zope.component.adapter(Data) ... @zope.interface.implementer(zope.copy.interfaces.ICopyHook) ... def data_copyfactory(obj): ... return _factory ... >>> zope.component.provideAdapter(data_copyfactory) >>> copy2 = zope.copy.copy(original) >>> copy2 is original False >>> copy2.isFrozen() False Much better. Post-copy functions ------------------- Now, let's look at the registration function that the hook can use. It is useful for resetting objects within the new copy -- for instance, back references such as __parent__ pointers. This is used concretely in the zc.objectlog.copier module; we will come up with a similar but artificial example here. Imagine an object with a subobject that is "located" (i.e., zope.location) on the parent and should be replaced whenever the main object is copied. >>> import zope.location.location >>> class Subobject(zope.location.location.Location): ... def __init__(self): ... self.counter = 0 ... def __call__(self): ... res = self.counter ... self.counter += 1 ... return res ... >>> o = zope.location.location.Location() >>> s = Subobject() >>> o.subobject = s >>> zope.location.location.locate(s, o, 'subobject') >>> s.__parent__ is o True >>> o.subobject() 0 >>> o.subobject() 1 >>> o.subobject() 2 Without an ICopyHook, this will simply duplicate the subobject, with correct new pointers. >>> c = zope.copy.copy(o) >>> c.subobject.__parent__ is c True Note that the subobject has also copied state. >>> c.subobject() 3 >>> o.subobject() 3 Our goal will be to make the counters restart when they are copied. We'll do that with a copy hook. This copy hook is different: it provides an object to replace the old object, but then it needs to set it up further after the copy is made. This is accomplished by registering a callable, ``reparent`` here, that sets up the __parent__. The callable is passed a function that can translate something from the original object into the equivalent on the new object. We use this to find the new parent, so we can set it. >>> import zope.component >>> import zope.interface >>> import zope.copy.interfaces >>> @zope.component.adapter(Subobject) ... @zope.interface.implementer(zope.copy.interfaces.ICopyHook) ... def subobject_copyfactory(original): ... def factory(obj, register): ... obj = Subobject() ... def reparent(translate): ... obj.__parent__ = translate(original.__parent__) ... register(reparent) ... return obj ... return factory ... >>> zope.component.provideAdapter(subobject_copyfactory) Now when we copy, the new subobject will have the correct, revised __parent__, but will be otherwise reset (here, just the counter) >>> c = zope.copy.copy(o) >>> c.subobject.__parent__ is c True >>> c.subobject() 0 >>> o.subobject() 4 Resuming recursive copy ----------------------- One thing we didn't examine yet is the use of ResumeCopy exception in the copy hooks. For example, when copying located objects we don't want to copy referenced subobjects that are not located in the object that is being copied. Imagine, we have a content object that has an image object, referenced by the ``cover`` attribute, but located in an independent place. >>> root = zope.location.location.Location() >>> content = zope.location.location.Location() >>> zope.location.location.locate(content, root, 'content') >>> image = zope.location.location.Location() >>> zope.location.location.locate(image, root, 'image.jpg') >>> content.cover = image Without any hooks, the image object will be cloned as well: >>> new = zope.copy.copy(content) >>> new.cover is image False That's not what we'd expect though, so, let's provide a copy hook to deal with that. The copy hook for this case is provided by zope.location package, but we'll create one from scratch as we want to check out the usage of the ResumeCopy. >>> @zope.component.adapter(zope.location.interfaces.ILocation) ... @zope.interface.implementer(zope.copy.interfaces.ICopyHook) ... def location_copyfactory(obj): ... def factory(location, register): ... if not zope.location.location.inside(obj, location): ... return obj ... raise zope.copy.interfaces.ResumeCopy ... return factory ... >>> zope.component.provideAdapter(location_copyfactory) This hook returns objects as they are if they are not located inside object that's being copied, or raises ResumeCopy to signal that the recursive copy should be continued and used for the object. >>> new = zope.copy.copy(content) >>> new.cover is image True Much better :-) ``clone`` vs ``copy`` --------------------- As we stated before, there's two functions that is used for copying objects. The ``clone`` - that does the job, and its wrapper, ``copy`` that calls ``clone`` and then clears copy's __parent__ and __name__ attribute values. Let's create a location object with __name__ and __parent__ set. >>> root = zope.location.location.Location() >>> folder = zope.location.location.Location() >>> folder.__name__ = 'files' >>> folder.__parent__ = root The ``clone`` function will leave those attributes as is. Note that the referenced __parent__ won't be cloned, as we registered a hook for locations in the previous section. >>> folder_clone = zope.copy.clone(folder) >>> folder_clone.__parent__ is root True >>> folder_clone.__name__ == 'files' True However, the ``copy`` function will reset those attributes to None, as we will probably want to place our object into another container with another name. >>> folder_clone = zope.copy.copy(folder) >>> folder_clone.__parent__ is None True >>> folder_clone.__name__ is None True Notice, that if your object doesn't have __parent__ and __name__ attributes at all, or these attributes could'nt be got or set because of some protections (as with zope.security's proxies, for example), you still can use the ``copy`` function, because it works for objects that don't have those attributes. It won't set them if original object doesn't have them: >>> class Something(object): ... pass >>> s = Something() >>> s_copy = zope.copy.copy(s) >>> s_copy.__parent__ Traceback (most recent call last): ... AttributeError: ... >>> s_copy.__name__ Traceback (most recent call last): ... AttributeError: ... And it won't fail if original object has them but doesn't allow to set them. >>> root = object() >>> class Something(object): ... ... @apply ... def __name__(): ... def fget(self): ... return 'something' ... def fset(self, value): ... raise AttributeError ... return property(fget, fset) ... ... @apply ... def __parent__(): ... def fget(self): ... return root ... def fset(self, value): ... raise AttributeError ... return property(fget, fset) >>> s = Something() >>> s_copy = zope.copy.copy(s) >>> s_copy.__parent__ is root True >>> s_copy.__name__ == 'something' True ======= CHANGES ======= 3.5.0 (2009-02-09) ------------------ - Initial release. The functionality was extracted from ``zc.copy`` to provide a generic object copying mechanism with minimal dependencies. Keywords: zope3 copying cloning Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Zope3 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Database zope.copy-3.5.0/bootstrap.py0000644000175000017500000000337211143656212014236 0ustar nyonyo############################################################################## # # Copyright (c) 2007 Zope Corporation 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. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 96250 2009-02-08 16:15:43Z nadako $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope.copy-3.5.0/setup.cfg0000644000175000017500000000007311143656274013473 0ustar nyonyo[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope.copy-3.5.0/LICENSE.txt0000644000175000017500000000420311143656212013464 0ustar nyonyoZope Public License (ZPL) Version 2.1 ------------------------------------- A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS`` AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. zope.copy-3.5.0/CHANGES.txt0000644000175000017500000000031711143656212013454 0ustar nyonyo======= CHANGES ======= 3.5.0 (2009-02-09) ------------------ - Initial release. The functionality was extracted from ``zc.copy`` to provide a generic object copying mechanism with minimal dependencies. zope.copy-3.5.0/setup.py0000644000175000017500000000407411143656212013361 0ustar nyonyo############################################################################## # # Copyright (c) 2009 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. # ############################################################################## """Setup for zope.copy package $Id: setup.py 96280 2009-02-08 22:27:11Z nadako $ """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name = 'zope.copy', version = '3.5.0', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Pluggable object copying mechanism', long_description=( read('src', 'zope', 'copy', 'README.txt') + '\n\n' + read('CHANGES.txt') ), keywords = "zope3 copying cloning", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Framework :: Zope3', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Database', ], url='http://pypi.python.org/pypi/zope.copy', license='ZPL 2.1', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], extras_require={'test': ['zope.component', 'zope.location', 'zope.testing']}, install_requires = ['setuptools', 'zope.interface', ], include_package_data = True, zip_safe = False, ) zope.copy-3.5.0/buildout.cfg0000644000175000017500000000014211143656212014147 0ustar nyonyo[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zope.copy [test]