Convenience helpers for named Redis locks.
Project description
redis-locking
redis-locking is a small, practical layer for named Redis locks.
from redis import Redis
from redis_locking import LockSettings, acquire_lock, build_key
redis_conn = Redis()
locks = LockSettings(key_prefix="billing:", lock_expire=120, acquire_timeout=10)
with acquire_lock(redis_conn, build_key("invoice", 42), settings=locks):
send_invoice(42)
Redis already has good locking primitives, and python-redis-lock handles the hard part of acquiring and releasing locks safely. This package focuses on the application code around it: namespacing, consistent settings, decorators, local lock disabling, and the occasional extra check that some device or external process is ready.
It is intentionally boring, which is useful when the alternative is twelve slightly different lock helpers spread across a codebase, all with their own timeout defaults and naming conventions.
Installing
$ python -m pip install redis-locking
redis-locking supports Python 3.8+ and uses python-redis-lock as its backend.
A small example
from redis import Redis
from redis_locking import LockSettings, acquire_lock, is_locked
redis_conn = Redis.from_url("redis://localhost:6379/0")
settings = LockSettings(key_prefix="worker:", lock_expire=120, acquire_timeout=5)
if is_locked(redis_conn, "daily-sync", settings=settings):
print("Someone else is already running this")
with acquire_lock(redis_conn, "daily-sync", settings=settings):
run_daily_sync()
By default, acquire_lock waits up to acquire_timeout seconds. Set blocking=False when the worker should skip work that is already running:
from redis_locking import LockAcquireError
try:
with acquire_lock(redis_conn, "daily-sync", settings=settings, blocking=False):
run_daily_sync()
except LockAcquireError:
print("Skipped, lock is already held")
Lock settings
Most services are easier to reason about when lock behavior is configured once, then reused everywhere.
from redis_locking import LockSettings
LOCKS = LockSettings(
key_prefix="api:",
acquire_timeout=10,
lock_expire=120,
auto_renewal=True,
)
Available settings:
key_prefix: prefix all lock keys for a service or environment.acquire_timeout: maximum seconds to wait when acquiring a blocking lock.lock_expire: Redis expiry for the lock key.blocking: wait for the lock by default, or fail immediately.auto_renewal: letpython-redis-lockrenew the lock while work is running.disabled: bypass locks, useful for local development, tests, or one-off scripts.
Busy checks
Sometimes the Redis key is only part of the story. You might lock a device, gateway, or job runner, then still need to ask it whether it is actually ready; lock_key puts that extra check behind the same context manager.
from typing import Optional
from redis_locking import lock_key
def is_gateway_busy(lock_name: str, timeout: Optional[int]) -> bool:
return gateway_client.is_busy(lock_name, timeout=timeout)
with lock_key(
redis_conn,
"gateway:42",
settings=LOCKS,
check_fn=is_gateway_busy,
check_retries=2,
check_delay=5,
check_timeout=30,
):
update_gateway(42)
Because the check runs after the Redis lock is acquired, only one worker asks the external resource at a time. If the check keeps reporting busy, LockBusyError is raised and the Redis lock is released.
Decorators
For jobs where the lock name is static, the decorator keeps the call site small.
from redis_locking import locked
@locked(redis_conn, "reports:nightly", settings=LOCKS)
def build_nightly_report():
generate_report()
Domain wrappers
The best use of this package is usually one layer deeper. Define names once, then use those wrappers everywhere.
from typing import Optional
from redis_locking import LockSettings, acquire_lock, build_key, is_locked
PROCESS_LOCKS = LockSettings(key_prefix="orders:", lock_expire=120, auto_renewal=True)
def lock_process(redis_conn, process_name: str, entity_id: Optional[int] = None):
key = build_key("process", process_name, entity_id)
return acquire_lock(redis_conn, key, settings=PROCESS_LOCKS, blocking=False)
def is_process_locked(redis_conn, process_name: str, entity_id: Optional[int] = None) -> bool:
key = build_key("process", process_name, entity_id)
return is_locked(redis_conn, key, settings=PROCESS_LOCKS)
Now the rest of the application does not have to remember key prefixes, expiry values, or whether a particular lock should block. It just calls lock_process and gets on with the work.
API
from redis_locking import (
LockAcquireError,
LockBusyError,
LockCheckError,
LockSettings,
acquire_lock,
build_key,
is_locked,
locked,
lock_key,
)
Development
$ python -m venv .venv
$ . .venv/bin/activate
$ python -m pip install -U pip
$ python -m pip install -e ".[dev]"
$ pytest
Project details
Release history Release notifications | RSS feed
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 redis_locking-0.1.1.tar.gz.
File metadata
- Download URL: redis_locking-0.1.1.tar.gz
- Upload date:
- Size: 7.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58084eba0c60f7d3992b29820048d2403b4ec4935d015293769f51a711b58568
|
|
| MD5 |
d293b9fb6f51c1588d4497521d86b7ee
|
|
| BLAKE2b-256 |
8c575c4339cc14625fa866fcd317a09b964db108444b12136dd858efcdfe41b5
|
Provenance
The following attestation bundles were made for redis_locking-0.1.1.tar.gz:
Publisher:
publish.yml on larsderidder/redis-locking
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
redis_locking-0.1.1.tar.gz -
Subject digest:
58084eba0c60f7d3992b29820048d2403b4ec4935d015293769f51a711b58568 - Sigstore transparency entry: 1573444724
- Sigstore integration time:
-
Permalink:
larsderidder/redis-locking@ee41f8ba111bf608574d4f143acd5410b6b2242e -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/larsderidder
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ee41f8ba111bf608574d4f143acd5410b6b2242e -
Trigger Event:
push
-
Statement type:
File details
Details for the file redis_locking-0.1.1-py3-none-any.whl.
File metadata
- Download URL: redis_locking-0.1.1-py3-none-any.whl
- Upload date:
- Size: 6.2 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 |
30c932d0cff6dc50f34353d091c7cb1ac1f29c5187addfdbebcb7ab8c21cb8d4
|
|
| MD5 |
668956cb15f3392c5780e9f222e0ea69
|
|
| BLAKE2b-256 |
d77030385edb5c8b639f637bdaa39ea7a2edaf392a33f53d148cefc9f0bbf806
|
Provenance
The following attestation bundles were made for redis_locking-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on larsderidder/redis-locking
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
redis_locking-0.1.1-py3-none-any.whl -
Subject digest:
30c932d0cff6dc50f34353d091c7cb1ac1f29c5187addfdbebcb7ab8c21cb8d4 - Sigstore transparency entry: 1573444739
- Sigstore integration time:
-
Permalink:
larsderidder/redis-locking@ee41f8ba111bf608574d4f143acd5410b6b2242e -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/larsderidder
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ee41f8ba111bf608574d4f143acd5410b6b2242e -
Trigger Event:
push
-
Statement type: