.. module:: koda_validate :noindex:
Koda Validate is a library and toolkit for building composable and typesafe validators. In many cases, validators can be derived from typehints :ref:`automatically<index:Derived Validators>` (e.g. TypedDicts, dataclasses, and NamedTuples). For everything else, you can compose validator callables or :ref:`write your own<how_to/extension:Extension>`. At its heart, Koda Validate is just a few kinds of functions that fit together, so the possibilities are endless. It is async-friendly and comparable in performance to Pydantic 2.
Koda Validate can be used in normal control flow or as a :ref:`runtime type checker<how_to/runtime_type_checking:Runtime Type Checking>`.
.. testcode:: scalars from koda_validate import StringValidator my_first_validator = StringValidator()
Easy enough. Let's see how it works:
>>> my_first_validator("a string")
Valid(val='a string')
>>> my_first_validator(0)
Invalid(err_type=TypeErr(expected_type=<class 'str'>), ...)
For both valid and invalid cases, a value is returned -- no exceptions are raised.
Working with :class:`Valid` and :class:`Invalid` types is covered more in :ref:`how_to/results:Validation Results`.
.. testcode:: collections from koda_validate import ListValidator, IntValidator list_int_validator = ListValidator(IntValidator())
Nesting validators works as one might expect.
>>> list_int_validator([1,2,3])
Valid(val=[1, 2, 3])
Koda Validate can inspect typehints and build :class:`Validator<koda_validate.Validator>`s automatically.
.. testcode:: derived from typing import List, TypedDict from koda_validate import TypedDictValidator class Person(TypedDict): name: str hobbies: List[str] validator = TypedDictValidator(Person)
Usage:
>>> validator({"name": "Bob", "hobbies": ["eating", "coding", "sleeping"]})
Valid(val={'name': 'Bob', 'hobbies': ['eating', 'coding', 'sleeping']})
See :ref:`how_to/dictionaries/derived:Derived Validators` for more.
.. testcode:: refinement from koda_validate import StringValidator, MinLength, MaxLength, StartsWith validator = StringValidator(MinLength(5), MaxLength(10), StartsWith("a")) print(validator("abc123"))
Outputs:
.. testoutput:: refinement Valid(val='abc123')
Note
MinLength(5)
, MaxLength(10)
, and StartsWith("a")
are all :ref:`philosophy/predicates:Predicates`.
We can build complex nested :class:`Validator`s with ease.
.. testcode:: nested from typing import Annotated, Union, TypedDict, Literal, List from koda_validate import TypedDictValidator class Group(TypedDict): name: str members: List[str] class Song(TypedDict): artist: Union[List[str], Group, Literal["unknown"]] title: str duration_seconds: int song_validator = TypedDictValidator(Song) stonehenge = { "artist": { "name": "Spinal Tap", "members": ["David St. Hubbins", "Nigel Tufnel", "Derek Smalls"] }, "title": "Stonehenge", "duration_seconds": 276 } drinkinstein = { "artist": ["Sylvester Stallone", "Dolly Parton"], "title": "Drinkin' Stein", "duration_seconds": 215 }
Usage:
>>> song_validator(stonehenge)
Valid(...)
>>> song_validator(drinkinstein)
Valid(...)
>>> song_validator({
... "artist": "unknown",
... "title": "druids chanting, archival recording number 10",
... "duration_seconds": 4_000
... })
Valid(...)
It's easy to keep nesting validators:
.. testcode:: nested from koda_validate import ListValidator, MinItems songs_validator = ListValidator(song_validator, predicates=[MinItems(2)]) class Playlist(TypedDict): title: str songs: Annotated[List[Song], songs_validator] playlist_validator = TypedDictValidator(Playlist)
Note
:class:`TypedDictValidator`, :class:`DataclassValidator` and :class:`NamedTupleValidator` will :ref:`use Annotated Validators<how_to/dictionaries/derived:Annotated>` if found.
Usage:
>>> playlist_validator({
... "title": "My Favorite Songs",
... "songs": [stonehenge, drinkinstein]
... })
Valid(...)
.. testcode:: own1 from typing import Any from koda_validate import Validator, ValidationResult, Valid, Invalid, TypeErr class IntegerValidator(Validator[int]): def __call__(self, val: Any) -> ValidationResult[int]: if isinstance(val, int): return Valid(val) else: return Invalid(TypeErr(int), val, self)
Usage:
>>> validator = IntegerValidator()
>>> validator(5)
Valid(val=5)
>>> invalid_result = validator("not an integer")
>>> invalid_result.err_type
TypeErr(expected_type=<class 'int'>)
In Koda Validate, you are encouraged to write your own :class:`Validator`s for custom needs. As long as you obey the typing rules when building custom :class:`Validator`s, you should be able to combine them with built-in :class:`Validator`s, however you wish. For guidance, take a look at :ref:`how_to/extension:Extension`.
.. toctree:: :maxdepth: 2 :caption: Getting Started setup/installation setup/type-checking
.. toctree:: :maxdepth: 3 :caption: Examples and Guides how_to/results how_to/dictionaries how_to/rest_apis how_to/runtime_type_checking how_to/async how_to/extension how_to/metadata how_to/performance how_to/type-checking
.. toctree:: :maxdepth: 3 :caption: API Reference api/koda_validate api/koda_validate.serialization api/koda_validate.signature
.. toctree:: :maxdepth: 3 :caption: Philosophy philosophy/overview philosophy/validators philosophy/predicates philosophy/coercion philosophy/processors philosophy/async philosophy/errors
.. toctree:: :maxdepth: 3 :caption: FAQ faq/pydantic