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

Revision 222, 24.4 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__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
3    'ConstructorError']
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 binascii, re, sys
20
21class ConstructorError(MarkedYAMLError):
22    pass
23
24class BaseConstructor(object):
25
26    yaml_constructors = {}
27    yaml_multi_constructors = {}
28
29    def __init__(self):
30        self.constructed_objects = {}
31        self.recursive_objects = {}
32        self.state_generators = []
33        self.deep_construct = False
34
35    def check_data(self):
36        # If there are more documents available?
37        return self.check_node()
38
39    def get_data(self):
40        # Construct and return the next document.
41        if self.check_node():
42            return self.construct_document(self.get_node())
43
44    def g(): yield None
45    generator_type = type(g())
46    del g
47
48    def construct_document(self, node):
49        data = self.construct_object(node)
50        while self.state_generators:
51            state_generators = self.state_generators
52            self.state_generators = []
53            for generator in state_generators:
54                for dummy in generator:
55                    pass
56        self.constructed_objects = {}
57        self.recursive_objects = {}
58        self.deep_construct = False
59        return data
60
61    def construct_object(self, node, deep=False):
62        if deep:
63            old_deep = self.deep_construct
64            self.deep_construct = True
65        if node in self.constructed_objects:
66            return self.constructed_objects[node]
67        if node in self.recursive_objects:
68            raise ConstructorError(None, None,
69                    "found unconstructable recursive node", node.start_mark)
70        self.recursive_objects[node] = None
71        constructor = None
72        state_constructor = None
73        tag_suffix = None
74        if node.tag in self.yaml_constructors:
75            constructor = self.yaml_constructors[node.tag]
76        else:
77            for tag_prefix in self.yaml_multi_constructors:
78                if node.tag.startswith(tag_prefix):
79                    tag_suffix = node.tag[len(tag_prefix):]
80                    constructor = self.yaml_multi_constructors[tag_prefix]
81                    break
82            else:
83                if None in self.yaml_multi_constructors:
84                    tag_suffix = node.tag
85                    constructor = self.yaml_multi_constructors[None]
86                elif None in self.yaml_constructors:
87                    constructor = self.yaml_constructors[None]
88                elif isinstance(node, ScalarNode):
89                    constructor = self.__class__.construct_scalar
90                elif isinstance(node, SequenceNode):
91                    constructor = self.__class__.construct_sequence
92                elif isinstance(node, MappingNode):
93                    constructor = self.__class__.construct_mapping
94        if tag_suffix is None:
95            data = constructor(self, node)
96        else:
97            data = constructor(self, tag_suffix, node)
98        if isinstance(data, self.generator_type):
99            generator = data
100            data = generator.next()
101            if self.deep_construct:
102                for dummy in generator:
103                    pass
104            else:
105                self.state_generators.append(generator)
106        self.constructed_objects[node] = data
107        del self.recursive_objects[node]
108        if deep:
109            self.deep_construct = old_deep
110        return data
111
112    def construct_scalar(self, node):
113        if not isinstance(node, ScalarNode):
114            raise ConstructorError(None, None,
115                    "expected a scalar node, but found %s" % node.id,
116                    node.start_mark)
117        return node.value
118
119    def construct_sequence(self, node, deep=False):
120        if not isinstance(node, SequenceNode):
121            raise ConstructorError(None, None,
122                    "expected a sequence node, but found %s" % node.id,
123                    node.start_mark)
124        return [self.construct_object(child, deep=deep)
125                for child in node.value]
126
127    def construct_mapping(self, node, deep=False):
128        if not isinstance(node, MappingNode):
129            raise ConstructorError(None, None,
130                    "expected a mapping node, but found %s" % node.id,
131                    node.start_mark)
132        mapping = {}
133        for key_node, value_node in node.value:
134            key = self.construct_object(key_node, deep=deep)
135            try:
136                hash(key)
137            except TypeError, exc:
138                raise ConstructorError("while constructing a mapping", node.start_mark,
139                        "found unacceptable key (%s)" % exc, key_node.start_mark)
140            value = self.construct_object(value_node, deep=deep)
141            mapping[key] = value
142        return mapping
143
144    def construct_pairs(self, node, deep=False):
145        if not isinstance(node, MappingNode):
146            raise ConstructorError(None, None,
147                    "expected a mapping node, but found %s" % node.id,
148                    node.start_mark)
149        pairs = []
150        for key_node, value_node in node.value:
151            key = self.construct_object(key_node, deep=deep)
152            value = self.construct_object(value_node, deep=deep)
153            pairs.append((key, value))
154        return pairs
155
156    def add_constructor(cls, tag, constructor):
157        if not 'yaml_constructors' in cls.__dict__:
158            cls.yaml_constructors = cls.yaml_constructors.copy()
159        cls.yaml_constructors[tag] = constructor
160    add_constructor = classmethod(add_constructor)
161
162    def add_multi_constructor(cls, tag_prefix, multi_constructor):
163        if not 'yaml_multi_constructors' in cls.__dict__:
164            cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy()
165        cls.yaml_multi_constructors[tag_prefix] = multi_constructor
166    add_multi_constructor = classmethod(add_multi_constructor)
167
168class SafeConstructor(BaseConstructor):
169
170    def construct_scalar(self, node):
171        if isinstance(node, MappingNode):
172            for key_node, value_node in node.value:
173                if key_node.tag == u'tag:yaml.org,2002:value':
174                    return self.construct_scalar(value_node)
175        return BaseConstructor.construct_scalar(self, node)
176
177    def flatten_mapping(self, node):
178        merge = []
179        index = 0
180        while index < len(node.value):
181            key_node, value_node = node.value[index]
182            if key_node.tag == u'tag:yaml.org,2002:merge':
183                del node.value[index]
184                if isinstance(value_node, MappingNode):
185                    self.flatten_mapping(value_node)
186                    merge.extend(value_node.value)
187                elif isinstance(value_node, SequenceNode):
188                    submerge = []
189                    for subnode in value_node.value:
190                        if not isinstance(subnode, MappingNode):
191                            raise ConstructorError("while constructing a mapping",
192                                    node.start_mark,
193                                    "expected a mapping for merging, but found %s"
194                                    % subnode.id, subnode.start_mark)
195                        self.flatten_mapping(subnode)
196                        submerge.append(subnode.value)
197                    submerge.reverse()
198                    for value in submerge:
199                        merge.extend(value)
200                else:
201                    raise ConstructorError("while constructing a mapping", node.start_mark,
202                            "expected a mapping or list of mappings for merging, but found %s"
203                            % value_node.id, value_node.start_mark)
204            elif key_node.tag == u'tag:yaml.org,2002:value':
205                key_node.tag = u'tag:yaml.org,2002:str'
206                index += 1
207            else:
208                index += 1
209        if merge:
210            node.value = merge + node.value
211
212    def construct_mapping(self, node, deep=False):
213        if isinstance(node, MappingNode):
214            self.flatten_mapping(node)
215        return BaseConstructor.construct_mapping(self, node, deep=deep)
216
217    def construct_yaml_null(self, node):
218        self.construct_scalar(node)
219        return None
220
221    bool_values = {
222        u'yes':     True,
223        u'no':      False,
224        u'true':    True,
225        u'false':   False,
226        u'on':      True,
227        u'off':     False,
228    }
229
230    def construct_yaml_bool(self, node):
231        value = self.construct_scalar(node)
232        return self.bool_values[value.lower()]
233
234    def construct_yaml_int(self, node):
235        value = str(self.construct_scalar(node))
236        value = value.replace('_', '')
237        sign = +1
238        if value[0] == '-':
239            sign = -1
240        if value[0] in '+-':
241            value = value[1:]
242        if value == '0':
243            return 0
244        elif value.startswith('0b'):
245            return sign*int(value[2:], 2)
246        elif value.startswith('0x'):
247            return sign*int(value[2:], 16)
248        elif value[0] == '0':
249            return sign*int(value, 8)
250        elif ':' in value:
251            digits = [int(part) for part in value.split(':')]
252            digits.reverse()
253            base = 1
254            value = 0
255            for digit in digits:
256                value += digit*base
257                base *= 60
258            return sign*value
259        else:
260            return sign*int(value)
261
262    inf_value = 1e300
263    while inf_value != inf_value*inf_value:
264        inf_value *= inf_value
265    nan_value = -inf_value/inf_value   # Trying to make a quiet NaN (like C99).
266
267    def construct_yaml_float(self, node):
268        value = str(self.construct_scalar(node))
269        value = value.replace('_', '').lower()
270        sign = +1
271        if value[0] == '-':
272            sign = -1
273        if value[0] in '+-':
274            value = value[1:]
275        if value == '.inf':
276            return sign*self.inf_value
277        elif value == '.nan':
278            return self.nan_value
279        elif ':' in value:
280            digits = [float(part) for part in value.split(':')]
281            digits.reverse()
282            base = 1
283            value = 0.0
284            for digit in digits:
285                value += digit*base
286                base *= 60
287            return sign*value
288        else:
289            return sign*float(value)
290
291    def construct_yaml_binary(self, node):
292        value = self.construct_scalar(node)
293        try:
294            return str(value).decode('base64')
295        except (binascii.Error, UnicodeEncodeError), exc:
296            raise ConstructorError(None, None,
297                    "failed to decode base64 data: %s" % exc, node.start_mark) 
298
299    timestamp_regexp = re.compile(
300            ur'''^(?P<year>[0-9][0-9][0-9][0-9])
301                -(?P<month>[0-9][0-9]?)
302                -(?P<day>[0-9][0-9]?)
303                (?:(?:[Tt]|[ \t]+)
304                (?P<hour>[0-9][0-9]?)
305                :(?P<minute>[0-9][0-9])
306                :(?P<second>[0-9][0-9])
307                (?:\.(?P<fraction>[0-9]*))?
308                (?:[ \t]*(?:Z|(?P<tz_hour>[-+][0-9][0-9]?)
309                (?::(?P<tz_minute>[0-9][0-9])?)?))?)?$''', re.X)
310
311    def construct_yaml_timestamp(self, node):
312        value = self.construct_scalar(node)
313        match = self.timestamp_regexp.match(node.value)
314        values = match.groupdict()
315        for key in values:
316            if values[key]:
317                values[key] = int(values[key])
318            else:
319                values[key] = 0
320        fraction = values['fraction']
321        if fraction:
322            while 10*fraction < 1000000:
323                fraction *= 10
324            values['fraction'] = fraction
325        stamp = datetime.datetime(values['year'], values['month'], values['day'],
326                values['hour'], values['minute'], values['second'], values['fraction'])
327        diff = datetime.timedelta(hours=values['tz_hour'], minutes=values['tz_minute'])
328        return stamp-diff
329
330    def construct_yaml_omap(self, node):
331        # Note: we do not check for duplicate keys, because it's too
332        # CPU-expensive.
333        omap = []
334        yield omap
335        if not isinstance(node, SequenceNode):
336            raise ConstructorError("while constructing an ordered map", node.start_mark,
337                    "expected a sequence, but found %s" % node.id, node.start_mark)
338        for subnode in node.value:
339            if not isinstance(subnode, MappingNode):
340                raise ConstructorError("while constructing an ordered map", node.start_mark,
341                        "expected a mapping of length 1, but found %s" % subnode.id,
342                        subnode.start_mark)
343            if len(subnode.value) != 1:
344                raise ConstructorError("while constructing an ordered map", node.start_mark,
345                        "expected a single mapping item, but found %d items" % len(subnode.value),
346                        subnode.start_mark)
347            key_node, value_node = subnode.value[0]
348            key = self.construct_object(key_node)
349            value = self.construct_object(value_node)
350            omap.append((key, value))
351
352    def construct_yaml_pairs(self, node):
353        # Note: the same code as `construct_yaml_omap`.
354        pairs = []
355        yield pairs
356        if not isinstance(node, SequenceNode):
357            raise ConstructorError("while constructing pairs", node.start_mark,
358                    "expected a sequence, but found %s" % node.id, node.start_mark)
359        for subnode in node.value:
360            if not isinstance(subnode, MappingNode):
361                raise ConstructorError("while constructing pairs", node.start_mark,
362                        "expected a mapping of length 1, but found %s" % subnode.id,
363                        subnode.start_mark)
364            if len(subnode.value) != 1:
365                raise ConstructorError("while constructing pairs", node.start_mark,
366                        "expected a single mapping item, but found %d items" % len(subnode.value),
367                        subnode.start_mark)
368            key_node, value_node = subnode.value[0]
369            key = self.construct_object(key_node)
370            value = self.construct_object(value_node)
371            pairs.append((key, value))
372
373    def construct_yaml_set(self, node):
374        data = set()
375        yield data
376        value = self.construct_mapping(node)
377        data.update(value)
378
379    def construct_yaml_str(self, node):
380        value = self.construct_scalar(node)
381        try:
382            return str(value)
383        except UnicodeEncodeError:
384            return value
385
386    def construct_yaml_seq(self, node):
387        data = []
388        yield data
389        data.extend(self.construct_sequence(node))
390
391    def construct_yaml_map(self, node):
392        data = {}
393        yield data
394        value = self.construct_mapping(node)
395        data.update(value)
396
397    def construct_yaml_object(self, node, cls):
398        data = cls.__new__(cls)
399        yield data
400        if hasattr(data, '__setstate__'):
401            state = self.construct_mapping(node, deep=True)
402            data.__setstate__(state)
403        else:
404            state = self.construct_mapping(node)
405            data.__dict__.update(state)
406
407    def construct_undefined(self, node):
408        raise ConstructorError(None, None,
409                "could not determine a constructor for the tag %r" % node.tag.encode('utf-8'),
410                node.start_mark)
411
412SafeConstructor.add_constructor(
413        u'tag:yaml.org,2002:null',
414        SafeConstructor.construct_yaml_null)
415
416SafeConstructor.add_constructor(
417        u'tag:yaml.org,2002:bool',
418        SafeConstructor.construct_yaml_bool)
419
420SafeConstructor.add_constructor(
421        u'tag:yaml.org,2002:int',
422        SafeConstructor.construct_yaml_int)
423
424SafeConstructor.add_constructor(
425        u'tag:yaml.org,2002:float',
426        SafeConstructor.construct_yaml_float)
427
428SafeConstructor.add_constructor(
429        u'tag:yaml.org,2002:binary',
430        SafeConstructor.construct_yaml_binary)
431
432if datetime_available:
433    SafeConstructor.add_constructor(
434            u'tag:yaml.org,2002:timestamp',
435            SafeConstructor.construct_yaml_timestamp)
436
437SafeConstructor.add_constructor(
438        u'tag:yaml.org,2002:omap',
439        SafeConstructor.construct_yaml_omap)
440
441SafeConstructor.add_constructor(
442        u'tag:yaml.org,2002:pairs',
443        SafeConstructor.construct_yaml_pairs)
444
445SafeConstructor.add_constructor(
446        u'tag:yaml.org,2002:set',
447        SafeConstructor.construct_yaml_set)
448
449SafeConstructor.add_constructor(
450        u'tag:yaml.org,2002:str',
451        SafeConstructor.construct_yaml_str)
452
453SafeConstructor.add_constructor(
454        u'tag:yaml.org,2002:seq',
455        SafeConstructor.construct_yaml_seq)
456
457SafeConstructor.add_constructor(
458        u'tag:yaml.org,2002:map',
459        SafeConstructor.construct_yaml_map)
460
461SafeConstructor.add_constructor(None,
462        SafeConstructor.construct_undefined)
463
464class Constructor(SafeConstructor):
465
466    def construct_python_str(self, node):
467        return self.construct_scalar(node).encode('utf-8')
468
469    def construct_python_unicode(self, node):
470        return self.construct_scalar(node)
471
472    def construct_python_long(self, node):
473        return long(self.construct_yaml_int(node))
474
475    def construct_python_complex(self, node):
476       return complex(self.construct_scalar(node))
477
478    def construct_python_tuple(self, node):
479        return tuple(self.construct_sequence(node))
480
481    def find_python_module(self, name, mark):
482        if not name:
483            raise ConstructorError("while constructing a Python module", mark,
484                    "expected non-empty name appended to the tag", mark)
485        try:
486            __import__(name)
487        except ImportError, exc:
488            raise ConstructorError("while constructing a Python module", mark,
489                    "cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
490        return sys.modules[name]
491
492    def find_python_name(self, name, mark):
493        if not name:
494            raise ConstructorError("while constructing a Python object", mark,
495                    "expected non-empty name appended to the tag", mark)
496        if u'.' in name:
497            # Python 2.4 only
498            #module_name, object_name = name.rsplit('.', 1)
499            items = name.split('.')
500            object_name = items.pop()
501            module_name = '.'.join(items)
502        else:
503            module_name = '__builtin__'
504            object_name = name
505        try:
506            __import__(module_name)
507        except ImportError, exc:
508            raise ConstructorError("while constructing a Python object", mark,
509                    "cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
510        module = sys.modules[module_name]
511        if not hasattr(module, object_name):
512            raise ConstructorError("while constructing a Python object", mark,
513                    "cannot find %r in the module %r" % (object_name.encode('utf-8'),
514                        module.__name__), mark)
515        return getattr(module, object_name)
516
517    def construct_python_name(self, suffix, node):
518        value = self.construct_scalar(node)
519        if value:
520            raise ConstructorError("while constructing a Python name", node.start_mark,
521                    "expected the empty value, but found %r" % value.encode('utf-8'),
522                    node.start_mark)
523        return self.find_python_name(suffix, node.start_mark)
524
525    def construct_python_module(self, suffix, node):
526        value = self.construct_scalar(node)
527        if value:
528            raise ConstructorError("while constructing a Python module", node.start_mark,
529                    "expected the empty value, but found %r" % value.encode('utf-8'),
530                    node.start_mark)
531        return self.find_python_module(suffix, node.start_mark)
532
533    class classobj: pass
534
535    def make_python_instance(self, suffix, node,
536            args=None, kwds=None, newobj=False):
537        if not args:
538            args = []
539        if not kwds:
540            kwds = {}
541        cls = self.find_python_name(suffix, node.start_mark)
542        if newobj and isinstance(cls, type(self.classobj))  \
543                and not args and not kwds:
544            instance = self.classobj()
545            instance.__class__ = cls
546            return instance
547        elif newobj and isinstance(cls, type):
548            return cls.__new__(cls, *args, **kwds)
549        else:
550            return cls(*args, **kwds)
551
552    def set_python_instance_state(self, instance, state):
553        if hasattr(instance, '__setstate__'):
554            instance.__setstate__(state)
555        else:
556            slotstate = {}
557            if isinstance(state, tuple) and len(state) == 2:
558                state, slotstate = state
559            if hasattr(instance, '__dict__'):
560                instance.__dict__.update(state)
561            elif state:
562                slotstate.update(state)
563            for key, value in slotstate.items():
564                setattr(object, key, value)
565
566    def construct_python_object(self, suffix, node):
567        # Format:
568        #   !!python/object:module.name { ... state ... }
569        instance = self.make_python_instance(suffix, node, newobj=True)
570        yield instance
571        deep = hasattr(instance, '__setstate__')
572        state = self.construct_mapping(node, deep=deep)
573        self.set_python_instance_state(instance, state)
574
575    def construct_python_object_apply(self, suffix, node, newobj=False):
576        # Format:
577        #   !!python/object/apply       # (or !!python/object/new)
578        #   args: [ ... arguments ... ]
579        #   kwds: { ... keywords ... }
580        #   state: ... state ...
581        #   listitems: [ ... listitems ... ]
582        #   dictitems: { ... dictitems ... }
583        # or short format:
584        #   !!python/object/apply [ ... arguments ... ]
585        # The difference between !!python/object/apply and !!python/object/new
586        # is how an object is created, check make_python_instance for details.
587        if isinstance(node, SequenceNode):
588            args = self.construct_sequence(node, deep=True)
589            kwds = {}
590            state = {}
591            listitems = []
592            dictitems = {}
593        else:
594            value = self.construct_mapping(node, deep=True)
595            args = value.get('args', [])
596            kwds = value.get('kwds', {})
597            state = value.get('state', {})
598            listitems = value.get('listitems', [])
599            dictitems = value.get('dictitems', {})
600        instance = self.make_python_instance(suffix, node, args, kwds, newobj)
601        if state:
602            self.set_python_instance_state(instance, state)
603        if listitems:
604            instance.extend(listitems)
605        if dictitems:
606            for key in dictitems:
607                instance[key] = dictitems[key]
608        return instance
609
610    def construct_python_object_new(self, suffix, node):
611        return self.construct_python_object_apply(suffix, node, newobj=True)
612
613Constructor.add_constructor(
614    u'tag:yaml.org,2002:python/none',
615    Constructor.construct_yaml_null)
616
617Constructor.add_constructor(
618    u'tag:yaml.org,2002:python/bool',
619    Constructor.construct_yaml_bool)
620
621Constructor.add_constructor(
622    u'tag:yaml.org,2002:python/str',
623    Constructor.construct_python_str)
624
625Constructor.add_constructor(
626    u'tag:yaml.org,2002:python/unicode',
627    Constructor.construct_python_unicode)
628
629Constructor.add_constructor(
630    u'tag:yaml.org,2002:python/int',
631    Constructor.construct_yaml_int)
632
633Constructor.add_constructor(
634    u'tag:yaml.org,2002:python/long',
635    Constructor.construct_python_long)
636
637Constructor.add_constructor(
638    u'tag:yaml.org,2002:python/float',
639    Constructor.construct_yaml_float)
640
641Constructor.add_constructor(
642    u'tag:yaml.org,2002:python/complex',
643    Constructor.construct_python_complex)
644
645Constructor.add_constructor(
646    u'tag:yaml.org,2002:python/list',
647    Constructor.construct_yaml_seq)
648
649Constructor.add_constructor(
650    u'tag:yaml.org,2002:python/tuple',
651    Constructor.construct_python_tuple)
652
653Constructor.add_constructor(
654    u'tag:yaml.org,2002:python/dict',
655    Constructor.construct_yaml_map)
656
657Constructor.add_multi_constructor(
658    u'tag:yaml.org,2002:python/name:',
659    Constructor.construct_python_name)
660
661Constructor.add_multi_constructor(
662    u'tag:yaml.org,2002:python/module:',
663    Constructor.construct_python_module)
664
665Constructor.add_multi_constructor(
666    u'tag:yaml.org,2002:python/object:',
667    Constructor.construct_python_object)
668
669Constructor.add_multi_constructor(
670    u'tag:yaml.org,2002:python/object/apply:',
671    Constructor.construct_python_object_apply)
672
673Constructor.add_multi_constructor(
674    u'tag:yaml.org,2002:python/object/new:',
675    Constructor.construct_python_object_new)
676
Note: See TracBrowser for help on using the repository browser.