import decimal
import re
import typing
from math import isfinite
from typesystem import formats
from typesystem.base import Message, ValidationError, ValidationResult
from typesystem.unique import Uniqueness

NO_DEFAULT = object()
FORMATS = {
    "date": formats.DateFormat(),
    "time": formats.TimeFormat(),
    "datetime": formats.DateTimeFormat(),
    "uuid": formats.UUIDFormat(),
}

class Union(Field):

    errors = {"null": "May not be null.", "union": "Did not match any valid type."}

    def __init__(self, any_of: typing.List[Field], **kwargs: typing.Any):
        super().__init__(**kwargs)

        self.any_of = any_of
        if any([child.allow_null for child in any_of]):
            self.allow_null = True

    def validate(self, value: typing.Any, strict: bool = False) -> typing.Any:
        if value is None and self.allow_null:
            return None
        elif value is None:
            raise self.validation_error("null")

        candidate_errors = []
        for child in self.any_of:
            validated, error = child.validate_or_error(value, strict=strict)
            if error is None:
                return validated
            else:
                # If a child returned anything other than a type error, then
                # it is a candidate for returning as the primary error.
                messages = error.messages()
                if (
                    len(messages) != 1
                    or messages[0].code != "type"
                    or messages[0].index
                ):
                    candidate_errors.append(error)

        if len(candidate_errors) == 1:
            # If exactly one child was of the correct type, then we can use
            # the error from the child.
            raise candidate_errors[0]
        raise self.validation_error("union")


import decimal
import re
import typing
from math import isfinite
from typesystem import formats
from typesystem.base import Message, ValidationError, ValidationResult
from typesystem.unique import Uniqueness

NO_DEFAULT = object()
FORMATS = {
    "date": formats.DateFormat(),
    "time": formats.TimeFormat(),
    "datetime": formats.DateTimeFormat(),
    "uuid": formats.UUIDFormat(),
}

class Const(Field):

    errors = {"only_null": "Must be null.", "const": "Must be the value '{const}'."}

    def __init__(self, const: typing.Any, **kwargs: typing.Any):
        assert "allow_null" not in kwargs
        super().__init__(**kwargs)
        self.const = const


import datetime
import re
import typing
import uuid
from typesystem.base import ValidationError

DATE_REGEX = re.compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
TIME_REGEX = re.compile(
    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
)
DATETIME_REGEX = re.compile(
    r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
    r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
)
UUID_REGEX = re.compile(
    r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
)

class DateFormat(BaseFormat):

    errors = {
        "format": "Must be a valid date format.",
        "invalid": "Must be a real date.",
    }

    def validate(self, value: typing.Any) -> datetime.date:
        match = DATE_REGEX.match(value)
        if not match:
            raise self.validation_error("format")

        kwargs = {k: int(v) for k, v in match.groupdict().items()}
        try:
            return datetime.date(**kwargs)
        except ValueError:
            raise self.validation_error("invalid")


import datetime
import re
import typing
import uuid
from typesystem.base import ValidationError

DATE_REGEX = re.compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
TIME_REGEX = re.compile(
    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
)
DATETIME_REGEX = re.compile(
    r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
    r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
)
UUID_REGEX = re.compile(
    r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
)

class DateFormat(BaseFormat):

    errors = {
        "format": "Must be a valid date format.",
        "invalid": "Must be a real date.",
    }

    def serialize(self, obj: typing.Any) -> typing.Union[str, None]:
        if obj is None:
            return None

        assert isinstance(obj, datetime.date)

        return obj.isoformat()


import datetime
import re
import typing
import uuid
from typesystem.base import ValidationError

DATE_REGEX = re.compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
TIME_REGEX = re.compile(
    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
)
DATETIME_REGEX = re.compile(
    r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
    r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
)
UUID_REGEX = re.compile(
    r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
)

class TimeFormat(BaseFormat):

    errors = {
        "format": "Must be a valid time format.",
        "invalid": "Must be a real time.",
    }

    def validate(self, value: typing.Any) -> datetime.time:
        match = TIME_REGEX.match(value)
        if not match:
            raise self.validation_error("format")

        groups = match.groupdict()
        if groups["microsecond"]:
            groups["microsecond"] = groups["microsecond"].ljust(6, "0")

        kwargs = {k: int(v) for k, v in groups.items() if v is not None}
        try:
            return datetime.time(tzinfo=None, **kwargs)
        except ValueError:
            raise self.validation_error("invalid")


import datetime
import re
import typing
import uuid
from typesystem.base import ValidationError

DATE_REGEX = re.compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
TIME_REGEX = re.compile(
    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
)
DATETIME_REGEX = re.compile(
    r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
    r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
)
UUID_REGEX = re.compile(
    r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
)

class TimeFormat(BaseFormat):

    errors = {
        "format": "Must be a valid time format.",
        "invalid": "Must be a real time.",
    }

    def serialize(self, obj: typing.Any) -> typing.Union[str, None]:
        if obj is None:
            return None

        assert isinstance(obj, datetime.time)

        return obj.isoformat()


import datetime
import re
import typing
import uuid
from typesystem.base import ValidationError

DATE_REGEX = re.compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
TIME_REGEX = re.compile(
    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
)
DATETIME_REGEX = re.compile(
    r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
    r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
)
UUID_REGEX = re.compile(
    r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
)

class DateTimeFormat(BaseFormat):

    errors = {
        "format": "Must be a valid datetime format.",
        "invalid": "Must be a real datetime.",
    }

    def validate(self, value: typing.Any) -> datetime.datetime:
        match = DATETIME_REGEX.match(value)
        if not match:
            raise self.validation_error("format")

        groups = match.groupdict()
        if groups["microsecond"]:
            groups["microsecond"] = groups["microsecond"].ljust(6, "0")

        tzinfo_str = groups.pop("tzinfo")
        if tzinfo_str == "Z":
            tzinfo = datetime.timezone.utc
        elif tzinfo_str is not None:
            offset_mins = int(tzinfo_str[-2:]) if len(tzinfo_str) > 3 else 0
            offset_hours = int(tzinfo_str[1:3])
            delta = datetime.timedelta(hours=offset_hours, minutes=offset_mins)
            if tzinfo_str[0] == "-":
                delta = -delta
            tzinfo = datetime.timezone(delta)
        else:
            tzinfo = None

        kwargs = {k: int(v) for k, v in groups.items() if v is not None}
        try:
            return datetime.datetime(**kwargs, tzinfo=tzinfo)  # type: ignore
        except ValueError:
            raise self.validation_error("invalid")


import datetime
import re
import typing
import uuid
from typesystem.base import ValidationError

DATE_REGEX = re.compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
TIME_REGEX = re.compile(
    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
)
DATETIME_REGEX = re.compile(
    r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
    r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
)
UUID_REGEX = re.compile(
    r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
)

class DateTimeFormat(BaseFormat):

    errors = {
        "format": "Must be a valid datetime format.",
        "invalid": "Must be a real datetime.",
    }

    def serialize(self, obj: typing.Any) -> typing.Union[str, None]:
        if obj is None:
            return None

        assert isinstance(obj, datetime.datetime)

        value = obj.isoformat()

        if value.endswith("+00:00"):
            value = value[:-6] + "Z"

        return value


import datetime
import re
import typing
import uuid
from typesystem.base import ValidationError

DATE_REGEX = re.compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
TIME_REGEX = re.compile(
    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
)
DATETIME_REGEX = re.compile(
    r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
    r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
)
UUID_REGEX = re.compile(
    r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
)

class UUIDFormat(BaseFormat):

    errors = {"format": "Must be valid UUID format."}

    def validate(self, value: typing.Any) -> uuid.UUID:
        match = UUID_REGEX.match(value)
        if not match:
            raise self.validation_error("format")

        return uuid.UUID(value)


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def from_json_schema(
    data: typing.Union[bool, dict], definitions: SchemaDefinitions = None
) -> Field:
    if isinstance(data, bool):
        return {True: Any(), False: NeverMatch()}[data]

    if definitions is None:
        definitions = SchemaDefinitions()
        for key, value in data.get("definitions", {}).items():
            ref = f"#/definitions/{key}"
            definitions[ref] = from_json_schema(value, definitions=definitions)

    if "$ref" in data:
        return ref_from_json_schema(data, definitions=definitions)

    constraints = []  # typing.List[Field]
    if any([property_name in data for property_name in TYPE_CONSTRAINTS]):
        constraints.append(type_from_json_schema(data, definitions=definitions))
    if "enum" in data:
        constraints.append(enum_from_json_schema(data, definitions=definitions))
    if "const" in data:
        constraints.append(const_from_json_schema(data, definitions=definitions))
    if "allOf" in data:
        constraints.append(all_of_from_json_schema(data, definitions=definitions))
    if "anyOf" in data:
        constraints.append(any_of_from_json_schema(data, definitions=definitions))
    if "oneOf" in data:
        constraints.append(one_of_from_json_schema(data, definitions=definitions))
    if "not" in data:
        constraints.append(not_from_json_schema(data, definitions=definitions))
    if "if" in data:
        constraints.append(if_then_else_from_json_schema(data, definitions=definitions))

    if len(constraints) == 1:
        return constraints[0]
    elif len(constraints) > 1:
        return AllOf(constraints)
    return Any()


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def type_from_json_schema(data: dict, definitions: SchemaDefinitions) -> Field:
    """
    Build a typed field or union of typed fields from a JSON schema object.
    """
    type_strings, allow_null = get_valid_types(data)

    if len(type_strings) > 1:
        items = [
            from_json_schema_type(
                data, type_string=type_string, allow_null=False, definitions=definitions
            )
            for type_string in type_strings
        ]
        return Union(any_of=items, allow_null=allow_null)

    if len(type_strings) == 0:
        return {True: Const(None), False: NeverMatch()}[allow_null]

    type_string = type_strings.pop()
    return from_json_schema_type(
        data, type_string=type_string, allow_null=allow_null, definitions=definitions
    )


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def get_valid_types(data: dict) -> typing.Tuple[typing.Set[str], bool]:
    """
    Returns a two-tuple of `(type_strings, allow_null)`.
    """

    type_strings = data.get("type", [])
    if isinstance(type_strings, str):
        type_strings = {type_strings}
    else:
        type_strings = set(type_strings)

    if not type_strings:
        type_strings = {"null", "boolean", "object", "array", "number", "string"}

    if "number" in type_strings:
        type_strings.discard("integer")

    allow_null = False
    if "null" in type_strings:
        allow_null = True
        type_strings.remove("null")

    return (type_strings, allow_null)


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def from_json_schema_type(
    data: dict, type_string: str, allow_null: bool, definitions: SchemaDefinitions
) -> Field:
    """
    Build a typed field from a JSON schema object.
    """

    if type_string == "number":
        kwargs = {
            "allow_null": allow_null,
            "minimum": data.get("minimum", None),
            "maximum": data.get("maximum", None),
            "exclusive_minimum": data.get("exclusiveMinimum", None),
            "exclusive_maximum": data.get("exclusiveMaximum", None),
            "multiple_of": data.get("multipleOf", None),
            "default": data.get("default", NO_DEFAULT),
        }
        return Float(**kwargs)

    elif type_string == "integer":
        kwargs = {
            "allow_null": allow_null,
            "minimum": data.get("minimum", None),
            "maximum": data.get("maximum", None),
            "exclusive_minimum": data.get("exclusiveMinimum", None),
            "exclusive_maximum": data.get("exclusiveMaximum", None),
            "multiple_of": data.get("multipleOf", None),
            "default": data.get("default", NO_DEFAULT),
        }
        return Integer(**kwargs)

    elif type_string == "string":
        min_length = data.get("minLength", 0)
        kwargs = {
            "allow_null": allow_null,
            "allow_blank": min_length == 0,
            "min_length": min_length if min_length > 1 else None,
            "max_length": data.get("maxLength", None),
            "format": data.get("format"),
            "pattern": data.get("pattern", None),
            "default": data.get("default", NO_DEFAULT),
        }
        return String(**kwargs)

    elif type_string == "boolean":
        kwargs = {"allow_null": allow_null, "default": data.get("default", NO_DEFAULT)}
        return Boolean(**kwargs)

    elif type_string == "array":
        items = data.get("items", None)
        if items is None:
            items_argument: typing.Union[None, Field, typing.List[Field]] = None
        elif isinstance(items, list):
            items_argument = [
                from_json_schema(item, definitions=definitions) for item in items
            ]
        else:
            items_argument = from_json_schema(items, definitions=definitions)

        additional_items = data.get("additionalItems", None)
        if additional_items is None:
            additional_items_argument: typing.Union[bool, Field] = True
        elif isinstance(additional_items, bool):
            additional_items_argument = additional_items
        else:
            additional_items_argument = from_json_schema(
                additional_items, definitions=definitions
            )

        kwargs = {
            "allow_null": allow_null,
            "min_items": data.get("minItems", 0),
            "max_items": data.get("maxItems", None),
            "additional_items": additional_items_argument,
            "items": items_argument,
            "unique_items": data.get("uniqueItems", False),
            "default": data.get("default", NO_DEFAULT),
        }
        return Array(**kwargs)

    elif type_string == "object":
        properties = data.get("properties", None)
        if properties is None:
            properties_argument: typing.Optional[typing.Dict[str, Field]] = None
        else:
            properties_argument = {
                key: from_json_schema(value, definitions=definitions)
                for key, value in properties.items()
            }

        pattern_properties = data.get("patternProperties", None)
        if pattern_properties is None:
            pattern_properties_argument: typing.Optional[typing.Dict[str, Field]] = (
                None
            )
        else:
            pattern_properties_argument = {
                key: from_json_schema(value, definitions=definitions)
                for key, value in pattern_properties.items()
            }

        additional_properties = data.get("additionalProperties", None)
        if additional_properties is None:
            additional_properties_argument: typing.Union[None, bool, Field] = (None)
        elif isinstance(additional_properties, bool):
            additional_properties_argument = additional_properties
        else:
            additional_properties_argument = from_json_schema(
                additional_properties, definitions=definitions
            )

        property_names = data.get("propertyNames", None)
        if property_names is None:
            property_names_argument: typing.Optional[Field] = None
        else:
            property_names_argument = from_json_schema(
                property_names, definitions=definitions
            )

        kwargs = {
            "allow_null": allow_null,
            "properties": properties_argument,
            "pattern_properties": pattern_properties_argument,
            "additional_properties": additional_properties_argument,
            "property_names": property_names_argument,
            "min_properties": data.get("minProperties", None),
            "max_properties": data.get("maxProperties", None),
            "required": data.get("required", None),
            "default": data.get("default", NO_DEFAULT),
        }
        return Object(**kwargs)

    assert False, f"Invalid argument type_string={type_string!r}"


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def ref_from_json_schema(data: dict, definitions: SchemaDefinitions) -> Field:
    reference_string = data["$ref"]
    assert reference_string.startswith("#/"), "Unsupported $ref style in document."
    return Reference(to=reference_string, definitions=definitions)


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def enum_from_json_schema(data: dict, definitions: SchemaDefinitions) -> Field:
    choices = [(item, item) for item in data["enum"]]
    kwargs = {"choices": choices, "default": data.get("default", NO_DEFAULT)}
    return Choice(**kwargs)


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def const_from_json_schema(data: dict, definitions: SchemaDefinitions) -> Field:
    const = data["const"]
    kwargs = {"const": const, "default": data.get("default", NO_DEFAULT)}
    return Const(**kwargs)


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def all_of_from_json_schema(data: dict, definitions: SchemaDefinitions) -> Field:
    all_of = [from_json_schema(item, definitions=definitions) for item in data["allOf"]]
    kwargs = {"all_of": all_of, "default": data.get("default", NO_DEFAULT)}
    return AllOf(**kwargs)


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def any_of_from_json_schema(data: dict, definitions: SchemaDefinitions) -> Field:
    any_of = [from_json_schema(item, definitions=definitions) for item in data["anyOf"]]
    kwargs = {"any_of": any_of, "default": data.get("default", NO_DEFAULT)}
    return Union(**kwargs)


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def one_of_from_json_schema(data: dict, definitions: SchemaDefinitions) -> Field:
    one_of = [from_json_schema(item, definitions=definitions) for item in data["oneOf"]]
    kwargs = {"one_of": one_of, "default": data.get("default", NO_DEFAULT)}
    return OneOf(**kwargs)


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def not_from_json_schema(data: dict, definitions: SchemaDefinitions) -> Field:
    negated = from_json_schema(data["not"], definitions=definitions)
    kwargs = {"negated": negated, "default": data.get("default", NO_DEFAULT)}
    return Not(**kwargs)


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def if_then_else_from_json_schema(data: dict, definitions: SchemaDefinitions) -> Field:
    if_clause = from_json_schema(data["if"], definitions=definitions)
    then_clause = (
        from_json_schema(data["then"], definitions=definitions)
        if "then" in data
        else None
    )
    else_clause = (
        from_json_schema(data["else"], definitions=definitions)
        if "else" in data
        else None
    )
    kwargs = {
        "if_clause": if_clause,
        "then_clause": then_clause,
        "else_clause": else_clause,
        "default": data.get("default", NO_DEFAULT),
    }
    return IfThenElse(**kwargs)


import re
import typing
from typesystem.composites import AllOf, IfThenElse, NeverMatch, Not, OneOf
from typesystem.fields import (
    NO_DEFAULT,
    Any,
    Array,
    Boolean,
    Choice,
    Const,
    Decimal,
    Field,
    Float,
    Integer,
    Number,
    Object,
    String,
    Union,
)
from typesystem.schemas import Reference, Schema, SchemaDefinitions

TYPE_CONSTRAINTS = {
    "additionalItems",
    "additionalProperties",
    "boolean_schema",
    "contains",
    "dependencies",
    "exclusiveMaximum",
    "exclusiveMinimum",
    "items",
    "maxItems",
    "maxLength",
    "maxProperties",
    "maximum",
    "minItems",
    "minLength",
    "minProperties",
    "minimum",
    "multipleOf",
    "pattern",
    "patternProperties",
    "properties",
    "propertyNames",
    "required",
    "type",
    "uniqueItems",
}
definitions["JSONSchema"] = JSONSchema
JSONSchema = (
    Object(
        properties={
            "$ref": String(),
            "type": String() | Array(items=String()),
            "enum": Array(unique_items=True, min_items=1),
            "definitions": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            # String
            "minLength": Integer(minimum=0),
            "maxLength": Integer(minimum=0),
            "pattern": String(format="regex"),
            "format": String(),
            # Numeric
            "minimum": Number(),
            "maximum": Number(),
            "exclusiveMinimum": Number(),
            "exclusiveMaximum": Number(),
            "multipleOf": Number(exclusive_minimum=0),
            # Object
            "properties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "minProperties": Integer(minimum=0),
            "maxProperties": Integer(minimum=0),
            "patternProperties": Object(
                additional_properties=Reference("JSONSchema", definitions=definitions)
            ),
            "additionalProperties": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "required": Array(items=String(), unique_items=True),
            # Array
            "items": (
                Reference("JSONSchema", definitions=definitions)
                | Array(
                    items=Reference("JSONSchema", definitions=definitions), min_items=1
                )
            ),
            "additionalItems": (
                Reference("JSONSchema", definitions=definitions) | Boolean()
            ),
            "minItems": Integer(minimum=0),
            "maxItems": Integer(minimum=0),
            "uniqueItems": Boolean(),
        }
    )
    | Boolean()
)

def to_json_schema(
    arg: typing.Union[Field, typing.Type[Schema]], _definitions: dict = None
) -> typing.Union[bool, dict]:

    if isinstance(arg, Any):
        return True
    elif isinstance(arg, NeverMatch):
        return False

    data: dict = {}
    is_root = _definitions is None
    definitions = {} if _definitions is None else _definitions

    if isinstance(arg, Field):
        field = arg
    elif isinstance(arg, SchemaDefinitions):
        field = None
        for key, value in arg.items():
            definitions[key] = to_json_schema(value, _definitions=definitions)
    else:
        field = arg.make_validator()

    if isinstance(field, Reference):
        data["$ref"] = f"#/definitions/{field.target_string}"
        definitions[field.target_string] = to_json_schema(
            field.target, _definitions=definitions
        )

    elif isinstance(field, String):
        data["type"] = ["string", "null"] if field.allow_null else "string"
        data.update(get_standard_properties(field))
        if field.min_length is not None or not field.allow_blank:
            data["minLength"] = field.min_length or 1
        if field.max_length is not None:
            data["maxLength"] = field.max_length
        if field.pattern_regex is not None:
            if field.pattern_regex.flags != re.RegexFlag.UNICODE:
                flags = re.RegexFlag(field.pattern_regex.flags)
                raise ValueError(
                    f"Cannot convert regular expression with non-standard flags "
                    f"to JSON schema: {flags!s}"
                )
            data["pattern"] = field.pattern_regex.pattern
        if field.format is not None:
            data["format"] = field.format

    elif isinstance(field, (Integer, Float, Decimal)):
        base_type = "integer" if isinstance(field, Integer) else "number"
        data["type"] = [base_type, "null"] if field.allow_null else base_type
        data.update(get_standard_properties(field))
        if field.minimum is not None:
            data["minimum"] = field.minimum
        if field.maximum is not None:
            data["maximum"] = field.maximum
        if field.exclusive_minimum is not None:
            data["exclusiveMinimum"] = field.exclusive_minimum
        if field.exclusive_maximum is not None:
            data["exclusiveMaximum"] = field.exclusive_maximum
        if field.multiple_of is not None:
            data["multipleOf"] = field.multiple_of

    elif isinstance(field, Boolean):
        data["type"] = ["boolean", "null"] if field.allow_null else "boolean"
        data.update(get_standard_properties(field))

    elif isinstance(field, Array):
        data["type"] = ["array", "null"] if field.allow_null else "array"
        data.update(get_standard_properties(field))
        if field.min_items is not None:
            data["minItems"] = field.min_items
        if field.max_items is not None:
            data["maxItems"] = field.max_items
        if field.items is not None:
            if isinstance(field.items, (list, tuple)):
                data["items"] = [
                    to_json_schema(item, _definitions=definitions)
                    for item in field.items
                ]
            else:
                data["items"] = to_json_schema(field.items, _definitions=definitions)
        if field.additional_items is not None:
            if isinstance(field.additional_items, bool):
                data["additionalItems"] = field.additional_items
            else:
                data["additionalItems"] = to_json_schema(
                    field.additional_items, _definitions=definitions
                )
        if field.unique_items is not False:
            data["uniqueItems"] = True

    elif isinstance(field, Object):
        data["type"] = ["object", "null"] if field.allow_null else "object"
        data.update(get_standard_properties(field))
        if field.properties:
            data["properties"] = {
                key: to_json_schema(value, _definitions=definitions)
                for key, value in field.properties.items()
            }
        if field.pattern_properties:
            data["patternProperties"] = {
                key: to_json_schema(value, _definitions=definitions)
                for key, value in field.pattern_properties.items()
            }
        if field.additional_properties is not None:
            if isinstance(field.additional_properties, bool):
                data["additionalProperties"] = field.additional_properties
            else:
                data["additionalProperties"] = to_json_schema(
                    field.additional_properties, _definitions=definitions
                )
        if field.property_names is not None:
            data["propertyNames"] = to_json_schema(
                field.property_names, _definitions=definitions
            )
        if field.max_properties is not None:
            data["maxProperties"] = field.max_properties
        if field.min_properties is not None:
            data["minProperties"] = field.min_properties
        if field.required:
            data["required"] = field.required

    elif isinstance(field, Choice):
        data["enum"] = [key for key, value in field.choices]
        data.update(get_standard_properties(field))

    elif isinstance(field, Const):
        data["const"] = field.const
        data.update(get_standard_properties(field))

    elif isinstance(field, Union):
        data["anyOf"] = [
            to_json_schema(item, _definitions=definitions) for item in field.any_of
        ]
        data.update(get_standard_properties(field))

    elif isinstance(field, OneOf):
        data["oneOf"] = [
            to_json_schema(item, _definitions=definitions) for item in field.one_of
        ]
        data.update(get_standard_properties(field))

    elif isinstance(field, AllOf):
        data["allOf"] = [
            to_json_schema(item, _definitions=definitions) for item in field.all_of
        ]
        data.update(get_standard_properties(field))

    elif isinstance(field, IfThenElse):
        data["if"] = to_json_schema(field.if_clause, _definitions=definitions)
        if field.then_clause is not None:
            data["then"] = to_json_schema(field.then_clause, _definitions=definitions)
        if field.else_clause is not None:
            data["else"] = to_json_schema(field.else_clause, _definitions=definitions)
        data.update(get_standard_properties(field))

    elif isinstance(field, Not):
        data["not"] = to_json_schema(field.negated, _definitions=definitions)
        data.update(get_standard_properties(field))

    elif field is not None:
        name = type(field).__qualname__
        raise ValueError(f"Cannot convert field type {name!r} to JSON Schema")

    if is_root and definitions:
        data["definitions"] = definitions
    return data


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class SchemaDefinitions(MutableMapping):

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        self._definitions = dict(*args, **kwargs)

class Schema(Mapping, metaclass=SchemaMetaclass):

    fields: typing.Dict[str, Field] = {}

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        if args:
            assert len(args) == 1
            assert not kwargs
            item = args[0]
            if isinstance(item, dict):
                for key in self.fields.keys():
                    if key in item:
                        setattr(self, key, item[key])
            else:
                for key in self.fields.keys():
                    if hasattr(item, key):
                        setattr(self, key, getattr(item, key))
            return

        for key, schema in self.fields.items():
            if key in kwargs:
                value = kwargs.pop(key)
                value, error = schema.validate_or_error(value)
                if error:
                    class_name = self.__class__.__name__
                    error_text = " ".join(
                        [message.text for message in error.messages()]
                    )
                    message = (
                        f"Invalid argument {key!r} for {class_name}(). {error_text}"
                    )
                    raise TypeError(message)
                setattr(self, key, value)
            elif schema.has_default():
                setattr(self, key, schema.get_default_value())

        if kwargs:
            key = list(kwargs.keys())[0]
            class_name = self.__class__.__name__
            message = f"{key!r} is an invalid keyword argument for {class_name}()."
            raise TypeError(message)

class Reference(Field):

    errors = {"null": "May not be null."}

    def __init__(
        self,
        to: typing.Union[str, typing.Type[Schema]],
        definitions: typing.Mapping = None,
        **kwargs: typing.Any,
    ) -> None:
        super().__init__(**kwargs)
        self.to = to
        self.definitions = definitions
        if isinstance(to, str):
            self._target_string = to
        else:
            assert issubclass(to, Schema)
            self._target = to

def set_definitions(field: Field, definitions: SchemaDefinitions) -> None:
    """
    Recursively set the definitions that string-referenced `Reference` fields
    should use.
    """
    if isinstance(field, Reference) and field.definitions is None:
        field.definitions = definitions
    elif isinstance(field, Array):
        if field.items is not None:
            if isinstance(field.items, (tuple, list)):
                for child in field.items:
                    set_definitions(child, definitions)
            else:
                set_definitions(field.items, definitions)
    elif isinstance(field, Object):
        for child in field.properties.values():
            set_definitions(child, definitions)


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class SchemaDefinitions(MutableMapping):

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        self._definitions = dict(*args, **kwargs)

    def __setitem__(self, key: typing.Any, value: typing.Any) -> None:
        assert (
            key not in self._definitions
        ), r"Definition for {key!r} has already been set."
        self._definitions[key] = value


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class SchemaDefinitions(MutableMapping):

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        self._definitions = dict(*args, **kwargs)

class Schema(Mapping, metaclass=SchemaMetaclass):

    fields: typing.Dict[str, Field] = {}

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        if args:
            assert len(args) == 1
            assert not kwargs
            item = args[0]
            if isinstance(item, dict):
                for key in self.fields.keys():
                    if key in item:
                        setattr(self, key, item[key])
            else:
                for key in self.fields.keys():
                    if hasattr(item, key):
                        setattr(self, key, getattr(item, key))
            return

        for key, schema in self.fields.items():
            if key in kwargs:
                value = kwargs.pop(key)
                value, error = schema.validate_or_error(value)
                if error:
                    class_name = self.__class__.__name__
                    error_text = " ".join(
                        [message.text for message in error.messages()]
                    )
                    message = (
                        f"Invalid argument {key!r} for {class_name}(). {error_text}"
                    )
                    raise TypeError(message)
                setattr(self, key, value)
            elif schema.has_default():
                setattr(self, key, schema.get_default_value())

        if kwargs:
            key = list(kwargs.keys())[0]
            class_name = self.__class__.__name__
            message = f"{key!r} is an invalid keyword argument for {class_name}()."
            raise TypeError(message)

class Reference(Field):

    errors = {"null": "May not be null."}

    def __init__(
        self,
        to: typing.Union[str, typing.Type[Schema]],
        definitions: typing.Mapping = None,
        **kwargs: typing.Any,
    ) -> None:
        super().__init__(**kwargs)
        self.to = to
        self.definitions = definitions
        if isinstance(to, str):
            self._target_string = to
        else:
            assert issubclass(to, Schema)
            self._target = to

class SchemaMetaclass(ABCMeta):

    def __new__(
        cls: type,
        name: str,
        bases: typing.Sequence[type],
        attrs: dict,
        definitions: SchemaDefinitions = None,
    ) -> type:
        fields: typing.Dict[str, Field] = {}

        for key, value in list(attrs.items()):
            if isinstance(value, Field):
                attrs.pop(key)
                fields[key] = value

        # If this class is subclassing other Schema classes, add their fields.
        for base in reversed(bases):
            base_fields = getattr(base, "fields", {})
            for key, value in base_fields.items():
                if isinstance(value, Field) and key not in fields:
                    fields[key] = value

        # Add the definitions to any `Reference` fields that we're including.
        if definitions is not None:
            for field in fields.values():
                set_definitions(field, definitions)

        #  Sort fields by their actual position in the source code,
        # using `Field._creation_counter`
        attrs["fields"] = dict(
            sorted(fields.items(), key=lambda item: item[1]._creation_counter)
        )

        new_type = super(SchemaMetaclass, cls).__new__(  # type: ignore
            cls, name, bases, attrs
        )
        if definitions is not None:
            definitions[name] = new_type
        return new_type


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class Schema(Mapping, metaclass=SchemaMetaclass):

    fields: typing.Dict[str, Field] = {}

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        if args:
            assert len(args) == 1
            assert not kwargs
            item = args[0]
            if isinstance(item, dict):
                for key in self.fields.keys():
                    if key in item:
                        setattr(self, key, item[key])
            else:
                for key in self.fields.keys():
                    if hasattr(item, key):
                        setattr(self, key, getattr(item, key))
            return

        for key, schema in self.fields.items():
            if key in kwargs:
                value = kwargs.pop(key)
                value, error = schema.validate_or_error(value)
                if error:
                    class_name = self.__class__.__name__
                    error_text = " ".join(
                        [message.text for message in error.messages()]
                    )
                    message = (
                        f"Invalid argument {key!r} for {class_name}(). {error_text}"
                    )
                    raise TypeError(message)
                setattr(self, key, value)
            elif schema.has_default():
                setattr(self, key, schema.get_default_value())

        if kwargs:
            key = list(kwargs.keys())[0]
            class_name = self.__class__.__name__
            message = f"{key!r} is an invalid keyword argument for {class_name}()."
            raise TypeError(message)


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class Schema(Mapping, metaclass=SchemaMetaclass):

    fields: typing.Dict[str, Field] = {}

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        if args:
            assert len(args) == 1
            assert not kwargs
            item = args[0]
            if isinstance(item, dict):
                for key in self.fields.keys():
                    if key in item:
                        setattr(self, key, item[key])
            else:
                for key in self.fields.keys():
                    if hasattr(item, key):
                        setattr(self, key, getattr(item, key))
            return

        for key, schema in self.fields.items():
            if key in kwargs:
                value = kwargs.pop(key)
                value, error = schema.validate_or_error(value)
                if error:
                    class_name = self.__class__.__name__
                    error_text = " ".join(
                        [message.text for message in error.messages()]
                    )
                    message = (
                        f"Invalid argument {key!r} for {class_name}(). {error_text}"
                    )
                    raise TypeError(message)
                setattr(self, key, value)
            elif schema.has_default():
                setattr(self, key, schema.get_default_value())

        if kwargs:
            key = list(kwargs.keys())[0]
            class_name = self.__class__.__name__
            message = f"{key!r} is an invalid keyword argument for {class_name}()."
            raise TypeError(message)

    def __eq__(self, other: typing.Any) -> bool:
        if not isinstance(other, self.__class__):
            return False

        for key in self.fields.keys():
            if getattr(self, key) != getattr(other, key):
                return False
        return True


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class Schema(Mapping, metaclass=SchemaMetaclass):

    fields: typing.Dict[str, Field] = {}

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        if args:
            assert len(args) == 1
            assert not kwargs
            item = args[0]
            if isinstance(item, dict):
                for key in self.fields.keys():
                    if key in item:
                        setattr(self, key, item[key])
            else:
                for key in self.fields.keys():
                    if hasattr(item, key):
                        setattr(self, key, getattr(item, key))
            return

        for key, schema in self.fields.items():
            if key in kwargs:
                value = kwargs.pop(key)
                value, error = schema.validate_or_error(value)
                if error:
                    class_name = self.__class__.__name__
                    error_text = " ".join(
                        [message.text for message in error.messages()]
                    )
                    message = (
                        f"Invalid argument {key!r} for {class_name}(). {error_text}"
                    )
                    raise TypeError(message)
                setattr(self, key, value)
            elif schema.has_default():
                setattr(self, key, schema.get_default_value())

        if kwargs:
            key = list(kwargs.keys())[0]
            class_name = self.__class__.__name__
            message = f"{key!r} is an invalid keyword argument for {class_name}()."
            raise TypeError(message)

    def __getitem__(self, key: typing.Any) -> typing.Any:
        try:
            field = self.fields[key]
            value = getattr(self, key)
        except (KeyError, AttributeError):
            raise KeyError(key) from None
        else:
            return field.serialize(value)


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class Schema(Mapping, metaclass=SchemaMetaclass):

    fields: typing.Dict[str, Field] = {}

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        if args:
            assert len(args) == 1
            assert not kwargs
            item = args[0]
            if isinstance(item, dict):
                for key in self.fields.keys():
                    if key in item:
                        setattr(self, key, item[key])
            else:
                for key in self.fields.keys():
                    if hasattr(item, key):
                        setattr(self, key, getattr(item, key))
            return

        for key, schema in self.fields.items():
            if key in kwargs:
                value = kwargs.pop(key)
                value, error = schema.validate_or_error(value)
                if error:
                    class_name = self.__class__.__name__
                    error_text = " ".join(
                        [message.text for message in error.messages()]
                    )
                    message = (
                        f"Invalid argument {key!r} for {class_name}(). {error_text}"
                    )
                    raise TypeError(message)
                setattr(self, key, value)
            elif schema.has_default():
                setattr(self, key, schema.get_default_value())

        if kwargs:
            key = list(kwargs.keys())[0]
            class_name = self.__class__.__name__
            message = f"{key!r} is an invalid keyword argument for {class_name}()."
            raise TypeError(message)

    def __iter__(self) -> typing.Iterator[str]:
        for key in self.fields:
            if hasattr(self, key):
                yield key


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class Schema(Mapping, metaclass=SchemaMetaclass):

    fields: typing.Dict[str, Field] = {}

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        if args:
            assert len(args) == 1
            assert not kwargs
            item = args[0]
            if isinstance(item, dict):
                for key in self.fields.keys():
                    if key in item:
                        setattr(self, key, item[key])
            else:
                for key in self.fields.keys():
                    if hasattr(item, key):
                        setattr(self, key, getattr(item, key))
            return

        for key, schema in self.fields.items():
            if key in kwargs:
                value = kwargs.pop(key)
                value, error = schema.validate_or_error(value)
                if error:
                    class_name = self.__class__.__name__
                    error_text = " ".join(
                        [message.text for message in error.messages()]
                    )
                    message = (
                        f"Invalid argument {key!r} for {class_name}(). {error_text}"
                    )
                    raise TypeError(message)
                setattr(self, key, value)
            elif schema.has_default():
                setattr(self, key, schema.get_default_value())

        if kwargs:
            key = list(kwargs.keys())[0]
            class_name = self.__class__.__name__
            message = f"{key!r} is an invalid keyword argument for {class_name}()."
            raise TypeError(message)

    def __len__(self) -> int:
        return len([key for key in self.fields if hasattr(self, key)])


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class Schema(Mapping, metaclass=SchemaMetaclass):

    fields: typing.Dict[str, Field] = {}

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        if args:
            assert len(args) == 1
            assert not kwargs
            item = args[0]
            if isinstance(item, dict):
                for key in self.fields.keys():
                    if key in item:
                        setattr(self, key, item[key])
            else:
                for key in self.fields.keys():
                    if hasattr(item, key):
                        setattr(self, key, getattr(item, key))
            return

        for key, schema in self.fields.items():
            if key in kwargs:
                value = kwargs.pop(key)
                value, error = schema.validate_or_error(value)
                if error:
                    class_name = self.__class__.__name__
                    error_text = " ".join(
                        [message.text for message in error.messages()]
                    )
                    message = (
                        f"Invalid argument {key!r} for {class_name}(). {error_text}"
                    )
                    raise TypeError(message)
                setattr(self, key, value)
            elif schema.has_default():
                setattr(self, key, schema.get_default_value())

        if kwargs:
            key = list(kwargs.keys())[0]
            class_name = self.__class__.__name__
            message = f"{key!r} is an invalid keyword argument for {class_name}()."
            raise TypeError(message)

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        arguments = {
            key: getattr(self, key) for key in self.fields.keys() if hasattr(self, key)
        }
        argument_str = ", ".join(
            [f"{key}={value!r}" for key, value in arguments.items()]
        )
        sparse_indicator = " [sparse]" if self.is_sparse else ""
        return f"{class_name}({argument_str}){sparse_indicator}"


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class Schema(Mapping, metaclass=SchemaMetaclass):

    fields: typing.Dict[str, Field] = {}

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        if args:
            assert len(args) == 1
            assert not kwargs
            item = args[0]
            if isinstance(item, dict):
                for key in self.fields.keys():
                    if key in item:
                        setattr(self, key, item[key])
            else:
                for key in self.fields.keys():
                    if hasattr(item, key):
                        setattr(self, key, getattr(item, key))
            return

        for key, schema in self.fields.items():
            if key in kwargs:
                value = kwargs.pop(key)
                value, error = schema.validate_or_error(value)
                if error:
                    class_name = self.__class__.__name__
                    error_text = " ".join(
                        [message.text for message in error.messages()]
                    )
                    message = (
                        f"Invalid argument {key!r} for {class_name}(). {error_text}"
                    )
                    raise TypeError(message)
                setattr(self, key, value)
            elif schema.has_default():
                setattr(self, key, schema.get_default_value())

        if kwargs:
            key = list(kwargs.keys())[0]
            class_name = self.__class__.__name__
            message = f"{key!r} is an invalid keyword argument for {class_name}()."
            raise TypeError(message)

class Reference(Field):

    errors = {"null": "May not be null."}

    def __init__(
        self,
        to: typing.Union[str, typing.Type[Schema]],
        definitions: typing.Mapping = None,
        **kwargs: typing.Any,
    ) -> None:
        super().__init__(**kwargs)
        self.to = to
        self.definitions = definitions
        if isinstance(to, str):
            self._target_string = to
        else:
            assert issubclass(to, Schema)
            self._target = to


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class Reference(Field):

    errors = {"null": "May not be null."}

    def __init__(
        self,
        to: typing.Union[str, typing.Type[Schema]],
        definitions: typing.Mapping = None,
        **kwargs: typing.Any,
    ) -> None:
        super().__init__(**kwargs)
        self.to = to
        self.definitions = definitions
        if isinstance(to, str):
            self._target_string = to
        else:
            assert issubclass(to, Schema)
            self._target = to

    def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
        if value is None and self.allow_null:
            return None
        elif value is None:
            raise self.validation_error("null")
        return self.target.validate(value, strict=strict)


import typing
from abc import ABCMeta
from collections.abc import Mapping, MutableMapping
from typesystem.base import ValidationError, ValidationResult
from typesystem.fields import Array, Field, Object

class Reference(Field):

    errors = {"null": "May not be null."}

    def __init__(
        self,
        to: typing.Union[str, typing.Type[Schema]],
        definitions: typing.Mapping = None,
        **kwargs: typing.Any,
    ) -> None:
        super().__init__(**kwargs)
        self.to = to
        self.definitions = definitions
        if isinstance(to, str):
            self._target_string = to
        else:
            assert issubclass(to, Schema)
            self._target = to

    def serialize(self, obj: typing.Any) -> typing.Any:
        if obj is None:
            return None
        return dict(obj)


import typing
from typesystem.base import Message, ValidationError
from typesystem.fields import Field
from typesystem.schemas import Schema
from typesystem.tokenize.tokens import Token

def validate_with_positions(
    *, token: Token, validator: typing.Union[Field, typing.Type[Schema]]
) -> typing.Any:
    try:
        return validator.validate(token.value)
    except ValidationError as error:
        messages = []
        for message in error.messages():
            if message.code == "required":
                field = message.index[-1]
                token = token.lookup(message.index[:-1])
                text = f"The field {field!r} is required."
            else:
                token = token.lookup(message.index)
                text = message.text

            positional_message = Message(
                text=text,
                code=message.code,
                index=message.index,
                start_position=token.start,
                end_position=token.end,
            )
            messages.append(positional_message)
        messages = sorted(
            messages, key=lambda m: m.start_position.char_index  # type: ignore
        )
        raise ValidationError(messages=messages)


import re
import typing
from json.decoder import JSONDecodeError, JSONDecoder, scanstring
from typesystem.base import Message, ParseError, Position, ValidationError
from typesystem.fields import Field
from typesystem.schemas import Schema
from typesystem.tokenize.positional_validation import validate_with_positions
from typesystem.tokenize.tokens import DictToken, ListToken, ScalarToken, Token

FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
WHITESPACE = re.compile(r"[ \t\n\r]*", FLAGS)
WHITESPACE_STR = " \t\n\r"
NUMBER_RE = re.compile(
    r"(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?",
    (re.VERBOSE | re.MULTILINE | re.DOTALL),
)

class _TokenizingDecoder(JSONDecoder):

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        content = kwargs.pop("content")
        super().__init__(*args, **kwargs)
        self.scan_once = _make_scanner(self, content)

def tokenize_json(content: typing.Union[str, bytes]) -> Token:
    if isinstance(content, bytes):
        content = content.decode("utf-8", "ignore")

    if not content.strip():
        # Handle the empty string case explicitly for clear error messaging.
        position = Position(column_no=1, line_no=1, char_index=0)
        raise ParseError(text="No content.", code="no_content", position=position)

    decoder = _TokenizingDecoder(content=content)
    try:
        return decoder.decode(content)
    except JSONDecodeError as exc:
        # Handle cases that result in a JSON parse error.
        position = Position(column_no=exc.colno, line_no=exc.lineno, char_index=exc.pos)
        raise ParseError(text=exc.msg + ".", code="parse_error", position=position)


import re
import typing
from json.decoder import JSONDecodeError, JSONDecoder, scanstring
from typesystem.base import Message, ParseError, Position, ValidationError
from typesystem.fields import Field
from typesystem.schemas import Schema
from typesystem.tokenize.positional_validation import validate_with_positions
from typesystem.tokenize.tokens import DictToken, ListToken, ScalarToken, Token

FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
WHITESPACE = re.compile(r"[ \t\n\r]*", FLAGS)
WHITESPACE_STR = " \t\n\r"
NUMBER_RE = re.compile(
    r"(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?",
    (re.VERBOSE | re.MULTILINE | re.DOTALL),
)

def validate_json(
    content: typing.Union[str, bytes],
    validator: typing.Union[Field, typing.Type[Schema]],
) -> typing.Any:
    """
    Parse and validate a JSON string, returning positionally marked error
    messages on parse or validation failures.

    content - A JSON string or bytestring.
    validator - A Field instance or Schema class to validate against.

    Returns a two-tuple of (value, error_messages)
    """
    token = tokenize_json(content)
    return validate_with_positions(token=token, validator=validator)


import typing
from typesystem.base import Message, ParseError, Position, ValidationError
from typesystem.fields import Field
from typesystem.schemas import Schema
from typesystem.tokenize.positional_validation import validate_with_positions
from typesystem.tokenize.tokens import DictToken, ListToken, ScalarToken, Token

def tokenize_yaml(content: typing.Union[str, bytes]) -> Token:
    assert yaml is not None, "'pyyaml' must be installed."

    if isinstance(content, bytes):
        str_content = content.decode("utf-8", "ignore")
    else:
        str_content = content

    if not str_content.strip():
        # Handle the empty string case explicitly for clear error messaging.
        position = Position(column_no=1, line_no=1, char_index=0)
        raise ParseError(text="No content.", code="no_content", position=position)

    class CustomSafeLoader(SafeLoader):
        pass

    def construct_mapping(loader: "yaml.Loader", node: "yaml.Node") -> DictToken:
        start = node.start_mark.index
        end = node.end_mark.index
        mapping = loader.construct_mapping(node)
        return DictToken(mapping, start, end - 1, content=str_content)

    def construct_sequence(loader: "yaml.Loader", node: "yaml.Node") -> ListToken:
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_sequence(node)
        return ListToken(value, start, end - 1, content=str_content)

    def construct_scalar(loader: "yaml.Loader", node: "yaml.Node") -> ScalarToken:
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_scalar(node)
        return ScalarToken(value, start, end - 1, content=str_content)

    def construct_int(loader: "yaml.Loader", node: "yaml.Node") -> ScalarToken:
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_yaml_int(node)
        return ScalarToken(value, start, end - 1, content=str_content)

    def construct_float(loader: "yaml.Loader", node: "yaml.Node") -> ScalarToken:
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_yaml_float(node)
        return ScalarToken(value, start, end - 1, content=str_content)

    def construct_bool(loader: "yaml.Loader", node: "yaml.Node") -> ScalarToken:
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_yaml_bool(node)
        return ScalarToken(value, start, end - 1, content=str_content)

    def construct_null(loader: "yaml.Loader", node: "yaml.Node") -> ScalarToken:
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_yaml_null(node)
        return ScalarToken(value, start, end - 1, content=str_content)

    CustomSafeLoader.add_constructor(
        yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, construct_mapping
    )

    CustomSafeLoader.add_constructor(
        yaml.resolver.BaseResolver.DEFAULT_SEQUENCE_TAG, construct_sequence
    )

    CustomSafeLoader.add_constructor(
        yaml.resolver.BaseResolver.DEFAULT_SCALAR_TAG, construct_scalar
    )

    CustomSafeLoader.add_constructor("tag:yaml.org,2002:int", construct_int)

    CustomSafeLoader.add_constructor("tag:yaml.org,2002:float", construct_float)

    CustomSafeLoader.add_constructor("tag:yaml.org,2002:bool", construct_bool)

    CustomSafeLoader.add_constructor("tag:yaml.org,2002:null", construct_null)

    try:
        return yaml.load(str_content, CustomSafeLoader)
    except (yaml.scanner.ScannerError, yaml.parser.ParserError) as exc:  # type: ignore
        # Handle cases that result in a YAML parse error.
        text = exc.problem + "."
        position = _get_position(str_content, index=exc.problem_mark.index)
        raise ParseError(text=text, code="parse_error", position=position)


import typing
from typesystem.base import Message, ParseError, Position, ValidationError
from typesystem.fields import Field
from typesystem.schemas import Schema
from typesystem.tokenize.positional_validation import validate_with_positions
from typesystem.tokenize.tokens import DictToken, ListToken, ScalarToken, Token

def validate_yaml(
    content: typing.Union[str, bytes],
    validator: typing.Union[Field, typing.Type[Schema]],
) -> typing.Any:
    """
    Parse and validate a YAML string, returning positionally marked error
    messages on parse or validation failures.

    content - A YAML string or bytestring.
    validator - A Field instance or Schema class to validate against.

    Returns a two-tuple of (value, error_messages)
    """
    assert yaml is not None, "'pyyaml' must be installed."

    token = tokenize_yaml(content)
    return validate_with_positions(token=token, validator=validator)


import typing
from typesystem.base import Position

class Token:

    def __init__(
        self, value: typing.Any, start_index: int, end_index: int, content: str = ""
    ) -> None:
        self._value = value
        self._start_index = start_index
        self._end_index = end_index
        self._content = content


import typing
from typesystem.base import Position

class Token:

    def __init__(
        self, value: typing.Any, start_index: int, end_index: int, content: str = ""
    ) -> None:
        self._value = value
        self._start_index = start_index
        self._end_index = end_index
        self._content = content

    def lookup(self, index: list) -> "Token":
        """
        Given an index, lookup a child token within this structure.
        """
        token = self
        for key in index:
            token = token._get_child_token(key)
        return token


import typing
from typesystem.base import Position

class Token:

    def __init__(
        self, value: typing.Any, start_index: int, end_index: int, content: str = ""
    ) -> None:
        self._value = value
        self._start_index = start_index
        self._end_index = end_index
        self._content = content

    def lookup_key(self, index: list) -> "Token":
        """
        Given an index, lookup a token for a dictionary key within this structure.
        """
        token = self.lookup(index[:-1])
        return token._get_key_token(index[-1])


import typing
from typesystem.base import Position

class Token:

    def __init__(
        self, value: typing.Any, start_index: int, end_index: int, content: str = ""
    ) -> None:
        self._value = value
        self._start_index = start_index
        self._end_index = end_index
        self._content = content

    def __repr__(self) -> str:
        return "%s(%s)" % (self.__class__.__name__, repr(self.string))


import typing
from typesystem.base import Position

class Token:

    def __init__(
        self, value: typing.Any, start_index: int, end_index: int, content: str = ""
    ) -> None:
        self._value = value
        self._start_index = start_index
        self._end_index = end_index
        self._content = content

    def __eq__(self, other: typing.Any) -> bool:
        return isinstance(other, Token) and (
            self._get_value() == other._get_value()
            and self._start_index == other._start_index
            and self._end_index == other._end_index
        )