Koda Validate#

Koda Validate is a library and toolkit for building composable and typesafe validators. In many cases, validators can be derived from typehints automatically (e.g. TypedDicts, dataclasses, and NamedTuples). For everything else, you can compose validator callables or write your own. 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 runtime type checker.

Basic Usage#

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 Valid and Invalid types is covered more in Validation Results.


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])

Derived Validators#

Koda Validate can inspect typehints and build Validators automatically.

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 Derived Validators for more.


Refinement#

from koda_validate import StringValidator, MinLength, MaxLength, StartsWith

validator = StringValidator(MinLength(5),
                            MaxLength(10),
                            StartsWith("a"))

print(validator("abc123"))

Outputs:

Valid(val='abc123')

Note

MinLength(5), MaxLength(10), and StartsWith("a") are all Predicates.


Nested Validators#

We can build complex nested Validators with ease.

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:

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)

Usage:

>>> playlist_validator({
...    "title": "My Favorite Songs",
...    "songs": [stonehenge, drinkinstein]
... })
Valid(...)

Custom Validators#

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 Validators for custom needs. As long as you obey the typing rules when building custom Validators, you should be able to combine them with built-in Validators, however you wish. For guidance, take a look at Extension.


Contents#

API Reference