Metadata-Version: 2.2
Name: type_enforced
Version: 2.12.0
Summary: A dependency free type enforcer for python type annotations
Author-Email: Connor Makowski <conmak@mit.edu>
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: C++
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Project-URL: Homepage, https://github.com/connor-makowski/type_enforced
Project-URL: Bug Tracker, https://github.com/connor-makowski/type_enforced/issues
Project-URL: Documentation, https://connor-makowski.github.io/type_enforced/type_enforced/enforcer.html
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: pytest>=9.0.3; extra == "dev"
Requires-Dist: autoflake>=2.3.1; extra == "dev"
Requires-Dist: black>=25.1.0; extra == "dev"
Requires-Dist: build>=1.2.2; extra == "dev"
Requires-Dist: nox>=2026.4.10; extra == "dev"
Requires-Dist: nox-uv>=0.8.0; extra == "dev"
Requires-Dist: pdoc>=15.0.1; extra == "dev"
Requires-Dist: twine>=6.1.0; extra == "dev"
Requires-Dist: pydantic==2.13.4; extra == "dev"
Requires-Dist: beartype==0.22.9; extra == "dev"
Requires-Dist: typeguard==4.6.0; extra == "dev"
Requires-Dist: msgspec>=0.21.1; implementation_name != "pypy" and extra == "dev"
Requires-Dist: cattrs>=26.1.0; extra == "dev"
Requires-Dist: scikit-build-core>=1.0.3; extra == "dev"
Requires-Dist: nanobind>=3.0.0; extra == "dev"
Description-Content-Type: text/markdown

# type_enforced

[![PyPI version](https://img.shields.io/pypi/v/type_enforced.svg?color=blue)](https://pypi.org/project/type_enforced/)
[![Python Version](https://img.shields.io/pypi/pyversions/type_enforced.svg)](https://pypi.org/project/type_enforced/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![DOI](https://joss.theoj.org/papers/10.21105/joss.08832/status.svg)](https://doi.org/10.21105/joss.08832)
[![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)

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

---

## Table of Contents
- [Quick Start](#quick-start)
- [Why type_enforced?](#why-type_enforced)
  - [Performance at a Glance](#performance-at-a-glance)
- [Installation](#installation)
  - [Requirements & Build Options](#requirements--build-options)
- [Usage Guide](#usage-guide)
  - [1. Functions and Methods](#1-functions-and-methods)
  - [2. Classes and Dataclasses](#2-classes-and-dataclasses)
  - [3. Module-Level Enforcement](#3-module-level-enforcement-moduleenforcer-or-fastmoduleenforcer)
- [Supported Type Annotations](#supported-type-annotations)
- [Value Validation with Constraints](#value-validation-with-constraints)
- [Configuration Reference](#configuration-reference)
  - [Configuration Options in Depth](#configuration-options-in-depth)
- [Production Best Practices](#production-best-practices)
- [Contributing](#contributing)
- [Academic Citation](#academic-citation)
- [License](#license)

---

## Quick Start

```python
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):

```python
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 ~15x 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](utils/benchmark.py) and [benchmark.md](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.015 µs         |      0.193 µs       |       1.926 µs       |       0.014 µs       |     0.502 µs     |     0.277 µs     |     0.117 µs     |     1.874 µs     |
| `Union[int, float]`    |        —         |         0.014 µs         |      0.217 µs       |       4.028 µs       |       0.014 µs       |     0.557 µs     |     0.422 µs     |     0.459 µs     |     3.976 µs     |
| `str`                  |        —         |         0.015 µs         |      0.200 µs       |       1.917 µs       |       0.014 µs       |     0.509 µs     |     0.265 µs     |    0.120 µs ⚠    |     1.864 µs     |
| `list[int]`            |   1 000 items    |        0.019 µs ⚠        |     0.342 µs ⚠      |      3.194 µs ⚠      |       0.440 µs       |    11.324 µs     |     5.145 µs     |    47.823 µs     |   1047.473 µs    |
| `list[int]`            |   10 000 items   |        0.019 µs ⚠        |     0.420 µs ⚠      |      3.157 µs ⚠      |       4.569 µs       |    107.136 µs    |    45.229 µs     |    485.161 µs    |   10477.726 µs   |
| `dict[str, int]`       |    1 000 keys    |        0.028 µs ⚠        |     0.353 µs ⚠      |      4.473 µs ⚠      |       3.233 µs       |    40.778 µs     |    26.982 µs     |    68.831 µs     |   2084.674 µs    |
| `dict[str, int]`       |   10 000 keys    |        0.026 µs ⚠        |     0.344 µs ⚠      |      4.405 µs ⚠      |      41.106 µs       |    449.057 µs    |    319.557 µs    |    724.451 µs    |   21080.617 µs   |
| `list[list[int]]`      | 100 x 100 items  |        0.023 µs ⚠        |     0.380 µs ⚠      |      4.439 µs ⚠      |       3.463 µs       |    108.277 µs    |    48.580 µs     |    480.083 µs    |   10552.618 µs   |
| `dict[str, list[int]]` | 100 x 100 items  |        0.032 µs ⚠        |     0.456 µs ⚠      |      5.819 µs ⚠      |       4.102 µs       |    115.546 µs    |    53.714 µs     |    495.185 µs    |   10784.123 µs   |
| `list[dict[str, int]]` | 100 x 100 items  |        0.032 µs ⚠        |     0.531 µs ⚠      |      5.869 µs ⚠      |      47.917 µs       |    420.268 µs    |    274.532 µs    |    714.918 µs    |   21578.933 µs   |

> **Sampled Validation:** When 1 sample validation is acceptable, `type_enforced.FastEnforcer` is **up to ~15x faster than Beartype**.

> **Full Validation:** When full validation is required, `type_enforced.Enforcer` is **up to ~40x faster than Pydantic on scalars and up to ~20x faster on larger data structures**.

---

## Installation

Install via `pip`:

```bash
pip install type_enforced
```

Or using `uv`:

```bash
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`)**:
  ```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)**:
  ```toml
  [tool.scikit-build.cmake.define]
  SKIP_CPP_BUILD = "ON"
  ```

  **`pip` (in `requirements.txt`)**:
  ```text
  type_enforced --config-settings=cmake.define.SKIP_CPP_BUILD=ON --no-binary type_enforced
  ```

  **`pip` (CLI)**:
  ```bash
  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:
  ```python
  import type_enforced

  print(type_enforced.has_cpp())  # True if C++ acceleration is active, False for pure Python
  ```

<details>
<summary>Legacy Python Compatibility</summary>

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"`
</details>

---

## 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.

```python
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`):

```python
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:

```python
@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:

```python
# 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:

```python
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
```python
@type_enforced.Enforcer
def fn(
    a: int,
    b: str | float,                    # Standard union syntax
    c: int | None = None,              # Optional syntax
) -> None:
    pass
```

### Collections & Nested Generics
```python
@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)`):

```python
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]`):

```python
@type_enforced.Enforcer
def make_instance(cls: type[Animal]) -> Animal:
    return cls()
```

### Literals & Special Types
```python
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:

```python
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:

```python
@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:

```python
@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:

```python
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:

```python
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:

```python
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):

```python
@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 ~15x 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.

```python
# 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`:

```python
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](https://docs.astral.sh/uv/) for dependency management and testing in a Unix-based environment (Linux, macOS, or WSL2 on Windows).

```bash
# 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](https://doi.org/10.21105/joss.08832):

```bibtex
@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](https://opensource.org/licenses/MIT). See `LICENSE` for details.