Completed
Push — master ( c73223...1ab817 )
by Satoru
01:13
created

_process_options()   A

Complexity

Conditions 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
c 2
b 0
f 0
dl 0
loc 8
rs 9.4285
1
#
2
# Copyright (C) 2015, 2016 Satoru SATOH <ssato redhat.com>
3
# License: MIT
4
#
5
"""anyconfig.schema module.
6
7
.. versionchanged:: 0.6.99
8
   allow passing `ac_schema_strict` to API :func:`gen_schema` to generate more
9
   strict and precise JSON schema object
10
11
.. versionadded:: 0.0.11
12
   Added new API :func:`gen_schema` to generate schema object
13
14
.. versionadded:: 0.0.10
15
   Added new API :func:`validate` to validate config with JSON schema
16
"""
17
from __future__ import absolute_import
18
try:
19
    import jsonschema
20
except ImportError:
21
    pass
22
23
import anyconfig.compat
24
25
26
_SIMPLETYPE_MAP = {list: "array", tuple: "array",
27
                   bool: "boolean",
28
                   int: "integer", float: "number",
29
                   dict: "object",
30
                   str: "string"}
31
_SIMPLE_TYPES = (bool, int, float, str)
32
33
if not anyconfig.compat.IS_PYTHON_3:
34
    try:
35
        _SIMPLETYPE_MAP[unicode] = "string"
36
        _SIMPLE_TYPES = (bool, int, float, str, unicode)
37
    except NameError:
38
        pass
39
40
41
def validate(obj, schema, **options):
42
    """
43
    Validate target object with given schema object, loaded from JSON schema.
44
45
    See also: https://python-jsonschema.readthedocs.org/en/latest/validate/
46
47
    :parae obj: Target object (a dict or a dict-like object) to validate
48
    :param schema: Schema object (a dict or a dict-like object)
49
        instantiated from schema JSON file or schema JSON string
50
    :param options: Other keyword options such as:
51
52
        - format_checker: A format property checker object of which class is
53
          inherited from jsonschema.FormatChecker, it's default if None given.
54
55
        - safe: Exception (jsonschema.ValidationError or jsonschema.SchemaError
56
          or others) will be thrown during validation process due to any
57
          validation or related errors. However, these will be catched by
58
          default, and will be re-raised if `safe` is False.
59
60
    :return: (True if validation succeeded else False, error message)
61
    """
62
    format_checker = options.get("format_checker", None)
63
    try:
64
        if format_checker is None:
65
            format_checker = jsonschema.FormatChecker()  # :raises: NameError
66
        try:
67
            jsonschema.validate(obj, schema, format_checker=format_checker)
68
            return (True, '')
69
        except (jsonschema.ValidationError, jsonschema.SchemaError,
70
                Exception) as exc:
71
            if options.get("safe", True):
72
                return (False, str(exc))
73
            else:
74
                raise
75
76
    except NameError:
77
        return (True, "Validation module (jsonschema) is not available")
78
79
    return (True, '')
80
81
82
def _process_options(**options):
83
    """
84
    Helper function to process keyword arguments passed to gen_schema.
85
86
    :return: A tuple of (typemap :: dict, strict :: bool)
87
    """
88
    return (options.get("ac_schema_typemap", _SIMPLETYPE_MAP),
89
            bool(options.get("ac_schema_strict", False)))
90
91
92
def array_to_schema(arr, **options):
93
    """
94
    Generate a JSON schema object with type annotation added for given object.
95
96
    :param arr: Array of dict or MergeableDict objects
97
    :param options: Other keyword options such as:
98
99
        - ac_schema_strict: True if more strict (precise) schema is needed
100
        - ac_schema_typemap: Type to JSON schema type mappings
101
102
    :return: Another MergeableDict instance represents JSON schema of items
103
    """
104
    (typemap, strict) = _process_options(**options)
105
106
    arr = list(arr)
107
    scm = dict(type=typemap[list],
108
               items=gen_schema(arr[0] if arr else "str", **options))
109
    if strict:
110
        nitems = len(arr)
111
        scm["minItems"] = nitems
112
        scm["uniqueItems"] = len(set(arr)) == nitems
113
114
    return scm
115
116
117
def object_to_schema(obj, **options):
118
    """
119
    Generate a node represents JSON schema object with type annotation added
120
    for given object node.
121
122
    :param obj: Dict or MergeableDict object
123
    :param options: Other keyword options such as:
124
125
        - ac_schema_strict: True if more strict (precise) schema is needed
126
        - ac_schema_typemap: Type to JSON schema type mappings
127
128
    :yield: Another MergeableDict instance represents JSON schema of object
129
    """
130
    (typemap, strict) = _process_options(**options)
131
132
    props = dict((k, gen_schema(v, **options)) for k, v in obj.items())
133
    scm = dict(type=typemap[dict], properties=props)
134
    if strict:
135
        scm["required"] = sorted(props.keys())
136
137
    return scm
138
139
140
def gen_schema(node, **options):
141
    """
142
    Generate a node represents JSON schema object with type annotation added
143
    for given object node.
144
145
    :param node: Config data object (dict[-like] or namedtuple)
146
    :param options: Other keyword options such as:
147
148
        - ac_schema_strict: True if more strict (precise) schema is needed
149
        - ac_schema_typemap: Type to JSON schema type mappings
150
151
    :return: A dict represents JSON schema of this node
152
    """
153
    if node is None:
154
        return dict(type="null")
155
156
    _type = type(node)
157
158
    if _type in _SIMPLE_TYPES:
159
        typemap = options.get("ac_schema_typemap", _SIMPLETYPE_MAP)
160
        scm = dict(type=typemap[_type])
161
162
    elif isinstance(node, dict):
163
        scm = object_to_schema(node, **options)
164
165
    elif _type in (list, tuple) or hasattr(node, "__iter__"):
166
        scm = array_to_schema(node, **options)
167
168
    return scm
169
170
# vim:sw=4:ts=4:et:
171