Metadata-Version: 2.4
Name: sqlalchemy-dlock
Version: 0.7.0
Summary: A distributed lock implementation based on SQLAlchemy
Author-email: liu xue yan <liu_xue_yan@foxmail.com>
License-Expression: BSD-3-Clause
Project-URL: homepage, https://github.com/tanbro/sqlalchemy-dlock
Project-URL: documentation, https://sqlalchemy-dlock.readthedocs.io/
Project-URL: repository, https://github.com/tanbro/sqlalchemy-dlock.git
Keywords: SQLAlchemy,lock,distributed,distributed lock,SQL,database,DBMS,RDBMS
Classifier: Development Status :: 5 - Production/Stable
Classifier: Topic :: Database :: Front-Ends
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: SQLAlchemy[asyncio]<3.0,>=1.4.3
Requires-Dist: typing-extensions; python_version < "3.12"
Provides-Extra: mysqlclient
Requires-Dist: mysqlclient; extra == "mysqlclient"
Provides-Extra: pymysql
Requires-Dist: pymysql; extra == "pymysql"
Provides-Extra: aiomysql
Requires-Dist: aiomysql; extra == "aiomysql"
Provides-Extra: psycopg2
Requires-Dist: psycopg2>=2.8; extra == "psycopg2"
Provides-Extra: psycopg2-binary
Requires-Dist: psycopg2-binary>=2.8; extra == "psycopg2-binary"
Provides-Extra: psycopg3
Requires-Dist: psycopg; extra == "psycopg3"
Provides-Extra: psycopg3-binary
Requires-Dist: psycopg[binary]; extra == "psycopg3-binary"
Provides-Extra: psycopg3-c
Requires-Dist: psycopg[c]; extra == "psycopg3-c"
Provides-Extra: asyncpg
Requires-Dist: asyncpg; extra == "asyncpg"
Dynamic: license-file

# sqlalchemy-dlock

[![Python package](https://github.com/tanbro/sqlalchemy-dlock/actions/workflows/python-package.yml/badge.svg)](https://github.com/tanbro/sqlalchemy-dlock/actions/workflows/python-package.yml)
[![PyPI](https://img.shields.io/pypi/v/sqlalchemy-dlock)](https://pypi.org/project/sqlalchemy-dlock/)
[![Documentation Status](https://readthedocs.org/projects/sqlalchemy-dlock/badge/?version=latest)](https://sqlalchemy-dlock.readthedocs.io/en/latest/)
[![codecov](https://codecov.io/gh/tanbro/sqlalchemy-dlock/branch/main/graph/badge.svg)](https://codecov.io/gh/tanbro/sqlalchemy-dlock)

`sqlalchemy-dlock` is a distributed-lock library based on Database and [SQLAlchemy][].

It currently supports below locks:

| Database   | Lock                                                                                          |
|------------|-----------------------------------------------------------------------------------------------|
| MySQL      | [named lock](https://dev.mysql.com/doc/refman/en/locking-functions.html)                      |
| PostgreSQL | [advisory lock](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS) |

## Install

```bash
pip install sqlalchemy-dlock
```

## Usage

- Work with [SQLAlchemy][] [`Connection`](https://docs.sqlalchemy.org/20/core/connections.html):

  ```python
  from sqlalchemy import create_engine
  from sqlalchemy_dlock import create_sadlock

  key = 'user/001'

  engine = create_engine('postgresql://scott:tiger@127.0.0.1/')
  conn = engine.connect()

  # Create the D-Lock on the connection
  lock = create_sadlock(conn, key)

  # it's not lock when constructed
  assert not lock.locked

  # lock
  lock.acquire()
  assert lock.locked

  # un-lock
  lock.release()
  assert not lock.locked
  ```

- `with` statement

  ```python
  from contextlib import closing

  from sqlalchemy import create_engine
  from sqlalchemy_dlock import create_sadlock

  key = 'user/001'

  engine = create_engine('postgresql://scott:tiger@127.0.0.1/')
  with engine.connect() as conn:

      # Create the D-Lock on the connection
      with create_sadlock(conn, key) as lock:
          # It's locked
          assert lock.locked

      # Auto un-locked
      assert not lock.locked

      # If do not want to be locked in `with`, a `closing` wrapper may help
      with closing(create_sadlock(conn, key)) as lock2:
          # It's NOT locked here !!!
          assert not lock2.locked
          # lock it now:
          lock2.acquire()
          assert lock2.locked

      # Auto un-locked
      assert not lock2.locked
  ```

- Work with [SQLAlchemy][] [`ORM` `Session`](https://docs.sqlalchemy.org/en/20/orm/session.html):

  ```python
  from sqlalchemy import create_engine
  from sqlalchemy.orm import sessionmaker
  from sqlalchemy_dlock import create_sadlock

  key = 'user/001'

  engine = create_engine('postgresql://scott:tiger@127.0.0.1/')
  Session = sessionmaker(bind=engine)

  with Session() as session:
    with create_sadlock(session, key) as lock:
        assert lock.locked
    assert not lock.locked
  ```

- Asynchronous I/O Support

  > 💡 **TIP**
  >
  > - [SQLAlchemy][] `1.x`'s asynchronous I/O: <https://docs.sqlalchemy.org/14/orm/extensions/asyncio.html>
  > - [SQLAlchemy][] `2.x`'s asynchronous I/O: <https://docs.sqlalchemy.org/20/orm/extensions/asyncio.html>

  ```python
  from sqlalchemy.ext.asyncio import create_async_engine
  from sqlalchemy_dlock import create_async_sadlock

  key = 'user/001'

  engine = create_async_engine('postgresql+asyncpg://scott:tiger@127.0.0.1/')

  async with engine.connect() as conn:
      async with create_async_sadlock(conn, key) as lock:
          assert lock.locked
          await lock.release()
          assert not lock.locked
          await lock.acquire()
      assert not lock.locked
  ```

  > ℹ️ **NOTE** \
  > [aiomysql][], [asyncpg][] and [psycopg][] are tested asynchronous drivers.

## Test

Following drivers are tested:

- MySQL:
  - [mysqlclient][] (synchronous)
  - [pymysql][] (synchronous)
  - [aiomysql][] (asynchronous)
- Postgres:
  - [psycopg2][] (synchronous)
  - [asyncpg][] (asynchronous)
  - [psycopg][] (synchronous and asynchronous)

You can run unit-tests

- on local environment:

  1. Install the project in editable mode with `asyncio` optional dependencies, and libraries/drivers needed in test. A virtual environment ([venv][]) is strongly advised:

     ```bash
     pip install -e . --group dev
     ```

  2. start up mysql and postgresql service

     There is a docker [compose][] file `db.docker-compose.yml` in project's top directory,
     which can be used to run mysql and postgresql develop environment conveniently:

     ```bash
     docker compose -f db.docker-compose.yml up
     ```

  3. set environment variables `TEST_URLS` and `TEST_ASYNC_URLS` for sync and async database connection url.
     Multiple connections separated by space.

     eg: (following values are also the defaults, and can be omitted)

     ```ini
     TEST_URLS=mysql://test:test@127.0.0.1/test postgresql://postgres:test@127.0.0.1/
     TEST_ASYNC_URLS=mysql+aiomysql://test:test@127.0.0.1/test postgresql+asyncpg://postgres:test@127.0.0.1/
     ```

     > ℹ️ **NOTE** \
     > The test cases would load environment variables from dot-env file `tests/.env`.

  4. run unit-test

     ```bash
     python -m unittest
     ```

- or on docker [compose][]:

  `tests/docker-compose.yml` defines a Python and [SQLAlchemy][] version matrix -- it combines multiple Python versions and [SQLAlchemy][] `v1`/`v2` for test cases. We can run it by:

  ```bash
  cd tests
  docker compose up --abort-on-container-exit
  ```

[SQLAlchemy]: https://www.sqlalchemy.org/ "The Python SQL Toolkit and Object Relational Mapper"
[venv]: https://docs.python.org/library/venv.html "The venv module supports creating lightweight “virtual environments”, each with their own independent set of Python packages installed in their site directories. "
[mysqlclient]: https://pypi.org/project/mysqlclient/ "Python interface to MySQL"
[psycopg2]: https://pypi.org/project/psycopg2/ "PostgreSQL database adapter for Python"
[psycopg]: https://pypi.org/project/psycopg/ "Psycopg 3 is a modern implementation of a PostgreSQL adapter for Python."
[aiomysql]: https://pypi.org/project/aiomysql/ "aiomysql is a “driver” for accessing a MySQL database from the asyncio (PEP-3156/tulip) framework."
[asyncpg]: https://pypi.org/project/asyncpg/ "asyncpg is a database interface library designed specifically for PostgreSQL and Python/asyncio. "
[pymysql]: https://pypi.org/project/pymysql/ "Pure Python MySQL Driver"
[compose]: https://docs.docker.com/compose/ "Compose is a tool for defining and running multi-container Docker applications."
