# 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.asyncio 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.
>
> We can install it with asynchronous DB libraries:
>
> ```bash
> pip install SQLAlchemy[asyncio] aiomysql sqlalchemy-dlock
> ```
>
> or
>
> ```bash
> pip install SQLAlchemy[asyncio] asyncpg sqlalchemy-dlock
> ```
## 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 .[asyncio] -r tests/requirements.txt
```
1. 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
```
1. 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`.
1. run unit-test
```bash
python -m unittest
```
- or on docker [compose][]:
`tests/docker-compose.yml` defines a Python and [SQLAlchemy][] version matrix -- it combines Python `3.8` to `3.12` 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."
Raw data
{
"_id": null,
"home_page": null,
"name": "sqlalchemy-dlock",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "SQLAlchemy, lock, distributed, distributed lock, SQL, database, DBMS",
"author": null,
"author_email": "liu xue yan <liu_xue_yan@foxmail.com>",
"download_url": "https://files.pythonhosted.org/packages/b3/ed/250d858c5ef783ca8bd010ac003a429ce7d7a3ec2e294044ebe0980f357a/sqlalchemy_dlock-0.6.1.post2.tar.gz",
"platform": null,
"description": "# sqlalchemy-dlock\n\n[![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)\n[![PyPI](https://img.shields.io/pypi/v/sqlalchemy-dlock)](https://pypi.org/project/sqlalchemy-dlock/)\n[![Documentation Status](https://readthedocs.org/projects/sqlalchemy-dlock/badge/?version=latest)](https://sqlalchemy-dlock.readthedocs.io/en/latest/)\n[![codecov](https://codecov.io/gh/tanbro/sqlalchemy-dlock/branch/main/graph/badge.svg)](https://codecov.io/gh/tanbro/sqlalchemy-dlock)\n\n`sqlalchemy-dlock` is a distributed-lock library based on Database and [SQLAlchemy][].\n\nIt currently supports below locks:\n\n Database | Lock\n---------- | ---------------------------------------------------------------------------------------------\nMySQL | [named lock](https://dev.mysql.com/doc/refman/en/locking-functions.html)\nPostgreSQL | [advisory lock](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS)\n\n## Install\n\n```bash\npip install sqlalchemy-dlock\n```\n\n## Usage\n\n- Work with [SQLAlchemy][] [`Connection`](https://docs.sqlalchemy.org/20/core/connections.html):\n\n ```python\n from sqlalchemy import create_engine\n from sqlalchemy_dlock import create_sadlock\n\n key = 'user/001'\n\n engine = create_engine('postgresql://scott:tiger@127.0.0.1/')\n conn = engine.connect()\n\n # Create the D-Lock on the connection\n lock = create_sadlock(conn, key)\n\n # it's not lock when constructed\n assert not lock.locked\n\n # lock\n lock.acquire()\n assert lock.locked\n\n # un-lock\n lock.release()\n assert not lock.locked\n ```\n\n- `with` statement\n\n ```python\n from contextlib import closing\n\n from sqlalchemy import create_engine\n from sqlalchemy_dlock import create_sadlock\n\n key = 'user/001'\n\n engine = create_engine('postgresql://scott:tiger@127.0.0.1/')\n with engine.connect() as conn:\n\n # Create the D-Lock on the connection\n with create_sadlock(conn, key) as lock:\n # It's locked\n assert lock.locked\n\n # Auto un-locked\n assert not lock.locked\n\n # If do not want to be locked in `with`, a `closing` wrapper may help\n with closing(create_sadlock(conn, key)) as lock2:\n # It's NOT locked here !!!\n assert not lock2.locked\n # lock it now:\n lock2.acquire()\n assert lock2.locked\n\n # Auto un-locked\n assert not lock2.locked\n ```\n\n- Work with [SQLAlchemy][] [`ORM` `Session`](https://docs.sqlalchemy.org/en/20/orm/session.html):\n\n ```python\n from sqlalchemy import create_engine\n from sqlalchemy.orm import sessionmaker\n from sqlalchemy_dlock import create_sadlock\n\n key = 'user/001'\n\n engine = create_engine('postgresql://scott:tiger@127.0.0.1/')\n Session = sessionmaker(bind=engine)\n\n with Session() as session:\n with create_sadlock(session, key) as lock:\n assert lock.locked\n assert not lock.locked\n ```\n\n- Asynchronous I/O Support\n\n > \ud83d\udca1 **TIP**\n >\n > - [SQLAlchemy][] `1.x`'s asynchronous I/O: <https://docs.sqlalchemy.org/14/orm/extensions/asyncio.html>\n > - [SQLAlchemy][] `2.x`'s asynchronous I/O: <https://docs.sqlalchemy.org/20/orm/extensions/asyncio.html>\n\n ```python\n from sqlalchemy.ext.asyncio import create_async_engine\n from sqlalchemy_dlock.asyncio import create_async_sadlock\n\n key = 'user/001'\n\n engine = create_async_engine('postgresql+asyncpg://scott:tiger@127.0.0.1/')\n\n async with engine.connect() as conn:\n async with create_async_sadlock(conn, key) as lock:\n assert lock.locked\n await lock.release()\n assert not lock.locked\n await lock.acquire()\n assert not lock.locked\n ```\n\n > \u2139\ufe0f **NOTE** \\\n > [aiomysql][], [asyncpg][] and [psycopg][] are tested asynchronous drivers.\n >\n > We can install it with asynchronous DB libraries:\n >\n > ```bash\n > pip install SQLAlchemy[asyncio] aiomysql sqlalchemy-dlock\n > ```\n >\n > or\n >\n > ```bash\n > pip install SQLAlchemy[asyncio] asyncpg sqlalchemy-dlock\n > ```\n\n## Test\n\nFollowing drivers are tested:\n\n- MySQL:\n - [mysqlclient][] (synchronous)\n - [pymysql][] (synchronous)\n - [aiomysql][] (asynchronous)\n- Postgres:\n - [psycopg2][] (synchronous)\n - [asyncpg][] (asynchronous)\n - [psycopg][] (synchronous and asynchronous)\n\nYou can run unit-tests\n\n- on local environment:\n\n 1. Install the project in editable mode with `asyncio` optional dependencies, and libraries/drivers needed in test. A virtual environment ([venv][]) is strongly advised:\n\n ```bash\n pip install -e .[asyncio] -r tests/requirements.txt\n ```\n\n 1. start up mysql and postgresql service\n\n There is a docker [compose][] file `db.docker-compose.yml` in project's top directory,\n which can be used to run mysql and postgresql develop environment conveniently:\n\n ```bash\n docker compose -f db.docker-compose.yml up\n ```\n\n 1. set environment variables `TEST_URLS` and `TEST_ASYNC_URLS` for sync and async database connection url.\n Multiple connections separated by space.\n\n eg: (following values are also the defaults, and can be omitted)\n\n ```ini\n TEST_URLS=mysql://test:test@127.0.0.1/test postgresql://postgres:test@127.0.0.1/\n TEST_ASYNC_URLS=mysql+aiomysql://test:test@127.0.0.1/test postgresql+asyncpg://postgres:test@127.0.0.1/\n ```\n\n > \u2139\ufe0f **NOTE** \\\n > The test cases would load environment variables from dot-env file `tests/.env`.\n\n 1. run unit-test\n\n ```bash\n python -m unittest\n ```\n\n- or on docker [compose][]:\n\n `tests/docker-compose.yml` defines a Python and [SQLAlchemy][] version matrix -- it combines Python `3.8` to `3.12` and [SQLAlchemy][] `v1`/`v2` for test cases. We can run it by:\n\n ```bash\n cd tests\n docker compose up --abort-on-container-exit\n ```\n\n[SQLAlchemy]: https://www.sqlalchemy.org/ \"The Python SQL Toolkit and Object Relational Mapper\"\n[venv]: https://docs.python.org/library/venv.html \"The venv module supports creating lightweight \u201cvirtual environments\u201d, each with their own independent set of Python packages installed in their site directories. \"\n[mysqlclient]: https://pypi.org/project/mysqlclient/ \"Python interface to MySQL\"\n[psycopg2]: https://pypi.org/project/psycopg2/ \"PostgreSQL database adapter for Python\"\n[psycopg]: https://pypi.org/project/psycopg/ \"Psycopg 3 is a modern implementation of a PostgreSQL adapter for Python.\"\n[aiomysql]: https://pypi.org/project/aiomysql/ \"aiomysql is a \u201cdriver\u201d for accessing a MySQL database from the asyncio (PEP-3156/tulip) framework.\"\n[asyncpg]: https://pypi.org/project/asyncpg/ \"asyncpg is a database interface library designed specifically for PostgreSQL and Python/asyncio. \"\n[pymysql]: https://pypi.org/project/pymysql/ \"Pure Python MySQL Driver\"\n[compose]: https://docs.docker.com/compose/ \"Compose is a tool for defining and running multi-container Docker applications.\"\n",
"bugtrack_url": null,
"license": "BSD-3-Clause",
"summary": "A distributed lock implementation based on SQLAlchemy",
"version": "0.6.1.post2",
"project_urls": {
"documentation": "https://sqlalchemy-dlock.readthedocs.io/",
"homepage": "https://github.com/tanbro/sqlalchemy-dlock",
"repository": "https://github.com/tanbro/sqlalchemy-dlock.git"
},
"split_keywords": [
"sqlalchemy",
" lock",
" distributed",
" distributed lock",
" sql",
" database",
" dbms"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4f0ec7405324dd7b94a5af6af6695a5443dc6c3b46083daebb4530cb38db5e13",
"md5": "3385508310929c46d0e457d8436d28e0",
"sha256": "dbeab085d81c0ca5adc241e5ceec2340d4cf9962096714299d70f5aa689eb36f"
},
"downloads": -1,
"filename": "sqlalchemy_dlock-0.6.1.post2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "3385508310929c46d0e457d8436d28e0",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 21981,
"upload_time": "2024-11-29T15:41:14",
"upload_time_iso_8601": "2024-11-29T15:41:14.081075Z",
"url": "https://files.pythonhosted.org/packages/4f/0e/c7405324dd7b94a5af6af6695a5443dc6c3b46083daebb4530cb38db5e13/sqlalchemy_dlock-0.6.1.post2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "b3ed250d858c5ef783ca8bd010ac003a429ce7d7a3ec2e294044ebe0980f357a",
"md5": "2ee504c5611fb71a00d9a05d19459dd0",
"sha256": "a79f012387635d57c589fb793c5b01769996ea0c0dc207d69e5ea6c8ee901097"
},
"downloads": -1,
"filename": "sqlalchemy_dlock-0.6.1.post2.tar.gz",
"has_sig": false,
"md5_digest": "2ee504c5611fb71a00d9a05d19459dd0",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 18991,
"upload_time": "2024-11-29T15:41:15",
"upload_time_iso_8601": "2024-11-29T15:41:15.298285Z",
"url": "https://files.pythonhosted.org/packages/b3/ed/250d858c5ef783ca8bd010ac003a429ce7d7a3ec2e294044ebe0980f357a/sqlalchemy_dlock-0.6.1.post2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-29 15:41:15",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "tanbro",
"github_project": "sqlalchemy-dlock",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"requirements": [],
"lcname": "sqlalchemy-dlock"
}