Near-zero-overhead refinement types for Python — runtime contracts via Annotated, lightweight enforcement powered by beartype, optional compile-to-inline guards via libcst, and a small mypy plugin for static ergonomics.
# runtime only (annotated-types + beartype)
pip install -e ".[runtime]"
# full dev suite (includes codegen, mypy, pytest, ruff)
pip install -e ".[dev]"Or with uv:
uv pip install -e ".[dev]"from refined_py import Gt, Ge, Lt, Le, MinLen, MaxLen, Interval, Matches, And, Or, NotAll predicates are frozen=True, slots=True dataclasses and compatible with annotated-types where they overlap.
from typing import Annotated
from refined_py import Gt, refine
PositiveInt = Annotated[int, Gt(0)]
@refine
def double(x: PositiveInt) -> int:
return x * 2
double(5) # OK
double(-1) # raises beartype exception at call time@refine is a no-op when Python runs with -O (i.e. __debug__ == False), so there is zero overhead in optimized/ production builds.
from dataclasses import dataclass
from typing import Annotated
from refined_py import MinLen, Ge, refine_dataclass
@refine_dataclass
@dataclass
class User:
name: Annotated[str, MinLen(1)]
age: Annotated[int, Ge(0)]
User("Alice", 30) # OK
User("", 30) # raises on __init__Transform decorated functions into flat inline isinstance guards:
python -m refined_py.codegen examples/codegen_target.py --dry-run
python -m refined_py.codegen examples/codegen_target.py # in-placeThe generated file contains no refined_py imports — just plain Python guards, suitable for environments where you cannot add a runtime dependency.
A minimal mypy plugin ships in refined_py.mypy_plugin. It expands Refined[T, Predicate] to T during type analysis so refinement annotations are erased statically while still being usable as subtypes of the base type.
from refined_py import Refined, Gt
def take_positive(x: Refined[int, Gt(0)]) -> int:
return x
take_positive(5) # OK (mypy)
take_positive("hi") # mypy error: str is not intEnable it in pyproject.toml:
[tool.mypy]
plugins = ["refined_py.mypy_plugin"]Note:
Annotated[T, Predicate]is already treated asTby mypy natively. The plugin mainly improves ergonomics for theRefinedalias.
┌──────────────────────────────────────────────────────────┐
│ Layer 4 mypy_plugin.py ── static ergonomics │
├──────────────────────────────────────────────────────────┤
│ Layer 3 codegen.py ── compile to inline guards │
├──────────────────────────────────────────────────────────┤
│ Layer 2 enforce.py ── @refine / @refine_dataclass│
├──────────────────────────────────────────────────────────┤
│ Layer 1 core.py ── predicate dataclasses │
└──────────────────────────────────────────────────────────┘
| Milestone | Status | Commit |
|---|---|---|
| 0 — Foundation | ✅ | 4a83bfe |
1 — Predicates + @refine |
✅ | 41aa562 |
2 — @refine_dataclass |
✅ | aafa2ee |
| 3 — Codegen CLI | ✅ | e9f15d1 |
| 4 — Mypy plugin | ✅ | 0d31337 |
# run tests
pytest tests/ -v
# run formatter / linter
./scripts/validate