philiprehberger-once
Ensure a function runs only once, regardless of how many times it's called.
Installation
pip install philiprehberger-once
Usage
from philiprehberger_once import once
@once
def load_config():
print("Loading...")
return {"debug": True}
load_config() # prints "Loading...", returns {"debug": True}
load_config() # returns {"debug": True} without printing
Async support
import asyncio
from philiprehberger_once import once
@once
async def fetch_token():
print("Fetching...")
return "abc-123"
asyncio.run(fetch_token()) # prints "Fetching...", returns "abc-123"
asyncio.run(fetch_token()) # returns "abc-123" without fetching
Once per key
from philiprehberger_once import once_per_key
@once_per_key
def connect(host, port=5432):
print(f"Connecting to {host}...")
return f"conn:{host}"
connect("db-1") # prints "Connecting to db-1...", returns "conn:db-1"
connect("db-1") # returns cached "conn:db-1"
connect("db-2") # prints "Connecting to db-2...", returns "conn:db-2"
Once per key (async)
import asyncio
from philiprehberger_once import once_per_key_async
@once_per_key_async
async def fetch_user(user_id: str):
print(f"Fetching {user_id}...")
return {"id": user_id}
async def main():
# Concurrent awaiters of the same key share one in-flight execution.
a, b, c = await asyncio.gather(
fetch_user("u1"),
fetch_user("u1"),
fetch_user("u2"),
)
# Only two prints: one for "u1" and one for "u2".
asyncio.run(main())
# Derive the cache key from a callable instead of the first arg:
@once_per_key_async(key=lambda req, **kw: kw["uid"])
async def load(req, *, uid: str):
return uid
Once per args
Use the entire call signature (positional + keyword) as the cache key — handy when the value depends on more than just the first argument.
from philiprehberger_once import once_per_args
@once_per_args
def init(host: str, port: int):
print(f"connecting to {host}:{port}")
return f"{host}:{port}"
init("a", 1) # prints, returns "a:1"
init("a", 1) # cached
init("a", 2) # prints, returns "a:2"
Cache only on success
Use @until_success when you want to retry on failure but cache the first successful result. Exceptions propagate uncached.
from philiprehberger_once import until_success
attempts = 0
@until_success
def load_token():
global attempts
attempts += 1
if attempts < 3:
raise RuntimeError("temporary failure")
return "abc-123"
# First two calls raise; third succeeds and is cached forever.
# load_token() # raises
# load_token() # raises
load_token() # returns "abc-123"
load_token() # returns "abc-123" (cached, not re-invoked)
load_token.succeeded # True
Forgetting a cached function
forget(fn) resets any wrapper in this package from a single entry point — handy when you don't know (or don't care) which decorator was used.
from philiprehberger_once import forget, once, once_per_key
@once
def init():
return 42
@once_per_key
def connect(host):
return f"conn:{host}"
init()
connect("db-1")
forget(init) # True — clears @once cache
forget(connect) # True — clears all @once_per_key entries
forget(lambda: 1) # False — plain function has no reset()
Reset and inspect
from philiprehberger_once import once
@once
def init():
return 42
init()
init.called # True
init.reset()
init.called # False
init() # runs again
API
| Function / Property | Description |
|---|---|
once(fn) |
Decorator. Runs fn once, caches and returns the result on subsequent calls. Thread-safe. Supports async. |
once_per_key(fn) |
Decorator. Runs fn once per unique first argument. Thread-safe. |
once_per_args(fn) |
Decorator. Runs fn once per unique combination of positional and keyword arguments. Thread-safe. All arguments must be hashable. |
once_per_key_async(fn=None, *, key=None) |
Decorator for async functions. Runs the coroutine once per unique key (first positional arg, or derived via key=...). Concurrent awaiters of the same key share one in-flight execution. |
until_success(fn) |
Decorator. Caches the result only after fn returns without raising. Exceptions propagate uncached, so the next call retries. Sync only. Exposes .succeeded and .reset(). |
forget(fn) |
Reset any wrapper in this package (@once, @once_per_key, @once_per_args, @once_per_key_async, @until_success). Returns True on success, False if fn has no reset() method. |
.called |
bool for once, dict[key, bool] for once_per_key and once_per_key_async. Whether the function has been called. |
.reset() |
Clear cached result so the function can run again. once_per_key and once_per_key_async accept an optional key argument. |
Development
pip install -e .
python -m pytest tests/ -v
Support
If you find this project useful:
License
Metadata
Release files for philiprehberger-once 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| philiprehberger_once-0.4.0.tar.gz | 190.2 kB | Without provenance | Details for philiprehberger_once-0.4.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| philiprehberger_once-0.4.0-py3-none-any.whl | Python 3 | none | any | Details for philiprehberger_once-0.4.0-py3-none-any.whl |
Total release size: 197.4 kB
Release files / philiprehberger_once-0.4.0.tar.gz
| Download URL | philiprehberger_once-0.4.0.tar.gz |
|---|---|
| Size | 190.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0acccc1099fc418be25b393d9b5bcfac7ee2e13a74d0bfe669ca0a7a78a13ef4
|
|
BLAKE2b-256 checksum How to use checksums |
f32da08cbc466db74693548d02358346e9abd2b1f06cd8218b89301befa334cb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / philiprehberger_once-0.4.0-py3-none-any.whl
| Download URL | philiprehberger_once-0.4.0-py3-none-any.whl |
|---|---|
| Size | 7.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ecb049d99e9da55a05737caad9c9eee94cb6ab6ffbd85bbea9df4981f3439262
|
|
BLAKE2b-256 checksum How to use checksums |
81ae0b9798f1a5afc712cc782a68525f483d32fc102d85a3a5fde7b5a5725e49
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Security for philiprehberger-once 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.