Skip to main content

Ensure a function runs only once, regardless of how many times it's called.

Project description

philiprehberger-once

Tests PyPI version Last updated

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:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT

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

philiprehberger_once-0.4.0.tar.gz (190.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

philiprehberger_once-0.4.0-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

Details for the file philiprehberger_once-0.4.0.tar.gz.

File metadata

  • Download URL: philiprehberger_once-0.4.0.tar.gz
  • Upload date:
  • Size: 190.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for philiprehberger_once-0.4.0.tar.gz
Algorithm Hash digest
SHA256 0acccc1099fc418be25b393d9b5bcfac7ee2e13a74d0bfe669ca0a7a78a13ef4
MD5 3fa4aa4bbfe6c3bdb7af62a118c37fad
BLAKE2b-256 f32da08cbc466db74693548d02358346e9abd2b1f06cd8218b89301befa334cb

See more details on using hashes here.

File details

Details for the file philiprehberger_once-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for philiprehberger_once-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ecb049d99e9da55a05737caad9c9eee94cb6ab6ffbd85bbea9df4981f3439262
MD5 83c2e5633195c348feb9969e34cd6fbc
BLAKE2b-256 81ae0b9798f1a5afc712cc782a68525f483d32fc102d85a3a5fde7b5a5725e49

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page