|
| 1 | +# Copyright 2009-2010 Ram Rachum. |
| 2 | +# This program is distributed under the LGPL2.1 license. |
| 3 | + |
| 4 | +''' |
| 5 | +This module defines the Persistent class. |
| 6 | +
|
| 7 | +See its documentation for more information. |
| 8 | +
|
| 9 | +
|
| 10 | +
|
| 11 | +Note: This module is still experimental. |
| 12 | +
|
| 13 | +todo: need to lock library to avoid thread trouble? |
| 14 | +
|
| 15 | +todo: need to raise an exception if we're getting pickled with |
| 16 | +an old protocol? |
| 17 | +
|
| 18 | +todo: make it polite to other similar classes |
| 19 | +''' |
| 20 | + |
| 21 | + |
| 22 | +import uuid |
| 23 | +import weakref |
| 24 | +import colorsys |
| 25 | + |
| 26 | +from copy_modes import DontCopyPersistent |
| 27 | +from garlicsim.general_misc import copy_tools |
| 28 | + |
| 29 | +from persistent import Persistent |
| 30 | +# Doing `from personality import Personality` at bottom of file |
| 31 | + |
| 32 | + |
| 33 | +library = weakref.WeakValueDictionary() |
| 34 | + |
| 35 | + |
| 36 | +class UuidToken(object): |
| 37 | + '''Token which contains a uuid with its attribute `.uuid`''' |
| 38 | + def __init__(self, uuid): |
| 39 | + self.uuid = uuid |
| 40 | + |
| 41 | +class CrossProcessPersistent(Persistent): |
| 42 | + ''' |
| 43 | + Object that sometimes shouldn't really be duplicated. |
| 44 | +
|
| 45 | + Say some plain object references a Persistent object. Then that plain object |
| 46 | + gets deepcopied with the DontCopyPersistent copy mode. The plain object will |
| 47 | + get deepcopied, but the Persistent object under it will not! The new copy of |
| 48 | + the plain object will refer to the same old copy of the Persistent object. |
| 49 | + |
| 50 | + This is useful for objects which are read-only and possibly heavy. You may |
| 51 | + use Persistent as a base class for these kinds of objects. |
| 52 | + |
| 53 | + When copying a Persistent, it is not really copied; The new "copy" is just |
| 54 | + the same object. When a Persistent is passed around between processes in |
| 55 | + queues, each process retains only one copy of it. |
| 56 | + |
| 57 | + Keep in mind that a Persistent is read-only. This means that starting from the |
| 58 | + first time that it is copied or put in a queue, it should not be changed. |
| 59 | +
|
| 60 | + There is no mechanism that enforces that the user doesn't change the object, |
| 61 | + so the user must remember not to change it. |
| 62 | + |
| 63 | + Note: This class is still experimental. |
| 64 | + ''' |
| 65 | + def __new__(cls, *args, **kwargs): |
| 66 | + |
| 67 | + # Here we need to check in what context __new__ was called. |
| 68 | + # There are two options: |
| 69 | + # 1. The object is being created. |
| 70 | + # 2. The object is being unpickled. |
| 71 | + # We check whether we are getting a uuid token. If we are, it's |
| 72 | + # unpickling. If we don't, it's creation. |
| 73 | + |
| 74 | + if len(args)==1 and len(kwargs)==0 and isinstance(args[0], UuidToken): |
| 75 | + received_uuid = args[0].uuid |
| 76 | + else: |
| 77 | + received_uuid = None |
| 78 | + |
| 79 | + if received_uuid: # The object is being unpickled |
| 80 | + thing = library.pop(received_uuid, None) |
| 81 | + if thing: |
| 82 | + thing._CrossProcessPersistent__skip_setstate = True |
| 83 | + return thing |
| 84 | + else: # This object does not exist in our library yet; Let's add it |
| 85 | + thing = super(CrossProcessPersistent, cls).__new__(cls) |
| 86 | + thing._CrossProcessPersistent__uuid = received_uuid |
| 87 | + library[received_uuid] = thing |
| 88 | + return thing |
| 89 | + |
| 90 | + else: # The object is being created |
| 91 | + thing = super(CrossProcessPersistent, cls).__new__(cls) |
| 92 | + new_uuid = uuid.uuid4() |
| 93 | + thing._CrossProcessPersistent__uuid = new_uuid |
| 94 | + library[new_uuid] = thing |
| 95 | + return thing |
| 96 | + |
| 97 | + def __getstate__(self): |
| 98 | + my_dict = dict(self.__dict__) |
| 99 | + del my_dict["_CrossProcessPersistent__uuid"] |
| 100 | + return my_dict |
| 101 | + |
| 102 | + def __getnewargs__(self): |
| 103 | + return (UuidToken(self._CrossProcessPersistent__uuid),) |
| 104 | + |
| 105 | + def __setstate__(self, state): |
| 106 | + if self.__dict__.pop("_CrossProcessPersistent__skip_setstate", None): |
| 107 | + return |
| 108 | + else: |
| 109 | + self.__dict__.update(state) |
| 110 | + |
| 111 | + def __deepcopy__(self, memo): |
| 112 | + ''' |
| 113 | + Deepcopy the object. If DontCopyPersistent is given, only mock-copy. |
| 114 | + |
| 115 | + When this method receieves an instance of DontCopyPersistent as a memo |
| 116 | + dictionary, it will not actually deepcopy the object but only return a |
| 117 | + reference to the original object. |
| 118 | + ''' |
| 119 | + if isinstance(memo, DontCopyPersistent): |
| 120 | + memo[id(self)] = self |
| 121 | + return self |
| 122 | + else: |
| 123 | + new_copy = copy_tools.deepcopy_as_simple_object(self, memo) |
| 124 | + new_copy._Persistent__uuid = uuid.uuid4() |
| 125 | + if hasattr(new_copy, '_CrossProcessPersistent__personality'): |
| 126 | + del new_copy._Persistent__personality |
| 127 | + return new_copy |
| 128 | + |
| 129 | + def get_personality(self): |
| 130 | + ''' |
| 131 | + Get the personality of this persistent object. |
| 132 | + |
| 133 | + See documentation of class garlicsim.misc.persistent.Personality for |
| 134 | + more information. |
| 135 | + ''' |
| 136 | + # Todo: consider doing __getattr__ thing, maybe `cache`? |
| 137 | + personality_exists = hasattr(self, '_CrossProcessPersistent__personality') |
| 138 | + |
| 139 | + if personality_exists is False: |
| 140 | + self.__personality = Personality(self) |
| 141 | + |
| 142 | + return self.__personality |
| 143 | + |
| 144 | + |
| 145 | + |
| 146 | +from personality import Personality |
0 commit comments