type_enforced
type_enforced
Fast where it counts, thorough where it matters. Runtime validation for Python type annotations. Zero dependencies and uncompromising performance.
Table of Contents
- Quick Start
- Why type_enforced?
- Installation
- Usage Guide
- Supported Type Annotations
- Value Validation with Constraints
- Configuration Reference
- Production Best Practices
- Contributing
- Academic Citation
- License
Quick Start
import type_enforced
# 1. Complete validation
@type_enforced.Enforcer
def greet(name: list[str], repeat: int = 1) -> str:
return f"Hello {', '.join(name)}!" * repeat
greet(["Alice"], 2) # Returns "Hello Alice!Hello Alice!"
greet(["Alice"], "twice") # Raises TypeError at runtime!
# 2. Fast O(1) validation (does not check every item in passed collections)
@type_enforced.FastEnforcer
def process_tags(tags: list[str]) -> int:
return len(tags)
process_tags(["admin", "user"]) # Returns 2
process_tags([123, "user"]) # Raises TypeError (first element is checked)
Enforce an entire module (complete or fast O(1) sampled validation):
import my_package
import type_enforced
# Enforce all functions and classes across my_package
type_enforced.ModuleEnforcer(my_package)
# Or for fast O(1) sampled validation across my_package:
# type_enforced.FastModuleEnforcer(my_package)
Why type_enforced?
Static type checkers (like mypy or pyright) catch errors during development, but offer zero protection at runtime against dynamic payloads, untyped API inputs, or user data.
Existing runtime type checkers force an unnecessary compromise:
- Pydantic provides thorough validation, but comes with heavy runtime overhead and steep execution slowdowns.
- Beartype achieves high speed primarily by taking shortcuts. It samples 1 element in collections and misses invalid items in unsampled data.
type_enforced eliminates this compromise:
- Guaranteed Complete Validation: Validates every single item across large collections and nested data structures (e.g.
list[dict[str, int]]or dicts with 10,000+ keys) by default, with zero shortcuts. - Fastest Full Validation: Delivers full, uncompromising validation at a fraction of other packages' overhead.
- Fastest Sampled Validation: Need O(1) or logarithmic sampling for massive collections? This is how Beartype works. Set
iterable_sample_pct='first','last','bookend','bookend_plus','log',0(random pick), or a percentage. Sampled validation intype_enforcedruns up to 8x faster than Beartype. - Zero Dependencies & Pure Python Compatible: Zero external runtime dependencies. Runs everywhere standard Python 3.11+ runs, with optional automatic C++ acceleration via nanobind when available.
- Rich Type Support & Constraints: Seamlessly supports standard Python
|unions, nested generics, Literals, Callables, Dataclasses, custom class inheritance, and custom validationConstraintrules. - Clean Tracebacks: Strips internal validation frames from tracebacks by default, pinpointing the exact line in your code that caused the issue.
Performance at a Glance
Timings represent the added differential validation time (enforced call time minus non-enforced baseline call time) in microseconds (µs), averaged over 100 runs when using the C++ backend. ⚠ = checker did not consistently catch invalid types for this case (generated by utils/minibench.py). For full benchmarks see utils/benchmark.py and benchmark.md.
| Type | Size | type_enforced (sample=1) | Beartype (sample=1) | Typeguard (sample=1) | type_enforced (100%) | Pydantic (100%) | msgspec (100%) | cattrs (100%) | Typeguard (100%) |
|---|---|---|---|---|---|---|---|---|---|
int |
— | 0.014 µs | 0.203 µs | 1.902 µs | 0.014 µs | 0.458 µs | 0.280 µs | 0.117 µs | 1.888 µs |
Union[int, float] |
— | 0.014 µs | 0.212 µs | 3.951 µs | 0.013 µs | 0.505 µs | 0.425 µs | 0.464 µs | 3.896 µs |
str |
— | 0.014 µs | 0.207 µs | 1.894 µs | 0.014 µs | 0.459 µs | 0.266 µs | 0.119 µs ⚠ | 1.847 µs |
list[int] |
1 000 items | 0.019 µs ⚠ | 0.335 µs ⚠ | 3.151 µs ⚠ | 0.429 µs | 10.946 µs | 5.053 µs | 47.520 µs | 1043.583 µs |
list[int] |
10 000 items | 0.020 µs ⚠ | 0.425 µs ⚠ | 3.165 µs ⚠ | 4.451 µs | 105.927 µs | 44.827 µs | 477.761 µs | 10451.041 µs |
dict[str, int] |
1 000 keys | 0.024 µs ⚠ | 0.348 µs ⚠ | 4.410 µs ⚠ | 3.149 µs | 40.282 µs | 26.908 µs | 69.954 µs | 2064.560 µs |
dict[str, int] |
10 000 keys | 0.027 µs ⚠ | 0.346 µs ⚠ | 4.390 µs ⚠ | 41.214 µs | 443.875 µs | 319.566 µs | 723.084 µs | 20530.390 µs |
list[list[int]] |
100 x 100 items | 0.026 µs ⚠ | 0.375 µs ⚠ | 4.456 µs ⚠ | 3.443 µs | 107.959 µs | 48.582 µs | 477.303 µs | 10526.167 µs |
dict[str, list[int]] |
100 x 100 items | 0.031 µs ⚠ | 0.453 µs ⚠ | 5.715 µs ⚠ | 4.084 µs | 114.067 µs | 54.316 µs | 484.277 µs | 10751.799 µs |
list[dict[str, int]] |
100 x 100 items | 0.034 µs ⚠ | 0.478 µs ⚠ | 5.810 µs ⚠ | 42.881 µs | 398.183 µs | 267.584 µs | 704.102 µs | 20922.011 µs |
Sampled Validation: When 1 sample validation is acceptable,
type_enforced.FastEnforceris up to 8x faster than Beartype.
Full Validation: When full validation is required,
type_enforced.Enforceris up to 26x faster than Pydantic.
Installation
Install via pip:
pip install type_enforced
Or using uv:
uv add type_enforced
Requirements & Build Options
- Python 3.11+
- Zero Runtime Dependencies: Self-contained package with zero external runtime dependencies.
- C++ Acceleration: If available,
type_enforcedleverages high-performance C++ validators viananobind. - Pure Python Fallback: If compiling from source on a system without a C++ compiler,
type_enforcedautomatically falls back to a pure-Python engine. Force Pure Python Fallback: To explicitly skip C++ compilation and force pure Python mode:
uv(inpyproject.toml):[tool.uv] no-binary-package = ["type-enforced"] config-settings-package = { type-enforced = { "cmake.define.SKIP_CPP_BUILD" = "ON" } }pip(inpyproject.tomlwhen building from source):[tool.scikit-build.cmake.define] SKIP_CPP_BUILD = "ON"pip(inrequirements.txt):type_enforced --config-settings=cmake.define.SKIP_CPP_BUILD=ON --no-binary type_enforcedpip(CLI):pip install type_enforced --no-binary type_enforced -Ccmake.define.SKIP_CPP_BUILD=ON(Or set
SKBUILD_CMAKE_ARGS="-DSKIP_CPP_BUILD=ON"andPIP_NO_BINARY="type_enforced"in your environment)Verify C++ Acceleration Status: Check whether C++ acceleration is active in the current environment:
import type_enforced print(type_enforced.has_cpp()) # True if C++ acceleration is active, False for pure Python
Legacy Python Compatibility
For older Python versions, pin to legacy releases:
- Python 3.10:
pip install "type_enforced<=1.10.2" - Python 3.9:
pip install "type_enforced<=1.9.0" - Python 3.7 – 3.8:
pip install "type_enforced==0.0.16"
Usage Guide
1. Functions and Methods
Apply @type_enforced.Enforcer or @type_enforced.FastEnforcer to any callable. It validates positional arguments, keyword arguments, default parameters, and the return type.
import type_enforced
@type_enforced.Enforcer
def process_user(user_id: int, tags: list[str], active: bool = True) -> dict[str, str | int]:
return {"user_id": user_id, "status": "active" if active else "inactive"}
# Passing invalid types raises a descriptive TypeError:
process_user("123", ["admin"])
# TypeError: TypeEnforced Exception (process_user): Type mismatch for typed variable `user_id`.
# Expected one of the following `[<class 'int'>]` but got `<class 'str'>` with value `123` instead.
2. Classes and Dataclasses
Decorating a class with @type_enforced.Enforcer or @type_enforced.FastEnforcer automatically enforces types on all annotated methods (including __init__, @classmethod, and @staticmethod):
import type_enforced
from dataclasses import dataclass
@type_enforced.Enforcer
class Account:
def __init__(self, username: str, balance: float):
self.username = username
self.balance = balance
def deposit(self, amount: float) -> float:
self.balance += amount
return self.balance
@staticmethod
def validate_code(code: str) -> bool:
return len(code) == 6
# Dataclasses work seamlessly:
@type_enforced.Enforcer
@dataclass
class UserConfig:
retries: int
endpoint: str
To disable enforcement on a specific method within an enforced class:
@type_enforced.Enforcer
class Worker:
def standard_job(self, task: str) -> None:
pass
@type_enforced.Enforcer(enabled=False)
def high_throughput_job(self, data):
# Type enforcement skipped for maximum throughput
pass
3. Module-Level Enforcement (ModuleEnforcer or FastModuleEnforcer)
Enforce typing across an entire module in a single line without decorating every function and class individually:
# Place at the top of your module file (e.g., my_package/core.py)
import type_enforced
type_enforced.ModuleEnforcer() # Complete validation across module
# Or for fast O(1) sampled validation across the module:
# type_enforced.FastModuleEnforcer()
def add(a: int, b: int) -> int:
return a + b
class Helper:
def run(self, flag: bool) -> str:
return "ok" if flag else "failed"
You can also enforce an imported module:
import my_package
import type_enforced
type_enforced.ModuleEnforcer(my_package)
# Or: type_enforced.FastModuleEnforcer(my_package)
Note: By default,
submodules=True, which recursively enforces all sub-packages/sub-modules in the same namespace (e.g.mypkg.submodule), while safely ignoring third-party and standard library imports.
Supported Type Annotations
type_enforced supports all standard Python 3.11+ typing constructs:
Standard Built-ins & Unions
@type_enforced.Enforcer
def fn(
a: int,
b: str | float, # Standard union syntax
c: int | None = None, # Optional syntax
) -> None:
pass
Collections & Nested Generics
@type_enforced.Enforcer
def fn(
items: list[int | float],
mapping: dict[str, list[int]], # Dicts require [KeyType, ValType]
unique_ids: set[str],
fixed_pair: tuple[str, int], # Exact positional tuple: (str, int)
var_tuple: tuple[int, ...], # Variable-length tuple
) -> None:
pass
Custom Classes & Subclass Inheritance
By default, subclasses pass type validation (e.g. Bar() satisfies Foo if class Bar(Foo)):
class Animal: pass
class Dog(Animal): pass
class Vehicle: pass
@type_enforced.Enforcer
def feed(animal: Animal) -> None:
pass
feed(Animal()) # OK
feed(Dog()) # OK (subclasses allowed)
feed(Vehicle()) # Raises TypeError
To enforce uninitialized class objects (the class itself, rather than an instance), use type[Animal] (or typing.Type[Animal]):
@type_enforced.Enforcer
def make_instance(cls: type[Animal]) -> Animal:
return cls()
Literals & Special Types
from typing import Literal, Callable, Sized, Any
@type_enforced.Enforcer
def fn(
mode: Literal["read", "write"], # Value check: must equal "read" or "write"
handler: Callable, # Functions, methods, generators
container: Sized, # list, dict, set, str, tuple, bytes, etc.
wildcard: Any, # Permissive bypass
) -> None:
pass
- Stacking Literals: Literals combine with unions using OR logic (
int | Literal['auto']allows anyintor the literal string'auto').
Modern Typing Constructs (PEP Standards)
type_enforced comprehensively supports modern typing features from recent Python PEPs:
from typing import (
Callable,
LiteralString,
Never,
NewType,
NoReturn,
Self,
TypeGuard,
TypeIs,
TypeVar,
TypedDict,
)
# 1. PEP 673: typing.Self
class Builder:
@type_enforced.Enforcer
def set_name(self, name: str) -> Self:
self.name = name
return self
# 2. PEP 589: typing.TypedDict (validates required keys & field types)
class UserPayload(TypedDict):
id: int
name: str
@type_enforced.Enforcer
def create_user(payload: UserPayload) -> str:
return payload["name"]
# 3. PEP 484: typing.NewType
UserId = NewType("UserId", int)
@type_enforced.Enforcer
def get_user(user_id: UserId) -> None:
pass
# 4. Subscripted Callables (PEP 484 & PEP 612)
@type_enforced.Enforcer
def apply_handler(callback: Callable[[int, str], bool]) -> None:
pass
# 5. PEP 675: typing.LiteralString
@type_enforced.Enforcer
def run_query(sql: LiteralString) -> None:
pass
# 6. PEP 484 / PEP 654: NoReturn and Never
@type_enforced.Enforcer
def terminate() -> NoReturn:
raise SystemExit(0)
# 7. PEP 647 & PEP 742: TypeGuard and TypeIs
@type_enforced.Enforcer
def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
return all(isinstance(x, str) for x in val)
# 8. TypeVar, ParamSpec, TypeVarTuple & PEP 695 (Python 3.12+)
T = TypeVar("T", bound=int | float)
@type_enforced.Enforcer
def scale(val: T, factor: float) -> float:
return val * factor
Collection & Nested Type Unions
Unions of collection types are evaluated per-variant, enforcing that each container strictly satisfies one schema rather than allowing mixed elements:
@type_enforced.Enforcer
def process_data(
coords: tuple[int, str] | tuple[str, int],
lookup: dict[str, list[int]] | dict[str, int],
tags: list[int] | list[str],
) -> None:
pass
# Distinct collection schemas match:
process_data((1, "north"), {"a": [1, 2]}, [1, 2, 3]) # OK
process_data(("north", 1), {"a": 10}, ["a", "b"]) # OK
# Mixed invalid structures fail:
process_data((1, 1), {"a": 10}, [1, 2]) # Raises TypeError for coords
process_data(
(1, "north"), {"a": 1, "b": [2]}, [1, 2]
) # Raises TypeError for lookup
process_data((1, "north"), {"a": 10}, [1, "two"]) # Raises TypeError for tags
Variadic Positional & Keyword Arguments
*args and **kwargs are fully supported with clear, indexed error messages:
@type_enforced.Enforcer
def configure(*flags: str, **settings: int | bool) -> None:
pass
configure("verbose", "debug", timeout=30, dry_run=True) # OK
configure("verbose", 123) # Raises TypeError: Type mismatch for typed variable `flags[1]`
configure(timeout="30s") # Raises TypeError: Type mismatch for typed variable `settings['timeout']`
Known Limitations / Currently Unsupported
- Generic parameterization of
Sized(e.g.Sized[int]— useSizedwithout inner type arguments)
Value Validation with Constraints
type_enforced allows post-type-check value constraints directly in type annotations.
Built-in Constraint
Validate bounds, numeric comparisons, string patterns (regex), and inclusion/exclusion:
import type_enforced
from type_enforced.utils import Constraint
@type_enforced.Enforcer
def set_score(
score: int | Constraint(ge=0, le=100),
code: str | Constraint(pattern=r"^[A-Z]{3}[0-9]{4}$"),
) -> bool:
return True
set_score(85, "ABC1234") # Passes
set_score(105, "ABC1234") # Raises TypeError (Constraint `Less Than Or Equal To (100)` not met)
set_score(85, "invalid") # Raises TypeError (Constraint `Regex Pattern Match` not met)
Available Constraint parameters:
gt,lt,ge,le,eq,ne(numeric / comparison bounds)pattern(regular expression string match)includes,excludes(membership checks)
Custom GenericConstraint
Write arbitrary validation logic using custom predicates:
import type_enforced
from type_enforced.utils import GenericConstraint
RGBColor = str | GenericConstraint({
"valid_hex_color": lambda c: c.startswith("#") and len(c) in (4, 7)
})
@type_enforced.Enforcer
def render(color: RGBColor) -> None:
pass
render("#ffffff") # Passes
render("red") # Raises TypeError (Constraint `valid_hex_color` not met)
Note: Constraints are evaluated after type checking. Constraints stack with unions:
int | Constraint(ge=0) | Constraint(le=10).
Configuration Reference
@Enforcer, @FastEnforcer, ModuleEnforcer, and FastModuleEnforcer accept the following configuration arguments:
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
True |
Toggle enforcement. Set False to bypass type checks (useful for production vs. debugging or per-method overrides). |
strict |
bool |
True |
When True, raises TypeError on mismatch. When False, logs a warning to the console instead of raising. |
clean_traceback |
bool |
True |
Filters internal type_enforced stack frames so unhandled tracebacks point directly to user code (see note below). |
iterable_sample_pct |
int, float, or str |
100 ('first' for Fast*) |
Sampling mode or percentage (0–100) of iterable items to validate. 'first' checks the first item, 'last' checks the last item (or first item for dicts/sets), 'bookend' checks first and last items (first 2 items for dicts/sets), 'bookend_plus' checks first, last, and a random middle item (first 2 items and 1 random item for dicts/sets), 'log' checks a sample of ceil(log2(n)) items using a pseudo-random start offset and even steps across sequences (first ceil(log2(n)) items for dicts/sets), 0 checks 1 random item, and 1..100 checks the specified percentage (rounding up) starting at a pseudo-random offset within each step interval for sequences (first $N$ items for dicts/sets). 100 validates all elements. Note: FastEnforcer and FastModuleEnforcer strictly accept 'first', 'last', 'bookend', 'bookend_plus', 'log', or 0. |
only_typed |
bool |
False |
When True, raises an exception upon decoration if any parameter or return value lacks a type hint. |
submodules (ModuleEnforcers only) |
bool |
True |
Recursively enforces all sub-packages/sub-modules in the same namespace. |
Configuration Options in Depth
1. Strict Typing Mode (only_typed=True)
To catch unannotated parameters or missing return annotations across your codebase, enable only_typed=True. This raises a TypeError at definition time if any parameter (excluding self/cls) or the return type lacks an annotation:
import type_enforced
@type_enforced.Enforcer(only_typed=True)
def calculate(a: int, b: int) -> int:
return a + b
# Missing annotation on parameter `b` or missing return annotation raises immediately:
@type_enforced.Enforcer(only_typed=True)
def invalid_fn(a: int, b):
return a
# TypeError: TypeEnforced Exception (invalid_fn): Untyped variable `b` found in function/method `invalid_fn`.
2. Warning Mode (strict=False)
Print warnings to the console instead of raising exceptions (useful for gradual adoption or debugging without breaking execution):
@type_enforced.Enforcer(strict=False)
def lenient_fn(x: int) -> int:
return x
lenient_fn("not_an_int")
# Logs: TypeEnforced Warning (lenient_fn): Type mismatch for typed variable `x`...
# Returns "not_an_int" without raising an exception.
3. Clean Tracebacks (clean_traceback=True)
By default, clean_traceback=True temporarily hooks sys.excepthook when a type exception is raised, stripping internal type_enforced library frames so that unhandled script tracebacks point directly to the line of user code that caused the issue.
Note on Interactive Terminals / REPLs: In interactive environments (such as the Python REPL / PyREPL, IPython, or Jupyter notebooks), the shell wraps execution in an internal
try...exceptloop and catches exceptions before they reachsys.excepthook. Consequently, interactive terminal sessions will still display the full traceback.
4. Sampled Validation (FastEnforcer, FastModuleEnforcer, iterable_sample_pct)
For large or performance-critical collections, use @type_enforced.FastEnforcer or configure sampling instead of full iteration:
'first'(default forFastEnforcer/FastModuleEnforcer): Validates the first element in O(1) time (runs up to 8x faster than Beartype).'last': Validates the last element in O(1) time for indexable sequences (list,tuple). For non-indexed collections likedictandset,'last'validates the first item to avoid reverse iteration and hash table lookup overhead.'bookend': Validates the first and last elements in O(1) time for sequences (the first 2 items fordictandset).'bookend_plus': Validates the first, last, and a random middle element in O(1) time for sequences (the first 2 items and 1 random item fordictandset).'log': For sequences (list,tuple), samplesceil(log2(n))items by picking a Weyl pseudo-random start offset and taking even step jumps across the collection. Fordictandset, validates the firstceil(log2(n))items.0: Validates one element chosen at random.1..99(int,Enforcer/ModuleEnforceronly): Validates the specified percentage of items (rounding up). For sequences, selects a Weyl pseudo-random start offset in[0, step - 1]and takes even step jumps across the collection, giving every index an equal probability of being checked. Fordictandset, validates the firstNitems.100: Complete validation of all items across the collection.
# Using FastEnforcer directly:
@type_enforced.FastEnforcer
def fast_check(items: list[int]) -> int:
return len(items)
fast_check([1, 2, 3]) # OK
fast_check(["bad_first", 2, 3]) # Raises TypeError
# Or configure Enforcer with a specific sample mode:
@type_enforced.Enforcer(iterable_sample_pct="last")
def check_last(items: list[int]) -> int:
return len(items)
Production Best Practices
Multi-Threaded Services & Web Frameworks (clean_traceback=False)
By default, clean_traceback=True temporarily hooks sys.excepthook to filter internal library frames for standalone scripts. In concurrent multi-threaded environments and applications using centralized error handlers, consider setting clean_traceback=False:
import type_enforced
@type_enforced.Enforcer(clean_traceback=False)
def process_request(user_id: int, tags: list[str]) -> dict:
return {"user_id": user_id, "tags": tags}
Contributing
Contributions are welcome!
Development Setup
We use uv for dependency management and testing in a Unix-based environment (Linux, macOS, or WSL2 on Windows).
# Clone the repository
git clone https://github.com/connor-makowski/type_enforced.git
cd type_enforced
# Install dev dependencies
uv sync --extra dev
Development Commands
| Command | Description |
|---|---|
uv run pytest |
Run tests in local environment |
uv run pytest -v |
Run tests with verbose output |
uv run nox |
Run test suite across Python 3.11–3.14 (C++ and pure-Python fallback) |
uv run nox -s tests-3.14 |
Run test suite on a specific Python version |
uv run python utils/minibench.py |
Run quick performance at a glance benchmark |
uv run python utils/cpp_vs_python_bench.py |
Run C++ accelerated vs pure Python benchmark |
uv run python utils/prettify.py |
Auto-format with autoflake and black (80 col) |
Guidelines
- Fork the repo and create your branch from
main. - Ensure all tests pass across versions (
uv run nox). - Format code before committing (
uv run python utils/prettify.py). - Keep commits atomic and clearly described.
- Submit a pull request.
Academic Citation
If you use type_enforced in academic research, please cite our JOSS paper:
@article{Makowski2026,
doi = {10.21105/joss.08832},
url = {https://doi.org/10.21105/joss.08832},
year = {2026},
publisher = {The Open Journal},
volume = {11},
number = {118},
pages = {8832},
author = {Connor Makowski},
title = {type_enforced: A pure Python runtime type enforcer},
journal = {Journal of Open Source Software}
}
License
Distributed under the MIT License. See LICENSE for details.
1""" 2# type_enforced 3 4[](https://pypi.org/project/type_enforced/) 5[](https://pypi.org/project/type_enforced/) 6[](https://opensource.org/licenses/MIT) 7[](https://doi.org/10.21105/joss.08832) 8[](https://pepy.tech/projects/type-enforced) 9 10Fast where it counts, thorough where it matters. Runtime validation for Python type annotations. Zero dependencies and uncompromising performance. 11 12--- 13 14## Table of Contents 15- [Quick Start](#quick-start) 16- [Why type_enforced?](#why-type_enforced) 17 - [Performance at a Glance](#performance-at-a-glance) 18- [Installation](#installation) 19 - [Requirements & Build Options](#requirements--build-options) 20- [Usage Guide](#usage-guide) 21 - [1. Functions and Methods](#1-functions-and-methods) 22 - [2. Classes and Dataclasses](#2-classes-and-dataclasses) 23 - [3. Module-Level Enforcement](#3-module-level-enforcement-moduleenforcer-or-fastmoduleenforcer) 24- [Supported Type Annotations](#supported-type-annotations) 25- [Value Validation with Constraints](#value-validation-with-constraints) 26- [Configuration Reference](#configuration-reference) 27 - [Configuration Options in Depth](#configuration-options-in-depth) 28- [Production Best Practices](#production-best-practices) 29- [Contributing](#contributing) 30- [Academic Citation](#academic-citation) 31- [License](#license) 32 33--- 34 35## Quick Start 36 37```python 38import type_enforced 39 40# 1. Complete validation 41@type_enforced.Enforcer 42def greet(name: list[str], repeat: int = 1) -> str: 43 return f"Hello {', '.join(name)}!" * repeat 44 45greet(["Alice"], 2) # Returns "Hello Alice!Hello Alice!" 46greet(["Alice"], "twice") # Raises TypeError at runtime! 47 48# 2. Fast O(1) validation (does not check every item in passed collections) 49@type_enforced.FastEnforcer 50def process_tags(tags: list[str]) -> int: 51 return len(tags) 52 53process_tags(["admin", "user"]) # Returns 2 54process_tags([123, "user"]) # Raises TypeError (first element is checked) 55``` 56 57Enforce an entire module (complete or fast O(1) sampled validation): 58 59```python 60import my_package 61import type_enforced 62 63# Enforce all functions and classes across my_package 64type_enforced.ModuleEnforcer(my_package) 65 66# Or for fast O(1) sampled validation across my_package: 67# type_enforced.FastModuleEnforcer(my_package) 68``` 69 70--- 71 72## Why type_enforced? 73 74Static type checkers (like `mypy` or `pyright`) catch errors during development, but offer zero protection at runtime against dynamic payloads, untyped API inputs, or user data. 75 76Existing runtime type checkers force an unnecessary compromise: 77- **Pydantic** provides thorough validation, but comes with heavy runtime overhead and steep execution slowdowns. 78- **Beartype** achieves high speed primarily by taking shortcuts. It samples 1 element in collections and misses invalid items in unsampled data. 79 80`type_enforced` eliminates this compromise: 81 82- **Guaranteed Complete Validation**: Validates every single item across large collections and nested data structures (e.g. `list[dict[str, int]]` or dicts with 10,000+ keys) by default, with zero shortcuts. 83- **Fastest Full Validation**: Delivers full, uncompromising validation at a fraction of other packages' overhead. 84- **Fastest Sampled Validation**: Need O(1) or logarithmic sampling for massive collections? This is how Beartype works. Set `iterable_sample_pct='first'`, `'last'`, `'bookend'`, `'bookend_plus'`, `'log'`, `0` (random pick), or a percentage. Sampled validation in `type_enforced` runs up to 8x faster than Beartype. 85- **Zero Dependencies & Pure Python Compatible**: Zero external runtime dependencies. Runs everywhere standard Python 3.11+ runs, with optional automatic C++ acceleration via nanobind when available. 86- **Rich Type Support & Constraints**: Seamlessly supports standard Python `|` unions, nested generics, Literals, Callables, Dataclasses, custom class inheritance, and custom validation `Constraint` rules. 87- **Clean Tracebacks**: Strips internal validation frames from tracebacks by default, pinpointing the exact line in your code that caused the issue. 88 89### Performance at a Glance 90 91Timings represent the added differential validation time (enforced call time minus non-enforced baseline call time) in microseconds (µs), averaged over 100 runs when using the C++ backend. ⚠ = checker did not consistently catch invalid types for this case (generated by utils/minibench.py). For full benchmarks see [utils/benchmark.py](utils/benchmark.py) and [benchmark.md](benchmark.md). 92 93| Type | Size | type_enforced (sample=1) | Beartype (sample=1) | Typeguard (sample=1) | type_enforced (100%) | Pydantic (100%) | msgspec (100%) | cattrs (100%) | Typeguard (100%) | 94| :--------------------- | :--------------: | :----------------------: | :-----------------: | :------------------: | :------------------: | :--------------: | :--------------: | :--------------: | :--------------: | 95| `int` | — | 0.014 µs | 0.203 µs | 1.902 µs | 0.014 µs | 0.458 µs | 0.280 µs | 0.117 µs | 1.888 µs | 96| `Union[int, float]` | — | 0.014 µs | 0.212 µs | 3.951 µs | 0.013 µs | 0.505 µs | 0.425 µs | 0.464 µs | 3.896 µs | 97| `str` | — | 0.014 µs | 0.207 µs | 1.894 µs | 0.014 µs | 0.459 µs | 0.266 µs | 0.119 µs ⚠ | 1.847 µs | 98| `list[int]` | 1 000 items | 0.019 µs ⚠ | 0.335 µs ⚠ | 3.151 µs ⚠ | 0.429 µs | 10.946 µs | 5.053 µs | 47.520 µs | 1043.583 µs | 99| `list[int]` | 10 000 items | 0.020 µs ⚠ | 0.425 µs ⚠ | 3.165 µs ⚠ | 4.451 µs | 105.927 µs | 44.827 µs | 477.761 µs | 10451.041 µs | 100| `dict[str, int]` | 1 000 keys | 0.024 µs ⚠ | 0.348 µs ⚠ | 4.410 µs ⚠ | 3.149 µs | 40.282 µs | 26.908 µs | 69.954 µs | 2064.560 µs | 101| `dict[str, int]` | 10 000 keys | 0.027 µs ⚠ | 0.346 µs ⚠ | 4.390 µs ⚠ | 41.214 µs | 443.875 µs | 319.566 µs | 723.084 µs | 20530.390 µs | 102| `list[list[int]]` | 100 x 100 items | 0.026 µs ⚠ | 0.375 µs ⚠ | 4.456 µs ⚠ | 3.443 µs | 107.959 µs | 48.582 µs | 477.303 µs | 10526.167 µs | 103| `dict[str, list[int]]` | 100 x 100 items | 0.031 µs ⚠ | 0.453 µs ⚠ | 5.715 µs ⚠ | 4.084 µs | 114.067 µs | 54.316 µs | 484.277 µs | 10751.799 µs | 104| `list[dict[str, int]]` | 100 x 100 items | 0.034 µs ⚠ | 0.478 µs ⚠ | 5.810 µs ⚠ | 42.881 µs | 398.183 µs | 267.584 µs | 704.102 µs | 20922.011 µs | 105 106> **Sampled Validation:** When 1 sample validation is acceptable, `type_enforced.FastEnforcer` is **up to 8x faster than Beartype**. 107 108> **Full Validation:** When full validation is required, `type_enforced.Enforcer` is **up to 26x faster than Pydantic**. 109 110--- 111 112## Installation 113 114Install via `pip`: 115 116```bash 117pip install type_enforced 118``` 119 120Or using `uv`: 121 122```bash 123uv add type_enforced 124``` 125 126### Requirements & Build Options 127- **Python 3.11+** 128- **Zero Runtime Dependencies**: Self-contained package with zero external runtime dependencies. 129- **C++ Acceleration**: If available, `type_enforced` leverages high-performance C++ validators via `nanobind`. 130- **Pure Python Fallback**: If compiling from source on a system without a C++ compiler, `type_enforced` automatically falls back to a pure-Python engine. 131- **Force Pure Python Fallback**: To explicitly skip C++ compilation and force pure Python mode: 132 133 **`uv` (in `pyproject.toml`)**: 134 ```toml 135 [tool.uv] 136 no-binary-package = ["type-enforced"] 137 config-settings-package = { type-enforced = { "cmake.define.SKIP_CPP_BUILD" = "ON" } } 138 ``` 139 140 **`pip` (in `pyproject.toml` when building from source)**: 141 ```toml 142 [tool.scikit-build.cmake.define] 143 SKIP_CPP_BUILD = "ON" 144 ``` 145 146 **`pip` (in `requirements.txt`)**: 147 ```text 148 type_enforced --config-settings=cmake.define.SKIP_CPP_BUILD=ON --no-binary type_enforced 149 ``` 150 151 **`pip` (CLI)**: 152 ```bash 153 pip install type_enforced --no-binary type_enforced -Ccmake.define.SKIP_CPP_BUILD=ON 154 ``` 155 *(Or set `SKBUILD_CMAKE_ARGS="-DSKIP_CPP_BUILD=ON"` and `PIP_NO_BINARY="type_enforced"` in your environment)* 156- **Verify C++ Acceleration Status**: Check whether C++ acceleration is active in the current environment: 157 ```python 158 import type_enforced 159 160 print(type_enforced.has_cpp()) # True if C++ acceleration is active, False for pure Python 161 ``` 162 163<details> 164<summary>Legacy Python Compatibility</summary> 165 166For older Python versions, pin to legacy releases: 167- **Python 3.10**: `pip install "type_enforced<=1.10.2"` 168- **Python 3.9**: `pip install "type_enforced<=1.9.0"` 169- **Python 3.7 – 3.8**: `pip install "type_enforced==0.0.16"` 170</details> 171 172--- 173 174## Usage Guide 175 176### 1. Functions and Methods 177 178Apply `@type_enforced.Enforcer` or `@type_enforced.FastEnforcer` to any callable. It validates positional arguments, keyword arguments, default parameters, and the return type. 179 180```python 181import type_enforced 182 183@type_enforced.Enforcer 184def process_user(user_id: int, tags: list[str], active: bool = True) -> dict[str, str | int]: 185 return {"user_id": user_id, "status": "active" if active else "inactive"} 186 187# Passing invalid types raises a descriptive TypeError: 188process_user("123", ["admin"]) 189# TypeError: TypeEnforced Exception (process_user): Type mismatch for typed variable `user_id`. 190# Expected one of the following `[<class 'int'>]` but got `<class 'str'>` with value `123` instead. 191``` 192 193### 2. Classes and Dataclasses 194 195Decorating a class with `@type_enforced.Enforcer` or `@type_enforced.FastEnforcer` automatically enforces types on all annotated methods (including `__init__`, `@classmethod`, and `@staticmethod`): 196 197```python 198import type_enforced 199from dataclasses import dataclass 200 201@type_enforced.Enforcer 202class Account: 203 def __init__(self, username: str, balance: float): 204 self.username = username 205 self.balance = balance 206 207 def deposit(self, amount: float) -> float: 208 self.balance += amount 209 return self.balance 210 211 @staticmethod 212 def validate_code(code: str) -> bool: 213 return len(code) == 6 214 215# Dataclasses work seamlessly: 216@type_enforced.Enforcer 217@dataclass 218class UserConfig: 219 retries: int 220 endpoint: str 221``` 222 223To disable enforcement on a specific method within an enforced class: 224 225```python 226@type_enforced.Enforcer 227class Worker: 228 def standard_job(self, task: str) -> None: 229 pass 230 231 @type_enforced.Enforcer(enabled=False) 232 def high_throughput_job(self, data): 233 # Type enforcement skipped for maximum throughput 234 pass 235``` 236 237### 3. Module-Level Enforcement (`ModuleEnforcer` or `FastModuleEnforcer`) 238 239Enforce typing across an entire module in a single line without decorating every function and class individually: 240 241```python 242# Place at the top of your module file (e.g., my_package/core.py) 243import type_enforced 244 245type_enforced.ModuleEnforcer() # Complete validation across module 246# Or for fast O(1) sampled validation across the module: 247# type_enforced.FastModuleEnforcer() 248 249def add(a: int, b: int) -> int: 250 return a + b 251 252class Helper: 253 def run(self, flag: bool) -> str: 254 return "ok" if flag else "failed" 255``` 256 257You can also enforce an imported module: 258 259```python 260import my_package 261import type_enforced 262 263type_enforced.ModuleEnforcer(my_package) 264# Or: type_enforced.FastModuleEnforcer(my_package) 265``` 266 267 268> **Note:** By default, `submodules=True`, which recursively enforces all sub-packages/sub-modules in the same namespace (e.g. `mypkg.submodule`), while safely ignoring third-party and standard library imports. 269 270--- 271 272## Supported Type Annotations 273 274`type_enforced` supports all standard Python 3.11+ typing constructs: 275 276### Standard Built-ins & Unions 277```python 278@type_enforced.Enforcer 279def fn( 280 a: int, 281 b: str | float, # Standard union syntax 282 c: int | None = None, # Optional syntax 283) -> None: 284 pass 285``` 286 287### Collections & Nested Generics 288```python 289@type_enforced.Enforcer 290def fn( 291 items: list[int | float], 292 mapping: dict[str, list[int]], # Dicts require [KeyType, ValType] 293 unique_ids: set[str], 294 fixed_pair: tuple[str, int], # Exact positional tuple: (str, int) 295 var_tuple: tuple[int, ...], # Variable-length tuple 296) -> None: 297 pass 298``` 299 300### Custom Classes & Subclass Inheritance 301By default, subclasses pass type validation (e.g. `Bar()` satisfies `Foo` if `class Bar(Foo)`): 302 303```python 304class Animal: pass 305class Dog(Animal): pass 306class Vehicle: pass 307 308@type_enforced.Enforcer 309def feed(animal: Animal) -> None: 310 pass 311 312feed(Animal()) # OK 313feed(Dog()) # OK (subclasses allowed) 314feed(Vehicle()) # Raises TypeError 315``` 316 317To enforce uninitialized class objects (the class itself, rather than an instance), use `type[Animal]` (or `typing.Type[Animal]`): 318 319```python 320@type_enforced.Enforcer 321def make_instance(cls: type[Animal]) -> Animal: 322 return cls() 323``` 324 325### Literals & Special Types 326```python 327from typing import Literal, Callable, Sized, Any 328 329@type_enforced.Enforcer 330def fn( 331 mode: Literal["read", "write"], # Value check: must equal "read" or "write" 332 handler: Callable, # Functions, methods, generators 333 container: Sized, # list, dict, set, str, tuple, bytes, etc. 334 wildcard: Any, # Permissive bypass 335) -> None: 336 pass 337``` 338 339- **Stacking Literals**: Literals combine with unions using OR logic (`int | Literal['auto']` allows any `int` or the literal string `'auto'`). 340 341### Modern Typing Constructs (PEP Standards) 342`type_enforced` comprehensively supports modern typing features from recent Python PEPs: 343 344```python 345from typing import ( 346 Callable, 347 LiteralString, 348 Never, 349 NewType, 350 NoReturn, 351 Self, 352 TypeGuard, 353 TypeIs, 354 TypeVar, 355 TypedDict, 356) 357 358# 1. PEP 673: typing.Self 359class Builder: 360 @type_enforced.Enforcer 361 def set_name(self, name: str) -> Self: 362 self.name = name 363 return self 364 365# 2. PEP 589: typing.TypedDict (validates required keys & field types) 366class UserPayload(TypedDict): 367 id: int 368 name: str 369 370@type_enforced.Enforcer 371def create_user(payload: UserPayload) -> str: 372 return payload["name"] 373 374# 3. PEP 484: typing.NewType 375UserId = NewType("UserId", int) 376 377@type_enforced.Enforcer 378def get_user(user_id: UserId) -> None: 379 pass 380 381# 4. Subscripted Callables (PEP 484 & PEP 612) 382@type_enforced.Enforcer 383def apply_handler(callback: Callable[[int, str], bool]) -> None: 384 pass 385 386# 5. PEP 675: typing.LiteralString 387@type_enforced.Enforcer 388def run_query(sql: LiteralString) -> None: 389 pass 390 391# 6. PEP 484 / PEP 654: NoReturn and Never 392@type_enforced.Enforcer 393def terminate() -> NoReturn: 394 raise SystemExit(0) 395 396# 7. PEP 647 & PEP 742: TypeGuard and TypeIs 397@type_enforced.Enforcer 398def is_str_list(val: list[object]) -> TypeGuard[list[str]]: 399 return all(isinstance(x, str) for x in val) 400 401# 8. TypeVar, ParamSpec, TypeVarTuple & PEP 695 (Python 3.12+) 402T = TypeVar("T", bound=int | float) 403 404@type_enforced.Enforcer 405def scale(val: T, factor: float) -> float: 406 return val * factor 407``` 408 409### Collection & Nested Type Unions 410Unions of collection types are evaluated per-variant, enforcing that each container strictly satisfies one schema rather than allowing mixed elements: 411 412```python 413@type_enforced.Enforcer 414def process_data( 415 coords: tuple[int, str] | tuple[str, int], 416 lookup: dict[str, list[int]] | dict[str, int], 417 tags: list[int] | list[str], 418) -> None: 419 pass 420 421# Distinct collection schemas match: 422process_data((1, "north"), {"a": [1, 2]}, [1, 2, 3]) # OK 423process_data(("north", 1), {"a": 10}, ["a", "b"]) # OK 424 425# Mixed invalid structures fail: 426process_data((1, 1), {"a": 10}, [1, 2]) # Raises TypeError for coords 427process_data( 428 (1, "north"), {"a": 1, "b": [2]}, [1, 2] 429) # Raises TypeError for lookup 430process_data((1, "north"), {"a": 10}, [1, "two"]) # Raises TypeError for tags 431``` 432 433### Variadic Positional & Keyword Arguments 434`*args` and `**kwargs` are fully supported with clear, indexed error messages: 435 436```python 437@type_enforced.Enforcer 438def configure(*flags: str, **settings: int | bool) -> None: 439 pass 440 441configure("verbose", "debug", timeout=30, dry_run=True) # OK 442configure("verbose", 123) # Raises TypeError: Type mismatch for typed variable `flags[1]` 443configure(timeout="30s") # Raises TypeError: Type mismatch for typed variable `settings['timeout']` 444``` 445 446### Known Limitations / Currently Unsupported 447- Generic parameterization of `Sized` (e.g. `Sized[int]` — use `Sized` without inner type arguments) 448 449--- 450 451## Value Validation with Constraints 452 453`type_enforced` allows post-type-check value constraints directly in type annotations. 454 455### Built-in `Constraint` 456Validate bounds, numeric comparisons, string patterns (regex), and inclusion/exclusion: 457 458```python 459import type_enforced 460from type_enforced.utils import Constraint 461 462@type_enforced.Enforcer 463def set_score( 464 score: int | Constraint(ge=0, le=100), 465 code: str | Constraint(pattern=r"^[A-Z]{3}[0-9]{4}$"), 466) -> bool: 467 return True 468 469set_score(85, "ABC1234") # Passes 470set_score(105, "ABC1234") # Raises TypeError (Constraint `Less Than Or Equal To (100)` not met) 471set_score(85, "invalid") # Raises TypeError (Constraint `Regex Pattern Match` not met) 472``` 473 474Available `Constraint` parameters: 475- `gt`, `lt`, `ge`, `le`, `eq`, `ne` (numeric / comparison bounds) 476- `pattern` (regular expression string match) 477- `includes`, `excludes` (membership checks) 478 479### Custom `GenericConstraint` 480Write arbitrary validation logic using custom predicates: 481 482```python 483import type_enforced 484from type_enforced.utils import GenericConstraint 485 486RGBColor = str | GenericConstraint({ 487 "valid_hex_color": lambda c: c.startswith("#") and len(c) in (4, 7) 488}) 489 490@type_enforced.Enforcer 491def render(color: RGBColor) -> None: 492 pass 493 494render("#ffffff") # Passes 495render("red") # Raises TypeError (Constraint `valid_hex_color` not met) 496``` 497 498> **Note:** Constraints are evaluated *after* type checking. Constraints stack with unions: `int | Constraint(ge=0) | Constraint(le=10)`. 499 500--- 501 502## Configuration Reference 503 504`@Enforcer`, `@FastEnforcer`, `ModuleEnforcer`, and `FastModuleEnforcer` accept the following configuration arguments: 505 506| Parameter | Type | Default | Description | 507|:---|:---:|:---:|:---| 508| `enabled` | `bool` | `True` | Toggle enforcement. Set `False` to bypass type checks (useful for production vs. debugging or per-method overrides). | 509| `strict` | `bool` | `True` | When `True`, raises `TypeError` on mismatch. When `False`, logs a warning to the console instead of raising. | 510| `clean_traceback` | `bool` | `True` | Filters internal `type_enforced` stack frames so unhandled tracebacks point directly to user code (see note below). | 511| `iterable_sample_pct` | `int, float, or str` | `100` (`'first'` for `Fast*`) | Sampling mode or percentage (0–100) of iterable items to validate. `'first'` checks the first item, `'last'` checks the last item (or first item for dicts/sets), `'bookend'` checks first and last items (first 2 items for dicts/sets), `'bookend_plus'` checks first, last, and a random middle item (first 2 items and 1 random item for dicts/sets), `'log'` checks a sample of ceil(log2(n)) items using a pseudo-random start offset and even steps across sequences (first ceil(log2(n)) items for dicts/sets), `0` checks 1 random item, and `1..100` checks the specified percentage (rounding up) starting at a pseudo-random offset within each step interval for sequences (first $N$ items for dicts/sets). `100` validates all elements. Note: `FastEnforcer` and `FastModuleEnforcer` strictly accept `'first'`, `'last'`, `'bookend'`, `'bookend_plus'`, `'log'`, or `0`. | 512| `only_typed` | `bool` | `False` | When `True`, raises an exception upon decoration if any parameter or return value lacks a type hint. | 513| `submodules` *(ModuleEnforcers only)* | `bool` | `True` | Recursively enforces all sub-packages/sub-modules in the same namespace. | 514 515### Configuration Options in Depth 516 517#### 1. Strict Typing Mode (`only_typed=True`) 518To catch unannotated parameters or missing return annotations across your codebase, enable `only_typed=True`. This raises a `TypeError` at definition time if any parameter (excluding `self`/`cls`) or the return type lacks an annotation: 519 520```python 521import type_enforced 522 523@type_enforced.Enforcer(only_typed=True) 524def calculate(a: int, b: int) -> int: 525 return a + b 526 527# Missing annotation on parameter `b` or missing return annotation raises immediately: 528@type_enforced.Enforcer(only_typed=True) 529def invalid_fn(a: int, b): 530 return a 531# TypeError: TypeEnforced Exception (invalid_fn): Untyped variable `b` found in function/method `invalid_fn`. 532``` 533 534#### 2. Warning Mode (`strict=False`) 535Print warnings to the console instead of raising exceptions (useful for gradual adoption or debugging without breaking execution): 536 537```python 538@type_enforced.Enforcer(strict=False) 539def lenient_fn(x: int) -> int: 540 return x 541 542lenient_fn("not_an_int") 543# Logs: TypeEnforced Warning (lenient_fn): Type mismatch for typed variable `x`... 544# Returns "not_an_int" without raising an exception. 545``` 546 547#### 3. Clean Tracebacks (`clean_traceback=True`) 548By default, `clean_traceback=True` temporarily hooks `sys.excepthook` when a type exception is raised, stripping internal `type_enforced` library frames so that unhandled script tracebacks point directly to the line of user code that caused the issue. 549 550> **Note on Interactive Terminals / REPLs:** In interactive environments (such as the Python REPL / PyREPL, IPython, or Jupyter notebooks), the shell wraps execution in an internal `try...except` loop and catches exceptions before they reach `sys.excepthook`. Consequently, interactive terminal sessions will still display the full traceback. 551 552#### 4. Sampled Validation (`FastEnforcer`, `FastModuleEnforcer`, `iterable_sample_pct`) 553For large or performance-critical collections, use `@type_enforced.FastEnforcer` or configure sampling instead of full iteration: 554- `'first'` (default for `FastEnforcer` / `FastModuleEnforcer`): Validates the first element in O(1) time (runs up to 8x faster than Beartype). 555- `'last'`: Validates the last element in O(1) time for indexable sequences (`list`, `tuple`). For non-indexed collections like `dict` and `set`, `'last'` validates the first item to avoid reverse iteration and hash table lookup overhead. 556- `'bookend'`: Validates the first and last elements in O(1) time for sequences (the first 2 items for `dict` and `set`). 557- `'bookend_plus'`: Validates the first, last, and a random middle element in O(1) time for sequences (the first 2 items and 1 random item for `dict` and `set`). 558- `'log'`: For sequences (`list`, `tuple`), samples `ceil(log2(n))` items by picking a Weyl pseudo-random start offset and taking even step jumps across the collection. For `dict` and `set`, validates the first `ceil(log2(n))` items. 559- `0`: Validates one element chosen at random. 560- `1..99` (int, `Enforcer` / `ModuleEnforcer` only): Validates the specified percentage of items (rounding up). For sequences, selects a Weyl pseudo-random start offset in `[0, step - 1]` and takes even step jumps across the collection, giving every index an equal probability of being checked. For `dict` and `set`, validates the first `N` items. 561- `100`: Complete validation of all items across the collection. 562 563```python 564# Using FastEnforcer directly: 565@type_enforced.FastEnforcer 566def fast_check(items: list[int]) -> int: 567 return len(items) 568 569fast_check([1, 2, 3]) # OK 570fast_check(["bad_first", 2, 3]) # Raises TypeError 571 572# Or configure Enforcer with a specific sample mode: 573@type_enforced.Enforcer(iterable_sample_pct="last") 574def check_last(items: list[int]) -> int: 575 return len(items) 576``` 577 578 579 580--- 581 582## Production Best Practices 583 584### Multi-Threaded Services & Web Frameworks (`clean_traceback=False`) 585By default, `clean_traceback=True` temporarily hooks `sys.excepthook` to filter internal library frames for standalone scripts. In concurrent multi-threaded environments and applications using centralized error handlers, consider setting `clean_traceback=False`: 586 587```python 588import type_enforced 589 590@type_enforced.Enforcer(clean_traceback=False) 591def process_request(user_id: int, tags: list[str]) -> dict: 592 return {"user_id": user_id, "tags": tags} 593``` 594 595--- 596 597## Contributing 598 599Contributions are welcome! 600 601### Development Setup 602 603We use [uv](https://docs.astral.sh/uv/) for dependency management and testing in a Unix-based environment (Linux, macOS, or WSL2 on Windows). 604 605```bash 606# Clone the repository 607git clone https://github.com/connor-makowski/type_enforced.git 608cd type_enforced 609 610# Install dev dependencies 611uv sync --extra dev 612``` 613 614### Development Commands 615 616| Command | Description | 617|:---|:---| 618| `uv run pytest` | Run tests in local environment | 619| `uv run pytest -v` | Run tests with verbose output | 620| `uv run nox` | Run test suite across Python 3.11–3.14 (C++ and pure-Python fallback) | 621| `uv run nox -s tests-3.14` | Run test suite on a specific Python version | 622| `uv run python utils/minibench.py` | Run quick performance at a glance benchmark | 623| `uv run python utils/cpp_vs_python_bench.py` | Run C++ accelerated vs pure Python benchmark | 624| `uv run python utils/prettify.py` | Auto-format with `autoflake` and `black` (80 col) | 625 626### Guidelines 6271. Fork the repo and create your branch from `main`. 6282. Ensure all tests pass across versions (`uv run nox`). 6293. Format code before committing (`uv run python utils/prettify.py`). 6304. Keep commits atomic and clearly described. 6315. Submit a pull request. 632 633--- 634 635## Academic Citation 636 637If you use `type_enforced` in academic research, please cite our [JOSS paper](https://doi.org/10.21105/joss.08832): 638 639```bibtex 640@article{Makowski2026, 641 doi = {10.21105/joss.08832}, 642 url = {https://doi.org/10.21105/joss.08832}, 643 year = {2026}, 644 publisher = {The Open Journal}, 645 volume = {11}, 646 number = {118}, 647 pages = {8832}, 648 author = {Connor Makowski}, 649 title = {type_enforced: A pure Python runtime type enforcer}, 650 journal = {Journal of Open Source Software} 651} 652``` 653 654--- 655 656## License 657 658Distributed under the [MIT License](https://opensource.org/licenses/MIT). See `LICENSE` for details. 659""" 660 661from .enforcer import Enforcer, FastEnforcer, FunctionMethodEnforcer 662from .module import ModuleEnforcer, FastModuleEnforcer 663from .utils import has_cpp