~ruby/pythoscope/itrade-fixes

« back to all changes in this revision

Viewing changes to pythoscope/serializer.py

Merged in preserve-objects-identity branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
import sets
4
4
import types
5
5
 
6
 
from pythoscope.astvisitor import parse_fragment, ParseError
7
 
from pythoscope.util import RePatternType, all, frozenset, \
8
 
    regexp_flags_as_string, set, underscore
 
6
from pythoscope.util import RePatternType, all, class_name, frozenset, \
 
7
    module_name, regexp_flags_as_string, set, underscore
9
8
 
10
9
 
11
10
# :: SerializedObject | [SerializedObject] -> bool
12
11
def can_be_constructed(obj):
13
12
    if isinstance(obj, list):
14
13
        return all(map(can_be_constructed, obj))
15
 
    return obj.reconstructor_with_imports is not None
 
14
    elif isinstance(obj, SequenceObject):
 
15
        return all(map(can_be_constructed, obj.contained_objects))
 
16
    return not isinstance(obj, UnknownObject)
16
17
 
17
18
# :: string -> string
18
19
def string2id(string):
21
22
    return re.sub(r'[^a-zA-Z0-9_]', '', re.sub(r'\s+', '_', string.strip()))
22
23
 
23
24
# :: object -> string
24
 
def get_type_name(obj):
25
 
    """A canonical representation of the type.
26
 
 
27
 
    >>> get_type_name([])
28
 
    'list'
29
 
    >>> get_type_name({})
30
 
    'dict'
31
 
 
32
 
    May contain dots, if type is not builtin.
33
 
        >>> get_type_name(lambda: None)
34
 
        'types.FunctionType'
35
 
    """
36
 
    mapping = {types.FunctionType: 'types.FunctionType',
37
 
               types.GeneratorType: 'types.GeneratorType'}
38
 
    objtype = type(obj)
39
 
    return mapping.get(objtype, objtype.__name__)
40
 
 
41
 
# :: object -> string
42
 
def get_module_name(obj):
43
 
    return type(obj).__module__
44
 
 
45
 
# :: object -> string
46
 
def get_partial_reconstructor(obj):
47
 
    """A string representation of a partial object reconstructor.
48
 
 
49
 
    It doesn't have to be parsable, as it will be part of a comment. Partial
50
 
    reconstructor should give all possible hints about an object to help
51
 
    the user correct the code.
52
 
    """
53
 
    mapping = {types.FunctionType: 'function',
54
 
               types.GeneratorType: 'generator'}
55
 
    objtype = type(obj)
56
 
    default = "%s.%s" % (objtype.__module__, objtype.__name__)
57
 
    return mapping.get(objtype, default)
58
 
 
59
 
# :: object -> string
60
25
def get_human_readable_id(obj):
61
 
    """A human-readable description of an object, suitable to be used as
62
 
    an identifier.
63
 
    """
64
26
    # Get human readable id based on object's value,
65
27
    if obj is True:
66
28
        return 'true'
95
57
        else:
96
58
            return string2id(string)
97
59
 
98
 
# :: string -> bool
99
 
def is_parsable(string):
100
 
    try:
101
 
        parse_fragment(string)
102
 
        return True
103
 
    except ParseError:
104
 
        return False
105
 
 
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.
110
 
 
111
 
    Returns None when given object cannot be reconstructed.
112
 
 
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'])
117
 
 
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'])
122
 
    """
123
 
    if isinstance(obj, RePatternType):
124
 
        flags = regexp_flags_as_string(obj.flags)
125
 
        if flags:
126
 
            return ('re.compile(%r, %s)' % (obj.pattern, flags), ['re'])
127
 
        else:
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.
136
 
        return repr(obj), []
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)):
140
 
        imports = set()
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
 
60
# :: object -> string
 
61
def get_type_name(obj):
 
62
    """
 
63
    >>> get_type_name([])
 
64
    'list'
 
65
    >>> get_type_name({})
 
66
    'dict'
 
67
 
 
68
    May contain dots, if type is not builtin.
 
69
        >>> get_type_name(lambda: None)
 
70
        'types.FunctionType'
 
71
    """
 
72
    mapping = {array.array: 'array.array',
 
73
               types.FunctionType: 'types.FunctionType',
 
74
               types.GeneratorType: 'types.GeneratorType'}
 
75
    objtype = type(obj)
 
76
    return mapping.get(objtype, class_name(obj))
151
77
 
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.
 
80
 
 
81
    This is an abstract class, see subclasses for descriptions of different
 
82
    types of serialized objects.
 
83
 
 
84
    :IVariables:
 
85
      timestamp : float
 
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
 
91
        an identifier.
 
92
      module_name : str
 
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.
 
98
      type_name : str
 
99
        A canonical representation of the type this object is an instance of.
 
100
    """
 
101
    _last_timestamp = 0
155
102
 
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)
162
 
 
163
 
        # An import needed for the type to be available in the testing
164
 
        # environment.
165
 
        self.type_import = (self.module_name, self.type_name)
 
106
 
 
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)
 
111
 
 
112
    def next_timestamp(cls):
 
113
        cls._last_timestamp += 1
 
114
        return cls._last_timestamp
 
115
    next_timestamp = classmethod(next_timestamp)
 
116
 
 
117
class ImmutableObject(SerializedObject):
 
118
    """A serialized object which identity doesn't matter.
 
119
 
 
120
    Immutable objects (like strings or integers) and objects with well-defined
 
121
    location (like non-anonymous functions) are all considered ImmutableObjects.
 
122
 
 
123
    ImmutableObjects are certain to have a reconstructor.
 
124
 
 
125
    :IVariables:
 
126
      reconstructor : str
 
127
        A string representing code that will construct the object.
 
128
      imports : set
 
129
        A set of import descriptions needed for the reconstructor code to work.
 
130
    """
 
131
    def __init__(self, obj):
 
132
        SerializedObject.__init__(self, obj)
 
133
 
 
134
        self.reconstructor, self.imports = ImmutableObject.get_reconstructor_with_imports(obj)
166
135
 
167
136
    def __eq__(self, other):
168
 
        if not isinstance(other, SerializedObject):
169
 
            return False
170
 
        for attr in SerializedObject.__slots__:
171
 
            if getattr(self, attr) != getattr(other, attr):
172
 
                return False
173
 
        return True
 
137
        return isinstance(other, ImmutableObject) and \
 
138
            self.reconstructor == other.reconstructor and \
 
139
            self.imports == other.imports
174
140
 
175
141
    def __hash__(self):
176
 
        return hash(self.partial_reconstructor)
177
 
 
178
 
    def __repr__(self):
179
 
        if self.reconstructor_with_imports is not None:
180
 
            return "SerializedObject(%r)" % self.reconstructor_with_imports[0]
181
 
        else:
182
 
            return "SerializedObject(%r)" % self.partial_reconstructor
183
 
 
184
 
def serialize(obj):
185
 
    return SerializedObject(obj)
186
 
 
187
 
def serialize_call_arguments(input):
188
 
    new_input = {}
189
 
    for key, value in input.iteritems():
190
 
        new_input[key] = serialize(value)
191
 
    return new_input
 
142
        return hash(self.reconstructor)
 
143
 
 
144
    def __repr__(self):
 
145
        return "ImmutableObject(%r, imports=%r)" % (self.reconstructor, self.imports)
 
146
 
 
147
    # :: object -> (string, set)
 
148
    def get_reconstructor_with_imports(obj):
 
149
        """
 
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']))
 
154
        """
 
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)
 
160
            if flags:
 
161
                return ('re.compile(%r, %s)' % (obj.pattern, flags), set(['re']))
 
162
            else:
 
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)]))
 
168
        else:
 
169
            raise TypeError("Unknown type of an ImmutableObject: %r." % obj)
 
170
    get_reconstructor_with_imports = staticmethod(get_reconstructor_with_imports)
 
171
 
 
172
class UnknownObject(SerializedObject):
 
173
    """A user object or a builtin value that we cannot recreate.
 
174
 
 
175
    :IVariables:
 
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.
 
181
    """
 
182
    def __init__(self, obj):
 
183
        SerializedObject.__init__(self, obj)
 
184
 
 
185
        self.module_name = module_name(obj)
 
186
        self.type_name = get_type_name(obj)
 
187
        self.partial_reconstructor = UnknownObject.get_partial_reconstructor(obj)
 
188
 
 
189
    def __repr__(self):
 
190
        return "UnknownObject(%r)" % self.partial_reconstructor
 
191
 
 
192
    # :: object -> string
 
193
    def get_partial_reconstructor(obj):
 
194
        mapping = {types.FunctionType: 'function',
 
195
                   types.GeneratorType: 'generator'}
 
196
        objtype = type(obj)
 
197
        default = "%s.%s" % (objtype.__module__, objtype.__name__)
 
198
        return mapping.get(objtype, default)
 
199
    get_partial_reconstructor = staticmethod(get_partial_reconstructor)
 
200
 
 
201
class CompositeObject(SerializedObject):
 
202
    """An object of a builtin type that may contain other objects, e.g. a list
 
203
    or a dictionary.
 
204
    """
 
205
    def __init__(self, obj):
 
206
        SerializedObject.__init__(self, obj)
 
207
 
 
208
        self.module_name = module_name(obj)
 
209
        self.type_name = get_type_name(obj)
 
210
 
 
211
class SequenceObject(CompositeObject):
 
212
    """A builtin object that contains an ordered sequence of other objects
 
213
    inside it.
 
214
 
 
215
    Tuples and other immutable builtin types are still serialized into
 
216
    a SequenceObject, because they may contain a mutable element inside them.
 
217
    """
 
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()),
 
225
    }
 
226
 
 
227
    def __init__(self, obj, serialize):
 
228
        CompositeObject.__init__(self, obj)
 
229
 
 
230
        self.contained_objects = map(serialize, obj)
 
231
 
 
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,)"
 
239
            self.imports = set()
 
240
        else:
 
241
            self.constructor_format = self.type_formats_with_imports[type(obj)][0]
 
242
            self.imports = self.type_formats_with_imports[type(obj)][1]
 
243
 
 
244
class MapObject(CompositeObject):
 
245
    """A mutable object that contains unordered mapping of key/value pairs.
 
246
    """
 
247
    def __init__(self, obj, serialize):
 
248
        CompositeObject.__init__(self, obj)
 
249
 
 
250
        self.mapping = [(serialize(k), serialize(v)) for k,v in obj.items()]
 
251
        self.constructor_format = "{%s}"
 
252
        self.imports = set()
 
253
 
 
254
def is_immutable(obj):
 
255
    # Bool class is a subclass of int, so True and False are included in this
 
256
    # condition.
 
257
    if isinstance(obj, (float, int, long, str, unicode, types.NoneType, RePatternType)):
 
258
        return True
 
259
    elif isinstance(obj, types.FunctionType) and obj.func_name != '<lambda>':
 
260
        return True
 
261
    return False
 
262
 
 
263
def is_mapping(obj):
 
264
    return isinstance(obj, dict)
 
265
 
 
266
def is_sequence(obj):
 
267
    return isinstance(obj, (array.array, list, frozenset, set,
 
268
                            sets.ImmutableSet, sets.Set, tuple))