# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['koda_validate']

package_data = \
{'': ['*']}

install_requires = \
['koda==1.4.0']

setup_kwargs = {
    'name': 'koda-validate',
    'version': '2.1.0',
    'description': 'Typesafe, composable validation',
    'long_description': '# Koda Validate\n\nValidate Anything. Faster!\n\nKoda Validate is:\n- flexible\n- explicit\n- fully asyncio-compatible\n- type-driven (works with type hints without plugins)\n- easily inspected -- build API schemas from validators\n\n\n### Contents\n- [Installation](#installation)\n- [The Basics](#the-basics)\n- [Philosophy](#philosophy)\n  - [Validators](#validators)\n  - [Predicates](#predicates)\n  - [Processors](#processors)\n- [Extension](#extension) \n- [Validation Errors](#validation-errors)\n- [Async Validation](#async-validation)\n- [Using Metadata](#using-metadata)\n- [Tour](#tour)\n- [Comparison to Pydantic](#comparison-to-pydantic)\n\n\n## Installation\nPython 3.8+\n\npip\n```bash\npip install koda-validate\n```\nPoetry\n```bash\npoetry add koda-validate\n```\n\n## The Basics\n\n### Scalars\n```python3\nfrom koda_validate import * \n\nstring_validator = StringValidator()\n\nstring_validator("hello world")\n# > Valid(\'hello world\')\n\nstring_validator(5)\n# > Invalid([\'expected a string\'])\n\n```\nNote that you can pattern match on validated data on python >= 3.10\n```python3\n# continued from above\n\nmatch string_validator("new string"):\n    case Valid(valid_val):\n        print(f"{valid_val} is valid!")\n    case Invalid(err):\n        print(f"got error: {err}")\n\n# prints: "new string is valid"\n```\nYou can also use `.is_valid` on python >= 3.8+:\n```python3\n# continued from above\n\nif (result := string_validator("another string")).is_valid:\n    print(f"{result.val} is valid!")\nelse:    \n    print(f"got error: {result.val}")\n# prints: "another string is valid"\n```\nMypy understands `.is_valid` and narrows the `Validated` type to `Valid` or `Invalid` appropriately.\n\n### Lists\n```python3\nfrom koda_validate import *\n\nvalidator = ListValidator(StringValidator())\n\nvalidator(["cool"])\n# > Valid([\'cool\'])\n\nvalidator([5])\n# > Invalid({\'0\': [\'expected a string\']}))\n\n```\n\n### Record-like Dictionaries\n\n```python3\nfrom dataclasses import dataclass\n\nfrom koda_validate import *\n\n\n@dataclass\nclass Person:\n  name: str\n  hobbies: list[str]\n\n\nperson_validator = RecordValidator(\n  keys=(\n    ("name", StringValidator()),\n    ("hobbies", ListValidator(StringValidator())),\n  ),\n  into=Person\n)\n\nprint(person_validator({"name": "Bob",\n                        "hobbies": ["eating", "running"]}))\n# > Valid(Person(name=\'Bob\', hobbies=[\'eating\', \'running\']))\n```\n\n### Map-like Dictionaries\n```python3\nfrom koda_validate import *\n\nstr_to_int_validator = MapValidator(key=StringValidator(),\n                                    value=IntValidator())\n\nassert str_to_int_validator({"a": 1, "b": 25, "xyz": 900}) == Valid(\n    {"a": 1, "b": 25, "xyz": 900}\n)\n\n```\n\n### Schema-ed Dictionaries  \n```python3\nfrom koda_validate import *\n\nperson_validator = DictValidatorAny({\n    "name": StringValidator(),\n    "age": IntValidator(),\n})\n\nresult = person_validator({"name": "John Doe", "age": 30})\nif isinstance(result, Valid):\n    print(f"{result.val[\'name\']} is {result.val[\'age\']} years old")\nelse:\n    print(result.val)\n\n# prints: "John Doe is 30 years old"\n```\nNote that `DictValidatorAny` is not typesafe.\n\nSome of what we\'ve seen so far:\n- All validators we\'ve created are simple `Callable`s that return an `Valid` instance when validation succeeds, or an `Invalid` instance when validation fails.\n- Nesting one validator within another is straightforward\n- We have multiple means of validating dictionaries\n- [RecordValidator](#recordvalidator) requires a separate target for its validated data; more on that [here](#recordvalidator).\n\nIt\'s worth noting that all this code is typesafe (aside from [DictValidatorAny](#dictvalidatorany), which is explicitly not typesafe). No plugins are required for mypy. \n\n## Philosophy\nAt it\'s core, Koda Validate is based on a few simple ideas about what validation _is_. This \nallows Koda Validate to be extended to validate essentially any kind of data. It also generally allows for \nless code, and clearer paths to optimization than other approaches.\n\n### Validators\nIn Koda Validate, `Validator` is the fundamental validation building block. It\'s based on the idea that  \nvalidation can be universally represented by the function signature (pseudocode): \n```\nInputType -> ValidType | InvalidType\n```\nIn Koda Validate this looks more like: \n```python3\nCallable[[InputType], Validated[ValidType, InvalidType]]\n``` \nA quick example:\n```python3\nfrom koda_validate import IntValidator, Valid, Invalid\n\nint_validator = IntValidator()\n\nassert int_validator(5) == Valid(5)\nassert int_validator("not an integer") == Invalid(["expected an integer"])\n```\nHere, we can tell the type of `int_validator` is something like `Callable[[Any], Validated[int, List[str]]` (it\'s not exactly \nthat in reality, but it isn\'t far off.) In this case, the `InputType` is `Any` -- any kind of data can be submitted to validation; if the data is valid it returns `Valid[int]`; and\nif it\'s invalid it returns `Invalid[List[str]]`. \n\nThis is a useful model to have for validation, because it means we can **combine**\nvalidators in different ways (i.e. nesting them), and have our model of validation be consistent throughout.\n\nTake a look at [Extension](#extension) to see how to build custom `Validator`s.\n\n### Predicates\nIn the world of validation, predicates are simple expressions that return a `True` or `False` for a given condition. Koda Validate uses a \nclass based on this concept, `Predicate`, to _enrich_ `Validator`s. Because the type and value of a `Validator`\'s valid state may\ndiffer from those of its input, it\'s difficult to do something like apply a list of `Validator`s to a given value:\neven _if_ the types all match up, there\'s no assurance that the values won\'t change from one validator to the next. \n\nThe role of a `Predicate` in Koda Validate is to perform additional validation _after_ the data has been verified to be \nof a specific type or shape. To this end, `Predicate`s in Koda Validate cannot change their input types or values. Let\'s go further with our `IntValidator`:\n```python3\nfrom koda_validate import * \n\nint_validator = IntValidator(Min(5))\n\nassert int_validator(6) == Valid(6)\n\nassert int_validator(4) == Invalid(["minimum allowed value is 5"])\n```\nIn this example `Min(5)` is a `Predicate`. As you can see the value 4\npasses the `int` check but fails to pass the `Min(5)` predicate.\n\nBecause we know that predicates don\'t change the type or value of their inputs, we can \nsequence an arbitrary number of them together, and validate them all.\n\n```python3\nfrom koda_validate import * \n\nint_validator = IntValidator(Min(5), Max(20), MultipleOf(4))\n\nassert int_validator(12) == Valid(12)\n\nassert int_validator(23) == Invalid([\n  "maximum allowed value is 20",\n  "must be a multiple of 4"\n])\n```\nHere we have 3 `Predicate`s, but we could easily have dozens. Note that the errors from all invalid\npredicates are returned. This is possible because we know that the value should be consistent from one predicate to the next.\n\n`Predicate`s are easy to write -- take a look at [Extension](#extension) for more details.\n\n\n### Processors\n`Processor`s allow us to take a value of a given type and transform it into another value of that type. Processors are most useful\n_after_ type validation, but _before_ predicates are checked. Here\'s an example:\n```python3\nfrom koda_validate import *\n\nmax_length_3_validator = StringValidator(\n  MaxLength(3),\n  preprocessors=[strip, upper_case]\n)\n\nassert max_length_3_validator(" hmm ") == Valid("HMM")\n```\nWe see that the `preprocessors` stripped the whitespace from `" hmm "` and then transformed it to upper-case before\nit was checked against the `MaxLength(3)` `Predicate`. \n\nProcessors are very simple to write -- see [Extension](#extension) for more details.\n\n## Extension\nKoda Validate aims to provide enough tools to handle most common validation needs; for the cases it doesn\'t\ncover, it aims to allow easy extension. \n\nEven though there is an existing `FloatValidator` in Koda Validate, we\'ll build our own. (Extension does not\nneed to be limited to new functionality; it can also be writing alternatives to the default for custom needs.)\n\n```python3\nfrom typing import Any\nfrom koda_validate import * \n\n\nclass SimpleFloatValidator(Validator[Any, float, Serializable]):\n    def __call__(self, val: Any) -> Validated[float, Serializable]:\n        if isinstance(val, float):\n            return Valid(val)\n        else:\n            return Invalid("expected a float")\n\n\nfloat_validator = SimpleFloatValidator()\n\ntest_val = 5.5\n\nassert float_validator(test_val) == Valid(test_val)\n\nassert float_validator(5) == Invalid("expected a float")\n```\n\nWhat is this doing? \n- extending `Validator`, using the following types:\n  - `Any`: any type of input can be passed in to be validated\n  - `float`: if the data is valid, a value of type `Valid[float]` will be returned \n  - `Serializable`: if it\'s invalid, a value of type `Invalid[Serializable]` will be returned\n  - note that mypy understands the role of all of these types \n- the `__call__` method performs any kind of validation needed, so long as the input and output type signatures -- as determined by the `Validator` type parameters - are abided\n\nWe accept `Any` because the type of input may be unknown before submitting to the `Validator`. After our \nvalidation in `SimpleFloatValidator` succeeds, we know the type must be `float`. (Note that we could have coerced the value\nto a `float` instead of checking its type -- that is 100% OK to do. For simplicity\'s sake, this validator does not coerce.)\n\nThis is all well and good, but we\'ll probably want to be able to validate against values of the floats, such as min, \nmax, or rough equality checks. For this we use `Predicate`s. For example, if we wanted to allow a single `Predicate` in \nour `SimpleFloatValidator` we could do it like this:\n\n```python3\nfrom dataclasses import dataclass\nfrom typing import Any, Optional\nfrom koda_validate import *\n\n@dataclass\nclass SimpleFloatValidator2(Validator[Any, float, Serializable]):\n    predicate: Optional[Predicate[float, Serializable]] = None\n\n    def __call__(self, val: Any) -> Validated[float, Serializable]:\n        if isinstance(val, float):\n            if self.predicate:\n                return self.predicate(val)\n            else:\n                return Valid(val)\n        else:\n            return Invalid(["expected a float"])\n\n```\nIf `predicate` is specified, we\'ll check it _after_ we\'ve verified the type of the value.\n\n`Predicate`s are meant to validate the _value_ of a known type -- as opposed to validating at the type-level (that\'s what the `Validator` does). \nFor example, this is how you might write and use a `Predicate` to validate a range of values:\n\n```python3\n# (continuing from previous example)\n\n@dataclass\nclass Range(Predicate[float, Serializable]):\n    minimum: float\n    maximum: float\n\n    def is_valid(self, val: float) -> bool:\n        return self.minimum <= val <= self.maximum\n\n    def err(self, val: float) -> Serializable:\n        return f"expected a value in the range of {self.minimum} and {self.maximum}"\n\n\nrange_validator = SimpleFloatValidator2(Range(0.5, 1.0))\ntest_val = 0.7\n\nassert range_validator(test_val) == Valid(test_val)\n\nassert range_validator(0.01) == Invalid(["expected a value in the range of 0.5 and 1.0"])\n\n```\n\nNotice that in `Predicate`s we define `is_valid` and `err` methods, while in `Validator`s we define the \nentire `__call__` method. This is because the base `Predicate` class is constructed in such a way that we limit how \nmuch it can actually do -- we don\'t want it to be able to alter the value being validated.\n\nFinally, let\'s add a `Processor`. A `Processor` is a function that takes a value of one type and then produces another \nvalue of that type. In our case, we want to preprocess our `float`s by converting them to their absolute value.\n\n```python3\n# (continuing from previous example)\n\n@dataclass\nclass SimpleFloatValidator3(Validator[Any, float, Serializable]):\n    predicate: Optional[Predicate[float, Serializable]] = None\n    preprocessor: Optional[Processor[float]] = None\n\n    def __call__(self, val: Any) -> Validated[float, Serializable]:\n        if isinstance(val, float):\n            if self.preprocessor:\n                val = self.preprocessor(val)\n\n            if self.predicate:\n                return self.predicate(val)\n            else:\n                return Valid(val)\n        else:\n            return Invalid(["expected a float"])\n\n\nclass AbsValue(Processor[float]):\n    def __call__(self, val: float) -> float:\n        return abs(val)\n\n\nrange_validator_2 = SimpleFloatValidator3(\n    predicate=Range(0.5, 1.0),\n    preprocessor=AbsValue()\n)\n\ntest_val = -0.7\n\nassert range_validator_2(test_val) == Valid(abs(test_val))\n\nassert range_validator_2(-0.01) == Invalid(\'expected a value in the range of 0.5 and 1.0\')\n```\nNote that we pre-process _after_ type checking but _before_ the predicates are run. This is the general approach \nKoda Validate takes on built-in validators. More specifically, the built-in validators expect there to be a pipeline of \nactions taken within a `Validator`: `type-check/coerce -> preprocess -> validate predicates`, where it can fail \nvalidation at either the first or last stage.\n\nNote that what we\'ve written are a number of classes that simply conform to some type constraints. It\'s worth remembering that\nthere\'s nothing enforcing the particular arrangement of logic we have in our `SimpleFloatValidator`. If you want to have a \npost-processing step, you can. If you want to validate an iso8601 string is a datetime, and then convert that to an unix \nepoch timestamp, and provide pre-processing, post-processing and predicates for all those steps, you can. It\'s important \nto remember that our `Validator`, `Predicate` (and `PredicateAsync` -- see [Async Validation](#async-validation)), and \n`Processor` objects are little more than functions with accessible metadata. You can do whatever you want with them.\n\n## Validation Errors\n\nIn Koda Validate errors are returned as data as part of normal control flow. `Invalid` instances from built-in `Validator`s\ncontain JSON/YAML serializable values. (Should you build your own custom validators, there is no contract enforcing \nthat constraint.) Here are a few examples of the kinds of errors you can expect to see out of the box.\n\n```python3\nfrom dataclasses import dataclass\n\nfrom koda import Maybe\n\nfrom koda_validate import *\n\n# Wrong type\nassert StringValidator()(None) == Invalid(["expected a string"])\n\n# All failing `Predicate`s are reported (not just the first)\nstr_choice_validator = StringValidator(MinLength(2),\n                                       Choices({"abc", "yz"}))\n\nassert str_choice_validator("") == Invalid(\n    ["minimum allowed length is 2", "expected one of [\'abc\', \'yz\']"]\n)\n\n\n@dataclass\nclass City:\n    name: str\n    region: Maybe[str]\n\n\ncity_validator = RecordValidator(\n    into=City,\n    keys=(\n        ("name", StringValidator(not_blank)),\n        ("region", KeyNotRequired(StringValidator(not_blank))),\n    ),\n)\n\n# We use the key "__container__" for object-level errors\nassert city_validator(None) == Invalid({"__container__": ["expected a dictionary"]})\n\n# Missing keys are errors\nprint(city_validator({}))\nassert city_validator({}) == Invalid({"name": ["key missing"]})\n\n# Extra keys are also errors\nassert city_validator(\n    {"region": "California", "population": 510, "country": "USA"}\n) == Invalid(\n    {"__container__": ["Received unknown keys. Only expected \'name\', \'region\'."]}\n)\n\n\n@dataclass\nclass Neighborhood:\n    name: str\n    city: City\n\n\nneighborhood_validator = RecordValidator(\n    into=Neighborhood,\n    keys=(("name", StringValidator(not_blank)), ("city", city_validator)),\n)\n\n# Errors are nested in predictable manner\nassert neighborhood_validator({"name": "Bushwick", "city": {}}) == Invalid(\n    {"city": {"name": ["key missing"]}}\n)\n\n```\nIf you have any concerns about being able to handle specific types of key or object requirements, please see  \nthe documentation on specific validators below:\n- [RecordValidator](#recordvalidator)\n- [DictValidatorAny](#dictvalidatorany)\n- [MapValidator](#mapvalidator)\n- [OneOf2 / OneOf3](#oneof2--oneof3)\n- [OptionalValidator](#optionalvalidator)\n- [is_dict_validator](#is_dict_validator)\n- [Lazy](#lazy)\n\n\n## Async Validation\nAll the built-in `Validator`s in Koda are asyncio-compatible, and there is a simple, consistent way to run async validation. You \njust call a `Validator` in this form:\n```python3\nawait validator.validate_async("abc")\n```\ninstead of:\n```python3\nvalidator("abc")\n```\n\n(Feel free to check out [The Basics](#the-basics) and [Philosophy](#philosophy) if you are missing any context on how \nvalidation has been documented up to this point.)\n\nFor example, this is how you could re-use the same `StringValidator` instance in both\nsync and async contexts:\n```python3\nimport asyncio\nfrom koda_validate import *\n\n\nshort_string_validator = StringValidator(MaxLength(10))\n\nassert short_string_validator("sync") == Valid("sync")\n\n# we\'re not in an async context, so we can\'t use `await` here; instead we use asyncio.run\nassert asyncio.run(short_string_validator.validate_async("async")) == Valid("async")\n```\n\nSynchronous validators can be used in both async and sync contexts. Nonetheless, while this Validator works in async mode,\nit isn\'t yielding any benefit for IO. It would be much more useful if we were doing something like querying a database\nasynchronously:\n```python3\nimport asyncio\nfrom koda_validate import *\n\n\nclass IsActiveUsername(PredicateAsync[str, Serializable]):\n    async def is_valid_async(self, val: str) -> bool:\n        # add some latency to pretend we\'re calling the db\n        await asyncio.sleep(.01)\n\n        return val in {"michael", "gob", "lindsay", "buster"}\n\n    async def err_async(self, val: str) -> Serializable:\n        return "invalid username"\n\n\nusername_validator = StringValidator(MinLength(1), \n                                     predicates_async=[IsActiveUsername()])\n\nassert asyncio.run(username_validator.validate_async("michael")) == Valid("michael")\n\nassert asyncio.run(username_validator.validate_async("tobias")) == Invalid(["invalid username"])\n\n# calling in sync mode raises an AssertionError\ntry:\n    username_validator("michael")\nexcept AssertionError as e:\n    print(e)\n\n```\nIn this example we are calling the database to verify a username. A few things worth pointing out:\n`PredicateAsync`s are specified in the `predicates_async` keyword argument -- separately from `Predicates`. We do this to be \nexplicit -- we don\'t want to be confused about whether a validator requires `asyncio`. (If you try to run this validator in \nsynchronous mode, it will raise an `AssertionError` -- instead make sure you call it like \n`await username_validator.validate_async("buster")`.)\n\nLike other validators, you can nest async `Validator`s. Again, the only difference needed is to use the `.validate_async`\nmethod of the outer-most validator.\n```python3\n# continued from previous example\n\nusername_list_validator = ListValidator(username_validator)\n\nassert asyncio.run(username_list_validator.validate_async(["michael", "gob", "lindsay", "buster"])) == Valid([\n  "michael", "gob", "lindsay", "buster"\n])\n\n```\nYou can run async validation on nested lists, dictionaries, tuples, strings, etc. All `Validator`s built into to Koda Validate\nunderstand the `.validate_async` method. \n\nKoda Validate makes no assumptions about running async `Validator`s or `PredicateAsync`s concurrently; it is expected that that is\nhandled by the surrounding context. That is to say, async validators will not block when performing IO -- as is normal -- but if you had, say, 10 async \npredicates, they would not be run in parallel by default. This is simply because that is too much of an assumption for this library to make -- we don\'t \nwant to accidentally send N simultaneous requests to some other service without the intent being explicitly defined. If you\'d like to have `Validator`s \nor `Predicate`s run in parallel _within_ the validation step, all you should need to do is write a simple wrapper class based on either `Validator` \nor `Predicate`, implementing whatever concurrency needs you have.\n\nFor custom async `Validator`s, all you need to do is implement the `validate_async` method on a `Validator` class. There is no\nseparate async-only `Validator` class. This is because we might want to re-use synchronous validators in either synchronous\nor asynchronous contexts. Here\'s an example of making a `SimpleFloatValidator` async-compatible:\n```python3\nimport asyncio\nfrom typing import Any\n\nfrom koda_validate import *\n\n\nclass SimpleFloatValidator(Validator[Any, float, Serializable]):\n    def __call__(self, val: Any) -> Validated[float, Serializable]:\n        if isinstance(val, float):\n            return Valid(val)\n        else:\n            return Invalid("expected a float")\n\n    # this validator doesn\'t do any IO, so we can just use the `__call__` method\n    async def validate_async(self, val: Any) -> Validated[float, Serializable]:\n        return self(val)\n\n\nfloat_validator = SimpleFloatValidator()\n\ntest_val = 5.5\n\nassert asyncio.run(float_validator.validate_async(test_val)) == Valid(test_val)\n\nassert asyncio.run(float_validator.validate_async(5)) == Invalid("expected a float")\n\n```\n\nIf your `Validator` only makes sense in an async context, then you probably don\'t need to implement the `__call__` method. \nInstead, you\'d just implement the `.validate_async` method and make sure that validator is always called by `await`-ing \nthe `.validate_async` method. A `NotImplementedError` will be raised if you try to use the `__call__` method on an \nasync-only `Validator`. \n\n## Using Metadata\nOne of Koda Validate\'s design objectives is to allow reuse of validator metadata. Principally this \nis useful in generating descriptions of the validator\'s constraints -- one example could be generating\nan OpenAPI (or other) schema. Here we\'ll do something simpler and use validator metadata to build a function which can \nreturn plaintext descriptions of validators:\n\n```python3\nfrom typing import Any\nfrom koda_validate import * \n\n\ndef describe_validator(validator: Validator[Any, Any, Any] | Predicate[Any, Any]) -> str:\n    # use `isinstance(...)` in python <= 3.10\n    match validator:\n        case StringValidator(predicates):\n            predicate_descriptions = [\n                f"- {describe_validator(pred)}" for pred in predicates\n            ]\n            return "\\n".join(["validates a string"] + predicate_descriptions)\n        case MinLength(length):\n            return f"minimum length {length}"\n        case MaxLength(length):\n            return f"maximum length {length}"\n        # ...etc\n        case _:\n            raise TypeError(f"unhandled validator type. got {type(validator)}")\n\n\nprint(describe_validator(StringValidator()))\n# validates a string\nprint(describe_validator(StringValidator(MinLength(5))))\n# validates a string\n# - minimum length 5\nprint(describe_validator(StringValidator(MinLength(3), MaxLength(8))))\n# validates a string\n# - minimum length 3\n# - maximum length 8\n\n```\nAll we\'re doing here, of course, is writing an interpreter. For the sake of brevity this one is very simple, but it\'s\nstraightforward to extend the logic. This is easy to do because, while the validators are `Callable`s at their \ncore, they are also classes that can easily be inspected. Interpreters are the recommended way to re-use validator metadata for\nnon-validation purposes.\n\n\n## Tour\nHere are some noteworthy parts of Koda Validate explained in more detail.\n\n### RecordValidator\n`RecordValidator` is a flexible way to validate a dictionary into some other form (or back into a dictionary, if desired). It\nis primarily for record-like dictionaries ([MapValidator](#mapvalidator) is recommended for map-like dictionaries).\n\n```python3\nfrom dataclasses import dataclass\nfrom koda import Maybe, Just\nfrom koda_validate import *\n\n\n@dataclass\nclass Person:\n  name: str\n  age: Maybe[int]\n\n\nperson_validator = RecordValidator(\n  into=Person,\n  keys=(\n    ("full name", StringValidator()),\n    ("age", KeyNotRequired(IntValidator())),\n  ),\n)\n\n# you can use `isinstance` in python 3.8 or 3.9 instead of `match`\nmatch person_validator({"full name": "John Doe", "age": 30}):\n    case Valid(person):\n        match person.age:\n            case Just(age):\n                age_message = f"{age} years old"\n            case nothing:\n                age_message = "ageless"\n        print(f"{person.name} is {age_message}")\n    case Invalid(errs):\n        print(errs)\n```\nWhat do we see here?\n- `into=Person`: `RecordValidator` is fundamentally de-coupled from it\'s target -- in this case the `Person` class. It can target any\n`Callable` that accepts the validated values from the keys, in the same order they are defined -- the names of the keys do not \nmatter to the target. Often it will make sense to target some kind of `dataclass` or `NamedTuple` that conforms to the needed \nargs, but you can use any Callable that accepts the correct arguments.\n- `KeyNotRequired` results in a `Maybe` value. In the example above, age was returned as `Just(30)` because\nthe key was present. If the key was not present, the validation could still have succeeded, but the value for age\nwould be `nothing`. This gives us an explicit indication of whether a key was present or not.\n- `"full name"`: keys are not bound to any kind of special form. They don\'t need to be strings that are valid attribute names; \nthey don\'t even need to be strings.\n\n`RecordValidator` is extremely flexible because it can handle any kind of dictionary key, and whether it is required or not. So,\nyou can validate _weird_ data like this:\n```python3\nfrom typing import List\nfrom dataclasses import dataclass\nfrom koda import Maybe, Just\nfrom koda_validate import *\n\n\n@dataclass\nclass Person:\n    name: str\n    age: Maybe[int]\n    hobbies: List[str]\n\n\nperson_validator = RecordValidator(\n    into=Person,\n    keys=(\n        (1, StringValidator()),\n        (False, KeyNotRequired(IntValidator())),\n        (("abc", 123), ListValidator(StringValidator()))\n    ),\n)\n\nassert person_validator({\n    1: "John Doe",\n    False: 30,\n    ("abc", 123): ["reading", "cooking"]\n}) == Valid(Person(\n    "John Doe",\n    Just(30),\n    ["reading", "cooking"]\n))\n```\nIn the opinion of this library, yes, dictionaries that use integers, bools, and tuples as keys simultaneously are _weird_. But we still\nvalidate it :sparkles:\n\nIn `RecordValidator`, you can also validate the entire object after keys are validated by providing a `validate_object` argument.\n```python3\nfrom dataclasses import dataclass\n\nfrom koda_validate import *\n\n\n@dataclass\nclass Employee:\n    title: str\n    name: str\n\n\ndef no_dwight_regional_manager(employee: Employee) -> Validated[Employee, Serializable]:\n    if (\n        "schrute" in employee.name.lower()\n        and employee.title.lower() == "assistant regional manager"\n    ):\n        return Invalid("Assistant TO THE Regional Manager!")\n    else:\n        return Valid(employee)\n\n\nemployee_validator = RecordValidator(\n    into=Employee,\n    keys=(\n        ("title", StringValidator(not_blank, MaxLength(100), preprocessors=[strip])),\n        ("name", StringValidator(not_blank, preprocessors=[strip])),\n    ),\n    # After we\'ve validated individual fields, we may want to validate them as a whole\n    validate_object=no_dwight_regional_manager,\n)\n\n\n# The fields are valid but the object as a whole is not.\nassert employee_validator(\n    {\n        "title": "Assistant Regional Manager",\n        "name": "Dwight Schrute",\n    }\n) == Invalid("Assistant TO THE Regional Manager!")\n\n```\nIn this case the values of individual keys are valid, but the object as a whole is not. \n\nIt\'s worth noting you can specify `validate_object_async` instead if you need to use asyncio in your validation. Remember, \nyou must use the `.validate_async` method when doing any kind of async validation.\n\n#### Limitations\n`RecordValidator` is currently limited to at-most 16 keys. This is simply because mypy gets slower and slower\nwhen typechecking against the `@overload`s for RecordValidator\'s `__init__` method. In the uncommon case that  \nyou need to validate 16+ fields on a record-like object, you may be able to use `DictValidatorAny`, `MapValidator`, or, in some cases,\n`OneOf2`/`OneOf3` in combination with `RecordValidator`. There is also the possibility to generate the code into your project\nif you want more keys:\n\n```bash\n# allow up to 30 keys\npython /path/to/koda-validate/codegen/generate.py /your/target/directory --num-keys 30\n```\n  \n\n### DictValidatorAny\n`DictValidatorAny` is somewhat similar to `RecordValidator`, but there are several key differences:\n- It accepts a dictionary `schema` instead of a tuple of key / value 2-tuples.\n- It does not narrow the types on either the key or the value. If valid, the type of returned data will be `Dict[Any, Any]`. (This is why it has `Any` in its name.)\n- It can allow for arbitrary amounts of keys\n- It passes along the keys, so the validated object may appear quite similar to the input. Note that \nit will always return a new dictionary (if valid), and it is legal for values to differ from the input.\n\nThis is an equivalent example to the last `RecordValidator` example above. \n\n```python3\nfrom typing import Any, Dict, Hashable\n\nfrom koda_validate import *\n\n\ndef no_dwight_regional_manager(\n    employee: Dict[Hashable, Any]\n) -> Validated[Dict[Hashable, Any], Serializable]:\n    if (\n        "schrute" in employee["name"].lower()\n        and employee["title"].lower() == "assistant regional manager"\n    ):\n        return Invalid("Assistant TO THE Regional Manager!")\n    else:\n        return Valid(employee)\n\n\nemployee_validator = DictValidatorAny(\n    {\n        "title": StringValidator(not_blank, MaxLength(100), preprocessors=[strip]),\n        "name": StringValidator(not_blank, preprocessors=[strip]),\n    },\n    # After we\'ve validated individual fields, we may want to validate them as a whole\n    validate_object=no_dwight_regional_manager,\n)\n\nassert employee_validator(\n    {"name": "Jim Halpert", "title": "Sales Representative"}\n) == Valid({"name": "Jim Halpert", "title": "Sales Representative"})\n\nassert employee_validator(\n    {\n        "title": "Assistant Regional Manager",\n        "name": "Dwight Schrute",\n    }\n) == Invalid("Assistant TO THE Regional Manager!")\n```\n\n#### ListValidator\n`ListValidator` validates whether some value is a list. It requires a validator to validate each item in the list. It \ncan have predicates (number of items, etc.), as well as preprocessors.\n\n```python3\nfrom koda_validate import *\n\nbinary_list_validator = ListValidator(\n    IntValidator(Choices({0, 1})),\n    predicates=[MinItems(2)]\n)\n\nassert binary_list_validator([1, 0, 0, 1, 0]) == Valid([1, 0, 0, 1, 0])\n\nassert binary_list_validator([1]) == Invalid({\'__container__\': [\'minimum allowed length is 2\']})\n\nassert binary_list_validator([0, 1.0, "0"]) == Invalid({\'1\': [\'expected an integer\'], \'2\': [\'expected an integer\']})\n```\nIn case you\'re looking at the last example and wondering why the indexes `\'1\'` and `\'2\'` are strings, it\'s because all \nbuilt-in validators in Koda Validate return JSON serializable data. In JSON, keys in objects are only allowed to \nbe strings.\n\n\n### MapValidator\n\n`MapValidator` allows us to validate dictionaries that are mappings of one type to another type, where we don\'t\nneed to be concerned about individual keys or values:\n\n```python3\nfrom koda_validate import *\n\nstr_to_int_validator = MapValidator(key=StringValidator(),\n                                    value=IntValidator())\n\nassert str_to_int_validator({"a": 1, "b": 25, "xyz": 900}) == Valid(\n    {"a": 1, "b": 25, "xyz": 900}\n)\n\nassert str_to_int_validator({3.14: "pi!"}) == Invalid({\n    \'3.14\': {\'key_error\': [\'expected a string\'],\n             \'value_error\': [\'expected an integer\']}\n})\n```\n\n\n### OneOf2 / OneOf3\n\nOneOfN validators are useful when you may have multiple valid shapes of data.\n```python3\nfrom koda import First, Second\n\nfrom koda_validate import * \n\nstring_or_list_string_validator = OneOf2(\n    StringValidator(), ListValidator(StringValidator())\n)\n\nassert string_or_list_string_validator("ok") == Valid(First("ok"))\n\nassert string_or_list_string_validator(["list", "of", "strings"]) == Valid(\n    Second(["list", "of", "strings"])\n)\n\n```\n\n\n### Tuple2Validator / Tuple3Validator\n\nThese `Validator`s work on `tuple`s as you might expect:\n```python3\nfrom koda_validate import * \n\nstring_int_validator = Tuple2Validator(StringValidator(), IntValidator())\n\nassert string_int_validator(("ok", 100)) == Valid(("ok", 100))\n\n# also ok with lists\nassert string_int_validator(["ok", 100]) == Valid(("ok", 100))\n\n```\n\n\n### Lazy\n`Lazy`\'s main purpose is to allow for the use of recursion in validation. An example use case of this might be replies\nin a comment thread. This can be done with mutually recursive functions. For simplicity, here\'s an example of parsing a \nkind of non-empty list.\n\n```python3\nfrom typing import Any, Optional, Tuple\n\nfrom koda_validate import *\n\n# if enable_recursive_aliases = true in mypy\n# NonEmptyList = Tuple[int, Optional["NonEmptyList"]]\nNonEmptyList = Tuple[int, Optional[Any]]\n\n\ndef recur_non_empty_list() -> Tuple2Validator[int, Optional[NonEmptyList]]:\n    return non_empty_list_validator\n\n\nnon_empty_list_validator = Tuple2Validator(\n    IntValidator(),\n    OptionalValidator(Lazy(recur_non_empty_list)),\n)\n\nassert non_empty_list_validator((1, (1, (2, (3, (5, None)))))) == Valid(\n    (1, (1, (2, (3, (5, None)))))\n)\n\n```\n\n\n### OptionalValidator\n\n`OptionalValidator` is very simple. It validates a value is either `None` or passes another validator\'s rules.\n\n```python3\nfrom koda_validate import *\n\noptional_int_validator = OptionalValidator(IntValidator())\n\nassert optional_int_validator(5) == Valid(5)\nassert optional_int_validator(None) == Valid(None)\n```\n\n### is_dict_validator\n\nA very simple validator that only validates that and object is a dict. It doesn\'t do any validation against keys or\nvalues.\n\n```python3\nfrom koda_validate import *\n\nassert is_dict_validator({}) == Valid({})\nassert is_dict_validator(None) == Invalid({"__container__": ["expected a dictionary"]})\nassert is_dict_validator({"a": 1, "b": 2, 5: "xyz"}) == Valid({"a": 1, "b": 2, 5: "xyz"})\n\n```\n\n### AlwaysValid\nWill always return `Valid` with the given value:\n```python3\nfrom koda_validate import *\n\nassert always_valid(123) == Valid(123)\nassert always_valid("abc") == Valid("abc")\n```\n\n## Comparison to Pydantic\nComparing Koda Validate and Pydantic is not exactly apples-to-apples, since Koda Validate is more narrowly\naimed at _just_ validation -- Pydantic has a lot of other bells and whistles. Nonetheless, this is one of the most \ncommon questions, and there are a number of noteworthy differences:\n- **Koda Validate is built around a simple, composable definition of what validation is.**\n- **Koda Validate treats validation explicitly.** It does not coerce types or mutate values in surprising ways.\n- **Koda Validate treats validation as part of normal control flow.** It does not raise exceptions for invalid data.\n- **Koda Validate is fully asyncio-compatible.**\n- **Koda Validate is ~1.5 - 12x faster.** You will see differences on different versions of Python\n(Python3.8 tends to show the least difference) and different systems. You can run the suite on your \nsystem with `python -m bench.run`. **Disclaimer that the benchmark suite is _not_ extensive.**\n- **Koda Validate is pure Python.** \n- **Koda Validate is intended to empower validator documentation.** You can easily produce things like API schemas from \n`Validator`s, `Predicate`s, and `Processor`s\n- **Koda Validate requires no plugins for mypy compatibility.** \n- **Pydantic has a large, mature ecosystem.** Lots of documentation, lots of searchable info on the web.\n- **Pydantic focuses on having a familiar, dataclass-like syntax.** \n- **Pydantic has a lot of features Koda Validate does not.** Plugins, ORM tie-ins, etc. There will probably never be \nfeature parity between the two libraries. \n\n## Something\'s Missing Or Wrong \nOpen an [issue on GitHub](https://github.com/keithasaurus/koda-validate/issues) please!\n',
    'author': 'Keith Philpott',
    'author_email': 'None',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/keithasaurus/koda-validate',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
