A focused library of useful Python decorators for reliability, caching, rate limiting, timeouts, and developer ergonomics.
Project description
PyDecorators
A focused Python library of useful decorators for everyday reliability, caching, rate limiting, timeouts, and developer ergonomics.
The goal is to provide small, typed, well-tested decorators that work in scripts, CLIs, services, and libraries without requiring a framework or a dependency shrubbery.
What is included
v0.1.2 includes the same public API as v0.1.0, with cleaned post-release documentation and PyPI-safe documentation links:
@deprecatedfor compatibility warnings.@cache_resultwith in-memory and disk-backed caching.@retryfor transient failures.@rate_limitfor simple in-process throttling.@timeoutfor async timeout boundaries.@log_callsand@measure_timefor lightweight observability.@validate_typesfor runtime argument checks.@require_envfor environment preflight checks.@circuit_breakerfor failing-fast around unhealthy dependencies.
Installation
Install from PyPI:
python -m pip install blakemere-wraptools
The distribution name is blakemere-wraptools; the import package is pydecorators:
from pydecorators import retry
For local development:
python -m pip install -e '.[dev]'
Quick start
Pick the smallest decorator that solves the immediate problem:
from pydecorators import retry
@retry(attempts=3, delay=0.25, backoff=2, exceptions=(ConnectionError, TimeoutError))
def call_service() -> str:
return "ok"
Then read the per-decorator docs and docs/composition.md before stacking decorators together. Decorator soup is still soup, even when typed.
Development status
Released as blakemere-wraptools 0.1.2. The public API is still pre-1.0: useful, tested, and documented, but compatibility can change when the library needs to get less weird.
Warnings use DeprecationWarning by default, which Python may hide depending on warning filters. See docs/deprecated.md for details.
Development
python -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'
ruff check .
ruff format --check .
mypy
python scripts/smoke_imports.py
python scripts/smoke_examples.py
pytest
python -m build
python scripts/smoke_wheel_install.py
python scripts/dogfood_local_wheel.py
python scripts/dogfood_external_project.py
pytest enforces coverage for pydecorators with terminal and XML coverage reports.
Optional local pre-commit hooks are available:
pre-commit install
pre-commit run --all-files
Dogfooding and release checks
Dogfood scenarios remain part of the release gate. Run them before cutting a new release:
python scripts/dogfood_local_wheel.py
python scripts/dogfood_external_project.py
Documentation
Start with the documentation index, or jump straight to the guide you need:
- API reference: public decorators, backends, serializers, result types, and exceptions.
- API design notes: the conventions behind decorator signatures, typing, and exports.
- Public API and compatibility: what is stable, what is internal, and what pre-1.0 compatibility means.
- Decorator composition: practical guidance for stacking decorators without making soup.
- Exceptions: the public exception hierarchy and when each error is raised.
- Security hardening: cache, logging, validation, and environment-safety notes.
- Contributing: how to add decorators, docs, tests, and release checks.
Executable examples are collected in docs/examples.
Release process
See RELEASE.md for the release checklist.
Decorator design docs
See docs/cache_result.md for the cache decorator design, docs/retry.md for retry behavior, docs/rate_limit.md for rate limiting, docs/timeout.md for async timeout behavior, docs/log_calls.md for call logging, docs/measure_time.md for timing hooks, docs/validate_types.md for lightweight runtime type validation, docs/require_env.md for environment checks, docs/circuit_breaker.md for circuit-breaker behavior, docs/composition.md for stacking guidance, and docs/API_REFERENCE.md for a compact API reference.
Decorator overview
@deprecated: warn when old APIs are used.@cache_result: cache expensive sync results in memory or trusted local disk storage.@retry: retry transient failures with explicit policy.@rate_limit: enforce in-process sliding-window call limits.@timeout: apply async deadlines usingasyncio.wait_for.@log_calls: log calls, durations, exceptions, and optional summaries.@measure_time: emit timing data to callbacks, loggers, or metrics hooks.@validate_types: shallow runtime checks for simple annotations.@require_env: check environment requirements at call time.@circuit_breaker: stop hammering a failing dependency until a reset window opens.
Security and operational notes
See docs/security_hardening.md for the centralized hardening checklist.
- Treat
DiskCacheBackendfiles as trusted local data. The default pickle serializer must not load untrusted cache databases. - Do not put disk cache files in world-writable directories.
- Keep cache values disposable; use namespaces or clear caches when semantics change.
- Argument/result logging is opt-in because logs preserve secrets with the enthusiasm of a museum curator.
@validate_typesis not a schema validator or security boundary.@rate_limitand@circuit_breakerare in-process only; they do not coordinate across workers, containers, or hosts.- Environment checks protect configuration mistakes, not secret storage. Use proper secret-management systems for real secrets.
Async support notes
@deprecated,@retry,@rate_limit,@timeout,@log_calls,@measure_time,@validate_types,@require_env, and@circuit_breakersupport async callables.@cache_resultis sync-only for now.@timeoutis deliberately async-only. Sync timeout strategies based on signals or worker threads have sharp edges, so sync functions currently raiseConfigurationErrorinstead of pretending the problem is easy.
Deprecated example
from pydecorators import deprecated
@deprecated("Kept for compatibility.", replacement="new_function", version="0.1.0")
def old_function() -> str:
return "still works"
Retry example
from pydecorators import retry
@retry(attempts=3, delay=0.25, backoff=2, exceptions=ConnectionError)
def call_service() -> str:
return "ok"
@retry supports sync and async functions, exception filtering, predicate-based retry decisions, attempt hooks, jitter, max-delay caps, and injectable sleep functions for fast tests.
Rate-limit example
from pydecorators import rate_limit
@rate_limit(calls=10, period=60, key=lambda user_id: user_id)
def call_user_api(user_id: str) -> str:
return "ok"
@rate_limit uses an in-process sliding window and supports global or keyed buckets, raise or block mode, async functions, and injectable clocks/sleep functions for tests.
Timeout example
from pydecorators import timeout
@timeout(seconds=2)
async def fetch_user(user_id: str) -> str:
return user_id
@timeout currently supports async functions using asyncio.wait_for. Sync functions are rejected deliberately until a safe, well-documented sync timeout strategy exists.
Logging example
from pydecorators import log_calls
@log_calls(include_args=True, redact_args={"password"})
def authenticate(*, username: str, password: str) -> bool:
return bool(username and password)
@log_calls logs start/finish duration and exceptions by default. Argument and result logging are opt-in; use redaction and summaries carefully because logs are where secrets go to become immortal.
Timing example
from pydecorators import TimingInfo, measure_time
timings: list[TimingInfo] = []
@measure_time(callback=timings.append)
def rebuild_index() -> None:
pass
@measure_time records sync and async durations through optional callback, logger, or metrics hooks.
Type-validation example
from pydecorators import validate_types
@validate_types(validate_return=True)
def double(value: int) -> int:
return value * 2
@validate_types provides lightweight, shallow runtime checks for simple annotations. It is not a full schema validator; sometimes the boring warning label is the difference between a tool and a liability.
Environment requirement example
from pydecorators import require_env
@require_env("API_TOKEN")
def call_service() -> str:
return "ok"
@require_env checks variables at call time, so tests and deployment systems can patch environment state after import.
Circuit-breaker example
from pydecorators import CircuitBreakerOpen, circuit_breaker
@circuit_breaker(failure_threshold=2, reset_timeout=10)
def call_vendor_api() -> str:
return "ok"
try:
call_vendor_api()
except CircuitBreakerOpen:
pass
@circuit_breaker is an in-process breaker with closed, open, and half-open states. Useful, not magic. Architecture remains annoyingly undefeated.
Cache example
from pydecorators import cache_result
@cache_result(maxsize=128)
def expensive_lookup(value: str) -> str:
return value.upper()
@cache_result uses MemoryCacheBackend by default and also includes DiskCacheBackend for trusted local persistent caches. Future backend work is planned for Redis storage.
Shared cache backends can be isolated with namespace= when multiple decorated functions use the same backend. For persistent disk caches, treat namespace names and custom key functions as part of the cache file's compatibility contract: changing either one can strand old entries, collide with another decorated function, or return values computed under older semantics. Use explicit namespaces for long-lived caches, keep custom key functions stable across releases, and clear or rotate the cache when function behavior, argument meaning, serializer format, or namespace strategy changes.
TTL is fixed from write time by default; pass refresh_ttl_on_hit=True to @cache_result or a backend when hot entries should use sliding expiry. Fixed TTL is better for predictable freshness and retention: an entry expires at a known time even if it is popular. Sliding TTL is better for expensive hot data that may stay cached while traffic continues, but it can keep stale or sensitive values alive indefinitely unless you also bound cache size, choose conservative TTLs, and clear caches when semantics change. Pass coalesce_misses=True to @cache_result when duplicate concurrent misses for the same key should share one in-flight computation.
A simple cache-versioning recipe is to include an application-controlled version in the namespace, such as namespace="users:v1", and bump it to users:v2 when cached value shape, authorization assumptions, serializer behavior, or key semantics change. That intentionally abandons old rows without needing to parse or migrate them. For larger applications, keep the version string near the code that defines the cached value contract, not buried in a random decorator where future-you will absolutely forget it.
For a trusted local persistent cache, create one DiskCacheBackend, pass it to @cache_result, and close it when your script or service shuts down:
from pathlib import Path
from pydecorators import DiskCacheBackend, cache_namespace, cache_result
backend = DiskCacheBackend(
Path(".cache/pydecorators.sqlite3"),
ttl=3600,
maxsize=10_000,
)
@cache_result(backend=backend, namespace=cache_namespace("users", 1))
def load_user_display_name(user_id: str) -> str:
return fetch_user_display_name(user_id)
try:
print(load_user_display_name("user-123"))
finally:
backend.close()
For long-running applications, keep the backend for the application lifetime and close it from your normal shutdown hook. For short scoped backend operations that are not decorator-bound, DiskCacheBackend also supports with DiskCacheBackend(...) as backend:.
Do not create a decorator-bound DiskCacheBackend inside a short with block unless all decorated calls happen before the block exits. The context manager closes the backend on exit; later calls through the decorated function raise CacheBackendClosedError.
Disk backend design lives in docs/disk_cache_backend.md; implementation uses SQLite and the cache serializer interface. The default disk payload serializer uses pickle, so cache databases must be treated as trusted local files only — do not load cache DBs from untrusted sources or place them in world-writable directories. For simple JSON-compatible payloads, use JsonCacheSerializer instead of pickle when values should be easier to inspect or consume from other languages.
DiskCacheBackend is intended for single-host local caching. It uses normal SQLite file locking, requests WAL mode by default, and configures a 5000 ms busy timeout to reduce transient database is locked failures. Those settings improve local reader/writer behavior, but they do not make it a distributed cache and they do not promise safe cross-host semantics on shared/network filesystems. If multiple processes use the same cache file, expect normal SQLite contention behavior and keep cached values disposable. If you need visibility into rows dropped because of serializer mismatches or corrupt payloads, pass on_drop= to DiskCacheBackend and log the DiskCacheDropEvent.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file blakemere_wraptools-0.1.2.tar.gz.
File metadata
- Download URL: blakemere_wraptools-0.1.2.tar.gz
- Upload date:
- Size: 167.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c1a16f3d29abd9f544ab27373f720616f92d8be69f32402296317a7e160de2a3
|
|
| MD5 |
e18a8724aed2a20a09c16bb541cd40f3
|
|
| BLAKE2b-256 |
8f1f6879bd5995666a95baa0577954441e83cbdffd360efa8f55501a1f94db93
|
Provenance
The following attestation bundles were made for blakemere_wraptools-0.1.2.tar.gz:
Publisher:
publish.yml on RusDavies/pydecorators
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
blakemere_wraptools-0.1.2.tar.gz -
Subject digest:
c1a16f3d29abd9f544ab27373f720616f92d8be69f32402296317a7e160de2a3 - Sigstore transparency entry: 1584877248
- Sigstore integration time:
-
Permalink:
RusDavies/pydecorators@4aedc7fcb69fb062f3a949bb9219b0615dc94e94 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/RusDavies
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4aedc7fcb69fb062f3a949bb9219b0615dc94e94 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file blakemere_wraptools-0.1.2-py3-none-any.whl.
File metadata
- Download URL: blakemere_wraptools-0.1.2-py3-none-any.whl
- Upload date:
- Size: 36.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47bcecd0f0b756be5ae77820e4e71e04aaadb7cf3d0980830c24af1e91ebf829
|
|
| MD5 |
80a30bd31aa61f159a4b48c2dc24d7ad
|
|
| BLAKE2b-256 |
4a673f5ef099ee36db922a413455e0c97a4cea5ed4a5c293c2cae9cb49f2f02d
|
Provenance
The following attestation bundles were made for blakemere_wraptools-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on RusDavies/pydecorators
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
blakemere_wraptools-0.1.2-py3-none-any.whl -
Subject digest:
47bcecd0f0b756be5ae77820e4e71e04aaadb7cf3d0980830c24af1e91ebf829 - Sigstore transparency entry: 1584877316
- Sigstore integration time:
-
Permalink:
RusDavies/pydecorators@4aedc7fcb69fb062f3a949bb9219b0615dc94e94 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/RusDavies
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4aedc7fcb69fb062f3a949bb9219b0615dc94e94 -
Trigger Event:
workflow_dispatch
-
Statement type: