Completed
Push — master ( c86ec0...c73223 )
by Satoru
01:21
created

gen_schema()   F

Complexity

Conditions 10

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 10
c 1
b 0
f 1
dl 0
loc 44
rs 3.1304

How to fix   Complexity   

Complexity

Complex classes like gen_schema() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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_type` ('basic' == default or 'strict') to API
9
   :func:`gen_schema` to switch type of schema object generated
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 array_to_schema_node(arr, **options):
83
    """
84
    Generate a node represents JSON schema object with type annotation added
85
    for given object node.
86
87
    :param arr: Array of dict or MergeableDict objects
88
    :param options: Other keyword options such as:
89
90
        - ac_schema_type: Specify the type of schema to generate from 'basic'
91
          (basic and minimum schema) and 'strict' (more precise schema)
92
        - ac_schema_typemap: Type to JSON schema type mappings
93
94
    :return: Another MergeableDict instance represents JSON schema of items
95
    """
96
    return gen_schema(arr[0] if arr else "str", **options)
97
98
99
def object_to_schema_nodes_iter(obj, **options):
100
    """
101
    Generate a node represents JSON schema object with type annotation added
102
    for given object node.
103
104
    :param obj: Dict or MergeableDict object
105
    :param options: Other keyword options such as:
106
107
        - ac_schema_type: Specify the type of schema to generate from 'basic'
108
          (basic and minimum schema) and 'strict' (more precise schema)
109
        - ac_schema_typemap: Type to JSON schema type mappings
110
111
    :yield: Another MergeableDict instance represents JSON schema of object
112
    """
113
    for key, val in anyconfig.compat.iteritems(obj):
114
        yield (key, gen_schema(val, **options))
115
116
117
_BASIC_SCHEMA_TYPE = "basic"
118
_STRICT_SCHEMA_TYPE = "strict"
119
120
121
def gen_schema(node, **options):
122
    """
123
    Generate a node represents JSON schema object with type annotation added
124
    for given object node.
125
126
    :param node: Config data object (dict[-like] or namedtuple)
127
    :param options: Other keyword options such as:
128
129
        - ac_schema_type: Specify the type of schema to generate from 'basic'
130
          (basic and minimum schema) and 'strict' (more precise schema)
131
        - ac_schema_typemap: Type to JSON schema type mappings
132
133
    :return: A dict represents JSON schema of this node
134
    """
135
    typemap = options["ac_schema_typemap"] \
136
        if "ac_schema_typemap" in options else _SIMPLETYPE_MAP
137
    strict = options.get("ac_schema_type", False) == _STRICT_SCHEMA_TYPE
138
139
    ret = dict(type="null")
140
141
    if node is None:
142
        return ret
143
144
    _type = type(node)
145
146
    if _type in _SIMPLE_TYPES:
147
        ret = dict(type=typemap[_type])
148
149
    elif isinstance(node, dict):
150
        props = list(object_to_schema_nodes_iter(node, **options))
151
        ret = dict(type=typemap[dict], properties=dict(props))
152
        if strict:
153
            ret["required"] = sorted(t[0] for t in props)
154
155
    elif _type in (list, tuple) or hasattr(node, "__iter__"):
156
        ret = dict(type=typemap[list],
157
                   items=array_to_schema_node(node, **options))
158
        if strict:
159
            items = list(node)
160
            nitems = len(items)
161
            ret["minItems"] = nitems
162
            ret["uniqueItems"] = len(set(items)) == nitems
163
164
    return ret
165
166
# vim:sw=4:ts=4:et:
167