~0x44/nova/extdoc

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/spread/jelly.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_jelly -*-
 
2
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
S-expression-based persistence of python objects.
 
7
 
 
8
It does something very much like L{Pickle<pickle>}; however, pickle's main goal
 
9
seems to be efficiency (both in space and time); jelly's main goals are
 
10
security, human readability, and portability to other environments.
 
11
 
 
12
This is how Jelly converts various objects to s-expressions.
 
13
 
 
14
Boolean::
 
15
    True --> ['boolean', 'true']
 
16
 
 
17
Integer::
 
18
    1 --> 1
 
19
 
 
20
List::
 
21
    [1, 2] --> ['list', 1, 2]
 
22
 
 
23
String::
 
24
    \"hello\" --> \"hello\"
 
25
 
 
26
Float::
 
27
    2.3 --> 2.3
 
28
 
 
29
Dictionary::
 
30
    {'a': 1, 'b': 'c'} --> ['dictionary', ['b', 'c'], ['a', 1]]
 
31
 
 
32
Module::
 
33
    UserString --> ['module', 'UserString']
 
34
 
 
35
Class::
 
36
    UserString.UserString --> ['class', ['module', 'UserString'], 'UserString']
 
37
 
 
38
Function::
 
39
    string.join --> ['function', 'join', ['module', 'string']]
 
40
 
 
41
Instance: s is an instance of UserString.UserString, with a __dict__
 
42
{'data': 'hello'}::
 
43
    [\"UserString.UserString\", ['dictionary', ['data', 'hello']]]
 
44
 
 
45
Class Method: UserString.UserString.center::
 
46
    ['method', 'center', ['None'], ['class', ['module', 'UserString'],
 
47
     'UserString']]
 
48
 
 
49
Instance Method: s.center, where s is an instance of UserString.UserString::
 
50
    ['method', 'center', ['instance', ['reference', 1, ['class',
 
51
    ['module', 'UserString'], 'UserString']], ['dictionary', ['data', 'd']]],
 
52
    ['dereference', 1]]
 
53
 
 
54
The C{set} builtin and the C{sets.Set} class are serialized to the same
 
55
thing, and unserialized to C{set} if available, else to C{sets.Set}. It means
 
56
that there's a possibility of type switching in the serialization process. The
 
57
solution is to always use C{set} if possible, and only use C{sets.Set} under
 
58
Python 2.3; this can be accomplished by using L{twisted.python.compat.set}.
 
59
 
 
60
The same rule applies for C{frozenset} and C{sets.ImmutableSet}.
 
61
 
 
62
@author: Glyph Lefkowitz
 
63
"""
 
64
 
 
65
# System Imports
 
66
import pickle
 
67
import types
 
68
import warnings
 
69
from types import StringType
 
70
from types import UnicodeType
 
71
from types import IntType
 
72
from types import TupleType
 
73
from types import ListType
 
74
from types import LongType
 
75
from types import FloatType
 
76
from types import FunctionType
 
77
from types import MethodType
 
78
from types import ModuleType
 
79
from types import DictionaryType
 
80
from types import InstanceType
 
81
from types import NoneType
 
82
from types import ClassType
 
83
import copy
 
84
 
 
85
import datetime
 
86
from types import BooleanType
 
87
 
 
88
try:
 
89
    import decimal
 
90
except ImportError:
 
91
    decimal = None
 
92
 
 
93
try:
 
94
    _set = set
 
95
except NameError:
 
96
    _set = None
 
97
 
 
98
try:
 
99
    # Filter out deprecation warning for Python >= 2.6
 
100
    warnings.filterwarnings("ignore", category=DeprecationWarning,
 
101
        message="the sets module is deprecated", append=True)
 
102
    import sets as _sets
 
103
finally:
 
104
    warnings.filters.pop()
 
105
 
 
106
 
 
107
from new import instance
 
108
from new import instancemethod
 
109
from zope.interface import implements
 
110
 
 
111
# Twisted Imports
 
112
from twisted.python.reflect import namedObject, qual
 
113
from twisted.persisted.crefutil import NotKnown, _Tuple, _InstanceMethod
 
114
from twisted.persisted.crefutil import _DictKeyAndValue, _Dereference
 
115
from twisted.persisted.crefutil import _Container
 
116
from twisted.python import runtime
 
117
from twisted.python.compat import reduce
 
118
 
 
119
from twisted.spread.interfaces import IJellyable, IUnjellyable
 
120
 
 
121
 
 
122
if runtime.platform.getType() == "java":
 
123
    from org.python.core import PyStringMap
 
124
    DictTypes = (DictionaryType, PyStringMap)
 
125
else:
 
126
    DictTypes = (DictionaryType,)
 
127
 
 
128
 
 
129
None_atom = "None"                  # N
 
130
# code
 
131
class_atom = "class"                # c
 
132
module_atom = "module"              # m
 
133
function_atom = "function"          # f
 
134
 
 
135
# references
 
136
dereference_atom = 'dereference'    # D
 
137
persistent_atom = 'persistent'      # p
 
138
reference_atom = 'reference'        # r
 
139
 
 
140
# mutable collections
 
141
dictionary_atom = "dictionary"      # d
 
142
list_atom = 'list'                  # l
 
143
set_atom = 'set'
 
144
 
 
145
# immutable collections
 
146
#   (assignment to __dict__ and __class__ still might go away!)
 
147
tuple_atom = "tuple"                # t
 
148
instance_atom = 'instance'          # i
 
149
frozenset_atom = 'frozenset'
 
150
 
 
151
 
 
152
# errors
 
153
unpersistable_atom = "unpersistable"# u
 
154
unjellyableRegistry = {}
 
155
unjellyableFactoryRegistry = {}
 
156
 
 
157
 
 
158
 
 
159
def _newInstance(cls, state):
 
160
    """
 
161
    Make a new instance of a class without calling its __init__ method.
 
162
    'state' will be used to update inst.__dict__ . Supports both new- and
 
163
    old-style classes.
 
164
    """
 
165
    if not isinstance(cls, types.ClassType):
 
166
        # new-style
 
167
        inst = cls.__new__(cls)
 
168
        inst.__dict__.update(state) # Copy 'instance' behaviour
 
169
    else:
 
170
        inst = instance(cls, state)
 
171
    return inst
 
172
 
 
173
 
 
174
 
 
175
def _maybeClass(classnamep):
 
176
    try:
 
177
        object
 
178
    except NameError:
 
179
        isObject = 0
 
180
    else:
 
181
        isObject = isinstance(classnamep, type)
 
182
    if isinstance(classnamep, ClassType) or isObject:
 
183
        return qual(classnamep)
 
184
    return classnamep
 
185
 
 
186
 
 
187
 
 
188
def setUnjellyableForClass(classname, unjellyable):
 
189
    """
 
190
    Set which local class will represent a remote type.
 
191
 
 
192
    If you have written a Copyable class that you expect your client to be
 
193
    receiving, write a local "copy" class to represent it, then call::
 
194
 
 
195
        jellier.setUnjellyableForClass('module.package.Class', MyCopier).
 
196
 
 
197
    Call this at the module level immediately after its class
 
198
    definition. MyCopier should be a subclass of RemoteCopy.
 
199
 
 
200
    The classname may be a special tag returned by
 
201
    'Copyable.getTypeToCopyFor' rather than an actual classname.
 
202
 
 
203
    This call is also for cached classes, since there will be no
 
204
    overlap.  The rules are the same.
 
205
    """
 
206
 
 
207
    global unjellyableRegistry
 
208
    classname = _maybeClass(classname)
 
209
    unjellyableRegistry[classname] = unjellyable
 
210
    globalSecurity.allowTypes(classname)
 
211
 
 
212
 
 
213
 
 
214
def setUnjellyableFactoryForClass(classname, copyFactory):
 
215
    """
 
216
    Set the factory to construct a remote instance of a type::
 
217
 
 
218
      jellier.setUnjellyableFactoryForClass('module.package.Class', MyFactory)
 
219
 
 
220
    Call this at the module level immediately after its class definition.
 
221
    C{copyFactory} should return an instance or subclass of
 
222
    L{RemoteCopy<pb.RemoteCopy>}.
 
223
 
 
224
    Similar to L{setUnjellyableForClass} except it uses a factory instead
 
225
    of creating an instance.
 
226
    """
 
227
 
 
228
    global unjellyableFactoryRegistry
 
229
    classname = _maybeClass(classname)
 
230
    unjellyableFactoryRegistry[classname] = copyFactory
 
231
    globalSecurity.allowTypes(classname)
 
232
 
 
233
 
 
234
 
 
235
def setUnjellyableForClassTree(module, baseClass, prefix=None):
 
236
    """
 
237
    Set all classes in a module derived from C{baseClass} as copiers for
 
238
    a corresponding remote class.
 
239
 
 
240
    When you have a heirarchy of Copyable (or Cacheable) classes on one
 
241
    side, and a mirror structure of Copied (or RemoteCache) classes on the
 
242
    other, use this to setUnjellyableForClass all your Copieds for the
 
243
    Copyables.
 
244
 
 
245
    Each copyTag (the \"classname\" argument to getTypeToCopyFor, and
 
246
    what the Copyable's getTypeToCopyFor returns) is formed from
 
247
    adding a prefix to the Copied's class name.  The prefix defaults
 
248
    to module.__name__.  If you wish the copy tag to consist of solely
 
249
    the classname, pass the empty string \'\'.
 
250
 
 
251
    @param module: a module object from which to pull the Copied classes.
 
252
        (passing sys.modules[__name__] might be useful)
 
253
 
 
254
    @param baseClass: the base class from which all your Copied classes derive.
 
255
 
 
256
    @param prefix: the string prefixed to classnames to form the
 
257
        unjellyableRegistry.
 
258
    """
 
259
    if prefix is None:
 
260
        prefix = module.__name__
 
261
 
 
262
    if prefix:
 
263
        prefix = "%s." % prefix
 
264
 
 
265
    for i in dir(module):
 
266
        i_ = getattr(module, i)
 
267
        if type(i_) == types.ClassType:
 
268
            if issubclass(i_, baseClass):
 
269
                setUnjellyableForClass('%s%s' % (prefix, i), i_)
 
270
 
 
271
 
 
272
 
 
273
def getInstanceState(inst, jellier):
 
274
    """
 
275
    Utility method to default to 'normal' state rules in serialization.
 
276
    """
 
277
    if hasattr(inst, "__getstate__"):
 
278
        state = inst.__getstate__()
 
279
    else:
 
280
        state = inst.__dict__
 
281
    sxp = jellier.prepare(inst)
 
282
    sxp.extend([qual(inst.__class__), jellier.jelly(state)])
 
283
    return jellier.preserve(inst, sxp)
 
284
 
 
285
 
 
286
 
 
287
def setInstanceState(inst, unjellier, jellyList):
 
288
    """
 
289
    Utility method to default to 'normal' state rules in unserialization.
 
290
    """
 
291
    state = unjellier.unjelly(jellyList[1])
 
292
    if hasattr(inst, "__setstate__"):
 
293
        inst.__setstate__(state)
 
294
    else:
 
295
        inst.__dict__ = state
 
296
    return inst
 
297
 
 
298
 
 
299
 
 
300
class Unpersistable:
 
301
    """
 
302
    This is an instance of a class that comes back when something couldn't be
 
303
    unpersisted.
 
304
    """
 
305
 
 
306
    def __init__(self, reason):
 
307
        """
 
308
        Initialize an unpersistable object with a descriptive C{reason} string.
 
309
        """
 
310
        self.reason = reason
 
311
 
 
312
 
 
313
    def __repr__(self):
 
314
        return "Unpersistable(%s)" % repr(self.reason)
 
315
 
 
316
 
 
317
 
 
318
class Jellyable:
 
319
    """
 
320
    Inherit from me to Jelly yourself directly with the `getStateFor'
 
321
    convenience method.
 
322
    """
 
323
    implements(IJellyable)
 
324
 
 
325
    def getStateFor(self, jellier):
 
326
        return self.__dict__
 
327
 
 
328
 
 
329
    def jellyFor(self, jellier):
 
330
        """
 
331
        @see: L{twisted.spread.interfaces.IJellyable.jellyFor}
 
332
        """
 
333
        sxp = jellier.prepare(self)
 
334
        sxp.extend([
 
335
            qual(self.__class__),
 
336
            jellier.jelly(self.getStateFor(jellier))])
 
337
        return jellier.preserve(self, sxp)
 
338
 
 
339
 
 
340
 
 
341
class Unjellyable:
 
342
    """
 
343
    Inherit from me to Unjelly yourself directly with the
 
344
    C{setStateFor} convenience method.
 
345
    """
 
346
    implements(IUnjellyable)
 
347
 
 
348
    def setStateFor(self, unjellier, state):
 
349
        self.__dict__ = state
 
350
 
 
351
 
 
352
    def unjellyFor(self, unjellier, jellyList):
 
353
        """
 
354
        Perform the inverse operation of L{Jellyable.jellyFor}.
 
355
 
 
356
        @see: L{twisted.spread.interfaces.IUnjellyable.unjellyFor}
 
357
        """
 
358
        state = unjellier.unjelly(jellyList[1])
 
359
        self.setStateFor(unjellier, state)
 
360
        return self
 
361
 
 
362
 
 
363
 
 
364
class _Jellier:
 
365
    """
 
366
    (Internal) This class manages state for a call to jelly()
 
367
    """
 
368
 
 
369
    def __init__(self, taster, persistentStore, invoker):
 
370
        """
 
371
        Initialize.
 
372
        """
 
373
        self.taster = taster
 
374
        # `preserved' is a dict of previously seen instances.
 
375
        self.preserved = {}
 
376
        # `cooked' is a dict of previously backreferenced instances to their
 
377
        # `ref' lists.
 
378
        self.cooked = {}
 
379
        self.cooker = {}
 
380
        self._ref_id = 1
 
381
        self.persistentStore = persistentStore
 
382
        self.invoker = invoker
 
383
 
 
384
 
 
385
    def _cook(self, object):
 
386
        """
 
387
        (internal) Backreference an object.
 
388
 
 
389
        Notes on this method for the hapless future maintainer: If I've already
 
390
        gone through the prepare/preserve cycle on the specified object (it is
 
391
        being referenced after the serializer is \"done with\" it, e.g. this
 
392
        reference is NOT circular), the copy-in-place of aList is relevant,
 
393
        since the list being modified is the actual, pre-existing jelly
 
394
        expression that was returned for that object. If not, it's technically
 
395
        superfluous, since the value in self.preserved didn't need to be set,
 
396
        but the invariant that self.preserved[id(object)] is a list is
 
397
        convenient because that means we don't have to test and create it or
 
398
        not create it here, creating fewer code-paths.  that's why
 
399
        self.preserved is always set to a list.
 
400
 
 
401
        Sorry that this code is so hard to follow, but Python objects are
 
402
        tricky to persist correctly. -glyph
 
403
        """
 
404
        aList = self.preserved[id(object)]
 
405
        newList = copy.copy(aList)
 
406
        # make a new reference ID
 
407
        refid = self._ref_id
 
408
        self._ref_id = self._ref_id + 1
 
409
        # replace the old list in-place, so that we don't have to track the
 
410
        # previous reference to it.
 
411
        aList[:] = [reference_atom, refid, newList]
 
412
        self.cooked[id(object)] = [dereference_atom, refid]
 
413
        return aList
 
414
 
 
415
 
 
416
    def prepare(self, object):
 
417
        """
 
418
        (internal) Create a list for persisting an object to.  This will allow
 
419
        backreferences to be made internal to the object. (circular
 
420
        references).
 
421
 
 
422
        The reason this needs to happen is that we don't generate an ID for
 
423
        every object, so we won't necessarily know which ID the object will
 
424
        have in the future.  When it is 'cooked' ( see _cook ), it will be
 
425
        assigned an ID, and the temporary placeholder list created here will be
 
426
        modified in-place to create an expression that gives this object an ID:
 
427
        [reference id# [object-jelly]].
 
428
        """
 
429
 
 
430
        # create a placeholder list to be preserved
 
431
        self.preserved[id(object)] = []
 
432
        # keep a reference to this object around, so it doesn't disappear!
 
433
        # (This isn't always necessary, but for cases where the objects are
 
434
        # dynamically generated by __getstate__ or getStateToCopyFor calls, it
 
435
        # is; id() will return the same value for a different object if it gets
 
436
        # garbage collected.  This may be optimized later.)
 
437
        self.cooker[id(object)] = object
 
438
        return []
 
439
 
 
440
 
 
441
    def preserve(self, object, sexp):
 
442
        """
 
443
        (internal) Mark an object's persistent list for later referral.
 
444
        """
 
445
        # if I've been cooked in the meanwhile,
 
446
        if id(object) in self.cooked:
 
447
            # replace the placeholder empty list with the real one
 
448
            self.preserved[id(object)][2] = sexp
 
449
            # but give this one back.
 
450
            sexp = self.preserved[id(object)]
 
451
        else:
 
452
            self.preserved[id(object)] = sexp
 
453
        return sexp
 
454
 
 
455
    constantTypes = {types.StringType : 1, types.IntType : 1,
 
456
                     types.FloatType : 1, types.LongType : 1}
 
457
 
 
458
 
 
459
    def _checkMutable(self,obj):
 
460
        objId = id(obj)
 
461
        if objId in self.cooked:
 
462
            return self.cooked[objId]
 
463
        if objId in self.preserved:
 
464
            self._cook(obj)
 
465
            return self.cooked[objId]
 
466
 
 
467
 
 
468
    def jelly(self, obj):
 
469
        if isinstance(obj, Jellyable):
 
470
            preRef = self._checkMutable(obj)
 
471
            if preRef:
 
472
                return preRef
 
473
            return obj.jellyFor(self)
 
474
        objType = type(obj)
 
475
        if self.taster.isTypeAllowed(qual(objType)):
 
476
            # "Immutable" Types
 
477
            if ((objType is StringType) or
 
478
                (objType is IntType) or
 
479
                (objType is LongType) or
 
480
                (objType is FloatType)):
 
481
                return obj
 
482
            elif objType is MethodType:
 
483
                return ["method",
 
484
                        obj.im_func.__name__,
 
485
                        self.jelly(obj.im_self),
 
486
                        self.jelly(obj.im_class)]
 
487
 
 
488
            elif UnicodeType and objType is UnicodeType:
 
489
                return ['unicode', obj.encode('UTF-8')]
 
490
            elif objType is NoneType:
 
491
                return ['None']
 
492
            elif objType is FunctionType:
 
493
                name = obj.__name__
 
494
                return ['function', str(pickle.whichmodule(obj, obj.__name__))
 
495
                        + '.' +
 
496
                        name]
 
497
            elif objType is ModuleType:
 
498
                return ['module', obj.__name__]
 
499
            elif objType is BooleanType:
 
500
                return ['boolean', obj and 'true' or 'false']
 
501
            elif objType is datetime.datetime:
 
502
                if obj.tzinfo:
 
503
                    raise NotImplementedError(
 
504
                        "Currently can't jelly datetime objects with tzinfo")
 
505
                return ['datetime', '%s %s %s %s %s %s %s' % (
 
506
                    obj.year, obj.month, obj.day, obj.hour,
 
507
                    obj.minute, obj.second, obj.microsecond)]
 
508
            elif objType is datetime.time:
 
509
                if obj.tzinfo:
 
510
                    raise NotImplementedError(
 
511
                        "Currently can't jelly datetime objects with tzinfo")
 
512
                return ['time', '%s %s %s %s' % (obj.hour, obj.minute,
 
513
                                                 obj.second, obj.microsecond)]
 
514
            elif objType is datetime.date:
 
515
                return ['date', '%s %s %s' % (obj.year, obj.month, obj.day)]
 
516
            elif objType is datetime.timedelta:
 
517
                return ['timedelta', '%s %s %s' % (obj.days, obj.seconds,
 
518
                                                   obj.microseconds)]
 
519
            elif objType is ClassType or issubclass(objType, type):
 
520
                return ['class', qual(obj)]
 
521
            elif decimal is not None and objType is decimal.Decimal:
 
522
                return self.jelly_decimal(obj)
 
523
            else:
 
524
                preRef = self._checkMutable(obj)
 
525
                if preRef:
 
526
                    return preRef
 
527
                # "Mutable" Types
 
528
                sxp = self.prepare(obj)
 
529
                if objType is ListType:
 
530
                    sxp.extend(self._jellyIterable(list_atom, obj))
 
531
                elif objType is TupleType:
 
532
                    sxp.extend(self._jellyIterable(tuple_atom, obj))
 
533
                elif objType in DictTypes:
 
534
                    sxp.append(dictionary_atom)
 
535
                    for key, val in obj.items():
 
536
                        sxp.append([self.jelly(key), self.jelly(val)])
 
537
                elif (_set is not None and objType is set or
 
538
                      objType is _sets.Set):
 
539
                    sxp.extend(self._jellyIterable(set_atom, obj))
 
540
                elif (_set is not None and objType is frozenset or
 
541
                      objType is _sets.ImmutableSet):
 
542
                    sxp.extend(self._jellyIterable(frozenset_atom, obj))
 
543
                else:
 
544
                    className = qual(obj.__class__)
 
545
                    persistent = None
 
546
                    if self.persistentStore:
 
547
                        persistent = self.persistentStore(obj, self)
 
548
                    if persistent is not None:
 
549
                        sxp.append(persistent_atom)
 
550
                        sxp.append(persistent)
 
551
                    elif self.taster.isClassAllowed(obj.__class__):
 
552
                        sxp.append(className)
 
553
                        if hasattr(obj, "__getstate__"):
 
554
                            state = obj.__getstate__()
 
555
                        else:
 
556
                            state = obj.__dict__
 
557
                        sxp.append(self.jelly(state))
 
558
                    else:
 
559
                        self.unpersistable(
 
560
                            "instance of class %s deemed insecure" %
 
561
                            qual(obj.__class__), sxp)
 
562
                return self.preserve(obj, sxp)
 
563
        else:
 
564
            if objType is InstanceType:
 
565
                raise InsecureJelly("Class not allowed for instance: %s %s" %
 
566
                                    (obj.__class__, obj))
 
567
            raise InsecureJelly("Type not allowed for object: %s %s" %
 
568
                                (objType, obj))
 
569
 
 
570
 
 
571
    def _jellyIterable(self, atom, obj):
 
572
        """
 
573
        Jelly an iterable object.
 
574
 
 
575
        @param atom: the identifier atom of the object.
 
576
        @type atom: C{str}
 
577
 
 
578
        @param obj: any iterable object.
 
579
        @type obj: C{iterable}
 
580
 
 
581
        @return: a generator of jellied data.
 
582
        @rtype: C{generator}
 
583
        """
 
584
        yield atom
 
585
        for item in obj:
 
586
            yield self.jelly(item)
 
587
 
 
588
 
 
589
    def jelly_decimal(self, d):
 
590
        """
 
591
        Jelly a decimal object.
 
592
 
 
593
        @param d: a decimal object to serialize.
 
594
        @type d: C{decimal.Decimal}
 
595
 
 
596
        @return: jelly for the decimal object.
 
597
        @rtype: C{list}
 
598
        """
 
599
        sign, guts, exponent = d.as_tuple()
 
600
        value = reduce(lambda left, right: left * 10 + right, guts)
 
601
        if sign:
 
602
            value = -value
 
603
        return ['decimal', value, exponent]
 
604
 
 
605
 
 
606
    def unpersistable(self, reason, sxp=None):
 
607
        """
 
608
        (internal) Returns an sexp: (unpersistable "reason").  Utility method
 
609
        for making note that a particular object could not be serialized.
 
610
        """
 
611
        if sxp is None:
 
612
            sxp = []
 
613
        sxp.append(unpersistable_atom)
 
614
        sxp.append(reason)
 
615
        return sxp
 
616
 
 
617
 
 
618
 
 
619
class _Unjellier:
 
620
 
 
621
    def __init__(self, taster, persistentLoad, invoker):
 
622
        self.taster = taster
 
623
        self.persistentLoad = persistentLoad
 
624
        self.references = {}
 
625
        self.postCallbacks = []
 
626
        self.invoker = invoker
 
627
 
 
628
 
 
629
    def unjellyFull(self, obj):
 
630
        o = self.unjelly(obj)
 
631
        for m in self.postCallbacks:
 
632
            m()
 
633
        return o
 
634
 
 
635
 
 
636
    def unjelly(self, obj):
 
637
        if type(obj) is not types.ListType:
 
638
            return obj
 
639
        jelType = obj[0]
 
640
        if not self.taster.isTypeAllowed(jelType):
 
641
            raise InsecureJelly(jelType)
 
642
        regClass = unjellyableRegistry.get(jelType)
 
643
        if regClass is not None:
 
644
            if isinstance(regClass, ClassType):
 
645
                inst = _Dummy() # XXX chomp, chomp
 
646
                inst.__class__ = regClass
 
647
                method = inst.unjellyFor
 
648
            elif isinstance(regClass, type):
 
649
                # regClass.__new__ does not call regClass.__init__
 
650
                inst = regClass.__new__(regClass)
 
651
                method = inst.unjellyFor
 
652
            else:
 
653
                method = regClass # this is how it ought to be done
 
654
            val = method(self, obj)
 
655
            if hasattr(val, 'postUnjelly'):
 
656
                self.postCallbacks.append(inst.postUnjelly)
 
657
            return val
 
658
        regFactory = unjellyableFactoryRegistry.get(jelType)
 
659
        if regFactory is not None:
 
660
            state = self.unjelly(obj[1])
 
661
            inst = regFactory(state)
 
662
            if hasattr(inst, 'postUnjelly'):
 
663
                self.postCallbacks.append(inst.postUnjelly)
 
664
            return inst
 
665
        thunk = getattr(self, '_unjelly_%s'%jelType, None)
 
666
        if thunk is not None:
 
667
            ret = thunk(obj[1:])
 
668
        else:
 
669
            nameSplit = jelType.split('.')
 
670
            modName = '.'.join(nameSplit[:-1])
 
671
            if not self.taster.isModuleAllowed(modName):
 
672
                raise InsecureJelly(
 
673
                    "Module %s not allowed (in type %s)." % (modName, jelType))
 
674
            clz = namedObject(jelType)
 
675
            if not self.taster.isClassAllowed(clz):
 
676
                raise InsecureJelly("Class %s not allowed." % jelType)
 
677
            if hasattr(clz, "__setstate__"):
 
678
                ret = _newInstance(clz, {})
 
679
                state = self.unjelly(obj[1])
 
680
                ret.__setstate__(state)
 
681
            else:
 
682
                state = self.unjelly(obj[1])
 
683
                ret = _newInstance(clz, state)
 
684
            if hasattr(clz, 'postUnjelly'):
 
685
                self.postCallbacks.append(ret.postUnjelly)
 
686
        return ret
 
687
 
 
688
 
 
689
    def _unjelly_None(self, exp):
 
690
        return None
 
691
 
 
692
 
 
693
    def _unjelly_unicode(self, exp):
 
694
        if UnicodeType:
 
695
            return unicode(exp[0], "UTF-8")
 
696
        else:
 
697
            return Unpersistable("Could not unpersist unicode: %s" % (exp[0],))
 
698
 
 
699
 
 
700
    def _unjelly_decimal(self, exp):
 
701
        """
 
702
        Unjelly decimal objects, if decimal is available. If not, return a
 
703
        L{Unpersistable} object instead.
 
704
        """
 
705
        if decimal is None:
 
706
            return Unpersistable(
 
707
                "Could not unpersist decimal: %s" % (exp[0] * (10**exp[1]),))
 
708
        value = exp[0]
 
709
        exponent = exp[1]
 
710
        if value < 0:
 
711
            sign = 1
 
712
        else:
 
713
            sign = 0
 
714
        guts = decimal.Decimal(value).as_tuple()[1]
 
715
        return decimal.Decimal((sign, guts, exponent))
 
716
 
 
717
 
 
718
    def _unjelly_boolean(self, exp):
 
719
        if BooleanType:
 
720
            assert exp[0] in ('true', 'false')
 
721
            return exp[0] == 'true'
 
722
        else:
 
723
            return Unpersistable("Could not unpersist boolean: %s" % (exp[0],))
 
724
 
 
725
 
 
726
    def _unjelly_datetime(self, exp):
 
727
        return datetime.datetime(*map(int, exp[0].split()))
 
728
 
 
729
 
 
730
    def _unjelly_date(self, exp):
 
731
        return datetime.date(*map(int, exp[0].split()))
 
732
 
 
733
 
 
734
    def _unjelly_time(self, exp):
 
735
        return datetime.time(*map(int, exp[0].split()))
 
736
 
 
737
 
 
738
    def _unjelly_timedelta(self, exp):
 
739
        days, seconds, microseconds = map(int, exp[0].split())
 
740
        return datetime.timedelta(
 
741
            days=days, seconds=seconds, microseconds=microseconds)
 
742
 
 
743
 
 
744
    def unjellyInto(self, obj, loc, jel):
 
745
        o = self.unjelly(jel)
 
746
        if isinstance(o, NotKnown):
 
747
            o.addDependant(obj, loc)
 
748
        obj[loc] = o
 
749
        return o
 
750
 
 
751
 
 
752
    def _unjelly_dereference(self, lst):
 
753
        refid = lst[0]
 
754
        x = self.references.get(refid)
 
755
        if x is not None:
 
756
            return x
 
757
        der = _Dereference(refid)
 
758
        self.references[refid] = der
 
759
        return der
 
760
 
 
761
 
 
762
    def _unjelly_reference(self, lst):
 
763
        refid = lst[0]
 
764
        exp = lst[1]
 
765
        o = self.unjelly(exp)
 
766
        ref = self.references.get(refid)
 
767
        if (ref is None):
 
768
            self.references[refid] = o
 
769
        elif isinstance(ref, NotKnown):
 
770
            ref.resolveDependants(o)
 
771
            self.references[refid] = o
 
772
        else:
 
773
            assert 0, "Multiple references with same ID!"
 
774
        return o
 
775
 
 
776
 
 
777
    def _unjelly_tuple(self, lst):
 
778
        l = range(len(lst))
 
779
        finished = 1
 
780
        for elem in l:
 
781
            if isinstance(self.unjellyInto(l, elem, lst[elem]), NotKnown):
 
782
                finished = 0
 
783
        if finished:
 
784
            return tuple(l)
 
785
        else:
 
786
            return _Tuple(l)
 
787
 
 
788
 
 
789
    def _unjelly_list(self, lst):
 
790
        l = range(len(lst))
 
791
        for elem in l:
 
792
            self.unjellyInto(l, elem, lst[elem])
 
793
        return l
 
794
 
 
795
 
 
796
    def _unjellySetOrFrozenset(self, lst, containerType):
 
797
        """
 
798
        Helper method to unjelly set or frozenset.
 
799
 
 
800
        @param lst: the content of the set.
 
801
        @type lst: C{list}
 
802
 
 
803
        @param containerType: the type of C{set} to use.
 
804
        """
 
805
        l = range(len(lst))
 
806
        finished = True
 
807
        for elem in l:
 
808
            data = self.unjellyInto(l, elem, lst[elem])
 
809
            if isinstance(data, NotKnown):
 
810
                finished = False
 
811
        if not finished:
 
812
            return _Container(l, containerType)
 
813
        else:
 
814
            return containerType(l)
 
815
 
 
816
 
 
817
    def _unjelly_set(self, lst):
 
818
        """
 
819
        Unjelly set using either the C{set} builtin if available, or
 
820
        C{sets.Set} as fallback.
 
821
        """
 
822
        if _set is not None:
 
823
            containerType = set
 
824
        else:
 
825
            containerType = _sets.Set
 
826
        return self._unjellySetOrFrozenset(lst, containerType)
 
827
 
 
828
 
 
829
    def _unjelly_frozenset(self, lst):
 
830
        """
 
831
        Unjelly frozenset using either the C{frozenset} builtin if available,
 
832
        or C{sets.ImmutableSet} as fallback.
 
833
        """
 
834
        if _set is not None:
 
835
            containerType = frozenset
 
836
        else:
 
837
            containerType = _sets.ImmutableSet
 
838
        return self._unjellySetOrFrozenset(lst, containerType)
 
839
 
 
840
 
 
841
    def _unjelly_dictionary(self, lst):
 
842
        d = {}
 
843
        for k, v in lst:
 
844
            kvd = _DictKeyAndValue(d)
 
845
            self.unjellyInto(kvd, 0, k)
 
846
            self.unjellyInto(kvd, 1, v)
 
847
        return d
 
848
 
 
849
 
 
850
    def _unjelly_module(self, rest):
 
851
        moduleName = rest[0]
 
852
        if type(moduleName) != types.StringType:
 
853
            raise InsecureJelly(
 
854
                "Attempted to unjelly a module with a non-string name.")
 
855
        if not self.taster.isModuleAllowed(moduleName):
 
856
            raise InsecureJelly(
 
857
                "Attempted to unjelly module named %r" % (moduleName,))
 
858
        mod = __import__(moduleName, {}, {},"x")
 
859
        return mod
 
860
 
 
861
 
 
862
    def _unjelly_class(self, rest):
 
863
        clist = rest[0].split('.')
 
864
        modName = '.'.join(clist[:-1])
 
865
        if not self.taster.isModuleAllowed(modName):
 
866
            raise InsecureJelly("module %s not allowed" % modName)
 
867
        klaus = namedObject(rest[0])
 
868
        objType = type(klaus)
 
869
        if objType not in (types.ClassType, types.TypeType):
 
870
            raise InsecureJelly(
 
871
                "class %r unjellied to something that isn't a class: %r" % (
 
872
                    rest[0], klaus))
 
873
        if not self.taster.isClassAllowed(klaus):
 
874
            raise InsecureJelly("class not allowed: %s" % qual(klaus))
 
875
        return klaus
 
876
 
 
877
 
 
878
    def _unjelly_function(self, rest):
 
879
        modSplit = rest[0].split('.')
 
880
        modName = '.'.join(modSplit[:-1])
 
881
        if not self.taster.isModuleAllowed(modName):
 
882
            raise InsecureJelly("Module not allowed: %s"% modName)
 
883
        # XXX do I need an isFunctionAllowed?
 
884
        function = namedObject(rest[0])
 
885
        return function
 
886
 
 
887
 
 
888
    def _unjelly_persistent(self, rest):
 
889
        if self.persistentLoad:
 
890
            pload = self.persistentLoad(rest[0], self)
 
891
            return pload
 
892
        else:
 
893
            return Unpersistable("Persistent callback not found")
 
894
 
 
895
 
 
896
    def _unjelly_instance(self, rest):
 
897
        clz = self.unjelly(rest[0])
 
898
        if type(clz) is not types.ClassType:
 
899
            raise InsecureJelly("Instance found with non-class class.")
 
900
        if hasattr(clz, "__setstate__"):
 
901
            inst = _newInstance(clz, {})
 
902
            state = self.unjelly(rest[1])
 
903
            inst.__setstate__(state)
 
904
        else:
 
905
            state = self.unjelly(rest[1])
 
906
            inst = _newInstance(clz, state)
 
907
        if hasattr(clz, 'postUnjelly'):
 
908
            self.postCallbacks.append(inst.postUnjelly)
 
909
        return inst
 
910
 
 
911
 
 
912
    def _unjelly_unpersistable(self, rest):
 
913
        return Unpersistable("Unpersistable data: %s" % (rest[0],))
 
914
 
 
915
 
 
916
    def _unjelly_method(self, rest):
 
917
        """
 
918
        (internal) Unjelly a method.
 
919
        """
 
920
        im_name = rest[0]
 
921
        im_self = self.unjelly(rest[1])
 
922
        im_class = self.unjelly(rest[2])
 
923
        if type(im_class) is not types.ClassType:
 
924
            raise InsecureJelly("Method found with non-class class.")
 
925
        if im_name in im_class.__dict__:
 
926
            if im_self is None:
 
927
                im = getattr(im_class, im_name)
 
928
            elif isinstance(im_self, NotKnown):
 
929
                im = _InstanceMethod(im_name, im_self, im_class)
 
930
            else:
 
931
                im = instancemethod(im_class.__dict__[im_name],
 
932
                                    im_self,
 
933
                                    im_class)
 
934
        else:
 
935
            raise TypeError('instance method changed')
 
936
        return im
 
937
 
 
938
 
 
939
 
 
940
class _Dummy:
 
941
    """
 
942
    (Internal) Dummy class, used for unserializing instances.
 
943
    """
 
944
 
 
945
 
 
946
 
 
947
class _DummyNewStyle(object):
 
948
    """
 
949
    (Internal) Dummy class, used for unserializing instances of new-style
 
950
    classes.
 
951
    """
 
952
 
 
953
 
 
954
 
 
955
#### Published Interface.
 
956
 
 
957
 
 
958
class InsecureJelly(Exception):
 
959
    """
 
960
    This exception will be raised when a jelly is deemed `insecure'; e.g. it
 
961
    contains a type, class, or module disallowed by the specified `taster'
 
962
    """
 
963
 
 
964
 
 
965
 
 
966
class DummySecurityOptions:
 
967
    """
 
968
    DummySecurityOptions() -> insecure security options
 
969
    Dummy security options -- this class will allow anything.
 
970
    """
 
971
 
 
972
    def isModuleAllowed(self, moduleName):
 
973
        """
 
974
        DummySecurityOptions.isModuleAllowed(moduleName) -> boolean
 
975
        returns 1 if a module by that name is allowed, 0 otherwise
 
976
        """
 
977
        return 1
 
978
 
 
979
 
 
980
    def isClassAllowed(self, klass):
 
981
        """
 
982
        DummySecurityOptions.isClassAllowed(class) -> boolean
 
983
        Assumes the module has already been allowed.  Returns 1 if the given
 
984
        class is allowed, 0 otherwise.
 
985
        """
 
986
        return 1
 
987
 
 
988
 
 
989
    def isTypeAllowed(self, typeName):
 
990
        """
 
991
        DummySecurityOptions.isTypeAllowed(typeName) -> boolean
 
992
        Returns 1 if the given type is allowed, 0 otherwise.
 
993
        """
 
994
        return 1
 
995
 
 
996
 
 
997
 
 
998
class SecurityOptions:
 
999
    """
 
1000
    This will by default disallow everything, except for 'none'.
 
1001
    """
 
1002
 
 
1003
    basicTypes = ["dictionary", "list", "tuple",
 
1004
                  "reference", "dereference", "unpersistable",
 
1005
                  "persistent", "long_int", "long", "dict"]
 
1006
 
 
1007
    def __init__(self):
 
1008
        """
 
1009
        SecurityOptions() initialize.
 
1010
        """
 
1011
        # I don't believe any of these types can ever pose a security hazard,
 
1012
        # except perhaps "reference"...
 
1013
        self.allowedTypes = {"None": 1,
 
1014
                             "bool": 1,
 
1015
                             "boolean": 1,
 
1016
                             "string": 1,
 
1017
                             "str": 1,
 
1018
                             "int": 1,
 
1019
                             "float": 1,
 
1020
                             "datetime": 1,
 
1021
                             "time": 1,
 
1022
                             "date": 1,
 
1023
                             "timedelta": 1,
 
1024
                             "NoneType": 1}
 
1025
        if hasattr(types, 'UnicodeType'):
 
1026
            self.allowedTypes['unicode'] = 1
 
1027
        if decimal is not None:
 
1028
            self.allowedTypes['decimal'] = 1
 
1029
        self.allowedTypes['set'] = 1
 
1030
        self.allowedTypes['frozenset'] = 1
 
1031
        self.allowedModules = {}
 
1032
        self.allowedClasses = {}
 
1033
 
 
1034
 
 
1035
    def allowBasicTypes(self):
 
1036
        """
 
1037
        Allow all `basic' types.  (Dictionary and list.  Int, string, and float
 
1038
        are implicitly allowed.)
 
1039
        """
 
1040
        self.allowTypes(*self.basicTypes)
 
1041
 
 
1042
 
 
1043
    def allowTypes(self, *types):
 
1044
        """
 
1045
        SecurityOptions.allowTypes(typeString): Allow a particular type, by its
 
1046
        name.
 
1047
        """
 
1048
        for typ in types:
 
1049
            if not isinstance(typ, str):
 
1050
                typ = qual(typ)
 
1051
            self.allowedTypes[typ] = 1
 
1052
 
 
1053
 
 
1054
    def allowInstancesOf(self, *classes):
 
1055
        """
 
1056
        SecurityOptions.allowInstances(klass, klass, ...): allow instances
 
1057
        of the specified classes
 
1058
 
 
1059
        This will also allow the 'instance', 'class' (renamed 'classobj' in
 
1060
        Python 2.3), and 'module' types, as well as basic types.
 
1061
        """
 
1062
        self.allowBasicTypes()
 
1063
        self.allowTypes("instance", "class", "classobj", "module")
 
1064
        for klass in classes:
 
1065
            self.allowTypes(qual(klass))
 
1066
            self.allowModules(klass.__module__)
 
1067
            self.allowedClasses[klass] = 1
 
1068
 
 
1069
 
 
1070
    def allowModules(self, *modules):
 
1071
        """
 
1072
        SecurityOptions.allowModules(module, module, ...): allow modules by
 
1073
        name. This will also allow the 'module' type.
 
1074
        """
 
1075
        for module in modules:
 
1076
            if type(module) == types.ModuleType:
 
1077
                module = module.__name__
 
1078
            self.allowedModules[module] = 1
 
1079
 
 
1080
 
 
1081
    def isModuleAllowed(self, moduleName):
 
1082
        """
 
1083
        SecurityOptions.isModuleAllowed(moduleName) -> boolean
 
1084
        returns 1 if a module by that name is allowed, 0 otherwise
 
1085
        """
 
1086
        return moduleName in self.allowedModules
 
1087
 
 
1088
 
 
1089
    def isClassAllowed(self, klass):
 
1090
        """
 
1091
        SecurityOptions.isClassAllowed(class) -> boolean
 
1092
        Assumes the module has already been allowed.  Returns 1 if the given
 
1093
        class is allowed, 0 otherwise.
 
1094
        """
 
1095
        return klass in self.allowedClasses
 
1096
 
 
1097
 
 
1098
    def isTypeAllowed(self, typeName):
 
1099
        """
 
1100
        SecurityOptions.isTypeAllowed(typeName) -> boolean
 
1101
        Returns 1 if the given type is allowed, 0 otherwise.
 
1102
        """
 
1103
        return (typeName in self.allowedTypes or '.' in typeName)
 
1104
 
 
1105
 
 
1106
globalSecurity = SecurityOptions()
 
1107
globalSecurity.allowBasicTypes()
 
1108
 
 
1109
 
 
1110
 
 
1111
def jelly(object, taster=DummySecurityOptions(), persistentStore=None,
 
1112
          invoker=None):
 
1113
    """
 
1114
    Serialize to s-expression.
 
1115
 
 
1116
    Returns a list which is the serialized representation of an object.  An
 
1117
    optional 'taster' argument takes a SecurityOptions and will mark any
 
1118
    insecure objects as unpersistable rather than serializing them.
 
1119
    """
 
1120
    return _Jellier(taster, persistentStore, invoker).jelly(object)
 
1121
 
 
1122
 
 
1123
 
 
1124
def unjelly(sexp, taster=DummySecurityOptions(), persistentLoad=None,
 
1125
            invoker=None):
 
1126
    """
 
1127
    Unserialize from s-expression.
 
1128
 
 
1129
    Takes an list that was the result from a call to jelly() and unserializes
 
1130
    an arbitrary object from it.  The optional 'taster' argument, an instance
 
1131
    of SecurityOptions, will cause an InsecureJelly exception to be raised if a
 
1132
    disallowed type, module, or class attempted to unserialize.
 
1133
    """
 
1134
    return _Unjellier(taster, persistentLoad, invoker).unjellyFull(sexp)