Skip to main content

Typedpy - Strict Type System for Python

typedpy is a library for type-safe, strict, validated Python data structures, with serialization, JSON Schema and IDE stub generation built in. A structure can never hold an invalid value: validation runs when it is created and on every change afterwards, including changes inside nested lists and dicts.

It is pure Python with zero dependencies, supports Python 3.9-3.14, and has run in production, including financial systems, since 2017.

pip install typedpy

(or conda install -c conda-forge typedpy)

Quick start

import enum
from typing import Optional

from typedpy import (
    ImmutableStructure, String, PositiveInt, Float, Enum,
    Serializer, Deserializer, mappers,
)


class Currency(enum.Enum):
    USD = 1
    EUR = 2


class Trader(ImmutableStructure):
    lei: String(pattern="[0-9A-Z]{18}[0-9]{2}$")
    alias: String(maxLength=32)

    _serialization_mapper = mappers.TO_CAMELCASE


class Trade(ImmutableStructure):
    order_id: String
    symbol: String(pattern="[A-Z]+$", maxLength=6)
    quantity: PositiveInt(multiplesOf=5)
    price: Float(minimum=0)
    currency: Enum[Currency]
    buyer: Trader
    tags: list[str]
    comment: Optional[str]

    _serialization_mapper = mappers.TO_CAMELCASE


# deserialize (and validate) JSON-like input; keys are camelCase on the wire
trade = Deserializer(Trade).deserialize({
    "orderId": "T-1001",
    "symbol": "AAPL",
    "quantity": 100,
    "price": 231.5,
    "currency": "USD",
    "buyer": {"lei": "5493001KJTIIGC8Y1R12", "alias": "desk-7"},
    "tags": ["equity"],
})
assert trade.currency is Currency.USD

Trade(order_id="T-1", symbol="AAPL", quantity=-5, price=1.0,
      currency=Currency.USD, buyer=trade.buyer, tags=[])
# ValueError: Trade.quantity: Got -5; Expected a positive number

trade.quantity = 200        # ValueError: Trade: Structure is immutable
trade.tags.append("bond")   # ValueError: tags: Field is immutable  (deep immutability)

Serializer(trade).serialize()
# {'orderId': 'T-1001', 'symbol': 'AAPL', 'quantity': 100, ..., 'buyer': {'lei': ..., 'alias': 'desk-7'}}

Fields can be typedpy field types with constraints (String(maxLength=32)), plain Python types (int, str), standard typing/PEP 585 annotations (list[str], Optional[...]), other structures, or any mix of them at any depth, e.g. Array[dict[String(minLength=5), int]].

A mutable Structure is still validated on every change:

from typedpy import Structure

class Order(Structure):
    quantity: PositiveInt
    tags: list[str]

order = Order(quantity=5, tags=["a"])
order.quantity = -1     # ValueError: quantity: Got -1; Expected a positive number
order.tags.append(3)    # TypeError: ... Expected a string

Highlights

Deep immutability

ImmutableStructure blocks reassignment and changes to nested lists, dicts and sets, reads return defensive copies, and later changes to the caller's input don't leak in. (@dataclass(frozen=True) and Pydantic's frozen only block reassignment.) Individual fields can be made immutable too (ImmutableField, ImmutableArray, ImmutableMap...).

A field system that is plain object-oriented Python

A custom field is an ordinary class. Constraints are mixins that combine through inheritance:

from typedpy import Field, Integer, Positive

class Even(Field):
    def __set__(self, instance, value):
        if value % 2:
            raise ValueError(f"{self._name}: Got {value}; Expected an even number")
        super().__set__(instance, value)

class EvenPositiveInt(Integer, Positive, Even):
    pass

class Batch(Structure):
    size: EvenPositiveInt

Batch(size=3)   # ValueError: Batch.size: Got 3; Expected an even number

Optional hooks plug a custom field into everything else: SerializableField for serialization, to_json_schema/from_json_schema for JSON Schema, and get_type for stubs. Classes you don't own can be used directly as field types, or wrapped with create_typed_field.

Discriminated unions

The discriminator is a real field reference (refactor-safe), and variants are discovered automatically from subclasses, so there's no Union[...] to forget to update:

from typedpy import AbstractStructure, Constant, DiscriminatedUnion

class Shape(AbstractStructure):
    kind: String

class Circle(Shape):
    kind = Constant("circle")
    radius: float

class Square(Shape):
    kind = Constant("square")
    side: float

class Drawing(Structure):
    shapes: list[DiscriminatedUnion(Shape, by=Shape.kind)]

drawing = Deserializer(Drawing).deserialize(
    {"shapes": [{"kind": "circle", "radius": 1.0}, {"kind": "square", "side": 2.0}]}
)
assert [type(s) for s in drawing.shapes] == [Circle, Square]

Unrelated classes work too: DiscriminatedUnion(Union[Circle, Square], by=Circle.kind).

Deriving models from existing ones

Partial, AllFieldsRequired, Omit, Pick and Extend work like TypeScript's utility types. They copy fields rather than inherit, so they work on immutable classes too:

from typedpy import Partial, Omit

class TradeUpdate(Partial[Omit[Trade, ("buyer", "currency")]]):
    pass

TradeUpdate(quantity=50)   # every remaining field is optional

Enum-keyed structures: @keys_of

Guarantee at import time that a structure has a field for every member of an enum, like TypeScript's Record<Role, ...>. Adding an enum member without updating the class fails fast:

from typedpy import keys_of

class Role(enum.Enum):
    admin = 1
    engineer = 2
    sales = 3

@keys_of(Role)
class SalaryBands(Structure):
    admin: int
    engineer: int
# TypeError: SalaryBands: missing fields: sales

Undefined vs None

Opt in to a real "no value" that is distinct from None, e.g. for PATCH-style APIs:

from typedpy import Undefined

class Patch(Structure):
    name: str
    email: str
    _required = []
    _ignore_none = True
    _enable_undefined_value = True

patch = Deserializer(Patch).deserialize({"email": None})
assert patch.name is Undefined and patch.email is None
Serializer(patch).serialize()   # {'email': None}

Serialization

  • Key mappers (TO_CAMELCASE, TO_LOWERCASE, rename dicts, function mappers), which can be chained as a list and compose through inheritance.

  • Schema versioning: a Versioned structure is migrated to its latest schema on deserialization.

  • Trusted deserialization for data you already trust (e.g. your own database), which skips validation and is several times faster, with a unit-test helper to make sure a class stays eligible:

    trade = Deserializer(Trade).deserialize(row, direct_trusted_mapping=True)
    
    # in a unit test:
    from typedpy.testing import assert_trusted_deserialization_mapper_is_safe
    assert_trusted_deserialization_mapper_is_safe(Trade)
    
  • Fast serialization: mark a class FastSerializable and call create_serializer(cls) for roughly 4-5x faster serialization, still in pure Python.

  • Pickling support.

JSON Schema, in both directions

from typedpy import structure_to_schema, schema_to_struct_code

schema, definitions = structure_to_schema(Trader, {})
print(schema_to_struct_code("Trader", schema, definitions))
# class Trader(Structure):
#     lei: String(pattern='[0-9A-Z]{18}[0-9]{2}$')
#     alias: String(maxLength=32)
#
#     _required = ['alias', 'lei']

IDE and type-checker support through generated stubs

typedpy generates .pyi stubs that turn its field types back into ordinary type hints, with full __init__ signatures, so any IDE or type checker understands your structures (no plugin needed):

create-stubs-for-dir <src_root_dir> <directory>
create-stub <src_root_dir> <path/to/module.py>

Also included

Structured, collectable errors (ErrorInfo), cross-field validation via __validate__, AbstractStructure/FinalStructure, shallow_clone_with_overrides, deep_get, typedpy.testing.find_diff, @default_factories, and global defaults through TypedPyDefaults.

Why pure Python?

typedpy is deliberately pure Python with no dependencies: you get full stack traces, it can be debugged and audited end to end, there are no compiled builds, and the supply-chain surface is just this package. That suits regulated and correctness-critical settings.

The trade-off is speed: validation is slower than Pydantic's Rust core. If validation dominates your workload, or you need Pydantic's ecosystem (FastAPI, SQLModel, settings), Pydantic is the better fit. typedpy's strengths are elsewhere: deep immutability, validation on every change, a simple extension model, and the features above. It also offers trusted deserialization and fast serialization for the hot paths.

Documentation

Full documentation: typedpy.readthedocs.io, including a tutorial, structures, fields, serialization, JSON Schema, stubs and limitations.

The tests are also a large collection of working examples.

License

MIT. See LICENSE.txt.

Metadata

Release files for typedpy 2.30.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for typedpy 2.30.3
File Size Uploaded
typedpy-2.30.3.tar.gz 160.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for typedpy 2.30.3
File Interpreter ABI Platform
typedpy-2.30.3-py3-none-any.whl Python 3 none any Details

Total release size: 267.5 kB

Release files / typedpy-2.30.3.tar.gz

Download URL typedpy-2.30.3.tar.gz
Size 160.1 kB
Tags Source
SHA-256 checksum
How to use checksums
280704178d0ec8f0be16f5322c66033cfd089466b3b68fa1a27513eb21d40366
BLAKE2b-256 checksum
How to use checksums
ee8ba1b3df4a73f655811cefe1d6ded80b2b2c10edbb7ca6213e05a5ca579761
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.12

Release files / typedpy-2.30.3-py3-none-any.whl

Download URL typedpy-2.30.3-py3-none-any.whl
Size 107.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f791aa6a8ec9e7ad38684e634cd6a6bfba558d345facdfcf60250eb8e3753b41
BLAKE2b-256 checksum
How to use checksums
ba597e0f29da7662d9d1f06a4594e05027d28ffddaa55478db60daeab7fc13fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.12

Release history Release notifications | RSS feed

This release

2.30.3 This release

2 release files

2.29.0

2 release files

2.28.3

1 release file

2.28.2

1 release file

2.28.1

1 release file

2.28.0

1 release file

2.27.6

1 release file

2.27.5

1 release file

2.27.4

1 release file

2.27.3

1 release file

2.27.1

1 release file

2.27.0

1 release file

2.26.2

1 release file

2.26.1

2 release files

2.26.0

2 release files

2.25.0

2 release files

2.24.9

2 release files

2.24.8

2 release files

2.24.6

2 release files

2.24.5

2 release files

2.24.4

2 release files

2.24.3

2 release files

2.24.2

2 release files

2.24.1

2 release files

2.24.0

2 release files

2.23.1

2 release files

2.23.0

2 release files

2.22.4

2 release files

2.22.2

2 release files

2.21.2

2 release files

2.20.3

2 release files

2.20.2

2 release files

2.20.1

2 release files

2.20.0

2 release files

2.19.5

3 release files

2.19.3

3 release files

2.19.2

3 release files

2.19.1

3 release files

2.19.0

3 release files

2.18.5

3 release files

2.18.3

2 release files

2.18.1

2 release files

2.18.0

3 release files

2.17.6

3 release files

2.17.5

3 release files

2.17.4

2 release files

2.17.3

3 release files

2.17.2

3 release files

2.16.7

3 release files

2.16.6

3 release files

2.16.5

3 release files

2.16.4

3 release files

2.16.0

3 release files

2.15.9

3 release files

2.15.8

3 release files

2.15.7

3 release files

2.15.6

3 release files

2.15.5

3 release files

2.15.4

3 release files

2.15.3

3 release files

2.15.2

2 release files

2.15.1

3 release files

2.14.5

3 release files

2.14.4

3 release files

2.14.3

3 release files

2.14.2

3 release files

2.14.1

3 release files

2.13.4

3 release files

2.13.2

3 release files

2.12.4

3 release files

2.12.3

3 release files

2.12.2

3 release files

2.12.1

3 release files

2.12.0

3 release files

2.11.1

3 release files

2.11.0

3 release files

2.10.1

3 release files

2.10.0

3 release files

2.9.0

3 release files

2.8.2

3 release files

2.8.1

3 release files

2.8.0

3 release files

2.7.3

3 release files

2.7.2

3 release files

2.7.1

3 release files

2.7.0

3 release files

2.6.11

3 release files

2.6.10

3 release files

2.6.9

3 release files

2.6.8

3 release files

2.6.7

1 release file

2.6.6

3 release files

2.6.5

3 release files

2.6.4

3 release files

2.6.3

3 release files

2.6.2

3 release files

2.6.1

3 release files

2.6

3 release files

2.5.3

3 release files

2.5.2

3 release files

2.5.1

3 release files

2.5.0

3 release files

2.4.14

3 release files

2.4.13

3 release files

2.4.12

3 release files

2.4.11

3 release files

2.4.10

3 release files

2.4.9

3 release files

2.4.8

3 release files

2.4.7

3 release files

2.4.6

3 release files

2.4.5

3 release files

2.4.4

3 release files

2.4.3

3 release files

2.4.2

3 release files

2.4.1

3 release files

2.4.0

3 release files

2.3.11

3 release files

2.3.10

3 release files

2.3.9

2 release files

2.3.8

3 release files

2.3.7

3 release files

2.3.6

3 release files

2.3.5

3 release files

2.3.4

3 release files

2.3.3

3 release files

2.3.2

3 release files

2.3.1

2 release files

2.3.0

3 release files

2.2.4

3 release files

2.2.3

3 release files

2.2.2

3 release files

2.2.1

3 release files

2.2.0

3 release files

2.1.2

3 release files

2.1.0

3 release files

2.0.1

3 release files

2.0.0

3 release files

1.50

3 release files

1.40

3 release files

1.34

3 release files

1.33

3 release files

1.32

3 release files

1.31

3 release files

1.30

3 release files

1.24

2 release files

1.23

2 release files

1.22

3 release files

1.21

3 release files

1.20

3 release files

1.10

3 release files

1.1

3 release files

1.0

2 release files

0.62

3 release files

0.61

3 release files

0.60

3 release files

0.51

1 release file

0.50

1 release file

0.40

3 release files

0.38

3 release files

0.37

3 release files

0.36

3 release files

0.35

3 release files

0.34

3 release files

0.33

3 release files

0.32

3 release files

0.31

2 release files

0.30

2 release files

0.25

2 release files

0.23

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page