django-pglock
#############

``django-pglock`` performs advisory locks, table locks, and helps manage blocking locks.
Here's some of the functionality at a glance:

* ``pglock.advisory`` for application-level locking, for example, ensuring that tasks don't overlap.
* ``pglock.model`` for locking an entire model.
* ``pglock.timeout`` for dynamically setting the timeout to acquire a lock.
* ``pglock.prioritize`` to kill blocking locks for critical code, such as migrations.
* The ``PGLock`` and ``BlockedPGLock`` models for querying active and blocked locks.
* The ``pglock`` management command that wraps the models and provides other utilities.

Quickstart
==========

Advisory Locks
--------------

Use ``pglock.advisory`` to acquire a `Postgres advisory lock <https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS>`__:

.. code-block:: python

    import pglock

    with pglock.advisory("my_lock_id"):
        # This code blocks until the "my_lock_id" lock is available


Above our code will block until the lock is available, meaning
no instances of the function will run simultaneously. Use
the ``timeout`` argument to configure how long to wait for
the lock. A timeout of zero will return immediately:

.. code-block:: python

    with pglock.advisory("my_lock_id", timeout=0) as acquired:
        if acquired:
            # The lock is acquired

Use ``side_effect=pglock.Raise`` to raise a ``django.db.utils.OperationalError`` if
the lock can't be acquired. When using the decorator, you can also use
``side_effect=pglock.Skip`` to skip the function if the lock can't be acquired:

.. code-block:: python

    @pglock.advisory(timeout=0, side_effect=pglock.Skip)
    def non_overlapping_func():
        # This function will not run if there's another one already running.
        # The decorator lock ID defaults to <module_name>.<function_name>

Model Locks
-----------

``pglock.model`` can take a lock on an entire model during a transaction. For example:

.. code-block:: python

    from django.db import transaction
    import pglock

    with transaction.atomic():
        pglock.model("auth.User")

        # Any operations on auth.User will be exclusive here. Even read access
        # for other transactions is blocked

``pglock.model`` uses `Postgres's LOCK statement <https://www.postgresql.org/docs/current/sql-lock.html>`__,
and it accepts the lock mode as a argument. See the
`Postgres docs for more information <https://www.postgresql.org/docs/current/sql-lock.html>`__.

**Note** ``pglock.model`` is similar to ``pglock.advisory``. Use the ``timeout`` argument
to avoid waiting for locks, and supply the appropriate ``side_effect`` to adjust runtime behavior.

Prioritizing Blocked Code
-------------------------

``pglock.prioritize`` will terminate any locks blocking the wrapped code:

.. code-block:: python

    import pglock

    @pglock.prioritize()
    def my_func():
        # Any other statements that have conflicting locks will be killed on a
        # periodic interval.
        MyModel.objects.update(val="value")

``pglock.prioritize`` is useful for prioritizing code, such as migrations, to avoid
situations where locks are held for too long.

Setting the Lock Timeout
------------------------

Use ``pglock.timeout`` to dynamically set `Postgres's lock_timeout runtime
setting <https://www.postgresql.org/docs/current/runtime-config-client.html>`__:

.. code-block:: python

    import pglock

    @pglock.timeout(1)
    def do_stuff():
        # This function will throw an exception if any code takes longer than
        # one second to acquire a lock

Querying Locks
--------------

Use ``pglock.models.PGLock`` to query active locks. It wraps
`Postgres's pg_locks view <https://www.postgresql.org/docs/current/view-pg-locks.html>`__.
Use ``pglock.models.BlockedPGLock`` to query locks and join the activity that's blocking
them.

Use ``python manage.py pglock`` to view and kill locks from the command line. It has
several options for dynamic filters and re-usable configuration.

Compatibility
=============

``django-pglock`` is compatible with Python 3.7 - 3.11, Django 3.2 - 4.2, Psycopg 2 - 3 and Postgres 12 - 15.

Documentation
=============

`View the django-pglock docs here
<https://django-pglock.readthedocs.io/>`_ to learn more about:

* Using advisory locks.
* Locking models.
* Setting dynamic lock timeouts.
* Killing blocking locks.
* The proxy models and custom queryset methods.
* Using and configuring the management command.

Installation
============

Install django-pglock with::

    pip3 install django-pglock

After this, add both ``pgactivity`` and ``pglock`` to the ``INSTALLED_APPS``
setting of your Django project.

Contributing Guide
==================

For information on setting up django-pglock for development and
contributing changes, view `CONTRIBUTING.rst <CONTRIBUTING.rst>`_.

Primary Authors
===============

- `Wes Kendall <https://github.com/wesleykendall>`__
- `Paul Gilmartin <https://github.com/PaulGilmartin>`__
