1"""
2Creation and extension of validators, with implementations for existing drafts.
3"""
4from __future__ import division
5
6from warnings import warn
7import contextlib
8import json
9import numbers
10
11from six import add_metaclass
12
13from jsonschema import (
14    _legacy_validators,
15    _types,
16    _utils,
17    _validators,
18    exceptions,
19)
20from jsonschema.compat import (
21    Sequence,
22    int_types,
23    iteritems,
24    lru_cache,
25    str_types,
26    unquote,
27    urldefrag,
28    urljoin,
29    urlopen,
30    urlsplit,
31)
32
33# Sigh. https://gitlab.com/pycqa/flake8/issues/280
34#       https://github.com/pyga/ebb-lint/issues/7
35# Imported for backwards compatibility.
36from jsonschema.exceptions import ErrorTree
37ErrorTree
38
39
40class _DontDoThat(Exception):
41    """
42    Raised when a Validators with non-default type checker is misused.
43
44    Asking one for DEFAULT_TYPES doesn't make sense, since type checkers
45    exist for the unrepresentable cases where DEFAULT_TYPES can't
46    represent the type relationship.
47    """
48
49    def __str__(self):
50        return "DEFAULT_TYPES cannot be used on Validators using TypeCheckers"
51
52
53validators = {}
54meta_schemas = _utils.URIDict()
55
56
57def _generate_legacy_type_checks(types=()):
58    """
59    Generate newer-style type checks out of JSON-type-name-to-type mappings.
60
61    Arguments:
62
63        types (dict):
64
65            A mapping of type names to their Python types
66
67    Returns:
68
69        A dictionary of definitions to pass to `TypeChecker`
70    """
71    types = dict(types)
72
73    def gen_type_check(pytypes):
74        pytypes = _utils.flatten(pytypes)
75
76        def type_check(checker, instance):
77            if isinstance(instance, bool):
78                if bool not in pytypes:
79                    return False
80            return isinstance(instance, pytypes)
81
82        return type_check
83
84    definitions = {}
85    for typename, pytypes in iteritems(types):
86        definitions[typename] = gen_type_check(pytypes)
87
88    return definitions
89
90
91_DEPRECATED_DEFAULT_TYPES = {
92    u"array": list,
93    u"boolean": bool,
94    u"integer": int_types,
95    u"null": type(None),
96    u"number": numbers.Number,
97    u"object": dict,
98    u"string": str_types,
99}
100_TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES = _types.TypeChecker(
101    type_checkers=_generate_legacy_type_checks(_DEPRECATED_DEFAULT_TYPES),
102)
103
104
105def validates(version):
106    """
107    Register the decorated validator for a ``version`` of the specification.
108
109    Registered validators and their meta schemas will be considered when
110    parsing ``$schema`` properties' URIs.
111
112    Arguments:
113
114        version (str):
115
116            An identifier to use as the version's name
117
118    Returns:
119
120        collections.Callable:
121
122            a class decorator to decorate the validator with the version
123    """
124
125    def _validates(cls):
126        validators[version] = cls
127        meta_schema_id = cls.ID_OF(cls.META_SCHEMA)
128        if meta_schema_id:
129            meta_schemas[meta_schema_id] = cls
130        return cls
131    return _validates
132
133
134def _DEFAULT_TYPES(self):
135    if self._CREATED_WITH_DEFAULT_TYPES is None:
136        raise _DontDoThat()
137
138    warn(
139        (
140            "The DEFAULT_TYPES attribute is deprecated. "
141            "See the type checker attached to this validator instead."
142        ),
143        DeprecationWarning,
144        stacklevel=2,
145    )
146    return self._DEFAULT_TYPES
147
148
149class _DefaultTypesDeprecatingMetaClass(type):
150    DEFAULT_TYPES = property(_DEFAULT_TYPES)
151
152
153def _id_of(schema):
154    if schema is True or schema is False:
155        return u""
156    return schema.get(u"$id", u"")
157
158
159def create(
160    meta_schema,
161    validators=(),
162    version=None,
163    default_types=None,
164    type_checker=None,
165    id_of=_id_of,
166):
167    """
168    Create a new validator class.
169
170    Arguments:
171
172        meta_schema (collections.Mapping):
173
174            the meta schema for the new validator class
175
176        validators (collections.Mapping):
177
178            a mapping from names to callables, where each callable will
179            validate the schema property with the given name.
180
181            Each callable should take 4 arguments:
182
183                1. a validator instance,
184                2. the value of the property being validated within the
185                   instance
186                3. the instance
187                4. the schema
188
189        version (str):
190
191            an identifier for the version that this validator class will
192            validate. If provided, the returned validator class will
193            have its ``__name__`` set to include the version, and also
194            will have `jsonschema.validators.validates` automatically
195            called for the given version.
196
197        type_checker (jsonschema.TypeChecker):
198
199            a type checker, used when applying the :validator:`type` validator.
200
201            If unprovided, a `jsonschema.TypeChecker` will be created
202            with a set of default types typical of JSON Schema drafts.
203
204        default_types (collections.Mapping):
205
206            .. deprecated:: 3.0.0
207
208                Please use the type_checker argument instead.
209
210            If set, it provides mappings of JSON types to Python types
211            that will be converted to functions and redefined in this
212            object's `jsonschema.TypeChecker`.
213
214        id_of (collections.Callable):
215
216            A function that given a schema, returns its ID.
217
218    Returns:
219
220        a new `jsonschema.IValidator` class
221    """
222
223    if default_types is not None:
224        if type_checker is not None:
225            raise TypeError(
226                "Do not specify default_types when providing a type checker.",
227            )
228        _created_with_default_types = True
229        warn(
230            (
231                "The default_types argument is deprecated. "
232                "Use the type_checker argument instead."
233            ),
234            DeprecationWarning,
235            stacklevel=2,
236        )
237        type_checker = _types.TypeChecker(
238            type_checkers=_generate_legacy_type_checks(default_types),
239        )
240    else:
241        default_types = _DEPRECATED_DEFAULT_TYPES
242        if type_checker is None:
243            _created_with_default_types = False
244            type_checker = _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES
245        elif type_checker is _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES:
246            _created_with_default_types = False
247        else:
248            _created_with_default_types = None
249
250    @add_metaclass(_DefaultTypesDeprecatingMetaClass)
251    class Validator(object):
252
253        VALIDATORS = dict(validators)
254        META_SCHEMA = dict(meta_schema)
255        TYPE_CHECKER = type_checker
256        ID_OF = staticmethod(id_of)
257
258        DEFAULT_TYPES = property(_DEFAULT_TYPES)
259        _DEFAULT_TYPES = dict(default_types)
260        _CREATED_WITH_DEFAULT_TYPES = _created_with_default_types
261
262        def __init__(
263            self,
264            schema,
265            types=(),
266            resolver=None,
267            format_checker=None,
268        ):
269            if types:
270                warn(
271                    (
272                        "The types argument is deprecated. Provide "
273                        "a type_checker to jsonschema.validators.extend "
274                        "instead."
275                    ),
276                    DeprecationWarning,
277                    stacklevel=2,
278                )
279
280                self.TYPE_CHECKER = self.TYPE_CHECKER.redefine_many(
281                    _generate_legacy_type_checks(types),
282                )
283
284            if resolver is None:
285                resolver = RefResolver.from_schema(schema, id_of=id_of)
286
287            self.resolver = resolver
288            self.format_checker = format_checker
289            self.schema = schema
290
291        @classmethod
292        def check_schema(cls, schema):
293            for error in cls(cls.META_SCHEMA).iter_errors(schema):
294                raise exceptions.SchemaError.create_from(error)
295
296        def iter_errors(self, instance, _schema=None):
297            if _schema is None:
298                _schema = self.schema
299
300            if _schema is True:
301                return
302            elif _schema is False:
303                yield exceptions.ValidationError(
304                    "False schema does not allow %r" % (instance,),
305                    validator=None,
306                    validator_value=None,
307                    instance=instance,
308                    schema=_schema,
309                )
310                return
311
312            scope = id_of(_schema)
313            if scope:
314                self.resolver.push_scope(scope)
315            try:
316                ref = _schema.get(u"$ref")
317                if ref is not None:
318                    validators = [(u"$ref", ref)]
319                else:
320                    validators = iteritems(_schema)
321
322                for k, v in validators:
323                    validator = self.VALIDATORS.get(k)
324                    if validator is None:
325                        continue
326
327                    errors = validator(self, v, instance, _schema) or ()
328                    for error in errors:
329                        # set details if not already set by the called fn
330                        error._set(
331                            validator=k,
332                            validator_value=v,
333                            instance=instance,
334                            schema=_schema,
335                        )
336                        if k != u"$ref":
337                            error.schema_path.appendleft(k)
338                        yield error
339            finally:
340                if scope:
341                    self.resolver.pop_scope()
342
343        def descend(self, instance, schema, path=None, schema_path=None):
344            for error in self.iter_errors(instance, schema):
345                if path is not None:
346                    error.path.appendleft(path)
347                if schema_path is not None:
348                    error.schema_path.appendleft(schema_path)
349                yield error
350
351        def validate(self, *args, **kwargs):
352            for error in self.iter_errors(*args, **kwargs):
353                raise error
354
355        def is_type(self, instance, type):
356            try:
357                return self.TYPE_CHECKER.is_type(instance, type)
358            except exceptions.UndefinedTypeCheck:
359                raise exceptions.UnknownType(type, instance, self.schema)
360
361        def is_valid(self, instance, _schema=None):
362            error = next(self.iter_errors(instance, _schema), None)
363            return error is None
364
365    if version is not None:
366        Validator = validates(version)(Validator)
367        Validator.__name__ = version.title().replace(" ", "") + "Validator"
368
369    return Validator
370
371
372def extend(validator, validators=(), version=None, type_checker=None):
373    """
374    Create a new validator class by extending an existing one.
375
376    Arguments:
377
378        validator (jsonschema.IValidator):
379
380            an existing validator class
381
382        validators (collections.Mapping):
383
384            a mapping of new validator callables to extend with, whose
385            structure is as in `create`.
386
387            .. note::
388
389                Any validator callables with the same name as an
390                existing one will (silently) replace the old validator
391                callable entirely, effectively overriding any validation
392                done in the "parent" validator class.
393
394                If you wish to instead extend the behavior of a parent's
395                validator callable, delegate and call it directly in
396                the new validator function by retrieving it using
397                ``OldValidator.VALIDATORS["validator_name"]``.
398
399        version (str):
400
401            a version for the new validator class
402
403        type_checker (jsonschema.TypeChecker):
404
405            a type checker, used when applying the :validator:`type` validator.
406
407            If unprovided, the type checker of the extended
408            `jsonschema.IValidator` will be carried along.`
409
410    Returns:
411
412        a new `jsonschema.IValidator` class extending the one provided
413
414    .. note:: Meta Schemas
415
416        The new validator class will have its parent's meta schema.
417
418        If you wish to change or extend the meta schema in the new
419        validator class, modify ``META_SCHEMA`` directly on the returned
420        class. Note that no implicit copying is done, so a copy should
421        likely be made before modifying it, in order to not affect the
422        old validator.
423    """
424
425    all_validators = dict(validator.VALIDATORS)
426    all_validators.update(validators)
427
428    if type_checker is None:
429        type_checker = validator.TYPE_CHECKER
430    elif validator._CREATED_WITH_DEFAULT_TYPES:
431        raise TypeError(
432            "Cannot extend a validator created with default_types "
433            "with a type_checker. Update the validator to use a "
434            "type_checker when created."
435        )
436    return create(
437        meta_schema=validator.META_SCHEMA,
438        validators=all_validators,
439        version=version,
440        type_checker=type_checker,
441        id_of=validator.ID_OF,
442    )
443
444
445Draft3Validator = create(
446    meta_schema=_utils.load_schema("draft3"),
447    validators={
448        u"$ref": _validators.ref,
449        u"additionalItems": _validators.additionalItems,
450        u"additionalProperties": _validators.additionalProperties,
451        u"dependencies": _legacy_validators.dependencies_draft3,
452        u"disallow": _legacy_validators.disallow_draft3,
453        u"divisibleBy": _validators.multipleOf,
454        u"enum": _validators.enum,
455        u"extends": _legacy_validators.extends_draft3,
456        u"format": _validators.format,
457        u"items": _legacy_validators.items_draft3_draft4,
458        u"maxItems": _validators.maxItems,
459        u"maxLength": _validators.maxLength,
460        u"maximum": _legacy_validators.maximum_draft3_draft4,
461        u"minItems": _validators.minItems,
462        u"minLength": _validators.minLength,
463        u"minimum": _legacy_validators.minimum_draft3_draft4,
464        u"pattern": _validators.pattern,
465        u"patternProperties": _validators.patternProperties,
466        u"properties": _legacy_validators.properties_draft3,
467        u"type": _legacy_validators.type_draft3,
468        u"uniqueItems": _validators.uniqueItems,
469    },
470    type_checker=_types.draft3_type_checker,
471    version="draft3",
472    id_of=lambda schema: schema.get(u"id", ""),
473)
474
475Draft4Validator = create(
476    meta_schema=_utils.load_schema("draft4"),
477    validators={
478        u"$ref": _validators.ref,
479        u"additionalItems": _validators.additionalItems,
480        u"additionalProperties": _validators.additionalProperties,
481        u"allOf": _validators.allOf,
482        u"anyOf": _validators.anyOf,
483        u"dependencies": _validators.dependencies,
484        u"enum": _validators.enum,
485        u"format": _validators.format,
486        u"items": _legacy_validators.items_draft3_draft4,
487        u"maxItems": _validators.maxItems,
488        u"maxLength": _validators.maxLength,
489        u"maxProperties": _validators.maxProperties,
490        u"maximum": _legacy_validators.maximum_draft3_draft4,
491        u"minItems": _validators.minItems,
492        u"minLength": _validators.minLength,
493        u"minProperties": _validators.minProperties,
494        u"minimum": _legacy_validators.minimum_draft3_draft4,
495        u"multipleOf": _validators.multipleOf,
496        u"not": _validators.not_,
497        u"oneOf": _validators.oneOf,
498        u"pattern": _validators.pattern,
499        u"patternProperties": _validators.patternProperties,
500        u"properties": _validators.properties,
501        u"required": _validators.required,
502        u"type": _validators.type,
503        u"uniqueItems": _validators.uniqueItems,
504    },
505    type_checker=_types.draft4_type_checker,
506    version="draft4",
507    id_of=lambda schema: schema.get(u"id", ""),
508)
509
510Draft6Validator = create(
511    meta_schema=_utils.load_schema("draft6"),
512    validators={
513        u"$ref": _validators.ref,
514        u"additionalItems": _validators.additionalItems,
515        u"additionalProperties": _validators.additionalProperties,
516        u"allOf": _validators.allOf,
517        u"anyOf": _validators.anyOf,
518        u"const": _validators.const,
519        u"contains": _validators.contains,
520        u"dependencies": _validators.dependencies,
521        u"enum": _validators.enum,
522        u"exclusiveMaximum": _validators.exclusiveMaximum,
523        u"exclusiveMinimum": _validators.exclusiveMinimum,
524        u"format": _validators.format,
525        u"items": _validators.items,
526        u"maxItems": _validators.maxItems,
527        u"maxLength": _validators.maxLength,
528        u"maxProperties": _validators.maxProperties,
529        u"maximum": _validators.maximum,
530        u"minItems": _validators.minItems,
531        u"minLength": _validators.minLength,
532        u"minProperties": _validators.minProperties,
533        u"minimum": _validators.minimum,
534        u"multipleOf": _validators.multipleOf,
535        u"not": _validators.not_,
536        u"oneOf": _validators.oneOf,
537        u"pattern": _validators.pattern,
538        u"patternProperties": _validators.patternProperties,
539        u"properties": _validators.properties,
540        u"propertyNames": _validators.propertyNames,
541        u"required": _validators.required,
542        u"type": _validators.type,
543        u"uniqueItems": _validators.uniqueItems,
544    },
545    type_checker=_types.draft6_type_checker,
546    version="draft6",
547)
548
549Draft7Validator = create(
550    meta_schema=_utils.load_schema("draft7"),
551    validators={
552        u"$ref": _validators.ref,
553        u"additionalItems": _validators.additionalItems,
554        u"additionalProperties": _validators.additionalProperties,
555        u"allOf": _validators.allOf,
556        u"anyOf": _validators.anyOf,
557        u"const": _validators.const,
558        u"contains": _validators.contains,
559        u"dependencies": _validators.dependencies,
560        u"enum": _validators.enum,
561        u"exclusiveMaximum": _validators.exclusiveMaximum,
562        u"exclusiveMinimum": _validators.exclusiveMinimum,
563        u"format": _validators.format,
564        u"if": _validators.if_,
565        u"items": _validators.items,
566        u"maxItems": _validators.maxItems,
567        u"maxLength": _validators.maxLength,
568        u"maxProperties": _validators.maxProperties,
569        u"maximum": _validators.maximum,
570        u"minItems": _validators.minItems,
571        u"minLength": _validators.minLength,
572        u"minProperties": _validators.minProperties,
573        u"minimum": _validators.minimum,
574        u"multipleOf": _validators.multipleOf,
575        u"oneOf": _validators.oneOf,
576        u"not": _validators.not_,
577        u"pattern": _validators.pattern,
578        u"patternProperties": _validators.patternProperties,
579        u"properties": _validators.properties,
580        u"propertyNames": _validators.propertyNames,
581        u"required": _validators.required,
582        u"type": _validators.type,
583        u"uniqueItems": _validators.uniqueItems,
584    },
585    type_checker=_types.draft7_type_checker,
586    version="draft7",
587)
588
589_LATEST_VERSION = Draft7Validator
590
591
592class RefResolver(object):
593    """
594    Resolve JSON References.
595
596    Arguments:
597
598        base_uri (str):
599
600            The URI of the referring document
601
602        referrer:
603
604            The actual referring document
605
606        store (dict):
607
608            A mapping from URIs to documents to cache
609
610        cache_remote (bool):
611
612            Whether remote refs should be cached after first resolution
613
614        handlers (dict):
615
616            A mapping from URI schemes to functions that should be used
617            to retrieve them
618
619        urljoin_cache (:func:`functools.lru_cache`):
620
621            A cache that will be used for caching the results of joining
622            the resolution scope to subscopes.
623
624        remote_cache (:func:`functools.lru_cache`):
625
626            A cache that will be used for caching the results of
627            resolved remote URLs.
628
629    Attributes:
630
631        cache_remote (bool):
632
633            Whether remote refs should be cached after first resolution
634    """
635
636    def __init__(
637        self,
638        base_uri,
639        referrer,
640        store=(),
641        cache_remote=True,
642        handlers=(),
643        urljoin_cache=None,
644        remote_cache=None,
645    ):
646        if urljoin_cache is None:
647            urljoin_cache = lru_cache(1024)(urljoin)
648        if remote_cache is None:
649            remote_cache = lru_cache(1024)(self.resolve_from_url)
650
651        self.referrer = referrer
652        self.cache_remote = cache_remote
653        self.handlers = dict(handlers)
654
655        self._scopes_stack = [base_uri]
656        self.store = _utils.URIDict(
657            (id, validator.META_SCHEMA)
658            for id, validator in iteritems(meta_schemas)
659        )
660        self.store.update(store)
661        self.store[base_uri] = referrer
662
663        self._urljoin_cache = urljoin_cache
664        self._remote_cache = remote_cache
665
666    @classmethod
667    def from_schema(cls, schema, id_of=_id_of, *args, **kwargs):
668        """
669        Construct a resolver from a JSON schema object.
670
671        Arguments:
672
673            schema:
674
675                the referring schema
676
677        Returns:
678
679            `RefResolver`
680        """
681
682        return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs)
683
684    def push_scope(self, scope):
685        """
686        Enter a given sub-scope.
687
688        Treats further dereferences as being performed underneath the
689        given scope.
690        """
691        self._scopes_stack.append(
692            self._urljoin_cache(self.resolution_scope, scope),
693        )
694
695    def pop_scope(self):
696        """
697        Exit the most recent entered scope.
698
699        Treats further dereferences as being performed underneath the
700        original scope.
701
702        Don't call this method more times than `push_scope` has been
703        called.
704        """
705        try:
706            self._scopes_stack.pop()
707        except IndexError:
708            raise exceptions.RefResolutionError(
709                "Failed to pop the scope from an empty stack. "
710                "`pop_scope()` should only be called once for every "
711                "`push_scope()`"
712            )
713
714    @property
715    def resolution_scope(self):
716        """
717        Retrieve the current resolution scope.
718        """
719        return self._scopes_stack[-1]
720
721    @property
722    def base_uri(self):
723        """
724        Retrieve the current base URI, not including any fragment.
725        """
726        uri, _ = urldefrag(self.resolution_scope)
727        return uri
728
729    @contextlib.contextmanager
730    def in_scope(self, scope):
731        """
732        Temporarily enter the given scope for the duration of the context.
733        """
734        self.push_scope(scope)
735        try:
736            yield
737        finally:
738            self.pop_scope()
739
740    @contextlib.contextmanager
741    def resolving(self, ref):
742        """
743        Resolve the given ``ref`` and enter its resolution scope.
744
745        Exits the scope on exit of this context manager.
746
747        Arguments:
748
749            ref (str):
750
751                The reference to resolve
752        """
753
754        url, resolved = self.resolve(ref)
755        self.push_scope(url)
756        try:
757            yield resolved
758        finally:
759            self.pop_scope()
760
761    def resolve(self, ref):
762        """
763        Resolve the given reference.
764        """
765        url = self._urljoin_cache(self.resolution_scope, ref)
766        return url, self._remote_cache(url)
767
768    def resolve_from_url(self, url):
769        """
770        Resolve the given remote URL.
771        """
772        url, fragment = urldefrag(url)
773        try:
774            document = self.store[url]
775        except KeyError:
776            try:
777                document = self.resolve_remote(url)
778            except Exception as exc:
779                raise exceptions.RefResolutionError(exc)
780
781        return self.resolve_fragment(document, fragment)
782
783    def resolve_fragment(self, document, fragment):
784        """
785        Resolve a ``fragment`` within the referenced ``document``.
786
787        Arguments:
788
789            document:
790
791                The referent document
792
793            fragment (str):
794
795                a URI fragment to resolve within it
796        """
797
798        fragment = fragment.lstrip(u"/")
799        parts = unquote(fragment).split(u"/") if fragment else []
800
801        for part in parts:
802            part = part.replace(u"~1", u"/").replace(u"~0", u"~")
803
804            if isinstance(document, Sequence):
805                # Array indexes should be turned into integers
806                try:
807                    part = int(part)
808                except ValueError:
809                    pass
810            try:
811                document = document[part]
812            except (TypeError, LookupError):
813                raise exceptions.RefResolutionError(
814                    "Unresolvable JSON pointer: %r" % fragment
815                )
816
817        return document
818
819    def resolve_remote(self, uri):
820        """
821        Resolve a remote ``uri``.
822
823        If called directly, does not check the store first, but after
824        retrieving the document at the specified URI it will be saved in
825        the store if :attr:`cache_remote` is True.
826
827        .. note::
828
829            If the requests_ library is present, ``jsonschema`` will use it to
830            request the remote ``uri``, so that the correct encoding is
831            detected and used.
832
833            If it isn't, or if the scheme of the ``uri`` is not ``http`` or
834            ``https``, UTF-8 is assumed.
835
836        Arguments:
837
838            uri (str):
839
840                The URI to resolve
841
842        Returns:
843
844            The retrieved document
845
846        .. _requests: https://pypi.org/project/requests/
847        """
848        try:
849            import requests
850        except ImportError:
851            requests = None
852
853        scheme = urlsplit(uri).scheme
854
855        if scheme in self.handlers:
856            result = self.handlers[scheme](uri)
857        elif scheme in [u"http", u"https"] and requests:
858            # Requests has support for detecting the correct encoding of
859            # json over http
860            result = requests.get(uri).json()
861        else:
862            # Otherwise, pass off to urllib and assume utf-8
863            with urlopen(uri) as url:
864                result = json.loads(url.read().decode("utf-8"))
865
866        if self.cache_remote:
867            self.store[uri] = result
868        return result
869
870
871def validate(instance, schema, cls=None, *args, **kwargs):
872    """
873    Validate an instance under the given schema.
874
875        >>> validate([2, 3, 4], {"maxItems": 2})
876        Traceback (most recent call last):
877            ...
878        ValidationError: [2, 3, 4] is too long
879
880    :func:`validate` will first verify that the provided schema is
881    itself valid, since not doing so can lead to less obvious error
882    messages and fail in less obvious or consistent ways.
883
884    If you know you have a valid schema already, especially if you
885    intend to validate multiple instances with the same schema, you
886    likely would prefer using the `IValidator.validate` method directly
887    on a specific validator (e.g. ``Draft7Validator.validate``).
888
889
890    Arguments:
891
892        instance:
893
894            The instance to validate
895
896        schema:
897
898            The schema to validate with
899
900        cls (IValidator):
901
902            The class that will be used to validate the instance.
903
904    If the ``cls`` argument is not provided, two things will happen
905    in accordance with the specification. First, if the schema has a
906    :validator:`$schema` property containing a known meta-schema [#]_
907    then the proper validator will be used. The specification recommends
908    that all schemas contain :validator:`$schema` properties for this
909    reason. If no :validator:`$schema` property is found, the default
910    validator class is the latest released draft.
911
912    Any other provided positional and keyword arguments will be passed
913    on when instantiating the ``cls``.
914
915    Raises:
916
917        `jsonschema.exceptions.ValidationError` if the instance
918            is invalid
919
920        `jsonschema.exceptions.SchemaError` if the schema itself
921            is invalid
922
923    .. rubric:: Footnotes
924    .. [#] known by a validator registered with
925        `jsonschema.validators.validates`
926    """
927    if cls is None:
928        cls = validator_for(schema)
929
930    cls.check_schema(schema)
931    validator = cls(schema, *args, **kwargs)
932    error = exceptions.best_match(validator.iter_errors(instance))
933    if error is not None:
934        raise error
935
936
937def validator_for(schema, default=_LATEST_VERSION):
938    """
939    Retrieve the validator class appropriate for validating the given schema.
940
941    Uses the :validator:`$schema` property that should be present in the
942    given schema to look up the appropriate validator class.
943
944    Arguments:
945
946        schema (collections.Mapping or bool):
947
948            the schema to look at
949
950        default:
951
952            the default to return if the appropriate validator class
953            cannot be determined.
954
955            If unprovided, the default is to return the latest supported
956            draft.
957    """
958    if schema is True or schema is False or u"$schema" not in schema:
959        return default
960    if schema[u"$schema"] not in meta_schemas:
961        warn(
962            (
963                "The metaschema specified by $schema was not found. "
964                "Using the latest draft to validate, but this will raise "
965                "an error in the future."
966            ),
967            DeprecationWarning,
968            stacklevel=2,
969        )
970    return meta_schemas.get(schema[u"$schema"], _LATEST_VERSION)
971