Metadata-Version: 2.4
Name: pure-function-decorators
Version: 0.1.3
Summary: Try to enforce various types of function purity in Python
Project-URL: homepage, https://github.com/jlmcgraw/pure-function-decorators
Project-URL: source, https://github.com/jlmcgraw/pure-function-decorators
Project-URL: changelog, https://github.com/jlmcgraw/pure-function-decorators/blob/main/CHANGELOG.md
Author-email: Jesse McGraw <jlmcgraw@gmail.com>
License-File: LICENSE.md
Requires-Python: <4,>=3.12
Description-Content-Type: text/markdown

[![Latest Version](https://img.shields.io/pypi/v/pure-function-decorators?label=pypi-version&logo=python&style=plastic)](https://pypi.org/project/pure-function-decorators/)
[![Python Versions](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fjlmcgraw%2Fpure-function-decorators%2Fmain%2Fpyproject.toml&style=plastic&logo=python&label=python-versions)](https://www.python.org/)
[![Build Status](https://github.com/jlmcgraw/pure-function-decorators/actions/workflows/main.yml/badge.svg)](https://github.com/jlmcgraw/pure-function-decorators/actions/workflows/main.yml)
[![Documentation Status](https://github.com/jlmcgraw/pure-function-decorators/actions/workflows/docs.yml/badge.svg)](https://jlmcgraw.github.io/pure-function-decorators/)

# pure-function-decorators

_Decorators to try to enforce various types of function purity in Python_

This is obviously mostly vibe-coded and perhaps better suited to static analysis anyhow.  I'm using it as an exercise and
any actual utility is just a bonus

## Super-quick Start

Requires: Python 3.10 to 3.13

Install through pip:

```bash
pip install pure-function-decorators
```

```python
from pure_function_decorators import forbid_globals


@forbid_globals()
def bad(x):
    return x + CONST


CONST = 10
bad(1)  # Raises NameError
```

```python
import datetime

from pure_function_decorators import enforce_deterministic


@enforce_deterministic
def bad():
    return datetime.datetime.now().microsecond


print(bad())
print(bad())  # raises ValueError
```

## Documentation

The complete documentation can be found at the
[pure-function-decorators home page](https://jlmcgraw.github.io/pure-function-decorators)

# Features

## Existing decorators

- `immutable_arguments` deep-copies inputs before invoking the wrapped callable so callers never see in-place mutations.
    - By default, the decorator raises when a mutation is detected
    - it can instead log warnings with `warn_only=True`.
- `enforce_deterministic` ensures that the decorated function consistently returns the same result for the same
  parameters.
- `forbid_globals` prevents a function from reading or mutating module-level state by sandboxing its globals.
    - `check_names=True` to also fail decoration when bytecode references globals outside the allow-list, or set
    - `sandbox=False` to keep only the bytecode-based validation.
- `forbid_side_effects` instruments builtin operations that commonly mutate process state (e.g. file writes, subprocess
  launches) to surface accidental side effects.

## Future purity checks to explore

The current decorators focus on globals, determinism, and structural immutability.

Additional checks that build on the same inspection hooks could include:

- **Environment isolation** &mdash; raise when a function touches environment variables, current working directory, or
  other process-wide configuration through `os.environ`, `os.chdir`, or similar APIs.
- **I/O safelists** &mdash; expand the `forbid_side_effects` strategy with dedicated helpers that specifically deny
  file, socket, or HTTP operations unless a pure-safe allowlist is provided.
- **Mutable default detection** &mdash; detect functions whose default arguments or closed-over state are mutable so
  callers do not accidentally share state across invocations.
- **Dependency purity enforcement** &mdash; verify that functions only call other decorated or allowlisted pure functions
  by walking the bytecode or AST.

These ideas could live alongside the existing decorators as optional opt-in guards so projects can combine them to match
their definition of purity.

## Frequently asked questions

### Can these decorators be enabled globally, like `perl`'s `strict` pragma?

No. Python does not provide a hook that automatically wraps every function that is imported or defined after a module
loads. The decorators in this project operate by returning a new callable, so each target function (or method) has to be
wrapped explicitly. You can build your own helpers that iterate over a module or class and decorate selected callables,
but the library cannot apply itself universally without the caller opting in on a per-function basis.

## Side Effects

A side effect is any interaction with outside state other than computing and returning a value.

Canonical side effects and impurity sources:

_State mutation_: modifying globals, nonlocals, class/static attributes, singletons, caches, or the objects passed in as
arguments (in-place changes).

_I/O_: printing/logging, reading/writing files, networking, databases, stdin/stdout/stderr, GUI operations.

_Process/system effects_: spawning threads/processes, signals, timers, sleeping, exiting the process.

_Nondeterministic sources_: reading clocks (now, time), random/URNGs (random, secrets, uuid4, os.urandom), environment
variables, current working directory, locale.

_Hidden global dependencies_: reading mutable module state or configuration not provided as explicit parameters.

_Observable control effects:_ raising exceptions or relying on exceptions for normal control can be considered effects
in stricter definitions; total purity disallows them, weaker purity allows deterministic exceptions for invalid inputs.
