Completed
Push — master ( bff2ab...5dc771 )
by Satoru
01:07
created

anyconfig.gen_schema()   C

Complexity

Conditions 7

Size

Total Lines 32

Duplication

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