Runtype is a collection of run-time type utilities for Python.
It is:
🏃 Fast! Uses an internal typesystem for maximum performance.
typing
, forward-references, constraints, auto-casting, and more.
Modules
-
⭐ validation - Provides a smarter alternative toisinstance
andissubclass
, with support for thetyping
module, and type constraints. -
⭐ 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 (read here)
-
⭐ dispatch - Provides fast multiple-dispatch for functions and methods, via a decorator.- Inspired by Julia.
-
⭐ type utilities - Provides a set of classes to implement your own type-system.
- 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.6 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]) # == True
assert not isa({'a': 'b'}, dict[str, int]) # == False
assert issubclass(dict[str, int], typing.Mapping[str, int]) # == True
assert not issubclass(dict[str, int], typing.Mapping[int, str]) # == False
Dataclasses
from runtype import dataclass
@dataclass(check_types='cast') # Cast values to the target type, when applicable
class Person:
name: str
birthday: datetime = None # Implicit optional
interests: list[str] = [] # The list is copied for each instance
print( Person("Beetlejuice") )
#> Person(name='Beetlejuice', birthday=None, interests=[])
print( Person("Albert", "1955-04-18T00:00", ['physics']) )
#> Person(name='Albert', birthday=datetime.datetime(1955, 4, 18, 0, 0), interests=['physics'])
print( Person("Bad", interests=['a', 1]) )
# TypeError: [Person] Attribute 'interests' expected value of type list[str]. Instead got ['a', 1]
# Failed on item: 1, expected type str
Multiple Dispatch
Runtype dispatches according to the most specific type match -
from runtype import Dispatch
dp = Dispatch()
@dp
def mul(a: Any, b: Any):
return a * b
@dp
def mul(a: list, b: Any):
return [ai*b for ai in a]
@dp
def mul(a: Any, b: list):
return [bi*b for bi in b]
@dp
def mul(a: list, b: list):
return [mul(i, j) for i, j in zip(a, b, strict=True)]
assert mul("a", 4) == "aaaa" # Any, Any
assert mul([1, 2, 3], 2) == [2, 4, 6] # list, Any
assert mul([1, 2], [3, 4]) == [3, 8] # list, list
Dispatch can also be used for extending the dataclass builtin __init__
:
dp = Dispatch()
@dataclass(frozen=False)
class Point:
x: int = 0
y: int = 0
@dp
def __init__(self, points: list | tuple):
self.x, self.y = points
@dp
def __init__(self, points: dict):
self.x = points['x']
self.y = points['y']
# Test constructors
p0 = Point() # Default constructor
assert p0 == Point(0, 0) # Default constructor
assert p0 == Point([0, 0]) # User constructor
assert p0 == Point((0, 0)) # User constructor
assert p0 == Point({"x": 0, "y": 0}) # User constructor
License
Runtype uses the MIT license.
Donate
If you like Runtype and want to show your appreciation, you can do so at my patreon page, or ko-fi page.