Skip to main content

invariants

PyPI Python License

Type-safe state modeling for Python. Split entity invariants into separate, validated, immutable state classes using Pydantic.

Why?

When an entity has multiple states, each state often has different field requirements. Typically, this is modeled with optional fields and runtime checks. invariants enforces these constraints at the type level:

  • Each state is a separate class with its own field types
  • A metaclass ensures child states override all abstract fields and don't add new ones
  • All states are immutable
  • StateMachine defines valid transitions with type-safe signatures

Installation

pip install invariants

Quick Start

Define a root state with Statefull fields (abstract markers), then create concrete states that override them:

from datetime import datetime
from typing import Literal

from invariants.state import State, Statefull, StateMachine


class LoanState(State):
    id: int
    status: Statefull
    postponement_date: Statefull


class ActiveLoan(LoanState):
    status: Literal["active"] = "active"
    postponement_date: datetime


class ClosedLoan(LoanState):
    status: Literal["closed"] = "closed"
    postponement_date: None = None


class OverdueLoan(LoanState):
    status: Literal["overdue"] = "overdue"
    postponement_date: datetime

The metaclass enforces two rules at class creation time:

  1. Child states cannot add new fields — only override parent fields
  2. Child states must override all Statefull fields with concrete types

Define state transitions with StateMachine:

class CloseLoan(StateMachine[LoanState]):
    def execute(self, loan: ActiveLoan | OverdueLoan) -> ClosedLoan:
        return ClosedLoan(id=loan.id)


class MakeLoanOverdue(StateMachine[LoanState]):
    def execute(self, loan: ActiveLoan) -> OverdueLoan:
        return OverdueLoan(id=loan.id, postponement_date=loan.postponement_date)

The execute method signature documents which transitions are valid — from which states to which.

Nested States

States can contain other states. Use ContainsOne to validate that a collection includes at least one instance of a specific type:

from typing import Annotated, Literal
from invariants.conditions import ContainsOne


class DebtState(State):
    status: Statefull
    loans: Statefull


class ActiveDebt(DebtState):
    status: Literal["active"] = "active"
    loans: Annotated[tuple[ActiveLoan | ClosedLoan, ...], ContainsOne(ActiveLoan)]


class ClosedDebt(DebtState):
    status: Literal["closed"] = "closed"
    loans: tuple[ClosedLoan, ...]


class OverdueDebt(DebtState):
    status: Literal["overdue"] = "overdue"
    loans: Annotated[
        tuple[OverdueLoan | ActiveLoan | ClosedLoan, ...], ContainsOne(OverdueLoan)
    ]

ActiveDebt guarantees at compile time that it only contains active or closed loans, and at runtime that at least one is active.

Factories (Testing)

Optional integration with polyfactory for generating test data. The key idea: a factory always produces a valid state — all business invariants are satisfied automatically.

pip install invariants[factories]

Define factories for concrete states — the model is inferred from the generic argument:

from invariants.factories import StateFactory


class ActiveLoanFactory(StateFactory[ActiveLoan]): ...
class ClosedLoanFactory(StateFactory[ClosedLoan]): ...
class ActiveDebtFactory(StateFactory[ActiveDebt]): ...

When you build a nested state, child entities are generated automatically:

debt = ActiveDebtFactory.build()

# loans are auto-populated with valid ActiveLoan | ClosedLoan instances
# ContainsOne(ActiveLoan) is always satisfied — at least one loan is active
assert any(isinstance(loan, ActiveLoan) for loan in debt.loans)

SQLAlchemy Integration

SQLAlchemyStateFactory maps Pydantic states to ORM models — build() returns ORM instances instead of Pydantic models:

from invariants.factories.sqlalchemy import SQLAlchemyStateFactory


class ActiveLoanORMFactory(SQLAlchemyStateFactory[ActiveLoan, LoanORM]): ...

loan = ActiveLoanORMFactory.build()
assert isinstance(loan, LoanORM)  # ORM instance, not Pydantic model

Both StateFactory and SQLAlchemyStateFactory are thin wrappers over polyfactory — all interfaces (sessions, persistence, batch creation) work the same as in the polyfactory docs.

XState Visualization

Generate XState v5 state machine code from your StateMachine subclasses:

# Print generated JS — paste it into https://sketch.stately.ai
python -m invariants.viz print examples.debt

# Start a local Stately Sketch with one document per state machine
# (clones https://github.com/statelyai/sketch on first run; needs Node.js with pnpm or npx)
python -m invariants.viz serve examples.debt

serve opens each machine in the browser

img.png

Metadata

Release files for invariants 0.1.0

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

Source distribution (sdist)

Source distribution for invariants 0.1.0
File Size Uploaded Provenance
invariants-0.1.0.tar.gz 35.2 kB With provenance Details

Built distribution (wheel)

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

Total release size: 57.2 kB

Release files / invariants-0.1.0.tar.gz

Download URL invariants-0.1.0.tar.gz
Size 35.2 kB
Tags Source
SHA-256 checksum
How to use checksums
872cbd3cb26b8eebdd279cbf451bbb28c02dd844114db9c78cc02a440f36e047
BLAKE2b-256 checksum
How to use checksums
7964dfad9e4e10685ef0c53844ebe52ce80a1bb8c05ad594b031ebcfade08961
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / invariants-0.1.0-py3-none-any.whl

Download URL invariants-0.1.0-py3-none-any.whl
Size 22.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
64cf7ecc0711e26dc1c7baca8ce0d5aa75b473309823f878b24e98268a30107a
BLAKE2b-256 checksum
How to use checksums
74cc5c85bdaaf84ee3fa3905af398d98292ffb834e5465741be05a8baaeea747
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for invariants 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for invariants-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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