96
58
return string2id(string)
99
def is_parsable(string):
101
parse_fragment(string)
106
# :: object -> (string, set) | None
107
def get_reconstructor_with_imports(obj):
108
"""A string representing code that will construct the object plus
109
a set of import descriptions needed for that code to work.
111
Returns None when given object cannot be reconstructed.
113
>>> get_reconstructor_with_imports(array.array('I', [1, 2, 3, 4]))
114
("array.array('I', [1L, 2L, 3L, 4L])", ['array'])
115
>>> get_reconstructor_with_imports(array.array('d', [1, 2, 3, 4]))
116
("array.array('d', [1.0, 2.0, 3.0, 4.0])", ['array'])
118
>>> get_reconstructor_with_imports(re.compile('abcd'))
119
("re.compile('abcd')", ['re'])
120
>>> get_reconstructor_with_imports(re.compile('abcd', re.I | re.M))
121
("re.compile('abcd', re.IGNORECASE | re.MULTILINE)", ['re'])
123
if isinstance(obj, RePatternType):
124
flags = regexp_flags_as_string(obj.flags)
126
return ('re.compile(%r, %s)' % (obj.pattern, flags), ['re'])
128
return ('re.compile(%r)' % obj.pattern, ['re'])
129
elif isinstance(obj, types.FunctionType):
130
function = obj.func_name
131
if function != '<lambda>':
132
module = obj.__module__
133
return (function, [(module, function)])
134
elif isinstance(obj, (int, long, float, str, unicode, types.NoneType)):
135
# Bultin types has very convienient representation.
137
elif isinstance(obj, array.array):
138
return "array." + repr(obj), ["array"]
139
elif isinstance(obj, (dict, frozenset, list, set, sets.ImmutableSet, sets.Set, tuple)):
141
if isinstance(obj, sets.ImmutableSet):
142
imports.add(("sets", "ImmutableSet"))
143
elif isinstance(obj, sets.Set):
144
imports.add(("sets", "Set"))
145
# Be careful not to generate wrong code.
146
# TODO: Current solution is a hack. Right way to do this is to make
147
# composite types call get_reconstructor_with_imports on all of their
148
# elements recursively.
149
if is_parsable(repr(obj)):
150
return repr(obj), imports
61
def get_type_name(obj):
68
May contain dots, if type is not builtin.
69
>>> get_type_name(lambda: None)
72
mapping = {array.array: 'array.array',
73
types.FunctionType: 'types.FunctionType',
74
types.GeneratorType: 'types.GeneratorType'}
76
return mapping.get(objtype, class_name(obj))
152
78
class SerializedObject(object):
153
__slots__ = ("human_readable_id", "module_name", "partial_reconstructor",
154
"reconstructor_with_imports", "type_import", "type_name")
79
"""An object captured during execution.
81
This is an abstract class, see subclasses for descriptions of different
82
types of serialized objects.
86
Number marking creation time of this SerializedObject. This number
87
cannot be converted to a meaningful time description. It should only
88
be used to arrange objects' creation on a timeline.
89
human_readable_id : str
90
A human-readable description of an object, suitable to be used as
93
Location of the module this object's class was defined in. Should be
94
usable in import statements.
95
type_import : tuple(str, str) or None
96
An import needed for the type to be available in the testing
97
environment. None if no import is needed.
99
A canonical representation of the type this object is an instance of.
156
103
def __init__(self, obj):
104
self.timestamp = SerializedObject.next_timestamp()
157
105
self.human_readable_id = get_human_readable_id(obj)
158
self.module_name = get_module_name(obj)
159
self.partial_reconstructor = get_partial_reconstructor(obj)
160
self.reconstructor_with_imports = get_reconstructor_with_imports(obj)
161
self.type_name = get_type_name(obj)
163
# An import needed for the type to be available in the testing
165
self.type_import = (self.module_name, self.type_name)
107
def _get_type_import(self):
108
if self.module_name not in ['__builtin__', 'exceptions']:
109
return (self.module_name, self.type_name)
110
type_import = property(_get_type_import)
112
def next_timestamp(cls):
113
cls._last_timestamp += 1
114
return cls._last_timestamp
115
next_timestamp = classmethod(next_timestamp)
117
class ImmutableObject(SerializedObject):
118
"""A serialized object which identity doesn't matter.
120
Immutable objects (like strings or integers) and objects with well-defined
121
location (like non-anonymous functions) are all considered ImmutableObjects.
123
ImmutableObjects are certain to have a reconstructor.
127
A string representing code that will construct the object.
129
A set of import descriptions needed for the reconstructor code to work.
131
def __init__(self, obj):
132
SerializedObject.__init__(self, obj)
134
self.reconstructor, self.imports = ImmutableObject.get_reconstructor_with_imports(obj)
167
136
def __eq__(self, other):
168
if not isinstance(other, SerializedObject):
170
for attr in SerializedObject.__slots__:
171
if getattr(self, attr) != getattr(other, attr):
137
return isinstance(other, ImmutableObject) and \
138
self.reconstructor == other.reconstructor and \
139
self.imports == other.imports
175
141
def __hash__(self):
176
return hash(self.partial_reconstructor)
179
if self.reconstructor_with_imports is not None:
180
return "SerializedObject(%r)" % self.reconstructor_with_imports[0]
182
return "SerializedObject(%r)" % self.partial_reconstructor
185
return SerializedObject(obj)
187
def serialize_call_arguments(input):
189
for key, value in input.iteritems():
190
new_input[key] = serialize(value)
142
return hash(self.reconstructor)
145
return "ImmutableObject(%r, imports=%r)" % (self.reconstructor, self.imports)
147
# :: object -> (string, set)
148
def get_reconstructor_with_imports(obj):
150
>>> ImmutableObject.get_reconstructor_with_imports(re.compile('abcd'))
151
("re.compile('abcd')", set(['re']))
152
>>> ImmutableObject.get_reconstructor_with_imports(re.compile('abcd', re.I | re.M))
153
("re.compile('abcd', re.IGNORECASE | re.MULTILINE)", set(['re']))
155
if isinstance(obj, (int, long, float, str, unicode, types.NoneType)):
156
# Bultin types has very convienient representation.
157
return repr(obj), set()
158
elif isinstance(obj, RePatternType):
159
flags = regexp_flags_as_string(obj.flags)
161
return ('re.compile(%r, %s)' % (obj.pattern, flags), set(['re']))
163
return ('re.compile(%r)' % obj.pattern, set(['re']))
164
elif isinstance(obj, types.FunctionType):
165
function = obj.func_name
166
module = obj.__module__
167
return (function, set([(module, function)]))
169
raise TypeError("Unknown type of an ImmutableObject: %r." % obj)
170
get_reconstructor_with_imports = staticmethod(get_reconstructor_with_imports)
172
class UnknownObject(SerializedObject):
173
"""A user object or a builtin value that we cannot recreate.
176
partial_reconstructor : str
177
A string representation of a partial object reconstructor. It doesn't
178
have to be parsable, as it will be part of a comment. Partial
179
reconstructor should give all possible hints about an object to help
180
the user complete the code.
182
def __init__(self, obj):
183
SerializedObject.__init__(self, obj)
185
self.module_name = module_name(obj)
186
self.type_name = get_type_name(obj)
187
self.partial_reconstructor = UnknownObject.get_partial_reconstructor(obj)
190
return "UnknownObject(%r)" % self.partial_reconstructor
192
# :: object -> string
193
def get_partial_reconstructor(obj):
194
mapping = {types.FunctionType: 'function',
195
types.GeneratorType: 'generator'}
197
default = "%s.%s" % (objtype.__module__, objtype.__name__)
198
return mapping.get(objtype, default)
199
get_partial_reconstructor = staticmethod(get_partial_reconstructor)
201
class CompositeObject(SerializedObject):
202
"""An object of a builtin type that may contain other objects, e.g. a list
205
def __init__(self, obj):
206
SerializedObject.__init__(self, obj)
208
self.module_name = module_name(obj)
209
self.type_name = get_type_name(obj)
211
class SequenceObject(CompositeObject):
212
"""A builtin object that contains an ordered sequence of other objects
215
Tuples and other immutable builtin types are still serialized into
216
a SequenceObject, because they may contain a mutable element inside them.
218
type_formats_with_imports = {
219
list: ("[%s]", set()),
220
frozenset: ("frozenset([%s])", set()),
221
set: ("set([%s])", set()),
222
sets.ImmutableSet: ("ImmutableSet([%s])", set([("sets", "ImmutableSet")])),
223
sets.Set: ("Set([%s])", set([("sets", "Set")])),
224
tuple: ("(%s)", set()),
227
def __init__(self, obj, serialize):
228
CompositeObject.__init__(self, obj)
230
self.contained_objects = map(serialize, obj)
232
# Arrays constructor needs to include a typecode.
233
if isinstance(obj, array.array):
234
self.constructor_format = "array.array('%s', [%%s])" % obj.typecode
235
self.imports = set(["array"])
236
# Special case for tuples with a single element.
237
elif isinstance(obj, tuple) and len(obj) == 1:
238
self.constructor_format = "(%s,)"
241
self.constructor_format = self.type_formats_with_imports[type(obj)][0]
242
self.imports = self.type_formats_with_imports[type(obj)][1]
244
class MapObject(CompositeObject):
245
"""A mutable object that contains unordered mapping of key/value pairs.
247
def __init__(self, obj, serialize):
248
CompositeObject.__init__(self, obj)
250
self.mapping = [(serialize(k), serialize(v)) for k,v in obj.items()]
251
self.constructor_format = "{%s}"
254
def is_immutable(obj):
255
# Bool class is a subclass of int, so True and False are included in this
257
if isinstance(obj, (float, int, long, str, unicode, types.NoneType, RePatternType)):
259
elif isinstance(obj, types.FunctionType) and obj.func_name != '<lambda>':
264
return isinstance(obj, dict)
266
def is_sequence(obj):
267
return isinstance(obj, (array.array, list, frozenset, set,
268
sets.ImmutableSet, sets.Set, tuple))