type_enforced

type_enforced

PyPI version Python Version License: MIT DOI PyPI Downloads

Fast where it counts, thorough where it matters. Runtime validation for Python type annotations. Zero dependencies and uncompromising performance.


Table of Contents


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 in type_enforced runs 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 validation Constraint rules.
  • 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.FastEnforcer is up to 8x faster than Beartype.

Full Validation: When full validation is required, type_enforced.Enforcer is 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_enforced leverages high-performance C++ validators via nanobind.
  • Pure Python Fallback: If compiling from source on a system without a C++ compiler, type_enforced automatically falls back to a pure-Python engine.
  • Force Pure Python Fallback: To explicitly skip C++ compilation and force pure Python mode:

    uv (in pyproject.toml):

    [tool.uv]
    no-binary-package = ["type-enforced"]
    config-settings-package = { type-enforced = { "cmake.define.SKIP_CPP_BUILD" = "ON" } }
    

    pip (in pyproject.toml when building from source):

    [tool.scikit-build.cmake.define]
    SKIP_CPP_BUILD = "ON"
    

    pip (in requirements.txt):

    type_enforced --config-settings=cmake.define.SKIP_CPP_BUILD=ON --no-binary type_enforced
    

    pip (CLI):

    pip install type_enforced --no-binary type_enforced -Ccmake.define.SKIP_CPP_BUILD=ON
    

    (Or set SKBUILD_CMAKE_ARGS="-DSKIP_CPP_BUILD=ON" and PIP_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 any int or 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] — use Sized without 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...except loop and catches exceptions before they reach sys.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 for FastEnforcer / 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 like dict and set, '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 for dict and set).
  • '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).
  • '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.
  • 0: Validates one element chosen at random.
  • 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.
  • 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

  1. Fork the repo and create your branch from main.
  2. Ensure all tests pass across versions (uv run nox).
  3. Format code before committing (uv run python utils/prettify.py).
  4. Keep commits atomic and clearly described.
  5. 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[![PyPI version](https://img.shields.io/pypi/v/type_enforced.svg?color=blue)](https://pypi.org/project/type_enforced/)
  5[![Python Version](https://img.shields.io/pypi/pyversions/type_enforced.svg)](https://pypi.org/project/type_enforced/)
  6[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
  7[![DOI](https://joss.theoj.org/papers/10.21105/joss.08832/status.svg)](https://doi.org/10.21105/joss.08832)
  8[![PyPI Downloads](https://static.pepy.tech/personalized-badge/type-enforced?period=total&units=INTERNATIONAL_SYSTEM&left_color=GREY&right_color=ORANGE&left_text=Downloads)](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