source: pyyaml/trunk/lib3/yaml/constructor.py @ 328

Revision 328, 24.7 KB checked in by xi, 6 years ago (diff)

Added basic support for Python 3 (Thanks idadesub(at)users(dot)sourceforge(dot)net).

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