django-pglock
Postgres advisory locks, table locks, and blocking-lock management for Django, with queryable lock models and a management command.
Repository Health
Technical Analysis
django-pglock gives Django applications direct, ergonomic access to Postgres’s locking primitives. pglock.advisory wraps pg_advisory_lock and its variants as a context manager or decorator so overlapping tasks (cron jobs, Celery tasks, management commands) can be serialized without a race condition, while pglock.model issues an explicit LOCK TABLE statement inside a transaction to exclusively lock an entire model during sensitive operations like backfills or migrations.
Beyond acquiring locks, the library exposes what’s already locked: PGLock and BlockedPGLock are unmanaged Django models built on custom SQL CTEs over pg_locks and pg_blocking_pids, so blocking activity can be queried, filtered, and joined with the ORM instead of hand-written SQL. pglock.prioritize takes this further with a background thread that periodically finds and terminates (or cancels) any queries blocking a critical section — useful for keeping migrations from getting stuck behind long-running transactions. A pglock management command and a PGLOCK_CONFIGS settings-driven config system expose the same querysets from the command line for ops and debugging.
What You Get
pglock.advisory— advisory locks usable as a context manager or function decorator, with shared/exclusive, transactional, and timeout variantspglock.model— an explicitLOCK TABLEhelper for taking an exclusive (or configurable-mode) lock on one or more Django models inside a transactionpglock.timeout— a context manager/decorator for dynamically setting Postgres’slock_timeoutruntime parameter, nestable and restorablepglock.prioritize— a background worker that periodically terminates or cancels queries blocking a wrapped critical sectionPGLockandBlockedPGLock— unmanaged Django models/querysets that exposepg_locksand blocking-PID relationships for direct querying- A
pglockmanagement command plusPGLOCK_CONFIGSsettings for inspecting, filtering, and killing locks from the CLI
Common Use Cases
- Preventing overlapping execution of scheduled tasks or management commands using
pglock.advisoryas a decorator - Safely running large data backfills or schema changes without other transactions reading half-migrated rows, via
pglock.model - Keeping Django migrations from stalling indefinitely behind a long-running query by wrapping them in
pglock.prioritize - Building an internal ops dashboard or CLI workflow that lists and kills blocking queries using the
PGLock/BlockedPGLockquerysets
Under The Hood
Architecture
The library is organized around a single core module, pglock/core.py, that wraps raw SQL lock-acquisition statements (pg_advisory_lock variants and LOCK TABLE) in context-manager/decorator classes (advisory, model, lock_timeout, prioritize) built directly on Django’s connections API and transaction machinery, with savepoint handling to avoid leaving transactions in an errored state on failed non-raising acquisitions. pglock/models.py layers on top of django-pgactivity’s PGTable/PGTableQuerySet machinery, overriding its SQL compiler (PGTableQueryCompiler.get_ctes) to inject two custom CTEs — _pglock_lock_cte over pg_locks/pg_class and _pglock_blocked_lock_cte using pg_blocking_pids — so lock and blocking-lock data can be queried as ordinary (unmanaged) Django models. pglock/management/commands/pglock.py is a thin BaseCommand that reuses the same querysets, driven by a small config resolver (pglock/config.py) that reads settings.PGLOCK_CONFIGS and merges CLI overrides.
Tech Stack
Built for Python 3.10–3.14 and Django 4.2 through 6.0, with a hard dependency on django-pgactivity (>=1.2,<2) for the underlying activity/CTE plumbing and dynamic support for both psycopg2 and psycopg3 (detected at import time via utils.psycopg_maj_version). Packaging uses Poetry with poetry-core; the CI matrix (CircleCI, via a shared ambitioneng orb) runs the full test suite against Postgres 14 through 18 in service containers. Docs are built with MkDocs Material and mkdocstrings-python; linting uses Ruff (E, F, B, I, G, C4) plus Pyright in standard mode with django-stubs for Django-aware typing.
Code Quality
The test suite (pglock/tests/, over 550 lines in test_core.py alone, plus dedicated files for config, the management command, and models) runs under pytest-django with --reuse-db, and pyproject.toml enforces fail_under = 100 branch coverage via pytest-cov. The public API ships a py.typed marker and is fully type-annotated with explicit TypeVar/ParamSpec/TypeAlias usage; failure modes raise specific, documented exceptions (ValueError, TypeError, RuntimeError, django.db.utils.OperationalError) rather than swallowing errors. Both Ruff and Pyright run in CI, and the CircleCI pipeline exercises the minimum and maximum supported Postgres versions.
What Makes It Unique
Most Postgres-locking helpers for Django stop at exposing pg_advisory_lock as a context manager. django-pglock goes further on two fronts: it turns pg_locks and pg_blocking_pids into genuinely queryable Django querysets via custom SQL CTEs injected through a compiler override, and pglock.prioritize runs an active background thread that periodically detects and kills whatever is blocking a wrapped critical section — an automated blocking-lock mitigation pattern (particularly aimed at migrations) that’s uncommon in comparable libraries, which typically only document the problem rather than actively resolving it at runtime.