Current File : //proc/self/root/usr/lib/python3/dist-packages/jsonschema/tests/test_validators.py
from __future__ import annotations

from collections import deque, namedtuple
from contextlib import contextmanager
from decimal import Decimal
from io import BytesIO
from unittest import TestCase, mock
from urllib.request import pathname2url
import json
import os
import sys
import tempfile
import unittest
import warnings

import attr

from jsonschema import (
    FormatChecker,
    TypeChecker,
    exceptions,
    protocols,
    validators,
)
from jsonschema.tests._helpers import bug


def fail(validator, errors, instance, schema):
    for each in errors:
        each.setdefault("message", "You told me to fail!")
        yield exceptions.ValidationError(**each)


class TestCreateAndExtend(TestCase):
    def setUp(self):
        self.addCleanup(
            self.assertEqual,
            validators._META_SCHEMAS,
            dict(validators._META_SCHEMAS),
        )

        self.meta_schema = {"$id": "some://meta/schema"}
        self.validators = {"fail": fail}
        self.type_checker = TypeChecker()
        self.Validator = validators.create(
            meta_schema=self.meta_schema,
            validators=self.validators,
            type_checker=self.type_checker,
        )

    def test_attrs(self):
        self.assertEqual(
            (
                self.Validator.VALIDATORS,
                self.Validator.META_SCHEMA,
                self.Validator.TYPE_CHECKER,
            ), (
                self.validators,
                self.meta_schema,
                self.type_checker,
            ),
        )

    def test_init(self):
        schema = {"fail": []}
        self.assertEqual(self.Validator(schema).schema, schema)

    def test_iter_errors_successful(self):
        schema = {"fail": []}
        validator = self.Validator(schema)

        errors = list(validator.iter_errors("hello"))
        self.assertEqual(errors, [])

    def test_iter_errors_one_error(self):
        schema = {"fail": [{"message": "Whoops!"}]}
        validator = self.Validator(schema)

        expected_error = exceptions.ValidationError(
            "Whoops!",
            instance="goodbye",
            schema=schema,
            validator="fail",
            validator_value=[{"message": "Whoops!"}],
            schema_path=deque(["fail"]),
        )

        errors = list(validator.iter_errors("goodbye"))
        self.assertEqual(len(errors), 1)
        self.assertEqual(errors[0]._contents(), expected_error._contents())

    def test_iter_errors_multiple_errors(self):
        schema = {
            "fail": [
                {"message": "First"},
                {"message": "Second!", "validator": "asdf"},
                {"message": "Third"},
            ],
        }
        validator = self.Validator(schema)

        errors = list(validator.iter_errors("goodbye"))
        self.assertEqual(len(errors), 3)

    def test_if_a_version_is_provided_it_is_registered(self):
        Validator = validators.create(
            meta_schema={"$id": "something"},
            version="my version",
        )
        self.addCleanup(validators._META_SCHEMAS.pop, "something")
        self.assertEqual(Validator.__name__, "MyVersionValidator")
        self.assertEqual(Validator.__qualname__, "MyVersionValidator")

    def test_repr(self):
        Validator = validators.create(
            meta_schema={"$id": "something"},
            version="my version",
        )
        self.addCleanup(validators._META_SCHEMAS.pop, "something")
        self.assertEqual(
            repr(Validator({})),
            "MyVersionValidator(schema={}, format_checker=None)",
        )

    def test_long_repr(self):
        Validator = validators.create(
            meta_schema={"$id": "something"},
            version="my version",
        )
        self.addCleanup(validators._META_SCHEMAS.pop, "something")
        self.assertEqual(
            repr(Validator({"a": list(range(1000))})), (
                "MyVersionValidator(schema={'a': [0, 1, 2, 3, 4, 5, ...]}, "
                "format_checker=None)"
            ),
        )

    def test_repr_no_version(self):
        Validator = validators.create(meta_schema={})
        self.assertEqual(
            repr(Validator({})),
            "Validator(schema={}, format_checker=None)",
        )

    def test_dashes_are_stripped_from_validator_names(self):
        Validator = validators.create(
            meta_schema={"$id": "something"},
            version="foo-bar",
        )
        self.addCleanup(validators._META_SCHEMAS.pop, "something")
        self.assertEqual(Validator.__qualname__, "FooBarValidator")

    def test_if_a_version_is_not_provided_it_is_not_registered(self):
        original = dict(validators._META_SCHEMAS)
        validators.create(meta_schema={"id": "id"})
        self.assertEqual(validators._META_SCHEMAS, original)

    def test_validates_registers_meta_schema_id(self):
        meta_schema_key = "meta schema id"
        my_meta_schema = {"id": meta_schema_key}

        validators.create(
            meta_schema=my_meta_schema,
            version="my version",
            id_of=lambda s: s.get("id", ""),
        )
        self.addCleanup(validators._META_SCHEMAS.pop, meta_schema_key)

        self.assertIn(meta_schema_key, validators._META_SCHEMAS)

    def test_validates_registers_meta_schema_draft6_id(self):
        meta_schema_key = "meta schema $id"
        my_meta_schema = {"$id": meta_schema_key}

        validators.create(
            meta_schema=my_meta_schema,
            version="my version",
        )
        self.addCleanup(validators._META_SCHEMAS.pop, meta_schema_key)

        self.assertIn(meta_schema_key, validators._META_SCHEMAS)

    def test_create_default_types(self):
        Validator = validators.create(meta_schema={}, validators=())
        self.assertTrue(
            all(
                Validator({}).is_type(instance=instance, type=type)
                for type, instance in [
                    ("array", []),
                    ("boolean", True),
                    ("integer", 12),
                    ("null", None),
                    ("number", 12.0),
                    ("object", {}),
                    ("string", "foo"),
                ]
            ),
        )

    def test_extend(self):
        original = dict(self.Validator.VALIDATORS)
        new = object()

        Extended = validators.extend(
            self.Validator,
            validators={"new": new},
        )
        self.assertEqual(
            (
                Extended.VALIDATORS,
                Extended.META_SCHEMA,
                Extended.TYPE_CHECKER,
                self.Validator.VALIDATORS,
            ), (
                dict(original, new=new),
                self.Validator.META_SCHEMA,
                self.Validator.TYPE_CHECKER,
                original,
            ),
        )

    def test_extend_idof(self):
        """
        Extending a validator preserves its notion of schema IDs.
        """
        def id_of(schema):
            return schema.get("__test__", self.Validator.ID_OF(schema))
        correct_id = "the://correct/id/"
        meta_schema = {
            "$id": "the://wrong/id/",
            "__test__": correct_id,
        }
        Original = validators.create(
            meta_schema=meta_schema,
            validators=self.validators,
            type_checker=self.type_checker,
            id_of=id_of,
        )
        self.assertEqual(Original.ID_OF(Original.META_SCHEMA), correct_id)

        Derived = validators.extend(Original)
        self.assertEqual(Derived.ID_OF(Derived.META_SCHEMA), correct_id)


class TestValidationErrorMessages(TestCase):
    def message_for(self, instance, schema, *args, **kwargs):
        cls = kwargs.pop("cls", validators._LATEST_VERSION)
        cls.check_schema(schema)
        validator = cls(schema, *args, **kwargs)
        errors = list(validator.iter_errors(instance))
        self.assertTrue(errors, msg=f"No errors were raised for {instance!r}")
        self.assertEqual(
            len(errors),
            1,
            msg=f"Expected exactly one error, found {errors!r}",
        )
        return errors[0].message

    def test_single_type_failure(self):
        message = self.message_for(instance=1, schema={"type": "string"})
        self.assertEqual(message, "1 is not of type 'string'")

    def test_single_type_list_failure(self):
        message = self.message_for(instance=1, schema={"type": ["string"]})
        self.assertEqual(message, "1 is not of type 'string'")

    def test_multiple_type_failure(self):
        types = "string", "object"
        message = self.message_for(instance=1, schema={"type": list(types)})
        self.assertEqual(message, "1 is not of type 'string', 'object'")

    def test_object_with_named_type_failure(self):
        schema = {"type": [{"name": "Foo", "minimum": 3}]}
        message = self.message_for(
            instance=1,
            schema=schema,
            cls=validators.Draft3Validator,
        )
        self.assertEqual(message, "1 is not of type 'Foo'")

    def test_minimum(self):
        message = self.message_for(instance=1, schema={"minimum": 2})
        self.assertEqual(message, "1 is less than the minimum of 2")

    def test_maximum(self):
        message = self.message_for(instance=1, schema={"maximum": 0})
        self.assertEqual(message, "1 is greater than the maximum of 0")

    def test_dependencies_single_element(self):
        depend, on = "bar", "foo"
        schema = {"dependencies": {depend: on}}
        message = self.message_for(
            instance={"bar": 2},
            schema=schema,
            cls=validators.Draft3Validator,
        )
        self.assertEqual(message, "'foo' is a dependency of 'bar'")

    def test_object_without_title_type_failure_draft3(self):
        type = {"type": [{"minimum": 3}]}
        message = self.message_for(
            instance=1,
            schema={"type": [type]},
            cls=validators.Draft3Validator,
        )
        self.assertEqual(
            message,
            "1 is not of type {'type': [{'minimum': 3}]}",
        )

    def test_dependencies_list_draft3(self):
        depend, on = "bar", "foo"
        schema = {"dependencies": {depend: [on]}}
        message = self.message_for(
            instance={"bar": 2},
            schema=schema,
            cls=validators.Draft3Validator,
        )
        self.assertEqual(message, "'foo' is a dependency of 'bar'")

    def test_dependencies_list_draft7(self):
        depend, on = "bar", "foo"
        schema = {"dependencies": {depend: [on]}}
        message = self.message_for(
            instance={"bar": 2},
            schema=schema,
            cls=validators.Draft7Validator,
        )
        self.assertEqual(message, "'foo' is a dependency of 'bar'")

    def test_additionalItems_single_failure(self):
        message = self.message_for(
            instance=[2],
            schema={"items": [], "additionalItems": False},
            cls=validators.Draft3Validator,
        )
        self.assertIn("(2 was unexpected)", message)

    def test_additionalItems_multiple_failures(self):
        message = self.message_for(
            instance=[1, 2, 3],
            schema={"items": [], "additionalItems": False},
            cls=validators.Draft3Validator,
        )
        self.assertIn("(1, 2, 3 were unexpected)", message)

    def test_additionalProperties_single_failure(self):
        additional = "foo"
        schema = {"additionalProperties": False}
        message = self.message_for(instance={additional: 2}, schema=schema)
        self.assertIn("('foo' was unexpected)", message)

    def test_additionalProperties_multiple_failures(self):
        schema = {"additionalProperties": False}
        message = self.message_for(
            instance=dict.fromkeys(["foo", "bar"]),
            schema=schema,
        )

        self.assertIn(repr("foo"), message)
        self.assertIn(repr("bar"), message)
        self.assertIn("were unexpected)", message)

    def test_const(self):
        schema = {"const": 12}
        message = self.message_for(
            instance={"foo": "bar"},
            schema=schema,
        )
        self.assertIn("12 was expected", message)

    def test_contains_draft_6(self):
        schema = {"contains": {"const": 12}}
        message = self.message_for(
            instance=[2, {}, []],
            schema=schema,
            cls=validators.Draft6Validator,
        )
        self.assertEqual(
            message,
            "None of [2, {}, []] are valid under the given schema",
        )

    def test_invalid_format_default_message(self):
        checker = FormatChecker(formats=())
        checker.checks("thing")(lambda value: False)

        schema = {"format": "thing"}
        message = self.message_for(
            instance="bla",
            schema=schema,
            format_checker=checker,
        )

        self.assertIn(repr("bla"), message)
        self.assertIn(repr("thing"), message)
        self.assertIn("is not a", message)

    def test_additionalProperties_false_patternProperties(self):
        schema = {"type": "object",
                  "additionalProperties": False,
                  "patternProperties": {
                      "^abc$": {"type": "string"},
                      "^def$": {"type": "string"},
                  }}
        message = self.message_for(
            instance={"zebra": 123},
            schema=schema,
            cls=validators.Draft4Validator,
        )
        self.assertEqual(
            message,
            "{} does not match any of the regexes: {}, {}".format(
                repr("zebra"), repr("^abc$"), repr("^def$"),
            ),
        )
        message = self.message_for(
            instance={"zebra": 123, "fish": 456},
            schema=schema,
            cls=validators.Draft4Validator,
        )
        self.assertEqual(
            message,
            "{}, {} do not match any of the regexes: {}, {}".format(
                repr("fish"), repr("zebra"), repr("^abc$"), repr("^def$"),
            ),
        )

    def test_False_schema(self):
        message = self.message_for(
            instance="something",
            schema=False,
        )
        self.assertEqual(message, "False schema does not allow 'something'")

    def test_multipleOf(self):
        message = self.message_for(
            instance=3,
            schema={"multipleOf": 2},
        )
        self.assertEqual(message, "3 is not a multiple of 2")

    def test_minItems(self):
        message = self.message_for(instance=[], schema={"minItems": 2})
        self.assertEqual(message, "[] is too short")

    def test_maxItems(self):
        message = self.message_for(instance=[1, 2, 3], schema={"maxItems": 2})
        self.assertEqual(message, "[1, 2, 3] is too long")

    def test_prefixItems_with_items(self):
        message = self.message_for(
            instance=[1, 2, "foo", 5],
            schema={"items": False, "prefixItems": [{}, {}]},
        )
        self.assertEqual(message, "Expected at most 2 items, but found 4")

    def test_minLength(self):
        message = self.message_for(
            instance="",
            schema={"minLength": 2},
        )
        self.assertEqual(message, "'' is too short")

    def test_maxLength(self):
        message = self.message_for(
            instance="abc",
            schema={"maxLength": 2},
        )
        self.assertEqual(message, "'abc' is too long")

    def test_pattern(self):
        message = self.message_for(
            instance="bbb",
            schema={"pattern": "^a*$"},
        )
        self.assertEqual(message, "'bbb' does not match '^a*$'")

    def test_does_not_contain(self):
        message = self.message_for(
            instance=[],
            schema={"contains": {"type": "string"}},
        )
        self.assertEqual(
            message,
            "[] does not contain items matching the given schema",
        )

    def test_contains_too_few(self):
        message = self.message_for(
            instance=["foo", 1],
            schema={"contains": {"type": "string"}, "minContains": 2},
        )
        self.assertEqual(
            message,
            "Too few items match the given schema "
            "(expected at least 2 but only 1 matched)",
        )

    def test_contains_too_few_both_constrained(self):
        message = self.message_for(
            instance=["foo", 1],
            schema={
                "contains": {"type": "string"},
                "minContains": 2,
                "maxContains": 4,
            },
        )
        self.assertEqual(
            message,
            "Too few items match the given schema (expected at least 2 but "
            "only 1 matched)",
        )

    def test_contains_too_many(self):
        message = self.message_for(
            instance=["foo", "bar", "baz"],
            schema={"contains": {"type": "string"}, "maxContains": 2},
        )
        self.assertEqual(
            message,
            "Too many items match the given schema (expected at most 2)",
        )

    def test_contains_too_many_both_constrained(self):
        message = self.message_for(
            instance=["foo"] * 5,
            schema={
                "contains": {"type": "string"},
                "minContains": 2,
                "maxContains": 4,
            },
        )
        self.assertEqual(
            message,
            "Too many items match the given schema (expected at most 4)",
        )

    def test_exclusiveMinimum(self):
        message = self.message_for(
            instance=3,
            schema={"exclusiveMinimum": 5},
        )
        self.assertEqual(
            message,
            "3 is less than or equal to the minimum of 5",
        )

    def test_exclusiveMaximum(self):
        message = self.message_for(instance=3, schema={"exclusiveMaximum": 2})
        self.assertEqual(
            message,
            "3 is greater than or equal to the maximum of 2",
        )

    def test_required(self):
        message = self.message_for(instance={}, schema={"required": ["foo"]})
        self.assertEqual(message, "'foo' is a required property")

    def test_dependentRequired(self):
        message = self.message_for(
            instance={"foo": {}},
            schema={"dependentRequired": {"foo": ["bar"]}},
        )
        self.assertEqual(message, "'bar' is a dependency of 'foo'")

    def test_minProperties(self):
        message = self.message_for(instance={}, schema={"minProperties": 2})
        self.assertEqual(message, "{} does not have enough properties")

    def test_maxProperties(self):
        message = self.message_for(
            instance={"a": {}, "b": {}, "c": {}},
            schema={"maxProperties": 2},
        )
        self.assertEqual(
            message,
            "{'a': {}, 'b': {}, 'c': {}} has too many properties",
        )

    def test_oneOf_matches_none(self):
        message = self.message_for(instance={}, schema={"oneOf": [False]})
        self.assertEqual(
            message,
            "{} is not valid under any of the given schemas",
        )

    def test_oneOf_matches_too_many(self):
        message = self.message_for(instance={}, schema={"oneOf": [True, True]})
        self.assertEqual(message, "{} is valid under each of True, True")

    def test_unevaluated_items(self):
        schema = {"type": "array", "unevaluatedItems": False}
        message = self.message_for(instance=["foo", "bar"], schema=schema)
        self.assertIn(
            message,
            "Unevaluated items are not allowed ('bar', 'foo' were unexpected)",
        )

    def test_unevaluated_items_on_invalid_type(self):
        schema = {"type": "array", "unevaluatedItems": False}
        message = self.message_for(instance="foo", schema=schema)
        self.assertEqual(message, "'foo' is not of type 'array'")

    def test_unevaluated_properties(self):
        schema = {"type": "object", "unevaluatedProperties": False}
        message = self.message_for(
            instance={
                "foo": "foo",
                "bar": "bar",
            },
            schema=schema,
        )
        self.assertEqual(
            message,
            "Unevaluated properties are not allowed "
            "('bar', 'foo' were unexpected)",
        )

    def test_unevaluated_properties_on_invalid_type(self):
        schema = {"type": "object", "unevaluatedProperties": False}
        message = self.message_for(instance="foo", schema=schema)
        self.assertEqual(message, "'foo' is not of type 'object'")


class TestValidationErrorDetails(TestCase):
    # TODO: These really need unit tests for each individual keyword, rather
    #       than just these higher level tests.
    def test_anyOf(self):
        instance = 5
        schema = {
            "anyOf": [
                {"minimum": 20},
                {"type": "string"},
            ],
        }

        validator = validators.Draft4Validator(schema)
        errors = list(validator.iter_errors(instance))
        self.assertEqual(len(errors), 1)
        e = errors[0]

        self.assertEqual(e.validator, "anyOf")
        self.assertEqual(e.validator_value, schema["anyOf"])
        self.assertEqual(e.instance, instance)
        self.assertEqual(e.schema, schema)
        self.assertIsNone(e.parent)

        self.assertEqual(e.path, deque([]))
        self.assertEqual(e.relative_path, deque([]))
        self.assertEqual(e.absolute_path, deque([]))
        self.assertEqual(e.json_path, "$")

        self.assertEqual(e.schema_path, deque(["anyOf"]))
        self.assertEqual(e.relative_schema_path, deque(["anyOf"]))
        self.assertEqual(e.absolute_schema_path, deque(["anyOf"]))

        self.assertEqual(len(e.context), 2)

        e1, e2 = sorted_errors(e.context)

        self.assertEqual(e1.validator, "minimum")
        self.assertEqual(e1.validator_value, schema["anyOf"][0]["minimum"])
        self.assertEqual(e1.instance, instance)
        self.assertEqual(e1.schema, schema["anyOf"][0])
        self.assertIs(e1.parent, e)

        self.assertEqual(e1.path, deque([]))
        self.assertEqual(e1.absolute_path, deque([]))
        self.assertEqual(e1.relative_path, deque([]))
        self.assertEqual(e1.json_path, "$")

        self.assertEqual(e1.schema_path, deque([0, "minimum"]))
        self.assertEqual(e1.relative_schema_path, deque([0, "minimum"]))
        self.assertEqual(
            e1.absolute_schema_path, deque(["anyOf", 0, "minimum"]),
        )

        self.assertFalse(e1.context)

        self.assertEqual(e2.validator, "type")
        self.assertEqual(e2.validator_value, schema["anyOf"][1]["type"])
        self.assertEqual(e2.instance, instance)
        self.assertEqual(e2.schema, schema["anyOf"][1])
        self.assertIs(e2.parent, e)

        self.assertEqual(e2.path, deque([]))
        self.assertEqual(e2.relative_path, deque([]))
        self.assertEqual(e2.absolute_path, deque([]))
        self.assertEqual(e2.json_path, "$")

        self.assertEqual(e2.schema_path, deque([1, "type"]))
        self.assertEqual(e2.relative_schema_path, deque([1, "type"]))
        self.assertEqual(e2.absolute_schema_path, deque(["anyOf", 1, "type"]))

        self.assertEqual(len(e2.context), 0)

    def test_type(self):
        instance = {"foo": 1}
        schema = {
            "type": [
                {"type": "integer"},
                {
                    "type": "object",
                    "properties": {"foo": {"enum": [2]}},
                },
            ],
        }

        validator = validators.Draft3Validator(schema)
        errors = list(validator.iter_errors(instance))
        self.assertEqual(len(errors), 1)
        e = errors[0]

        self.assertEqual(e.validator, "type")
        self.assertEqual(e.validator_value, schema["type"])
        self.assertEqual(e.instance, instance)
        self.assertEqual(e.schema, schema)
        self.assertIsNone(e.parent)

        self.assertEqual(e.path, deque([]))
        self.assertEqual(e.relative_path, deque([]))
        self.assertEqual(e.absolute_path, deque([]))
        self.assertEqual(e.json_path, "$")

        self.assertEqual(e.schema_path, deque(["type"]))
        self.assertEqual(e.relative_schema_path, deque(["type"]))
        self.assertEqual(e.absolute_schema_path, deque(["type"]))

        self.assertEqual(len(e.context), 2)

        e1, e2 = sorted_errors(e.context)

        self.assertEqual(e1.validator, "type")
        self.assertEqual(e1.validator_value, schema["type"][0]["type"])
        self.assertEqual(e1.instance, instance)
        self.assertEqual(e1.schema, schema["type"][0])
        self.assertIs(e1.parent, e)

        self.assertEqual(e1.path, deque([]))
        self.assertEqual(e1.relative_path, deque([]))
        self.assertEqual(e1.absolute_path, deque([]))
        self.assertEqual(e1.json_path, "$")

        self.assertEqual(e1.schema_path, deque([0, "type"]))
        self.assertEqual(e1.relative_schema_path, deque([0, "type"]))
        self.assertEqual(e1.absolute_schema_path, deque(["type", 0, "type"]))

        self.assertFalse(e1.context)

        self.assertEqual(e2.validator, "enum")
        self.assertEqual(e2.validator_value, [2])
        self.assertEqual(e2.instance, 1)
        self.assertEqual(e2.schema, {"enum": [2]})
        self.assertIs(e2.parent, e)

        self.assertEqual(e2.path, deque(["foo"]))
        self.assertEqual(e2.relative_path, deque(["foo"]))
        self.assertEqual(e2.absolute_path, deque(["foo"]))
        self.assertEqual(e2.json_path, "$.foo")

        self.assertEqual(
            e2.schema_path, deque([1, "properties", "foo", "enum"]),
        )
        self.assertEqual(
            e2.relative_schema_path, deque([1, "properties", "foo", "enum"]),
        )
        self.assertEqual(
            e2.absolute_schema_path,
            deque(["type", 1, "properties", "foo", "enum"]),
        )

        self.assertFalse(e2.context)

    def test_single_nesting(self):
        instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"}
        schema = {
            "properties": {
                "foo": {"type": "string"},
                "bar": {"minItems": 2},
                "baz": {"maximum": 10, "enum": [2, 4, 6, 8]},
            },
        }

        validator = validators.Draft3Validator(schema)
        errors = validator.iter_errors(instance)
        e1, e2, e3, e4 = sorted_errors(errors)

        self.assertEqual(e1.path, deque(["bar"]))
        self.assertEqual(e2.path, deque(["baz"]))
        self.assertEqual(e3.path, deque(["baz"]))
        self.assertEqual(e4.path, deque(["foo"]))

        self.assertEqual(e1.relative_path, deque(["bar"]))
        self.assertEqual(e2.relative_path, deque(["baz"]))
        self.assertEqual(e3.relative_path, deque(["baz"]))
        self.assertEqual(e4.relative_path, deque(["foo"]))

        self.assertEqual(e1.absolute_path, deque(["bar"]))
        self.assertEqual(e2.absolute_path, deque(["baz"]))
        self.assertEqual(e3.absolute_path, deque(["baz"]))
        self.assertEqual(e4.absolute_path, deque(["foo"]))

        self.assertEqual(e1.json_path, "$.bar")
        self.assertEqual(e2.json_path, "$.baz")
        self.assertEqual(e3.json_path, "$.baz")
        self.assertEqual(e4.json_path, "$.foo")

        self.assertEqual(e1.validator, "minItems")
        self.assertEqual(e2.validator, "enum")
        self.assertEqual(e3.validator, "maximum")
        self.assertEqual(e4.validator, "type")

    def test_multiple_nesting(self):
        instance = [1, {"foo": 2, "bar": {"baz": [1]}}, "quux"]
        schema = {
            "type": "string",
            "items": {
                "type": ["string", "object"],
                "properties": {
                    "foo": {"enum": [1, 3]},
                    "bar": {
                        "type": "array",
                        "properties": {
                            "bar": {"required": True},
                            "baz": {"minItems": 2},
                        },
                    },
                },
            },
        }

        validator = validators.Draft3Validator(schema)
        errors = validator.iter_errors(instance)
        e1, e2, e3, e4, e5, e6 = sorted_errors(errors)

        self.assertEqual(e1.path, deque([]))
        self.assertEqual(e2.path, deque([0]))
        self.assertEqual(e3.path, deque([1, "bar"]))
        self.assertEqual(e4.path, deque([1, "bar", "bar"]))
        self.assertEqual(e5.path, deque([1, "bar", "baz"]))
        self.assertEqual(e6.path, deque([1, "foo"]))

        self.assertEqual(e1.json_path, "$")
        self.assertEqual(e2.json_path, "$[0]")
        self.assertEqual(e3.json_path, "$[1].bar")
        self.assertEqual(e4.json_path, "$[1].bar.bar")
        self.assertEqual(e5.json_path, "$[1].bar.baz")
        self.assertEqual(e6.json_path, "$[1].foo")

        self.assertEqual(e1.schema_path, deque(["type"]))
        self.assertEqual(e2.schema_path, deque(["items", "type"]))
        self.assertEqual(
            list(e3.schema_path), ["items", "properties", "bar", "type"],
        )
        self.assertEqual(
            list(e4.schema_path),
            ["items", "properties", "bar", "properties", "bar", "required"],
        )
        self.assertEqual(
            list(e5.schema_path),
            ["items", "properties", "bar", "properties", "baz", "minItems"],
        )
        self.assertEqual(
            list(e6.schema_path), ["items", "properties", "foo", "enum"],
        )

        self.assertEqual(e1.validator, "type")
        self.assertEqual(e2.validator, "type")
        self.assertEqual(e3.validator, "type")
        self.assertEqual(e4.validator, "required")
        self.assertEqual(e5.validator, "minItems")
        self.assertEqual(e6.validator, "enum")

    def test_recursive(self):
        schema = {
            "definitions": {
                "node": {
                    "anyOf": [{
                        "type": "object",
                        "required": ["name", "children"],
                        "properties": {
                            "name": {
                                "type": "string",
                            },
                            "children": {
                                "type": "object",
                                "patternProperties": {
                                    "^.*$": {
                                        "$ref": "#/definitions/node",
                                    },
                                },
                            },
                        },
                    }],
                },
            },
            "type": "object",
            "required": ["root"],
            "properties": {"root": {"$ref": "#/definitions/node"}},
        }

        instance = {
            "root": {
                "name": "root",
                "children": {
                    "a": {
                        "name": "a",
                        "children": {
                            "ab": {
                                "name": "ab",
                                # missing "children"
                            },
                        },
                    },
                },
            },
        }
        validator = validators.Draft4Validator(schema)

        e, = validator.iter_errors(instance)
        self.assertEqual(e.absolute_path, deque(["root"]))
        self.assertEqual(
            e.absolute_schema_path, deque(["properties", "root", "anyOf"]),
        )
        self.assertEqual(e.json_path, "$.root")

        e1, = e.context
        self.assertEqual(e1.absolute_path, deque(["root", "children", "a"]))
        self.assertEqual(
            e1.absolute_schema_path, deque(
                [
                    "properties",
                    "root",
                    "anyOf",
                    0,
                    "properties",
                    "children",
                    "patternProperties",
                    "^.*$",
                    "anyOf",
                ],
            ),
        )
        self.assertEqual(e1.json_path, "$.root.children.a")

        e2, = e1.context
        self.assertEqual(
            e2.absolute_path, deque(
                ["root", "children", "a", "children", "ab"],
            ),
        )
        self.assertEqual(
            e2.absolute_schema_path, deque(
                [
                    "properties",
                    "root",
                    "anyOf",
                    0,
                    "properties",
                    "children",
                    "patternProperties",
                    "^.*$",
                    "anyOf",
                    0,
                    "properties",
                    "children",
                    "patternProperties",
                    "^.*$",
                    "anyOf",
                ],
            ),
        )
        self.assertEqual(e2.json_path, "$.root.children.a.children.ab")

    def test_additionalProperties(self):
        instance = {"bar": "bar", "foo": 2}
        schema = {"additionalProperties": {"type": "integer", "minimum": 5}}

        validator = validators.Draft3Validator(schema)
        errors = validator.iter_errors(instance)
        e1, e2 = sorted_errors(errors)

        self.assertEqual(e1.path, deque(["bar"]))
        self.assertEqual(e2.path, deque(["foo"]))

        self.assertEqual(e1.json_path, "$.bar")
        self.assertEqual(e2.json_path, "$.foo")

        self.assertEqual(e1.validator, "type")
        self.assertEqual(e2.validator, "minimum")

    def test_patternProperties(self):
        instance = {"bar": 1, "foo": 2}
        schema = {
            "patternProperties": {
                "bar": {"type": "string"},
                "foo": {"minimum": 5},
            },
        }

        validator = validators.Draft3Validator(schema)
        errors = validator.iter_errors(instance)
        e1, e2 = sorted_errors(errors)

        self.assertEqual(e1.path, deque(["bar"]))
        self.assertEqual(e2.path, deque(["foo"]))

        self.assertEqual(e1.json_path, "$.bar")
        self.assertEqual(e2.json_path, "$.foo")

        self.assertEqual(e1.validator, "type")
        self.assertEqual(e2.validator, "minimum")

    def test_additionalItems(self):
        instance = ["foo", 1]
        schema = {
            "items": [],
            "additionalItems": {"type": "integer", "minimum": 5},
        }

        validator = validators.Draft3Validator(schema)
        errors = validator.iter_errors(instance)
        e1, e2 = sorted_errors(errors)

        self.assertEqual(e1.path, deque([0]))
        self.assertEqual(e2.path, deque([1]))

        self.assertEqual(e1.json_path, "$[0]")
        self.assertEqual(e2.json_path, "$[1]")

        self.assertEqual(e1.validator, "type")
        self.assertEqual(e2.validator, "minimum")

    def test_additionalItems_with_items(self):
        instance = ["foo", "bar", 1]
        schema = {
            "items": [{}],
            "additionalItems": {"type": "integer", "minimum": 5},
        }

        validator = validators.Draft3Validator(schema)
        errors = validator.iter_errors(instance)
        e1, e2 = sorted_errors(errors)

        self.assertEqual(e1.path, deque([1]))
        self.assertEqual(e2.path, deque([2]))

        self.assertEqual(e1.json_path, "$[1]")
        self.assertEqual(e2.json_path, "$[2]")

        self.assertEqual(e1.validator, "type")
        self.assertEqual(e2.validator, "minimum")

    def test_propertyNames(self):
        instance = {"foo": 12}
        schema = {"propertyNames": {"not": {"const": "foo"}}}

        validator = validators.Draft7Validator(schema)
        error, = validator.iter_errors(instance)

        self.assertEqual(error.validator, "not")
        self.assertEqual(
            error.message,
            "'foo' should not be valid under {'const': 'foo'}",
        )
        self.assertEqual(error.path, deque([]))
        self.assertEqual(error.json_path, "$")
        self.assertEqual(error.schema_path, deque(["propertyNames", "not"]))

    def test_if_then(self):
        schema = {
            "if": {"const": 12},
            "then": {"const": 13},
        }

        validator = validators.Draft7Validator(schema)
        error, = validator.iter_errors(12)

        self.assertEqual(error.validator, "const")
        self.assertEqual(error.message, "13 was expected")
        self.assertEqual(error.path, deque([]))
        self.assertEqual(error.json_path, "$")
        self.assertEqual(error.schema_path, deque(["then", "const"]))

    def test_if_else(self):
        schema = {
            "if": {"const": 12},
            "else": {"const": 13},
        }

        validator = validators.Draft7Validator(schema)
        error, = validator.iter_errors(15)

        self.assertEqual(error.validator, "const")
        self.assertEqual(error.message, "13 was expected")
        self.assertEqual(error.path, deque([]))
        self.assertEqual(error.json_path, "$")
        self.assertEqual(error.schema_path, deque(["else", "const"]))

    def test_boolean_schema_False(self):
        validator = validators.Draft7Validator(False)
        error, = validator.iter_errors(12)

        self.assertEqual(
            (
                error.message,
                error.validator,
                error.validator_value,
                error.instance,
                error.schema,
                error.schema_path,
                error.json_path,
            ),
            (
                "False schema does not allow 12",
                None,
                None,
                12,
                False,
                deque([]),
                "$",
            ),
        )

    def test_ref(self):
        ref, schema = "someRef", {"additionalProperties": {"type": "integer"}}
        validator = validators.Draft7Validator(
            {"$ref": ref},
            resolver=validators.RefResolver("", {}, store={ref: schema}),
        )
        error, = validator.iter_errors({"foo": "notAnInteger"})

        self.assertEqual(
            (
                error.message,
                error.validator,
                error.validator_value,
                error.instance,
                error.absolute_path,
                error.schema,
                error.schema_path,
                error.json_path,
            ),
            (
                "'notAnInteger' is not of type 'integer'",
                "type",
                "integer",
                "notAnInteger",
                deque(["foo"]),
                {"type": "integer"},
                deque(["additionalProperties", "type"]),
                "$.foo",
            ),
        )

    def test_prefixItems(self):
        schema = {"prefixItems": [{"type": "string"}, {}, {}, {"maximum": 3}]}
        validator = validators.Draft202012Validator(schema)
        type_error, min_error = validator.iter_errors([1, 2, "foo", 5])
        self.assertEqual(
            (
                type_error.message,
                type_error.validator,
                type_error.validator_value,
                type_error.instance,
                type_error.absolute_path,
                type_error.schema,
                type_error.schema_path,
                type_error.json_path,
            ),
            (
                "1 is not of type 'string'",
                "type",
                "string",
                1,
                deque([0]),
                {"type": "string"},
                deque(["prefixItems", 0, "type"]),
                "$[0]",
            ),
        )
        self.assertEqual(
            (
                min_error.message,
                min_error.validator,
                min_error.validator_value,
                min_error.instance,
                min_error.absolute_path,
                min_error.schema,
                min_error.schema_path,
                min_error.json_path,
            ),
            (
                "5 is greater than the maximum of 3",
                "maximum",
                3,
                5,
                deque([3]),
                {"maximum": 3},
                deque(["prefixItems", 3, "maximum"]),
                "$[3]",
            ),
        )

    def test_prefixItems_with_items(self):
        schema = {
            "items": {"type": "string"},
            "prefixItems": [{}],
        }
        validator = validators.Draft202012Validator(schema)
        e1, e2 = validator.iter_errors(["foo", 2, "bar", 4, "baz"])
        self.assertEqual(
            (
                e1.message,
                e1.validator,
                e1.validator_value,
                e1.instance,
                e1.absolute_path,
                e1.schema,
                e1.schema_path,
                e1.json_path,
            ),
            (
                "2 is not of type 'string'",
                "type",
                "string",
                2,
                deque([1]),
                {"type": "string"},
                deque(["items", "type"]),
                "$[1]",
            ),
        )
        self.assertEqual(
            (
                e2.message,
                e2.validator,
                e2.validator_value,
                e2.instance,
                e2.absolute_path,
                e2.schema,
                e2.schema_path,
                e2.json_path,
            ),
            (
                "4 is not of type 'string'",
                "type",
                "string",
                4,
                deque([3]),
                {"type": "string"},
                deque(["items", "type"]),
                "$[3]",
            ),
        )

    def test_contains_too_many(self):
        """
        `contains` + `maxContains` produces only one error, even if there are
        many more incorrectly matching elements.
        """
        schema = {"contains": {"type": "string"}, "maxContains": 2}
        validator = validators.Draft202012Validator(schema)
        error, = validator.iter_errors(["foo", 2, "bar", 4, "baz", "quux"])
        self.assertEqual(
            (
                error.message,
                error.validator,
                error.validator_value,
                error.instance,
                error.absolute_path,
                error.schema,
                error.schema_path,
                error.json_path,
            ),
            (
                "Too many items match the given schema (expected at most 2)",
                "maxContains",
                2,
                ["foo", 2, "bar", 4, "baz", "quux"],
                deque([]),
                {"contains": {"type": "string"}, "maxContains": 2},
                deque(["contains"]),
                "$",
            ),
        )

    def test_contains_too_few(self):
        schema = {"contains": {"type": "string"}, "minContains": 2}
        validator = validators.Draft202012Validator(schema)
        error, = validator.iter_errors(["foo", 2, 4])
        self.assertEqual(
            (
                error.message,
                error.validator,
                error.validator_value,
                error.instance,
                error.absolute_path,
                error.schema,
                error.schema_path,
                error.json_path,
            ),
            (
                (
                    "Too few items match the given schema "
                    "(expected at least 2 but only 1 matched)"
                ),
                "minContains",
                2,
                ["foo", 2, 4],
                deque([]),
                {"contains": {"type": "string"}, "minContains": 2},
                deque(["contains"]),
                "$",
            ),
        )

    def test_contains_none(self):
        schema = {"contains": {"type": "string"}, "minContains": 2}
        validator = validators.Draft202012Validator(schema)
        error, = validator.iter_errors([2, 4])
        self.assertEqual(
            (
                error.message,
                error.validator,
                error.validator_value,
                error.instance,
                error.absolute_path,
                error.schema,
                error.schema_path,
                error.json_path,
            ),
            (
                "[2, 4] does not contain items matching the given schema",
                "contains",
                {"type": "string"},
                [2, 4],
                deque([]),
                {"contains": {"type": "string"}, "minContains": 2},
                deque(["contains"]),
                "$",
            ),
        )

    def test_ref_sibling(self):
        schema = {
            "$defs": {"foo": {"required": ["bar"]}},
            "properties": {
                "aprop": {
                    "$ref": "#/$defs/foo",
                    "required": ["baz"],
                },
            },
        }

        validator = validators.Draft202012Validator(schema)
        e1, e2 = validator.iter_errors({"aprop": {}})
        self.assertEqual(
            (
                e1.message,
                e1.validator,
                e1.validator_value,
                e1.instance,
                e1.absolute_path,
                e1.schema,
                e1.schema_path,
                e1.relative_schema_path,
                e1.json_path,
            ),
            (
                "'bar' is a required property",
                "required",
                ["bar"],
                {},
                deque(["aprop"]),
                {"required": ["bar"]},
                deque(["properties", "aprop", "required"]),
                deque(["properties", "aprop", "required"]),
                "$.aprop",
            ),
        )
        self.assertEqual(
            (
                e2.message,
                e2.validator,
                e2.validator_value,
                e2.instance,
                e2.absolute_path,
                e2.schema,
                e2.schema_path,
                e2.relative_schema_path,
                e2.json_path,
            ),
            (
                "'baz' is a required property",
                "required",
                ["baz"],
                {},
                deque(["aprop"]),
                {"$ref": "#/$defs/foo", "required": ["baz"]},
                deque(["properties", "aprop", "required"]),
                deque(["properties", "aprop", "required"]),
                "$.aprop",
            ),
        )


class MetaSchemaTestsMixin(object):
    # TODO: These all belong upstream
    def test_invalid_properties(self):
        with self.assertRaises(exceptions.SchemaError):
            self.Validator.check_schema({"properties": 12})

    def test_minItems_invalid_string(self):
        with self.assertRaises(exceptions.SchemaError):
            # needs to be an integer
            self.Validator.check_schema({"minItems": "1"})

    def test_enum_allows_empty_arrays(self):
        """
        Technically, all the spec says is they SHOULD have elements, not MUST.

        See #529.
        """
        self.Validator.check_schema({"enum": []})

    def test_enum_allows_non_unique_items(self):
        """
        Technically, all the spec says is they SHOULD be unique, not MUST.

        See #529.
        """
        self.Validator.check_schema({"enum": [12, 12]})


class ValidatorTestMixin(MetaSchemaTestsMixin, object):
    def test_it_implements_the_validator_protocol(self):
        self.assertIsInstance(self.Validator({}), protocols.Validator)

    def test_valid_instances_are_valid(self):
        schema, instance = self.valid
        self.assertTrue(self.Validator(schema).is_valid(instance))

    def test_invalid_instances_are_not_valid(self):
        schema, instance = self.invalid
        self.assertFalse(self.Validator(schema).is_valid(instance))

    def test_non_existent_properties_are_ignored(self):
        self.Validator({object(): object()}).validate(instance=object())

    def test_it_creates_a_ref_resolver_if_not_provided(self):
        self.assertIsInstance(
            self.Validator({}).resolver,
            validators.RefResolver,
        )

    def test_it_delegates_to_a_ref_resolver(self):
        ref, schema = "someCoolRef", {"type": "integer"}
        resolver = validators.RefResolver("", {}, store={ref: schema})
        validator = self.Validator({"$ref": ref}, resolver=resolver)

        with self.assertRaises(exceptions.ValidationError):
            validator.validate(None)

    def test_evolve(self):
        ref, schema = "someCoolRef", {"type": "integer"}
        resolver = validators.RefResolver("", {}, store={ref: schema})

        validator = self.Validator(schema, resolver=resolver)
        new = validator.evolve(schema={"type": "string"})

        expected = self.Validator({"type": "string"}, resolver=resolver)

        self.assertEqual(new, expected)
        self.assertNotEqual(new, validator)

    def test_evolve_with_subclass(self):
        """
        Subclassing validators isn't supported public API, but some users have
        done it, because we don't actually error entirely when it's done :/

        We need to deprecate doing so first to help as many of these users
        ensure they can move to supported APIs, but this test ensures that in
        the interim, we haven't broken those users.
        """

        @attr.s
        class OhNo(self.Validator):
            foo = attr.ib(factory=lambda: [1, 2, 3])
            _bar = attr.ib(default=37)

        validator = OhNo({}, bar=12)
        self.assertEqual(validator.foo, [1, 2, 3])

        new = validator.evolve(schema={"type": "integer"})
        self.assertEqual(new.foo, [1, 2, 3])
        self.assertEqual(new._bar, 12)

    def test_it_delegates_to_a_legacy_ref_resolver(self):
        """
        Legacy RefResolvers support only the context manager form of
        resolution.
        """

        class LegacyRefResolver(object):
            @contextmanager
            def resolving(this, ref):
                self.assertEqual(ref, "the ref")
                yield {"type": "integer"}

        resolver = LegacyRefResolver()
        schema = {"$ref": "the ref"}

        with self.assertRaises(exceptions.ValidationError):
            self.Validator(schema, resolver=resolver).validate(None)

    def test_is_type_is_true_for_valid_type(self):
        self.assertTrue(self.Validator({}).is_type("foo", "string"))

    def test_is_type_is_false_for_invalid_type(self):
        self.assertFalse(self.Validator({}).is_type("foo", "array"))

    def test_is_type_evades_bool_inheriting_from_int(self):
        self.assertFalse(self.Validator({}).is_type(True, "integer"))
        self.assertFalse(self.Validator({}).is_type(True, "number"))

    def test_it_can_validate_with_decimals(self):
        schema = {"items": {"type": "number"}}
        Validator = validators.extend(
            self.Validator,
            type_checker=self.Validator.TYPE_CHECKER.redefine(
                "number",
                lambda checker, thing: isinstance(
                    thing, (int, float, Decimal),
                ) and not isinstance(thing, bool),
            ),
        )

        validator = Validator(schema)
        validator.validate([1, 1.1, Decimal(1) / Decimal(8)])

        invalid = ["foo", {}, [], True, None]
        self.assertEqual(
            [error.instance for error in validator.iter_errors(invalid)],
            invalid,
        )

    def test_it_returns_true_for_formats_it_does_not_know_about(self):
        validator = self.Validator(
            {"format": "carrot"}, format_checker=FormatChecker(),
        )
        validator.validate("bugs")

    def test_it_does_not_validate_formats_by_default(self):
        validator = self.Validator({})
        self.assertIsNone(validator.format_checker)

    def test_it_validates_formats_if_a_checker_is_provided(self):
        checker = FormatChecker()
        bad = ValueError("Bad!")

        @checker.checks("foo", raises=ValueError)
        def check(value):
            if value == "good":
                return True
            elif value == "bad":
                raise bad
            else:  # pragma: no cover
                self.fail("What is {}? [Baby Don't Hurt Me]".format(value))

        validator = self.Validator(
            {"format": "foo"}, format_checker=checker,
        )

        validator.validate("good")
        with self.assertRaises(exceptions.ValidationError) as cm:
            validator.validate("bad")

        # Make sure original cause is attached
        self.assertIs(cm.exception.cause, bad)

    def test_non_string_custom_type(self):
        non_string_type = object()
        schema = {"type": [non_string_type]}
        Crazy = validators.extend(
            self.Validator,
            type_checker=self.Validator.TYPE_CHECKER.redefine(
                non_string_type,
                lambda checker, thing: isinstance(thing, int),
            ),
        )
        Crazy(schema).validate(15)

    def test_it_properly_formats_tuples_in_errors(self):
        """
        A tuple instance properly formats validation errors for uniqueItems.

        See #224
        """
        TupleValidator = validators.extend(
            self.Validator,
            type_checker=self.Validator.TYPE_CHECKER.redefine(
                "array",
                lambda checker, thing: isinstance(thing, tuple),
            ),
        )
        with self.assertRaises(exceptions.ValidationError) as e:
            TupleValidator({"uniqueItems": True}).validate((1, 1))
        self.assertIn("(1, 1) has non-unique elements", str(e.exception))

    def test_check_redefined_sequence(self):
        """
        Allow array to validate against another defined sequence type
        """
        schema = {"type": "array", "uniqueItems": True}
        MyMapping = namedtuple("MyMapping", "a, b")
        Validator = validators.extend(
            self.Validator,
            type_checker=self.Validator.TYPE_CHECKER.redefine_many(
                {
                    "array": lambda checker, thing: isinstance(
                        thing, (list, deque),
                    ),
                    "object": lambda checker, thing: isinstance(
                        thing, (dict, MyMapping),
                    ),
                },
            ),
        )
        validator = Validator(schema)

        valid_instances = [
            deque(["a", None, "1", "", True]),
            deque([[False], [0]]),
            [deque([False]), deque([0])],
            [[deque([False])], [deque([0])]],
            [[[[[deque([False])]]]], [[[[deque([0])]]]]],
            [deque([deque([False])]), deque([deque([0])])],
            [MyMapping("a", 0), MyMapping("a", False)],
            [
                MyMapping("a", [deque([0])]),
                MyMapping("a", [deque([False])]),
            ],
            [
                MyMapping("a", [MyMapping("a", deque([0]))]),
                MyMapping("a", [MyMapping("a", deque([False]))]),
            ],
            [deque(deque(deque([False]))), deque(deque(deque([0])))],
        ]

        for instance in valid_instances:
            validator.validate(instance)

        invalid_instances = [
            deque(["a", "b", "a"]),
            deque([[False], [False]]),
            [deque([False]), deque([False])],
            [[deque([False])], [deque([False])]],
            [[[[[deque([False])]]]], [[[[deque([False])]]]]],
            [deque([deque([False])]), deque([deque([False])])],
            [MyMapping("a", False), MyMapping("a", False)],
            [
                MyMapping("a", [deque([False])]),
                MyMapping("a", [deque([False])]),
            ],
            [
                MyMapping("a", [MyMapping("a", deque([False]))]),
                MyMapping("a", [MyMapping("a", deque([False]))]),
            ],
            [deque(deque(deque([False]))), deque(deque(deque([False])))],
        ]

        for instance in invalid_instances:
            with self.assertRaises(exceptions.ValidationError):
                validator.validate(instance)


class AntiDraft6LeakMixin(object):
    """
    Make sure functionality from draft 6 doesn't leak backwards in time.
    """

    def test_True_is_not_a_schema(self):
        with self.assertRaises(exceptions.SchemaError) as e:
            self.Validator.check_schema(True)
        self.assertIn("True is not of type", str(e.exception))

    def test_False_is_not_a_schema(self):
        with self.assertRaises(exceptions.SchemaError) as e:
            self.Validator.check_schema(False)
        self.assertIn("False is not of type", str(e.exception))

    @unittest.skip(bug(523))
    def test_True_is_not_a_schema_even_if_you_forget_to_check(self):
        resolver = validators.RefResolver("", {})
        with self.assertRaises(Exception) as e:
            self.Validator(True, resolver=resolver).validate(12)
        self.assertNotIsInstance(e.exception, exceptions.ValidationError)

    @unittest.skip(bug(523))
    def test_False_is_not_a_schema_even_if_you_forget_to_check(self):
        resolver = validators.RefResolver("", {})
        with self.assertRaises(Exception) as e:
            self.Validator(False, resolver=resolver).validate(12)
        self.assertNotIsInstance(e.exception, exceptions.ValidationError)


class TestDraft3Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase):
    Validator = validators.Draft3Validator
    valid: tuple[dict, dict] = ({}, {})
    invalid = {"type": "integer"}, "foo"

    def test_any_type_is_valid_for_type_any(self):
        validator = self.Validator({"type": "any"})
        validator.validate(object())

    def test_any_type_is_redefinable(self):
        """
        Sigh, because why not.
        """
        Crazy = validators.extend(
            self.Validator,
            type_checker=self.Validator.TYPE_CHECKER.redefine(
                "any", lambda checker, thing: isinstance(thing, int),
            ),
        )
        validator = Crazy({"type": "any"})
        validator.validate(12)
        with self.assertRaises(exceptions.ValidationError):
            validator.validate("foo")

    def test_is_type_is_true_for_any_type(self):
        self.assertTrue(self.Validator({"type": "any"}).is_valid(object()))

    def test_is_type_does_not_evade_bool_if_it_is_being_tested(self):
        self.assertTrue(self.Validator({}).is_type(True, "boolean"))
        self.assertTrue(self.Validator({"type": "any"}).is_valid(True))


class TestDraft4Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase):
    Validator = validators.Draft4Validator
    valid: tuple[dict, dict] = ({}, {})
    invalid = {"type": "integer"}, "foo"


class TestDraft6Validator(ValidatorTestMixin, TestCase):
    Validator = validators.Draft6Validator
    valid: tuple[dict, dict] = ({}, {})
    invalid = {"type": "integer"}, "foo"


class TestDraft7Validator(ValidatorTestMixin, TestCase):
    Validator = validators.Draft7Validator
    valid: tuple[dict, dict] = ({}, {})
    invalid = {"type": "integer"}, "foo"


class TestDraft201909Validator(ValidatorTestMixin, TestCase):
    Validator = validators.Draft201909Validator
    valid: tuple[dict, dict] = ({}, {})
    invalid = {"type": "integer"}, "foo"


class TestDraft202012Validator(ValidatorTestMixin, TestCase):
    Validator = validators.Draft202012Validator
    valid: tuple[dict, dict] = ({}, {})
    invalid = {"type": "integer"}, "foo"


class TestValidatorFor(TestCase):
    def test_draft_3(self):
        schema = {"$schema": "http://json-schema.org/draft-03/schema"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft3Validator,
        )

        schema = {"$schema": "http://json-schema.org/draft-03/schema#"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft3Validator,
        )

    def test_draft_4(self):
        schema = {"$schema": "http://json-schema.org/draft-04/schema"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft4Validator,
        )

        schema = {"$schema": "http://json-schema.org/draft-04/schema#"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft4Validator,
        )

    def test_draft_6(self):
        schema = {"$schema": "http://json-schema.org/draft-06/schema"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft6Validator,
        )

        schema = {"$schema": "http://json-schema.org/draft-06/schema#"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft6Validator,
        )

    def test_draft_7(self):
        schema = {"$schema": "http://json-schema.org/draft-07/schema"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft7Validator,
        )

        schema = {"$schema": "http://json-schema.org/draft-07/schema#"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft7Validator,
        )

    def test_draft_201909(self):
        schema = {"$schema": "https://json-schema.org/draft/2019-09/schema"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft201909Validator,
        )

        schema = {"$schema": "https://json-schema.org/draft/2019-09/schema#"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft201909Validator,
        )

    def test_draft_202012(self):
        schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft202012Validator,
        )

        schema = {"$schema": "https://json-schema.org/draft/2020-12/schema#"}
        self.assertIs(
            validators.validator_for(schema),
            validators.Draft202012Validator,
        )

    def test_True(self):
        self.assertIs(
            validators.validator_for(True),
            validators._LATEST_VERSION,
        )

    def test_False(self):
        self.assertIs(
            validators.validator_for(False),
            validators._LATEST_VERSION,
        )

    def test_custom_validator(self):
        Validator = validators.create(
            meta_schema={"id": "meta schema id"},
            version="12",
            id_of=lambda s: s.get("id", ""),
        )
        schema = {"$schema": "meta schema id"}
        self.assertIs(
            validators.validator_for(schema),
            Validator,
        )

    def test_custom_validator_draft6(self):
        Validator = validators.create(
            meta_schema={"$id": "meta schema $id"},
            version="13",
        )
        schema = {"$schema": "meta schema $id"}
        self.assertIs(
            validators.validator_for(schema),
            Validator,
        )

    def test_validator_for_jsonschema_default(self):
        self.assertIs(validators.validator_for({}), validators._LATEST_VERSION)

    def test_validator_for_custom_default(self):
        self.assertIs(validators.validator_for({}, default=None), None)

    def test_warns_if_meta_schema_specified_was_not_found(self):
        with self.assertWarns(DeprecationWarning) as cm:
            validators.validator_for(schema={"$schema": "unknownSchema"})

        self.assertEqual(cm.filename, __file__)
        self.assertEqual(
            str(cm.warning),
            "The metaschema specified by $schema was not found. "
            "Using the latest draft to validate, but this will raise "
            "an error in the future.",
        )

    def test_does_not_warn_if_meta_schema_is_unspecified(self):
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            validators.validator_for(schema={}, default={})
        self.assertFalse(w)

    def test_validator_for_custom_default_with_schema(self):
        schema, default = {"$schema": "mailto:foo@example.com"}, object()
        self.assertIs(validators.validator_for(schema, default), default)


class TestValidate(TestCase):
    def assertUses(self, schema, Validator):
        result = []
        with mock.patch.object(Validator, "check_schema", result.append):
            validators.validate({}, schema)
        self.assertEqual(result, [schema])

    def test_draft3_validator_is_chosen(self):
        self.assertUses(
            schema={"$schema": "http://json-schema.org/draft-03/schema#"},
            Validator=validators.Draft3Validator,
        )
        # Make sure it works without the empty fragment
        self.assertUses(
            schema={"$schema": "http://json-schema.org/draft-03/schema"},
            Validator=validators.Draft3Validator,
        )

    def test_draft4_validator_is_chosen(self):
        self.assertUses(
            schema={"$schema": "http://json-schema.org/draft-04/schema#"},
            Validator=validators.Draft4Validator,
        )
        # Make sure it works without the empty fragment
        self.assertUses(
            schema={"$schema": "http://json-schema.org/draft-04/schema"},
            Validator=validators.Draft4Validator,
        )

    def test_draft6_validator_is_chosen(self):
        self.assertUses(
            schema={"$schema": "http://json-schema.org/draft-06/schema#"},
            Validator=validators.Draft6Validator,
        )
        # Make sure it works without the empty fragment
        self.assertUses(
            schema={"$schema": "http://json-schema.org/draft-06/schema"},
            Validator=validators.Draft6Validator,
        )

    def test_draft7_validator_is_chosen(self):
        self.assertUses(
            schema={"$schema": "http://json-schema.org/draft-07/schema#"},
            Validator=validators.Draft7Validator,
        )
        # Make sure it works without the empty fragment
        self.assertUses(
            schema={"$schema": "http://json-schema.org/draft-07/schema"},
            Validator=validators.Draft7Validator,
        )

    def test_draft202012_validator_is_chosen(self):
        self.assertUses(
            schema={
                "$schema": "https://json-schema.org/draft/2020-12/schema#",
            },
            Validator=validators.Draft202012Validator,
        )
        # Make sure it works without the empty fragment
        self.assertUses(
            schema={
                "$schema": "https://json-schema.org/draft/2020-12/schema",
            },
            Validator=validators.Draft202012Validator,
        )

    def test_draft202012_validator_is_the_default(self):
        self.assertUses(schema={}, Validator=validators.Draft202012Validator)

    def test_validation_error_message(self):
        with self.assertRaises(exceptions.ValidationError) as e:
            validators.validate(12, {"type": "string"})
        self.assertRegex(
            str(e.exception),
            "(?s)Failed validating '.*' in schema.*On instance",
        )

    def test_schema_error_message(self):
        with self.assertRaises(exceptions.SchemaError) as e:
            validators.validate(12, {"type": 12})
        self.assertRegex(
            str(e.exception),
            "(?s)Failed validating '.*' in metaschema.*On schema",
        )

    def test_it_uses_best_match(self):
        schema = {
            "oneOf": [
                {"type": "number", "minimum": 20},
                {"type": "array"},
            ],
        }
        with self.assertRaises(exceptions.ValidationError) as e:
            validators.validate(12, schema)
        self.assertIn("12 is less than the minimum of 20", str(e.exception))


class TestRefResolver(TestCase):

    base_uri = ""
    stored_uri = "foo://stored"
    stored_schema = {"stored": "schema"}

    def setUp(self):
        self.referrer = {}
        self.store = {self.stored_uri: self.stored_schema}
        self.resolver = validators.RefResolver(
            self.base_uri, self.referrer, self.store,
        )

    def test_it_does_not_retrieve_schema_urls_from_the_network(self):
        ref = validators.Draft3Validator.META_SCHEMA["id"]
        with mock.patch.object(self.resolver, "resolve_remote") as patched:
            with self.resolver.resolving(ref) as resolved:
                pass
        self.assertEqual(resolved, validators.Draft3Validator.META_SCHEMA)
        self.assertFalse(patched.called)

    def test_it_resolves_local_refs(self):
        ref = "#/properties/foo"
        self.referrer["properties"] = {"foo": object()}
        with self.resolver.resolving(ref) as resolved:
            self.assertEqual(resolved, self.referrer["properties"]["foo"])

    def test_it_resolves_local_refs_with_id(self):
        schema = {"id": "http://bar/schema#", "a": {"foo": "bar"}}
        resolver = validators.RefResolver.from_schema(
            schema,
            id_of=lambda schema: schema.get("id", ""),
        )
        with resolver.resolving("#/a") as resolved:
            self.assertEqual(resolved, schema["a"])
        with resolver.resolving("http://bar/schema#/a") as resolved:
            self.assertEqual(resolved, schema["a"])

    def test_it_retrieves_stored_refs(self):
        with self.resolver.resolving(self.stored_uri) as resolved:
            self.assertIs(resolved, self.stored_schema)

        self.resolver.store["cached_ref"] = {"foo": 12}
        with self.resolver.resolving("cached_ref#/foo") as resolved:
            self.assertEqual(resolved, 12)

    def test_it_retrieves_unstored_refs_via_requests(self):
        ref = "http://bar#baz"
        schema = {"baz": 12}

        if "requests" in sys.modules:
            self.addCleanup(
                sys.modules.__setitem__, "requests", sys.modules["requests"],
            )
        sys.modules["requests"] = ReallyFakeRequests({"http://bar": schema})

        with self.resolver.resolving(ref) as resolved:
            self.assertEqual(resolved, 12)

    def test_it_retrieves_unstored_refs_via_urlopen(self):
        ref = "http://bar#baz"
        schema = {"baz": 12}

        if "requests" in sys.modules:
            self.addCleanup(
                sys.modules.__setitem__, "requests", sys.modules["requests"],
            )
        sys.modules["requests"] = None

        @contextmanager
        def fake_urlopen(url):
            self.assertEqual(url, "http://bar")
            yield BytesIO(json.dumps(schema).encode("utf8"))

        self.addCleanup(setattr, validators, "urlopen", validators.urlopen)
        validators.urlopen = fake_urlopen

        with self.resolver.resolving(ref) as resolved:
            pass
        self.assertEqual(resolved, 12)

    def test_it_retrieves_local_refs_via_urlopen(self):
        with tempfile.NamedTemporaryFile(delete=False, mode="wt") as tempf:
            self.addCleanup(os.remove, tempf.name)
            json.dump({"foo": "bar"}, tempf)

        ref = "file://{}#foo".format(pathname2url(tempf.name))
        with self.resolver.resolving(ref) as resolved:
            self.assertEqual(resolved, "bar")

    def test_it_can_construct_a_base_uri_from_a_schema(self):
        schema = {"id": "foo"}
        resolver = validators.RefResolver.from_schema(
            schema,
            id_of=lambda schema: schema.get("id", ""),
        )
        self.assertEqual(resolver.base_uri, "foo")
        self.assertEqual(resolver.resolution_scope, "foo")
        with resolver.resolving("") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("#") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("foo") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("foo#") as resolved:
            self.assertEqual(resolved, schema)

    def test_it_can_construct_a_base_uri_from_a_schema_without_id(self):
        schema = {}
        resolver = validators.RefResolver.from_schema(schema)
        self.assertEqual(resolver.base_uri, "")
        self.assertEqual(resolver.resolution_scope, "")
        with resolver.resolving("") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("#") as resolved:
            self.assertEqual(resolved, schema)

    def test_custom_uri_scheme_handlers(self):
        def handler(url):
            self.assertEqual(url, ref)
            return schema

        schema = {"foo": "bar"}
        ref = "foo://bar"
        resolver = validators.RefResolver("", {}, handlers={"foo": handler})
        with resolver.resolving(ref) as resolved:
            self.assertEqual(resolved, schema)

    def test_cache_remote_on(self):
        response = [object()]

        def handler(url):
            try:
                return response.pop()
            except IndexError:  # pragma: no cover
                self.fail("Response must not have been cached!")

        ref = "foo://bar"
        resolver = validators.RefResolver(
            "", {}, cache_remote=True, handlers={"foo": handler},
        )
        with resolver.resolving(ref):
            pass
        with resolver.resolving(ref):
            pass

    def test_cache_remote_off(self):
        response = [object()]

        def handler(url):
            try:
                return response.pop()
            except IndexError:  # pragma: no cover
                self.fail("Handler called twice!")

        ref = "foo://bar"
        resolver = validators.RefResolver(
            "", {}, cache_remote=False, handlers={"foo": handler},
        )
        with resolver.resolving(ref):
            pass

    def test_if_you_give_it_junk_you_get_a_resolution_error(self):
        error = ValueError("Oh no! What's this?")

        def handler(url):
            raise error

        ref = "foo://bar"
        resolver = validators.RefResolver("", {}, handlers={"foo": handler})
        with self.assertRaises(exceptions.RefResolutionError) as err:
            with resolver.resolving(ref):
                self.fail("Shouldn't get this far!")  # pragma: no cover
        self.assertEqual(err.exception, exceptions.RefResolutionError(error))

    def test_helpful_error_message_on_failed_pop_scope(self):
        resolver = validators.RefResolver("", {})
        resolver.pop_scope()
        with self.assertRaises(exceptions.RefResolutionError) as exc:
            resolver.pop_scope()
        self.assertIn("Failed to pop the scope", str(exc.exception))


def sorted_errors(errors):
    def key(error):
        return (
            [str(e) for e in error.path],
            [str(e) for e in error.schema_path],
        )
    return sorted(errors, key=key)


@attr.s
class ReallyFakeRequests(object):

    _responses = attr.ib()

    def get(self, url):
        response = self._responses.get(url)
        if url is None:  # pragma: no cover
            raise ValueError("Unknown URL: " + repr(url))
        return _ReallyFakeJSONResponse(json.dumps(response))


@attr.s
class _ReallyFakeJSONResponse(object):

    _response = attr.ib()

    def json(self):
        return json.loads(self._response)
¿Qué es la limpieza dental de perros? - Clínica veterinaria


Es la eliminación del sarro y la placa adherida a la superficie de los dientes mediante un equipo de ultrasonidos que garantiza la integridad de las piezas dentales a la vez que elimina en profundidad cualquier resto de suciedad.

A continuación se procede al pulido de los dientes mediante una fresa especial que elimina la placa bacteriana y devuelve a los dientes el aspecto sano que deben tener.

Una vez terminado todo el proceso, se mantiene al perro en observación hasta que se despierta de la anestesia, bajo la atenta supervisión de un veterinario.

¿Cada cuánto tiempo tengo que hacerle una limpieza dental a mi perro?

A partir de cierta edad, los perros pueden necesitar una limpieza dental anual o bianual. Depende de cada caso. En líneas generales, puede decirse que los perros de razas pequeñas suelen acumular más sarro y suelen necesitar una atención mayor en cuanto a higiene dental.


Riesgos de una mala higiene


Los riesgos más evidentes de una mala higiene dental en los perros son los siguientes:

  • Cuando la acumulación de sarro no se trata, se puede producir una inflamación y retracción de las encías que puede descalzar el diente y provocar caídas.
  • Mal aliento (halitosis).
  • Sarro perros
  • Puede ir a más
  • Las bacterias de la placa pueden trasladarse a través del torrente circulatorio a órganos vitales como el corazón ocasionando problemas de endocarditis en las válvulas. Las bacterias pueden incluso acantonarse en huesos (La osteomielitis es la infección ósea, tanto cortical como medular) provocando mucho dolor y una artritis séptica).

¿Cómo se forma el sarro?

El sarro es la calcificación de la placa dental. Los restos de alimentos, junto con las bacterias presentes en la boca, van a formar la placa bacteriana o placa dental. Si la placa no se retira, al mezclarse con la saliva y los minerales presentes en ella, reaccionará formando una costra. La placa se calcifica y se forma el sarro.

El sarro, cuando se forma, es de color blanquecino pero a medida que pasa el tiempo se va poniendo amarillo y luego marrón.

Síntomas de una pobre higiene dental
La señal más obvia de una mala salud dental canina es el mal aliento.

Sin embargo, a veces no es tan fácil de detectar
Y hay perros que no se dejan abrir la boca por su dueño. Por ejemplo…

Recientemente nos trajeron a la clínica a un perro que parpadeaba de un ojo y decía su dueño que le picaba un lado de la cara. Tenía molestias y dificultad para comer, lo que había llevado a sus dueños a comprarle comida blanda (que suele ser un poco más cara y llevar más contenido en grasa) durante medio año. Después de una exploración oftalmológica, nos dimos cuenta de que el ojo tenía una úlcera en la córnea probablemente de rascarse . Además, el canto lateral del ojo estaba inflamado. Tenía lo que en humanos llamamos flemón pero como era un perro de pelo largo, no se le notaba a simple vista. Al abrirle la boca nos llamó la atención el ver una muela llena de sarro. Le realizamos una radiografía y encontramos una fístula que llegaba hasta la parte inferior del ojo.

Le tuvimos que extraer la muela. Tras esto, el ojo se curó completamente con unos colirios y una lentilla protectora de úlcera. Afortunadamente, la úlcera no profundizó y no perforó el ojo. Ahora el perro come perfectamente a pesar de haber perdido una muela.

¿Cómo mantener la higiene dental de tu perro?
Hay varias maneras de prevenir problemas derivados de la salud dental de tu perro.

Limpiezas de dientes en casa
Es recomendable limpiar los dientes de tu perro semanal o diariamente si se puede. Existe una gran variedad de productos que se pueden utilizar:

Pastas de dientes.
Cepillos de dientes o dedales para el dedo índice, que hacen más fácil la limpieza.
Colutorios para echar en agua de bebida o directamente sobre el diente en líquido o en spray.

En la Clínica Tus Veterinarios enseñamos a nuestros clientes a tomar el hábito de limpiar los dientes de sus perros desde que son cachorros. Esto responde a nuestro compromiso con la prevención de enfermedades caninas.

Hoy en día tenemos muchos clientes que limpian los dientes todos los días a su mascota, y como resultado, se ahorran el dinero de hacer limpiezas dentales profesionales y consiguen una mejor salud de su perro.


Limpiezas dentales profesionales de perros y gatos

Recomendamos hacer una limpieza dental especializada anualmente. La realizamos con un aparato de ultrasonidos que utiliza agua para quitar el sarro. Después, procedemos a pulir los dientes con un cepillo de alta velocidad y una pasta especial. Hacemos esto para proteger el esmalte.

La frecuencia de limpiezas dentales necesaria varía mucho entre razas. En general, las razas grandes tienen buena calidad de esmalte, por lo que no necesitan hacerlo tan a menudo e incluso pueden pasarse la vida sin requerir una limpieza. Sin embargo, razas pequeñas como el Yorkshire o el Maltés, deben hacérselas todos los años desde cachorros si se quiere conservar sus piezas dentales.

Otro factor fundamental es la calidad del pienso. Algunas marcas han diseñado croquetas que limpian la superficie del diente y de la muela al masticarse.

Ultrasonido para perros

¿Se necesita anestesia para las limpiezas dentales de perros y gatos?

La limpieza dental en perros no es una técnica que pueda practicarse sin anestesia general , aunque hay veces que los propietarios no quieren anestesiar y si tiene poco sarro y el perro es muy bueno se puede intentar…… , pero no se va a poder pulir ni acceder a todas la zona de la boca …. Además los limpiadores dentales van a irrigar agua y hay riesgo de aspiración a vías respiratorias si no se realiza una anestesia correcta con intubación traqueal . En resumen , sin anestesia no se va hacer una correcta limpieza dental.

Tampoco sirve la sedación ya que necesitamos que el animal esté totalmente quieto, y el veterinario tenga un acceso completo a todas sus piezas dentales y encías.

Alimentos para la limpieza dental

Hay que tener cierto cuidado a la hora de comprar determinados alimentos porque no todos son saludables. Algunos tienen demasiado contenido graso, que en exceso puede causar problemas cardiovasculares y obesidad.

Los mejores alimentos para los dientes son aquellos que están elaborados por empresas farmacéuticas y llevan componentes químicos con tratamientos específicos para el diente del perro. Esto implica no solo limpieza a través de la acción mecánica de morder sino también un tratamiento antibacteriano para prevenir el sarro.

Conclusión

Si eres como la mayoría de dueños, por falta de tiempo , es probable que no estés prestando la suficiente atención a la limpieza dental de tu perro. Por eso te animamos a que comiences a limpiar los dientes de tu perro y consideres atender a su higiene bucal con frecuencia.

Estas simples medidas pueden conllevar a que tu perro tenga una vida más larga y mucho más saludable.

Si te resulta imposible introducir un cepillo de dientes a tu perro en la boca, pásate con él por clínica Tus Veterinarios y te explicamos cómo hacerlo.

Necesitas hacer una limpieza dental profesional a tu mascota?
Llámanos al 622575274 o contacta con nosotros

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

¡Hola!