1import numbers
2
3from pyrsistent import pmap
4import attr
5
6from jsonschema.compat import int_types, str_types
7from jsonschema.exceptions import UndefinedTypeCheck
8
9
10def is_array(checker, instance):
11    return isinstance(instance, list)
12
13
14def is_bool(checker, instance):
15    return isinstance(instance, bool)
16
17
18def is_integer(checker, instance):
19    # bool inherits from int, so ensure bools aren't reported as ints
20    if isinstance(instance, bool):
21        return False
22    return isinstance(instance, int_types)
23
24
25def is_null(checker, instance):
26    return instance is None
27
28
29def is_number(checker, instance):
30    # bool inherits from int, so ensure bools aren't reported as ints
31    if isinstance(instance, bool):
32        return False
33    return isinstance(instance, numbers.Number)
34
35
36def is_object(checker, instance):
37    return isinstance(instance, dict)
38
39
40def is_string(checker, instance):
41    return isinstance(instance, str_types)
42
43
44def is_any(checker, instance):
45    return True
46
47
48@attr.s(frozen=True)
49class TypeChecker(object):
50    """
51    A ``type`` property checker.
52
53    A `TypeChecker` performs type checking for an `IValidator`. Type
54    checks to perform are updated using `TypeChecker.redefine` or
55    `TypeChecker.redefine_many` and removed via `TypeChecker.remove`.
56    Each of these return a new `TypeChecker` object.
57
58    Arguments:
59
60        type_checkers (dict):
61
62            The initial mapping of types to their checking functions.
63    """
64    _type_checkers = attr.ib(default=pmap(), converter=pmap)
65
66    def is_type(self, instance, type):
67        """
68        Check if the instance is of the appropriate type.
69
70        Arguments:
71
72            instance (object):
73
74                The instance to check
75
76            type (str):
77
78                The name of the type that is expected.
79
80        Returns:
81
82            bool: Whether it conformed.
83
84
85        Raises:
86
87            `jsonschema.exceptions.UndefinedTypeCheck`:
88                if type is unknown to this object.
89        """
90        try:
91            fn = self._type_checkers[type]
92        except KeyError:
93            raise UndefinedTypeCheck(type)
94
95        return fn(self, instance)
96
97    def redefine(self, type, fn):
98        """
99        Produce a new checker with the given type redefined.
100
101        Arguments:
102
103            type (str):
104
105                The name of the type to check.
106
107            fn (collections.Callable):
108
109                A function taking exactly two parameters - the type
110                checker calling the function and the instance to check.
111                The function should return true if instance is of this
112                type and false otherwise.
113
114        Returns:
115
116            A new `TypeChecker` instance.
117        """
118        return self.redefine_many({type: fn})
119
120    def redefine_many(self, definitions=()):
121        """
122        Produce a new checker with the given types redefined.
123
124        Arguments:
125
126            definitions (dict):
127
128                A dictionary mapping types to their checking functions.
129
130        Returns:
131
132            A new `TypeChecker` instance.
133        """
134        return attr.evolve(
135            self, type_checkers=self._type_checkers.update(definitions),
136        )
137
138    def remove(self, *types):
139        """
140        Produce a new checker with the given types forgotten.
141
142        Arguments:
143
144            types (~collections.Iterable):
145
146                the names of the types to remove.
147
148        Returns:
149
150            A new `TypeChecker` instance
151
152        Raises:
153
154            `jsonschema.exceptions.UndefinedTypeCheck`:
155
156                if any given type is unknown to this object
157        """
158
159        checkers = self._type_checkers
160        for each in types:
161            try:
162                checkers = checkers.remove(each)
163            except KeyError:
164                raise UndefinedTypeCheck(each)
165        return attr.evolve(self, type_checkers=checkers)
166
167
168draft3_type_checker = TypeChecker(
169    {
170        u"any": is_any,
171        u"array": is_array,
172        u"boolean": is_bool,
173        u"integer": is_integer,
174        u"object": is_object,
175        u"null": is_null,
176        u"number": is_number,
177        u"string": is_string,
178    },
179)
180draft4_type_checker = draft3_type_checker.remove(u"any")
181draft6_type_checker = draft4_type_checker.redefine(
182    u"integer",
183    lambda checker, instance: (
184        is_integer(checker, instance) or
185        isinstance(instance, float) and instance.is_integer()
186    ),
187)
188draft7_type_checker = draft6_type_checker
189