Dataclass

Decorator

runtype.dataclass.dataclass(*, check_types: Union[bool, str] = CHECK_TYPES, config: Configuration = python_config, init: bool = True, repr: bool = True, eq: bool = True, order: bool = False, unsafe_hash: bool = False, frozen: bool = True, slots: bool = Ellipsis) Callable[[Type[_T]], Type[_T]]
runtype.dataclass.dataclass(_cls: Type[_T]) Type[_T]

Runtype’s dataclass is a drop-in replacement to Python’s built-in dataclass, with added functionality.

Differences from builtin dataclass:

  1. Type validation

  • Adds run-time type validation (when check_types is nonzero)

  • Performs automatic casting (when check_types == ‘cast’)

  1. Ergonomics

  • Supports assigning mutable literals (i.e. list, set, and dict). Each instance gets a new copy.

  • Adds convenience methods: replace(), aslist(), astuple(), and iterator for dict(this). These methods won’t override existing ones. They will be added only if the names aren’t used.

  • Setting the default as None automatically makes the type into Optional, if it isn’t already.

  • Members without a default are allowed after members with a default (but they are required in order to create the instance)

  1. Misc

  • Frozen by default

All of the above differences are configurable and extendable.

Parameters:
  • check_types (Union[bool, str]) – Whether or not to validate the values, according to the given type annotations. Possible values: False, True, or ‘cast’

  • config (Configuration) – Configuration to modify dataclass behavior, mostly regarding type validation.

Example

>>> @dataclass
>>> class Point:
...     x: int
...     y: int

>>> p = Point(2, 3)
>>> p
Point(x=2, y=3)
>>> dict(p)         # Maintains order
{'x': 2, 'y': 3}

>>> p.replace(x=30)  # New instance
Point(x=30, y=3)

Part of the added ergonomics and functionality were influenced by Pydantic:

  • Members that are assigned None, automatically become Optional. (Unless specified otherwise through config)

  • Members without a default value, following members with a default value, are now allowed (and will fail if not assigned on init).

Added methods

The following functions, which are available as at the module level, will also be available as methods of the dataclass instances. These methods won’t override existing ones; They will be added only if the names aren’t already used.

runtype.dataclass.replace(inst, **kwargs)

Returns a new instance, with the given attibutes and values overwriting the existing ones.

Useful for making copies with small updates.

Examples

>>> @dataclass
... class A:
...     a: int
...     b: int
>>> A(1, 2).replace(a=-2)
A(a=-2, b=2)
>>> some_instance.replace() == copy(some_instance)   # Equivalent operations
True
runtype.dataclass.astuple(inst)

Returns a tuple of the values

runtype.dataclass.aslist(inst)

Returns a list of the values

runtype.dataclass.json(inst)

Returns a JSON of values, going recursively into other objects (if possible)

Configuration

class runtype.dataclass.Configuration

Generic configuration template for dataclass. Mainly for type-checking.

To modify dataclass behavior, inherit and extend this class, and pass it to the dataclass() function as the config parameter. (parameter check_types must be nonzero)

Example

class IsMember(Configuration):
    @staticmethod
    def ensure_isa(a, b):
        if a not in b:
            raise TypeError(f"{a} is not in {b}")

@dataclass(config=IsMember())
class Form:
    answer1: ("yes", "no")
    score: range(1, 11)

...

>>> Form("no", 3)
Form(answer1='no', score=3)

>>> Form("no", 12)
Traceback (most recent call last):
    ...
TypeError: 12 is not in range(1, 11)
on_default(default)

Called whenever a dataclass member is assigned a default value.

abstract ensure_isa(a, b, sampler=None)

Ensure that ‘a’ is an instance of type ‘b’. If not, raise a TypeError.

abstract cast(obj, t)

Attempt to cast ‘obj’ to type ‘t’. If such a cast is not possible, raise a TypeError.

The result is expected to pass self.ensure_isa(res, t) without an error, however this assertion is not validated, for performance reasons.

class runtype.dataclass.PythonConfiguration

Configuration to support Mypy-like and Pydantic-like features

This is the default class given to the dataclass() function.

Casting

When called with the option check_types="cast", values that are provided to instanciate the dataclass will be cast instead of validated.

Runtype will only attempt to cast in situations when no data is lost when converting the value.

The following casts are currently implemented:

  • str -> int

  • str -> datetime

  • int -> float

If a cast fails, Runtype raises a TypeError. (same as when validation fails)

More casts will be added in time.

For non-builtin types, Runtype will attempt to call the cast_from class-method, if one exists.

Example:

@dataclass
class Name:
    first: str
    last: str = None

    @classmethod
    def cast_from(cls, s: str):
        return cls(*s.split())

@dataclass(check_types='cast')
class Person:
    name: Name

p = Person("Albert Einstein")
assert p.name.first == 'Albert'
assert p.name.last == 'Einstein'

Sampling

When called with the option check_types="sample", lists and dictionaries will only have a sample of their items validated, instead of each item.

This approach will validate big lists and dicts much faster, but at the cost of possibly missing anomalies in them.

Performance

Type verification in classes introduces a small run-time overhead.

When running in production, it’s recommended to use the -O switch for Python. It will make Runtype skip type verification in dataclasses. (unless check_types is specified.)

Alternatively, you can use a shared dataclass decorator, and enable/disable type-checking with a single change.

Example:

# common.py
import runtype

from .settings import DEBUG   # Define DEBUG however you want

dataclass = runtype.dataclass(check_types=DEBUG)

Compared to Pydantic

Using Pydantic’s own benchmark, runtype performs twice faster than Pydantic. (or, Pydantic is twice slower than Runtype)

pydantic best=63.839μs/iter avg=65.501μs/iter stdev=1.763μs/iter version=1.9.1
attrs + cattrs best=45.607μs/iter avg=45.804μs/iter stdev=0.386μs/iter version=21.4.0
runtype best=31.500μs/iter avg=32.281μs/iter stdev=0.753μs/iter version=0.2.7

See the code here.