Completed
Push — master ( 5316ba...5569c2 )
by Satoru
01:10
created

_yml_load()   B

Complexity

Conditions 5

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
cc 5
c 5
b 1
f 0
dl 0
loc 21
rs 8.439
1
#
2
# Copyright (C) 2011 - 2017 Satoru SATOH <ssato @ redhat.com>
3
# License: MIT
4
#
5
r"""YAML backend:
6
7
- Format to support: YAML, http://yaml.org
8
- Requirements: PyYAML (yaml), http://pyyaml.org
9
- Development Status :: 5 - Production/Stable
10
- Limitations: ac_ordered is not effective and just ignored.
11
- Special options:
12
13
  - All keyword options of yaml.safe_load, yaml.load, yaml.safe_dump and
14
    yaml.dump should work.
15
16
  - Use 'ac_safe' boolean keyword option if you prefer to call yaml.safe_load
17
    and yaml.safe_dump instead of yaml.load and yaml.dump
18
19
  - See also: http://pyyaml.org/wiki/PyYAMLDocumentation
20
21
Changelog:
22
23
.. versionchanged:: 0.3
24
25
   - Changed special keyword option 'ac_safe' from 'safe' to avoid
26
     possibility of option conflicts in the future.
27
"""
28
from __future__ import absolute_import
29
30
import yaml
31
try:
32
    from yaml import CSafeLoader as Loader, CSafeDumper as Dumper
33
except ImportError:
34
    from yaml import SafeLoader as Loader, SafeDumper as Dumper
35
36
import anyconfig.backend.base
37
import anyconfig.utils
38
39
40
def _setup_loader_and_dumper(container, loader=Loader, dumper=Dumper):
41
    """
42
    Force set container (dict, OrderedDict, ...) used to construct python
43
    object from yaml node internally.
44
45
    .. note::
46
       It cannot be used with ac_safe keyword option at the same time yet.
47
48
    :param container: Set container used internally
49
    """
50
    map_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
51
52
    def construct_mapping(loader, node, deep=False):
53
        """Constructor to construct python object from yaml mapping node.
54
55
        :seealso: :meth:`yaml.BaseConstructor.construct_mapping`
56
        """
57
        if not isinstance(node, yaml.MappingNode):
58
            msg = "expected a mapping node, but found %s" % node.id
59
            raise yaml.constructor.ConstructorError(None, None, msg,
60
                                                    node.start_mark)
61
        mapping = container()
62
        for key_node, value_node in node.value:
63
            key = loader.construct_object(key_node, deep=deep)
64
            try:
65
                hash(key)
66
            except TypeError as exc:
67
                eargs = ("while constructing a mapping",
68
                         node.start_mark,
69
                         "found unacceptable key (%s)" % exc,
70
                         key_node.start_mark)
71
                raise yaml.constructor.ConstructorError(*eargs)
72
            value = loader.construct_object(value_node, deep=deep)
73
            mapping[key] = value
74
75
        return mapping
76
77
    def container_representer(dumper, data):
78
        """Container representer.
79
        """
80
        return dumper.represent_mapping(map_tag, data.items())
81
82
    loader.add_constructor(map_tag, construct_mapping)
83
    dumper.add_representer(container, container_representer)
84
85
86
def _yml_fnc(fname, *args, **kwargs):
87
    """An wrapper of yaml.safe_load, yaml.load, yaml.safe_dump and yaml.dump.
88
89
    :param fname:
90
        "load" or "dump", not checked but it should be OK.
91
        see also :func:`_yml_load` and :func:`_yml_dump`
92
    :param args: [stream] for load or [cnf, stream] for dump
93
    :param kwargs: keyword args may contain "ac_safe" to load/dump safely
94
    """
95
    key = "ac_safe"
96
    fnc = getattr(yaml, kwargs.get(key, False) and r"safe_" + fname or fname)
97
    kwargs = anyconfig.utils.filter_options([k for k in kwargs.keys()
98
                                             if k != key], kwargs)
99
    return fnc(*args, **kwargs)
100
101
102
def _yml_load(stream, container, **kwargs):
103
    """An wrapper of yaml.safe_load and yaml.load.
104
105
    :param stream: a file or file-like object to load YAML content
106
    :param container: callble to make a container object
107
108
    :return: Mapping object
109
    """
110
    if "ac_safe" in kwargs:  # yaml.safe_load does not process Loader opts.
111
        kwargs = {}
112
    else:
113
        maybe_container = kwargs.get("ac_dict", None)
114
        loader = kwargs.get("Loader", Loader)
115
        dumper = kwargs.get("Dumper", Dumper)
116
        if maybe_container is not None and callable(maybe_container):
117
            _setup_loader_and_dumper(maybe_container, loader=loader,
118
                                     dumper=dumper)
119
            container = maybe_container
120
121
    ret = _yml_fnc("load", stream, **kwargs)
122
    return container() if ret is None else container(ret)
123
124
125
def _yml_dump(cnf, stream, **kwargs):
126
    """An wrapper of yaml.safe_dump and yaml.dump.
127
128
    :param cnf: Mapping object to dump
129
    :param stream: a file or file-like object to dump YAML data
130
    """
131
    if kwargs.get("ac_safe", False):
132
        cnf = anyconfig.dicts.convert_to(cnf)
133
    return _yml_fnc("dump", cnf, stream, **kwargs)
134
135
136
class Parser(anyconfig.backend.base.FromStreamLoader,
137
             anyconfig.backend.base.ToStreamDumper):
138
    """
139
    Parser for YAML files.
140
    """
141
    _type = "yaml"
142
    _extensions = ["yaml", "yml"]
143
    _load_opts = ["Loader", "ac_safe"]
144
    _dump_opts = ["stream", "ac_safe", "Dumper", "default_style",
145
                  "default_flow_style", "canonical", "indent", "width",
146
                  "allow_unicode", "line_break", "encoding", "explicit_start",
147
                  "explicit_end", "version", "tags"]
148
    # _ordered = True  # Not yet.
149
150
    load_from_stream = anyconfig.backend.base.to_method(_yml_load)
151
    dump_to_stream = anyconfig.backend.base.to_method(_yml_dump)
152
153
# vim:sw=4:ts=4:et:
154