Skip to content

erezsh/runtype

Repository files navigation

alt text

Runtype is a collection of run-time type utilities for Python.

It is:

🏃 Fast! Uses an internal typesystem for maximum performance.

🧠 Smart! Supportstyping,forward-references, constraints, auto-casting, and more.

⚙️ Configurative! Write your own type system, and use it withdataclassanddispatch.


Modules

  • validation- Provides a smarter alternative toisinstanceandissubclass,with support for thetypingmodule, and type constraints.

  • dataclass- Adds run-time type validation to the built-in dataclass.

    • Improves dataclass ergonomics.
    • Supports most mypy constructs, liketypingand 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)
  • dispatch- Provides fast multiple-dispatch for functions and methods, via a decorator.

    • Dispatch on multiple arguments
    • Fullspecificityresolution
    • Supports mypy,by utilizing the@overloaddecorator
    • Inspired by Julia.
  • 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.

codecov

Examples

Validation (Isa & Subclass)

Useisaandissubclassas a smarter alternative to the builtin isinstance & issubclass -

fromruntypeimportisa,issubclass

assertisa({'a':1},dict[str,int])# == True
assertnotisa({'a':'b'},dict[str,int])# == False

assertissubclass(dict[str,int],typing.Mapping[str,int])# == True
assertnotissubclass(dict[str,int],typing.Mapping[int,str])# == False

Dataclasses

fromruntypeimportdataclass

@dataclass(check_types='cast')# Cast values to the target type, when applicable
classPerson:
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 -

fromruntypeimportmultidispatchasmd

@md
defmul(a:list,b:list):
return[mul(i,j)fori,jinzip(a,b,strict=True)]
@md
defmul(a:list,b:Any):
return[ai*bforaiina]
@md
defmul(a:Any,b:list):
return[bi*bforbiinb]
@md
defmul(a:Any,b:Any):
returna*b

assertmul("a",4)=="aaaa"# Any, Any
assertmul([1,2,3],2)==[2,4,6]# list, Any
assertmul([1,2], [3,4])==[3,8]# list, list

Dispatch can also be used for extending the dataclass builtin__init__:

@dataclass
classPoint:
x:int=0
y:int=0

@md
def__init__(self,points:list|tuple):
# Call default constructor
self.__init__(*points)

@md
def__init__(self,points:dict):
# Call default constructor
self.__init__(points['x'],points['y'])

# Test constructors
p0=Point()# Default constructor
assertp0==Point(0,0)# Default constructor
assertp0==Point([0,0])# User constructor
assertp0==Point((0,0))# User constructor
assertp0==Point({"x":0,"y":0})# User constructor

Benchmarks

Runtype beats its competition handily. It is significantly faster than bothbeartypeandplum,and in some cases is even faster than regular Python code.

See thebenchmarks pagein the documentation for detailed benchmarks.

alt text

alt text

License

Runtype uses theMIT 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