Package foolscap :: Module copyable
[hide private]
[frames] | no frames]

Source Code for Module foolscap.copyable

  1  # -*- test-case-name: foolscap.test.test_copyable -*- 
  2   
  3  # this module is responsible for all copy-by-value objects 
  4   
  5  from zope.interface import interface, implements 
  6  from twisted.python import reflect, log 
  7  from twisted.python.components import registerAdapter 
  8  from twisted.internet import defer 
  9   
 10  import slicer, tokens 
 11  from tokens import BananaError, Violation 
 12  from foolscap.constraint import OpenerConstraint, IConstraint, \ 
 13       ByteStringConstraint, UnboundedSchema, Optional 
 14   
 15  Interface = interface.Interface 
 16   
 17  ############################################################ 
 18  # the first half of this file is sending/serialization 
 19   
20 -class ICopyable(Interface):
21 """I represent an object which is passed-by-value across PB connections. 22 """ 23
24 - def getTypeToCopy():
25 """Return a string which names the class. This string must match the 26 one that gets registered at the receiving end. This is typically a 27 URL of some sort, in a namespace which you control."""
28 - def getStateToCopy():
29 """Return a state dictionary (with plain-string keys) which will be 30 serialized and sent to the remote end. This state object will be 31 given to the receiving object's setCopyableState method."""
32
33 -class Copyable(object):
34 implements(ICopyable) 35 # you *must* set 'typeToCopy' 36
37 - def getTypeToCopy(self):
38 try: 39 copytype = self.typeToCopy 40 except AttributeError: 41 raise RuntimeError("Copyable subclasses must specify 'typeToCopy'") 42 return copytype
43 - def getStateToCopy(self):
44 return self.__dict__
45
46 -class CopyableSlicer(slicer.BaseSlicer):
47 """I handle ICopyable objects (things which are copied by value)."""
48 - def slice(self, streamable, banana):
49 self.streamable = streamable 50 yield 'copyable' 51 copytype = self.obj.getTypeToCopy() 52 assert isinstance(copytype, str) 53 yield copytype 54 state = self.obj.getStateToCopy() 55 for k,v in state.iteritems(): 56 yield k 57 yield v
58 - def describe(self):
59 return "<%s>" % self.obj.getTypeToCopy()
60 registerAdapter(CopyableSlicer, ICopyable, tokens.ISlicer) 61 62
63 -class Copyable2(slicer.BaseSlicer):
64 # I am my own Slicer. This has more methods than you'd usually want in a 65 # base class, but if you can't register an Adapter for a whole class 66 # hierarchy then you may have to use it.
67 - def getTypeToCopy(self):
68 return reflect.qual(self.__class__)
69 - def getStateToCopy(self):
70 return self.__dict__
71 - def slice(self, streamable, banana):
72 self.streamable = streamable 73 yield 'instance' 74 yield self.getTypeToCopy() 75 yield self.getStateToCopy()
76 - def describe(self):
77 return "<%s>" % self.getTypeToCopy()
78 79 #registerRemoteCopy(typename, factory) 80 #registerUnslicer(typename, factory) 81
82 -def registerCopier(klass, copier):
83 """This is a shortcut for arranging to serialize third-party clases. 84 'copier' must be a callable which accepts an instance of the class you 85 want to serialize, and returns a tuple of (typename, state_dictionary). 86 If it returns a typename of None, the original class's fully-qualified 87 classname is used. 88 """ 89 klassname = reflect.qual(klass) 90 class _CopierAdapter: 91 implements(ICopyable) 92 def __init__(self, original): 93 self.nameToCopy, self.state = copier(original) 94 if self.nameToCopy is None: 95 self.nameToCopy = klassname
96 def getTypeToCopy(self): 97 return self.nameToCopy 98 def getStateToCopy(self): 99 return self.state 100 registerAdapter(_CopierAdapter, klass, ICopyable) 101 102 ############################################################ 103 # beyond here is the receiving/deserialization side 104
105 -class RemoteCopyUnslicer(slicer.BaseUnslicer):
106 attrname = None 107 attrConstraint = None 108
109 - def __init__(self, factory, stateSchema):
110 self.factory = factory 111 self.schema = stateSchema
112
113 - def start(self, count):
114 self.d = {} 115 self.count = count 116 self.deferred = defer.Deferred() 117 self.protocol.setObject(count, self.deferred)
118
119 - def checkToken(self, typebyte, size):
120 if self.attrname == None: 121 if typebyte not in (tokens.STRING, tokens.VOCAB): 122 raise BananaError("RemoteCopyUnslicer keys must be STRINGs") 123 else: 124 if self.attrConstraint: 125 self.attrConstraint.checkToken(typebyte, size)
126
127 - def doOpen(self, opentype):
128 if self.attrConstraint: 129 self.attrConstraint.checkOpentype(opentype) 130 unslicer = self.open(opentype) 131 if unslicer: 132 if self.attrConstraint: 133 unslicer.setConstraint(self.attrConstraint) 134 return unslicer
135
136 - def receiveChild(self, obj, ready_deferred=None):
137 assert not isinstance(obj, defer.Deferred) 138 assert ready_deferred is None 139 if self.attrname == None: 140 attrname = obj 141 if self.d.has_key(attrname): 142 raise BananaError("duplicate attribute name '%s'" % attrname) 143 s = self.schema 144 if s: 145 accept, self.attrConstraint = s.getAttrConstraint(attrname) 146 assert accept 147 self.attrname = attrname 148 else: 149 if isinstance(obj, defer.Deferred): 150 # TODO: this is an artificial restriction, and it might 151 # be possible to remove it, but I need to think through 152 # it carefully first 153 raise BananaError("unreferenceable object in attribute") 154 self.setAttribute(self.attrname, obj) 155 self.attrname = None 156 self.attrConstraint = None
157
158 - def setAttribute(self, name, value):
159 self.d[name] = value
160
161 - def receiveClose(self):
162 try: 163 obj = self.factory(self.d) 164 except: 165 log.msg("%s.receiveClose: problem in factory %s" % 166 (self.__class__.__name__, self.factory)) 167 log.err() 168 raise 169 self.protocol.setObject(self.count, obj) 170 self.deferred.callback(obj) 171 return obj, None
172
173 - def describe(self):
174 if self.classname == None: 175 return "<??>" 176 me = "<%s>" % self.classname 177 if self.attrname is None: 178 return "%s.attrname??" % me 179 else: 180 return "%s.%s" % (me, self.attrname)
181 182
183 -class NonCyclicRemoteCopyUnslicer(RemoteCopyUnslicer):
184 # The Deferred used in RemoteCopyUnslicer (used in case the RemoteCopy 185 # is participating in a reference cycle, say 'obj.foo = obj') makes it 186 # unsuitable for holding Failures (which cannot be passed through 187 # Deferred.callback). Use this class for Failures. It cannot handle 188 # reference cycles (they will cause a KeyError when the reference is 189 # followed). 190
191 - def start(self, count):
192 self.d = {} 193 self.count = count 194 self.gettingAttrname = True
195
196 - def receiveClose(self):
197 obj = self.factory(self.d) 198 return obj, None
199 200
201 -class IRemoteCopy(Interface):
202 """This interface defines what a RemoteCopy class must do. RemoteCopy 203 subclasses are used as factories to create objects that correspond to 204 Copyables sent over the wire. 205 206 Note that the constructor of an IRemoteCopy class will be called without 207 any arguments. 208 """ 209
210 - def setCopyableState(statedict):
211 """I accept an attribute dictionary name/value pairs and use it to 212 set my internal state. 213 214 Some of the values may be Deferreds, which are placeholders for the 215 as-yet-unreferenceable object which will eventually go there. If you 216 receive a Deferred, you are responsible for adding a callback to 217 update the attribute when it fires. [note: 218 RemoteCopyUnslicer.receiveChild currently has a restriction which 219 prevents this from happening, but that may go away in the future] 220 221 Some of the objects referenced by the attribute values may have 222 Deferreds in them (e.g. containers which reference recursive tuples). 223 Such containers are responsible for updating their own state when 224 those Deferreds fire, but until that point their state is still 225 subject to change. Therefore you must be careful about how much state 226 inspection you perform within this method."""
227 228 stateSchema = interface.Attribute("""I return an AttributeDictConstraint 229 object which places restrictions on incoming attribute values. These 230 restrictions are enforced as the tokens are received, before the state is 231 passed to setCopyableState.""")
232 233 234 # This maps typename to an Unslicer factory 235 CopyableRegistry = {}
236 -def registerRemoteCopyUnslicerFactory(typename, unslicerfactory, 237 registry=None):
238 """Tell PB that unslicerfactory can be used to handle Copyable objects 239 that provide a getTypeToCopy name of 'typename'. 'unslicerfactory' must 240 be a callable which takes no arguments and returns an object which 241 provides IUnslicer. 242 """ 243 assert callable(unslicerfactory) 244 # in addition, it must produce a tokens.IUnslicer . This is safe to do 245 # because Unslicers don't do anything significant when they are created. 246 test_unslicer = unslicerfactory() 247 assert tokens.IUnslicer.providedBy(test_unslicer) 248 assert type(typename) is str 249 250 if registry == None: 251 registry = CopyableRegistry 252 assert not registry.has_key(typename) 253 registry[typename] = unslicerfactory
254 255 # this keeps track of everything submitted to registerRemoteCopyFactory 256 debug_CopyableFactories = {}
257 -def registerRemoteCopyFactory(typename, factory, stateSchema=None, 258 cyclic=True, registry=None):
259 """Tell PB that 'factory' can be used to handle Copyable objects that 260 provide a getTypeToCopy name of 'typename'. 'factory' must be a callable 261 which accepts a state dictionary and returns a fully-formed instance. 262 263 'cyclic' is a boolean, which should be set to False to avoid using a 264 Deferred to provide the resulting RemoteCopy instance. This is needed to 265 deserialize Failures (or instances which inherit from one, like 266 CopiedFailure). In exchange for this, it cannot handle reference cycles. 267 """ 268 assert callable(factory) 269 debug_CopyableFactories[typename] = (factory, stateSchema, cyclic) 270 if cyclic: 271 def _RemoteCopyUnslicerFactory(): 272 return RemoteCopyUnslicer(factory, stateSchema)
273 registerRemoteCopyUnslicerFactory(typename, 274 _RemoteCopyUnslicerFactory, 275 registry) 276 else: 277 def _RemoteCopyUnslicerFactoryNonCyclic(): 278 return NonCyclicRemoteCopyUnslicer(factory, stateSchema) 279 registerRemoteCopyUnslicerFactory(typename, 280 _RemoteCopyUnslicerFactoryNonCyclic, 281 registry) 282 283 # this keeps track of everything submitted to registerRemoteCopy, which may 284 # be useful when you're wondering what's been auto-registered by the 285 # RemoteCopy metaclass magic 286 debug_RemoteCopyClasses = {}
287 -def registerRemoteCopy(typename, remote_copy_class, registry=None):
288 """Tell PB that remote_copy_class is the appropriate RemoteCopy class to 289 use when deserializing a Copyable sequence that is tagged with 290 'typename'. 'remote_copy_class' should be a RemoteCopy subclass or 291 implement the same interface, which means its constructor takes no 292 arguments and it has a setCopyableState(state) method to actually set the 293 instance's state after initialization. It must also have a nonCyclic 294 attribute. 295 """ 296 assert IRemoteCopy.implementedBy(remote_copy_class) 297 assert type(typename) is str 298 299