Completed
Push — master ( 98e1d4...d1821a )
by Satoru
01:27
created

container_representer()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 4
rs 10
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
    if "ac_safe" in kwargs:  # yaml.safe_load does not process Loader opts.
109
        kwargs = {}
110
    else:
111
        maybe_container = kwargs.get("ac_dict", None)
112
        loader = kwargs.get("Loader", Loader)
113
        dumper = kwargs.get("Dumper", Dumper)
114
        if maybe_container is not None and callable(maybe_container):
115
            _setup_loader_and_dumper(maybe_container, loader=loader,
116
                                     dumper=dumper)
117
            container = maybe_container
118
119
    return container(_yml_fnc("load", stream, **kwargs))
120
121
122
def _yml_dump(cnf, stream, **kwargs):
123
    """An wrapper of yaml.safe_dump and yaml.dump.
124
125
    :param cnf: Mapping object to dump
126
    :param stream: a file or file-like object to dump YAML data
127
    """
128
    if kwargs.get("ac_safe", False):
129
        cnf = anyconfig.dicts.convert_to(cnf)
130
    return _yml_fnc("dump", cnf, stream, **kwargs)
131
132
133
class Parser(anyconfig.backend.base.FromStreamLoader,
134
             anyconfig.backend.base.ToStreamDumper):
135
    """
136
    Parser for YAML files.
137
    """
138
    _type = "yaml"
139
    _extensions = ["yaml", "yml"]
140
    _load_opts = ["Loader", "ac_safe"]
141
    _dump_opts = ["stream", "ac_safe", "Dumper", "default_style",
142
                  "default_flow_style", "canonical", "indent", "width",
143
                  "allow_unicode", "line_break", "encoding", "explicit_start",
144
                  "explicit_end", "version", "tags"]
145
    # _ordered = True  # Not yet.
146
147
    load_from_stream = anyconfig.backend.base.to_method(_yml_load)
148
    dump_to_stream = anyconfig.backend.base.to_method(_yml_dump)
149
150
# vim:sw=4:ts=4:et:
151