source: pyyaml/trunk/lib/yaml/representer.py @ 222

Revision 222, 17.5 KB checked in by xi, 8 years ago (diff)

Subclass all base classes from object.

Hold references to the objects being represented (should fix #22).

The value of a mapping node is represented as a list of pairs (key, value)
now.

Sort dictionary items (fix #23).

Recursive structures are now loaded and dumped correctly, including complex
structures like recursive tuples (fix #5). Thanks Peter Murphy for the patches.
To make it possible, representer functions are allowed to be generators.
In this case, the first generated value is an object. Other values produced
by the representer are ignored.

Make Representer not try to guess !!pairs when a list is represented.
You need to construct a !!pairs node explicitly now.

Do not check for duplicate mapping keys as it didn't work correctly anyway.

Line 
1
2__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer',
3    'RepresenterError']
4
5from error import *
6from nodes import *
7
8try:
9    import datetime
10    datetime_available = True
11except ImportError:
12    datetime_available = False
13
14try:
15    set
16except NameError:
17    from sets import Set as set
18
19import sys, copy_reg
20
21class RepresenterError(YAMLError):
22    pass
23
24class BaseRepresenter(object):
25
26    yaml_representers = {}
27    yaml_multi_representers = {}
28
29    def __init__(self, default_style=None, default_flow_style=None):
30        self.default_style = default_style
31        self.default_flow_style = default_flow_style
32        self.represented_objects = {}
33        self.object_keeper = []
34        self.alias_key = None
35
36    def represent(self, data):
37        node = self.represent_data(data)
38        self.serialize(node)
39        self.represented_objects = {}
40        self.object_keeper = []
41        self.alias_key = None
42
43    class C: pass
44    c = C()
45    def f(): pass
46    def g(): yield None
47    classobj_type = type(C)
48    instance_type = type(c)
49    function_type = type(f)
50    generator_type = type(g())
51    builtin_function_type = type(abs)
52    module_type = type(sys)
53    del C, c, f, g
54
55    def get_classobj_bases(self, cls):
56        bases = [cls]
57        for base in cls.__bases__:
58            bases.extend(self.get_classobj_bases(base))
59        return bases
60
61    def represent_data(self, data):
62        if self.ignore_aliases(data):
63            self.alias_key = None
64        else:
65            self.alias_key = id(data)
66        if self.alias_key is not None:
67            if self.alias_key in self.represented_objects:
68                node = self.represented_objects[self.alias_key]
69                #if node is None:
70                #    raise RepresenterError("recursive objects are not allowed: %r" % data)
71                return node
72            #self.represented_objects[alias_key] = None
73            self.object_keeper.append(data)
74        data_types = type(data).__mro__
75        if type(data) is self.instance_type:
76            data_types = self.get_classobj_bases(data.__class__)+list(data_types)
77        if data_types[0] in self.yaml_representers:
78            node = self.yaml_representers[data_types[0]](self, data)
79        else:
80            for data_type in data_types:
81                if data_type in self.yaml_multi_representers:
82                    node = self.yaml_multi_representers[data_type](self, data)
83                    break
84            else:
85                if None in self.yaml_multi_representers:
86                    node = self.yaml_multi_representers[None](self, data)
87                elif None in self.yaml_representers:
88                    node = self.yaml_representers[None](self, data)
89                else:
90                    node = ScalarNode(None, unicode(data))
91        #if alias_key is not None:
92        #    self.represented_objects[alias_key] = node
93        return node
94
95    def add_representer(cls, data_type, representer):
96        if not 'yaml_representers' in cls.__dict__:
97            cls.yaml_representers = cls.yaml_representers.copy()
98        cls.yaml_representers[data_type] = representer
99    add_representer = classmethod(add_representer)
100
101    def add_multi_representer(cls, data_type, representer):
102        if not 'yaml_multi_representers' in cls.__dict__:
103            cls.yaml_multi_representers = cls.yaml_multi_representers.copy()
104        cls.yaml_multi_representers[data_type] = representer
105    add_multi_representer = classmethod(add_multi_representer)
106
107    def represent_scalar(self, tag, value, style=None):
108        if style is None:
109            style = self.default_style
110        node = ScalarNode(tag, value, style=style)
111        if self.alias_key is not None:
112            self.represented_objects[self.alias_key] = node
113        return node
114
115    def represent_sequence(self, tag, sequence, flow_style=None):
116        value = []
117        node = SequenceNode(tag, value, flow_style=flow_style)
118        if self.alias_key is not None:
119            self.represented_objects[self.alias_key] = node
120        best_style = True
121        for item in sequence:
122            node_item = self.represent_data(item)
123            if not (isinstance(node_item, ScalarNode) and not node_item.style):
124                best_style = False
125            value.append(node_item)
126        if flow_style is None:
127            if self.default_flow_style is not None:
128                node.flow_style = self.default_flow_style
129            else:
130                node.flow_style = best_style
131        return node
132
133    def represent_mapping(self, tag, mapping, flow_style=None):
134        value = []
135        node = MappingNode(tag, value, flow_style=flow_style)
136        if self.alias_key is not None:
137            self.represented_objects[self.alias_key] = node
138        best_style = True
139        if hasattr(mapping, 'items'):
140            mapping = mapping.items()
141            mapping.sort()
142        for item_key, item_value in mapping:
143            node_key = self.represent_data(item_key)
144            node_value = self.represent_data(item_value)
145            if not (isinstance(node_key, ScalarNode) and not node_key.style):
146                best_style = False
147            if not (isinstance(node_value, ScalarNode) and not node_value.style):
148                best_style = False
149            value.append((node_key, node_value))
150        if flow_style is None:
151            if self.default_flow_style is not None:
152                node.flow_style = self.default_flow_style
153            else:
154                node.flow_style = best_style
155        return node
156
157    def ignore_aliases(self, data):
158        return False
159
160class SafeRepresenter(BaseRepresenter):
161
162    def ignore_aliases(self, data):
163        if data in [None, ()]:
164            return True
165        if isinstance(data, (str, unicode, bool, int, float)):
166            return True
167
168    def represent_none(self, data):
169        return self.represent_scalar(u'tag:yaml.org,2002:null',
170                u'null')
171
172    def represent_str(self, data):
173        tag = None
174        style = None
175        try:
176            data = unicode(data, 'ascii')
177            tag = u'tag:yaml.org,2002:str'
178        except UnicodeDecodeError:
179            try:
180                data = unicode(data, 'utf-8')
181                tag = u'tag:yaml.org,2002:str'
182            except UnicodeDecodeError:
183                data = data.encode('base64')
184                tag = u'tag:yaml.org,2002:binary'
185                style = '|'
186        return self.represent_scalar(tag, data, style=style)
187
188    def represent_unicode(self, data):
189        return self.represent_scalar(u'tag:yaml.org,2002:str', data)
190
191    def represent_bool(self, data):
192        if data:
193            value = u'true'
194        else:
195            value = u'false'
196        return self.represent_scalar(u'tag:yaml.org,2002:bool', value)
197
198    def represent_int(self, data):
199        return self.represent_scalar(u'tag:yaml.org,2002:int', unicode(data))
200
201    def represent_long(self, data):
202        return self.represent_scalar(u'tag:yaml.org,2002:int', unicode(data))
203
204    inf_value = 1e300
205    while repr(inf_value) != repr(inf_value*inf_value):
206        inf_value *= inf_value
207
208    def represent_float(self, data):
209        if data != data or (data == 0.0 and data == 1.0):
210            value = u'.nan'
211        elif data == self.inf_value:
212            value = u'.inf'
213        elif data == -self.inf_value:
214            value = u'-.inf'
215        else:
216            value = unicode(repr(data))
217        return self.represent_scalar(u'tag:yaml.org,2002:float', value)
218
219    def represent_list(self, data):
220        #pairs = (len(data) > 0 and isinstance(data, list))
221        #if pairs:
222        #    for item in data:
223        #        if not isinstance(item, tuple) or len(item) != 2:
224        #            pairs = False
225        #            break
226        #if not pairs:
227            return self.represent_sequence(u'tag:yaml.org,2002:seq', data)
228        #value = []
229        #for item_key, item_value in data:
230        #    value.append(self.represent_mapping(u'tag:yaml.org,2002:map',
231        #        [(item_key, item_value)]))
232        #return SequenceNode(u'tag:yaml.org,2002:pairs', value)
233
234    def represent_dict(self, data):
235        return self.represent_mapping(u'tag:yaml.org,2002:map', data)
236
237    def represent_set(self, data):
238        value = {}
239        for key in data:
240            value[key] = None
241        return self.represent_mapping(u'tag:yaml.org,2002:set', value)
242
243    def represent_date(self, data):
244        value = u'%04d-%02d-%02d' % (data.year, data.month, data.day)
245        return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value)
246
247    def represent_datetime(self, data):
248        value = u'%04d-%02d-%02d %02d:%02d:%02d' \
249                % (data.year, data.month, data.day,
250                    data.hour, data.minute, data.second)
251        if data.microsecond:
252            value += u'.' + unicode(data.microsecond/1000000.0).split(u'.')[1]
253        if data.utcoffset():
254            value += unicode(data.utcoffset())
255        return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value)
256
257    def represent_yaml_object(self, tag, data, cls, flow_style=None):
258        if hasattr(data, '__getstate__'):
259            state = data.__getstate__()
260        else:
261            state = data.__dict__.copy()
262        return self.represent_mapping(tag, state, flow_style=flow_style)
263
264    def represent_undefined(self, data):
265        raise RepresenterError("cannot represent an object: %s" % data)
266
267SafeRepresenter.add_representer(type(None),
268        SafeRepresenter.represent_none)
269
270SafeRepresenter.add_representer(str,
271        SafeRepresenter.represent_str)
272
273SafeRepresenter.add_representer(unicode,
274        SafeRepresenter.represent_unicode)
275
276SafeRepresenter.add_representer(bool,
277        SafeRepresenter.represent_bool)
278
279SafeRepresenter.add_representer(int,
280        SafeRepresenter.represent_int)
281
282SafeRepresenter.add_representer(long,
283        SafeRepresenter.represent_long)
284
285SafeRepresenter.add_representer(float,
286        SafeRepresenter.represent_float)
287
288SafeRepresenter.add_representer(list,
289        SafeRepresenter.represent_list)
290
291SafeRepresenter.add_representer(tuple,
292        SafeRepresenter.represent_list)
293
294SafeRepresenter.add_representer(dict,
295        SafeRepresenter.represent_dict)
296
297SafeRepresenter.add_representer(set,
298        SafeRepresenter.represent_set)
299
300if datetime_available:
301    SafeRepresenter.add_representer(datetime.date,
302            SafeRepresenter.represent_date)
303    SafeRepresenter.add_representer(datetime.datetime,
304            SafeRepresenter.represent_datetime)
305
306SafeRepresenter.add_representer(None,
307        SafeRepresenter.represent_undefined)
308
309class Representer(SafeRepresenter):
310
311    def represent_str(self, data):
312        tag = None
313        style = None
314        try:
315            data = unicode(data, 'ascii')
316            tag = u'tag:yaml.org,2002:str'
317        except UnicodeDecodeError:
318            try:
319                data = unicode(data, 'utf-8')
320                tag = u'tag:yaml.org,2002:python/str'
321            except UnicodeDecodeError:
322                data = data.encode('base64')
323                tag = u'tag:yaml.org,2002:binary'
324                style = '|'
325        return self.represent_scalar(tag, data, style=style)
326
327    def represent_unicode(self, data):
328        tag = None
329        try:
330            data.encode('ascii')
331            tag = u'tag:yaml.org,2002:python/unicode'
332        except UnicodeEncodeError:
333            tag = u'tag:yaml.org,2002:str'
334        return self.represent_scalar(tag, data)
335
336    def represent_long(self, data):
337        tag = u'tag:yaml.org,2002:int'
338        if int(data) is not data:
339            tag = u'tag:yaml.org,2002:python/long'
340        return self.represent_scalar(tag, unicode(data))
341
342    def represent_complex(self, data):
343        if data.imag == 0.0:
344            data = u'%r' % data.real
345        elif data.real == 0.0:
346            data = u'%rj' % data.imag
347        elif data.imag > 0:
348            data = u'%r+%rj' % (data.real, data.imag)
349        else:
350            data = u'%r%rj' % (data.real, data.imag)
351        return self.represent_scalar(u'tag:yaml.org,2002:python/complex', data)
352
353    def represent_tuple(self, data):
354        return self.represent_sequence(u'tag:yaml.org,2002:python/tuple', data)
355
356    def represent_name(self, data):
357        name = u'%s.%s' % (data.__module__, data.__name__)
358        return self.represent_scalar(u'tag:yaml.org,2002:python/name:'+name, u'')
359
360    def represent_module(self, data):
361        return self.represent_scalar(
362                u'tag:yaml.org,2002:python/module:'+data.__name__, u'')
363
364    def represent_instance(self, data):
365        # For instances of classic classes, we use __getinitargs__ and
366        # __getstate__ to serialize the data.
367
368        # If data.__getinitargs__ exists, the object must be reconstructed by
369        # calling cls(**args), where args is a tuple returned by
370        # __getinitargs__. Otherwise, the cls.__init__ method should never be
371        # called and the class instance is created by instantiating a trivial
372        # class and assigning to the instance's __class__ variable.
373
374        # If data.__getstate__ exists, it returns the state of the object.
375        # Otherwise, the state of the object is data.__dict__.
376
377        # We produce either a !!python/object or !!python/object/new node.
378        # If data.__getinitargs__ does not exist and state is a dictionary, we
379        # produce a !!python/object node . Otherwise we produce a
380        # !!python/object/new node.
381
382        cls = data.__class__
383        class_name = u'%s.%s' % (cls.__module__, cls.__name__)
384        args = None
385        state = None
386        if hasattr(data, '__getinitargs__'):
387            args = list(data.__getinitargs__())
388        if hasattr(data, '__getstate__'):
389            state = data.__getstate__()
390        else:
391            state = data.__dict__
392        if args is None and isinstance(state, dict):
393            return self.represent_mapping(
394                    u'tag:yaml.org,2002:python/object:'+class_name, state)
395        if isinstance(state, dict) and not state:
396            return self.represent_sequence(
397                    u'tag:yaml.org,2002:python/object/new:'+class_name, args)
398        value = {}
399        if args:
400            value['args'] = args
401        value['state'] = state
402        return self.represent_mapping(
403                u'tag:yaml.org,2002:python/object/new:'+class_name, value)
404
405    def represent_object(self, data):
406        # We use __reduce__ API to save the data. data.__reduce__ returns
407        # a tuple of length 2-5:
408        #   (function, args, state, listitems, dictitems)
409
410        # For reconstructing, we calls function(*args), then set its state,
411        # listitems, and dictitems if they are not None.
412
413        # A special case is when function.__name__ == '__newobj__'. In this
414        # case we create the object with args[0].__new__(*args).
415
416        # Another special case is when __reduce__ returns a string - we don't
417        # support it.
418
419        # We produce a !!python/object, !!python/object/new or
420        # !!python/object/apply node.
421
422        cls = type(data)
423        if cls in copy_reg.dispatch_table:
424            reduce = copy_reg.dispatch_table[cls](data)
425        elif hasattr(data, '__reduce_ex__'):
426            reduce = data.__reduce_ex__(2)
427        elif hasattr(data, '__reduce__'):
428            reduce = data.__reduce__()
429        else:
430            raise RepresenterError("cannot represent object: %r" % data)
431        reduce = (list(reduce)+[None]*5)[:5]
432        function, args, state, listitems, dictitems = reduce
433        args = list(args)
434        if state is None:
435            state = {}
436        if listitems is not None:
437            listitems = list(listitems)
438        if dictitems is not None:
439            dictitems = dict(dictitems)
440        if function.__name__ == '__newobj__':
441            function = args[0]
442            args = args[1:]
443            tag = u'tag:yaml.org,2002:python/object/new:'
444            newobj = True
445        else:
446            tag = u'tag:yaml.org,2002:python/object/apply:'
447            newobj = False
448        function_name = u'%s.%s' % (function.__module__, function.__name__)
449        if not args and not listitems and not dictitems \
450                and isinstance(state, dict) and newobj:
451            return self.represent_mapping(
452                    u'tag:yaml.org,2002:python/object:'+function_name, state)
453        if not listitems and not dictitems  \
454                and isinstance(state, dict) and not state:
455            return self.represent_sequence(tag+function_name, args)
456        value = {}
457        if args:
458            value['args'] = args
459        if state or not isinstance(state, dict):
460            value['state'] = state
461        if listitems:
462            value['listitems'] = listitems
463        if dictitems:
464            value['dictitems'] = dictitems
465        return self.represent_mapping(tag+function_name, value)
466
467Representer.add_representer(str,
468        Representer.represent_str)
469
470Representer.add_representer(unicode,
471        Representer.represent_unicode)
472
473Representer.add_representer(long,
474        Representer.represent_long)
475
476Representer.add_representer(complex,
477        Representer.represent_complex)
478
479Representer.add_representer(tuple,
480        Representer.represent_tuple)
481
482Representer.add_representer(type,
483        Representer.represent_name)
484
485Representer.add_representer(Representer.classobj_type,
486        Representer.represent_name)
487
488Representer.add_representer(Representer.function_type,
489        Representer.represent_name)
490
491Representer.add_representer(Representer.builtin_function_type,
492        Representer.represent_name)
493
494Representer.add_representer(Representer.module_type,
495        Representer.represent_module)
496
497Representer.add_multi_representer(Representer.instance_type,
498        Representer.represent_instance)
499
500Representer.add_multi_representer(object,
501        Representer.represent_object)
502
Note: See TracBrowser for help on using the repository browser.