Runtype is a collection of run-time type utilities for Python.
It is:
:runner: Fast! Uses an internal typesystem for maximum performance.
:brain: Smart! Supports typing
, forward-references, constraints, auto-casting, and more.
:gear: Configurative! Write your own type system, and use it with dataclass and dispatch.
Modules
-
:star: validation - Provides a smarter alternative to isinstance
and issubclass
, with support for the typing
module, and type constraints.
-
:star: dataclass - Adds run-time type validation to the built-in dataclass.
- Improves dataclass ergonomics.
- Supports most mypy constructs, like
typing
and forward-references (foo: 'Bar'
). - Supports automatic value casting, Pydantic-style. (Optional, off by default)
- Supports types with constraints. (e.g.
String(max_length=10)
) - Supports optional sampling for faster validation of big lists and dicts.
- Twice faster than Pydantic-v1 with pure Python (read here)
-
:star: dispatch - Provides fast multiple-dispatch for functions and methods, via a decorator.
- Dispatch on multiple arguments
- Full specificity resolution
- Supports mypy, by utilizing the
@overload
decorator - Inspired by Julia.
-
:star: type utilities - Provides a set of classes to implement your own type-system.
- Supports generics, constraints, phantom types
- Used by runtype itself, to emulate the Python type-system.
Docs
Read the docs here: https://runtype.readthedocs.io/
Install
pip install runtype
No dependencies.
Requires Python 3.8 or up.
Examples
Validation (Isa & Subclass)
Use isa
and issubclass
as a smarter alternative to the builtin isinstance & issubclass -
from runtype import isa, issubclass
assert isa({'a': 1}, dict[str, int])
assert not isa({'a': 'b'}, dict[str, int])
assert issubclass(dict[str, int], typing.Mapping[str, int])
assert not issubclass(dict[str, int], typing.Mapping[int, str])
Dataclasses
from runtype import dataclass
@dataclass(check_types='cast')
class Person:
name: str
birthday: datetime = None
interests: list[str] = []
print( Person("Beetlejuice") )
print( Person("Albert", "1955-04-18T00:00", ['physics']) )
print( Person("Bad", interests=['a', 1]) )
Multiple Dispatch
Runtype dispatches according to the most specific type match -
from runtype import multidispatch as md
@md
def mul(a: list, b: list):
return [mul(i, j) for i, j in zip(a, b, strict=True)]
@md
def mul(a: list, b: Any):
return [ai*b for ai in a]
@md
def mul(a: Any, b: list):
return [bi*b for bi in b]
@md
def mul(a: Any, b: Any):
return a * b
assert mul("a", 4) == "aaaa"
assert mul([1, 2, 3], 2) == [2, 4, 6]
assert mul([1, 2], [3, 4]) == [3, 8]
Dispatch can also be used for extending the dataclass builtin __init__
:
@dataclass
class Point:
x: int = 0
y: int = 0
@md
def __init__(self, points: list | tuple):
self.__init__(*points)
@md
def __init__(self, points: dict):
self.__init__(points['x'], points['y'])
p0 = Point()
assert p0 == Point(0, 0)
assert p0 == Point([0, 0])
assert p0 == Point((0, 0))
assert p0 == Point({"x": 0, "y": 0})
Benchmarks
Runtype beats its competition handily. It is significantly faster than both beartype and plum, and in some cases is even faster than regular Python code.
See the benchmarks page in the documentation for detailed benchmarks.
License
Runtype uses the MIT license.
Contribute
If you like Runtype and want to see it grow, you can help by:
-
Reporting bugs or suggesting features
-
Submitting pull requests (better to ask me first)
-
Writing about runtype in a blogpost or even a tweet