{"id":17001198,"url":"https://github.com/tanbro/sqlalchemy-dlock","last_synced_at":"2026-02-12T09:09:24.939Z","repository":{"id":43014481,"uuid":"330951970","full_name":"tanbro/sqlalchemy-dlock","owner":"tanbro","description":"Distributed lock based on Database and SQLAlchemy.","archived":false,"fork":false,"pushed_at":"2026-02-11T05:51:08.000Z","size":459,"stargazers_count":16,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-11T06:12:52.021Z","etag":null,"topics":["dlock","mysql","orm","postgres","postgresql","sql","sqlalchemy"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tanbro.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS.md","dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-01-19T11:04:53.000Z","updated_at":"2026-02-11T05:51:11.000Z","dependencies_parsed_at":"2024-01-02T10:52:08.534Z","dependency_job_id":"6936c6f7-04d1-4687-831f-5693294a3375","html_url":"https://github.com/tanbro/sqlalchemy-dlock","commit_stats":{"total_commits":164,"total_committers":3,"mean_commits":"54.666666666666664","dds":0.07317073170731703,"last_synced_commit":"665ca72e4c4202d0750843d71eff4f17f281ac22"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/tanbro/sqlalchemy-dlock","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanbro%2Fsqlalchemy-dlock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanbro%2Fsqlalchemy-dlock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanbro%2Fsqlalchemy-dlock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanbro%2Fsqlalchemy-dlock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tanbro","download_url":"https://codeload.github.com/tanbro/sqlalchemy-dlock/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanbro%2Fsqlalchemy-dlock/sbom","scorecard":{"id":867483,"data":{"date":"2025-08-11","repo":{"name":"github.com/tanbro/sqlalchemy-dlock","commit":"9fbdfe604074e462c3e449c92fd1c5a1ba255354"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.2,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/22 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":5,"reason":"7 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/python-package.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: BSD 3-Clause \"New\" or \"Revised\" License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-package.yml:93: update your workflow using https://app.stepsecurity.io/secureworkflow/tanbro/sqlalchemy-dlock/python-package.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-package.yml:95: update your workflow using https://app.stepsecurity.io/secureworkflow/tanbro/sqlalchemy-dlock/python-package.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-package.yml:103: update your workflow using https://app.stepsecurity.io/secureworkflow/tanbro/sqlalchemy-dlock/python-package.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-package.yml:117: update your workflow using https://app.stepsecurity.io/secureworkflow/tanbro/sqlalchemy-dlock/python-package.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/python-package.yml:122: update your workflow using https://app.stepsecurity.io/secureworkflow/tanbro/sqlalchemy-dlock/python-package.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-package.yml:47: update your workflow using https://app.stepsecurity.io/secureworkflow/tanbro/sqlalchemy-dlock/python-package.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-package.yml:57: update your workflow using https://app.stepsecurity.io/secureworkflow/tanbro/sqlalchemy-dlock/python-package.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/python-package.yml:83: update your workflow using https://app.stepsecurity.io/secureworkflow/tanbro/sqlalchemy-dlock/python-package.yml/main?enable=pin","Warn: containerImage not pinned by hash: tests/Dockerfile:3: pin your Docker image by updating quay.io/pypa/manylinux_2_28_x86_64 to quay.io/pypa/manylinux_2_28_x86_64@sha256:6f42f4382bc73e9584206e6722e001922c0d4846c932fccaa04c0e35903b717d","Warn: pipCommand not pinned by hash: .github/workflows/python-package.yml:64","Warn: pipCommand not pinned by hash: .github/workflows/python-package.yml:100","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned","Info:   0 out of   2 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2020-24 / GHSA-2xpj-f5g2-8p7m"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/python-package.yml:110"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 10 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-24T03:04:51.234Z","repository_id":43014481,"created_at":"2025-08-24T03:04:51.235Z","updated_at":"2025-08-24T03:04:51.235Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29362241,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-12T08:51:36.827Z","status":"ssl_error","status_checked_at":"2026-02-12T08:51:26.849Z","response_time":55,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dlock","mysql","orm","postgres","postgresql","sql","sqlalchemy"],"created_at":"2024-10-14T04:24:06.638Z","updated_at":"2026-02-12T09:09:24.933Z","avatar_url":"https://github.com/tanbro.png","language":"Python","readme":"# sqlalchemy-dlock\n\n[![CI](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[![GitHub Release](https://img.shields.io/github/v/release/tanbro/sqlalchemy-dlock)](https://github.com/tanbro/sqlalchemy-dlock/releases)\n[![PyPI version](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[![License](https://img.shields.io/pypi/l/sqlalchemy-dlock)](LICENSE)\n[![Code style: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n\nA distributed lock library based on databases and [SQLAlchemy][].\n\nsqlalchemy-dlock provides distributed locking capabilities using your existing database infrastructure—no additional services like Redis or ZooKeeper required. It currently supports:\n\n| Database   | Lock Mechanism                                                                                                                                 |\n| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |\n| MySQL      | [Named Lock](https://dev.mysql.com/doc/refman/en/locking-functions.html) (`GET_LOCK` / `RELEASE_LOCK`)                                         |\n| MariaDB    | [Named Lock](https://mariadb.com/kb/en/get_lock/) (compatible with MySQL)                                                                      |\n| MSSQL      | [Application Lock](https://learn.microsoft.com/sql/relational-databases/system-stored-procedures/sp-getapplock-transact-sql) (`sp_getapplock`) |\n| Oracle     | [User Lock](https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_LOCK.html) (`DBMS_LOCK`) |\n| PostgreSQL | [Advisory Lock](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS) |\n\n\u003e ⚠️ **Oracle Not Tested:**\n\u003e Oracle Database Free (23c/23ai) does NOT support `DBMS_LOCK.REQUEST`. We do NOT test Oracle in CI or integration tests. Use with Oracle Enterprise/Standard Edition at your own risk.\n\n---\n\n## Why sqlalchemy-dlock?\n\n**Distributed locks** coordinate access to shared resources across multiple processes or servers. Here's how database-based locking compares to other approaches:\n\n| Solution          | Pros                                          | Cons                                                | Best For                             |\n| ----------------- | --------------------------------------------- | --------------------------------------------------- | ------------------------------------ |\n| **Redis**         | High performance                              | Additional infrastructure, consistency complexities | High-throughput scenarios            |\n| **ZooKeeper**     | Strong consistency                            | Complex deployment, high operational cost           | Financial/mission-critical systems   |\n| **Database Lock** | Zero additional dependencies, ACID guarantees | Lower performance than in-memory solutions          | Applications with existing databases |\n\n**sqlalchemy-dlock is ideal for:**\n- Projects already using MySQL, MariaDB, MSSQL, Oracle, or PostgreSQL\n- Teams wanting zero additional infrastructure\n- Low to medium concurrency distributed synchronization\n- Applications requiring strong consistency guarantees\n\n**Not recommended for:**\n- High-concurrency scenarios (consider Redis instead)\n- Situations sensitive to database load\n\n---\n\n## Quick Start\n\n### Installation\n\n```bash\npip install sqlalchemy-dlock\n```\n\n**Requirements:**\n- Python 3.9+\n- SQLAlchemy 1.4.3+ or 2.x\n- Appropriate database driver for your database (see below)\n\n### Database Drivers\n\nThis library requires a database driver to be installed separately. Since you're already using SQLAlchemy, you likely have the appropriate driver installed. For a complete list of SQLAlchemy-supported drivers, see the [SQLAlchemy Dialects documentation](https://docs.sqlalchemy.org/en/latest/dialects/).\n\n\u003e ℹ️ **Notes**:\n\u003e - **MSSQL**: The `pyodbc` driver requires the Microsoft ODBC driver to be installed on your system. On Ubuntu/Debian:\n\u003e   ```bash\n\u003e   sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18\n\u003e   ```\n\u003e - **Oracle**:\n\u003e   Oracle Database Free (23c/23ai) does NOT support `DBMS_LOCK.REQUEST` which is required for distributed lock functionality. For production use with Oracle, a full Oracle Database (Enterprise/Standard Edition) installation is required.\n\n### Basic Usage\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('postgresql://user:pass@localhost/db')\n\nwith engine.connect() as conn:\n    # Create a lock\n    lock = create_sadlock(conn, 'my-resource-key')\n\n    # Acquire the lock\n    lock.acquire()\n    assert lock.locked\n\n    # Release the lock\n    lock.release()\n    assert not lock.locked\n```\n\n### Using Context Managers\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('postgresql://user:pass@localhost/db')\n\nwith engine.connect() as conn:\n    # Automatically acquires and releases the lock\n    with create_sadlock(conn, 'my-resource-key') as lock:\n        assert lock.locked\n        # Your critical section here\n\n    # Lock is automatically released\n    assert not lock.locked\n```\n\n### With Timeout\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('postgresql://user:pass@localhost/db')\n\nwith engine.connect() as conn:\n    try:\n        # Raises TimeoutError if lock cannot be acquired within 5 seconds\n        with create_sadlock(conn, 'my-resource-key', contextual_timeout=5) as lock:\n            pass\n    except TimeoutError:\n        print(\"Could not acquire lock - resource is busy\")\n```\n\n---\n\n## Common Use Cases\n\n### Use Case 1: Preventing Duplicate Task Execution\n\nPrevent multiple workers from processing the same task simultaneously:\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\ndef process_monthly_billing(user_id: int):\n    engine = create_engine('postgresql://user:pass@localhost/db')\n    with engine.connect() as conn:\n        # Ensure billing for a user is only processed once at a time\n        lock_key = f'billing:user:{user_id}'\n        with create_sadlock(conn, lock_key, contextual_timeout=0):\n            # If another worker is already processing this user's billing,\n            # this will fail immediately (timeout=0)\n            perform_billing_calculation(user_id)\n            send_bill(user_id)\n```\n\n### Use Case 2: API Rate Limiting \u0026 Debouncing\n\nPrevent simultaneous expensive operations on the same resource:\n\n```python\nfrom fastapi import FastAPI, HTTPException\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSession\nfrom sqlalchemy.orm import sessionmaker\nfrom sqlalchemy_dlock import create_async_sadlock\n\napp = FastAPI()\nengine = create_async_engine('postgresql+asyncpg://user:pass@localhost/db')\nAsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)\n\n@app.post(\"/api/resources/{resource_id}/export\")\nasync def export_resource(resource_id: str):\n    async with AsyncSessionLocal() as session:\n        # Try to acquire lock without blocking\n        lock = create_async_sadlock(session, f'export:{resource_id}')\n        acquired = await lock.acquire(block=False)\n        if not acquired:\n            raise HTTPException(status_code=409, detail=\"Export already in progress\")\n\n        try:\n            return await perform_export(resource_id)\n        finally:\n            await lock.release()\n```\n\n### Use Case 3: Scheduled Job Coordination\n\nEnsure scheduled jobs don't overlap across multiple servers:\n\n```python\nfrom apscheduler.schedulers.background import BackgroundScheduler\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\ndef data_sync_job():\n    engine = create_engine('mysql://user:pass@localhost/db')\n    with engine.connect() as conn:\n        lock_key = 'scheduled-job:data-sync'\n\n        # Only proceed if no other server is running this job\n        lock = create_sadlock(conn, lock_key, contextual_timeout=60)\n        with lock:\n            perform_data_sync()\n\nscheduler = BackgroundScheduler()\nscheduler.add_job(data_sync_job, 'interval', minutes=30)\nscheduler.start()\n```\n\n### Use Case 4: Decorator Pattern for Clean Code\n\nCreate a reusable decorator for locking functions:\n\n```python\nfrom functools import wraps\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\ndef with_db_lock(key_func, timeout=None):\n    \"\"\"Decorator that acquires a database lock before executing the function.\"\"\"\n    def decorator(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            engine = create_engine('postgresql://user:pass@localhost/db')\n            lock_key = key_func(*args, **kwargs)\n\n            with engine.connect() as conn:\n                with create_sadlock(conn, lock_key, contextual_timeout=timeout):\n                    return func(*args, **kwargs)\n        return wrapper\n    return decorator\n\n# Usage\n@with_db_lock(lambda user_id: f'user:update:{user_id}', timeout=10)\ndef update_user_profile(user_id: int, profile_data: dict):\n    # This function is protected from concurrent execution\n    # for the same user_id\n    ...\n```\n\n---\n\n## Working with SQLAlchemy ORM\n\nUsing locks with SQLAlchemy ORM sessions:\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.orm import sessionmaker\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('postgresql://user:pass@localhost/db')\nSession = sessionmaker(bind=engine)\n\nwith Session() as session:\n    with create_sadlock(session, 'my-resource-key') as lock:\n        # Use the session within the locked context\n        user = session.query(User).get(user_id)\n        user.balance += 100\n        session.commit()\n```\n\n---\n\n## Asynchronous I/O Support\n\nFull async/await support for asynchronous applications:\n\n```python\nfrom sqlalchemy.ext.asyncio import create_async_engine\nfrom sqlalchemy_dlock import create_async_sadlock\n\nengine = create_async_engine('postgresql+asyncpg://user:pass@localhost/db')\n\nasync def main():\n    async with engine.connect() as conn:\n        async with create_async_sadlock(conn, 'my-resource-key') as lock:\n            assert lock.locked\n            # Your async critical section here\n        assert not lock.locked\n```\n\n**Supported async drivers:**\n- MySQL: [aiomysql](https://pypi.org/project/aiomysql/)\n- PostgreSQL: [asyncpg](https://pypi.org/project/asyncpg/), [psycopg](https://pypi.org/project/psycopg/) (v3+)\n\n---\n\n## PostgreSQL Lock Types\n\nPostgreSQL provides multiple advisory lock types. Choose based on your scenario:\n\n| Lock Type             | Parameters               | Description                                  | Use Case                           |\n| --------------------- | ------------------------ | -------------------------------------------- | ---------------------------------- |\n| Session-exclusive     | (default)                | Held until manually released or session ends | Long-running tasks                 |\n| Session-shared        | `shared=True`            | Multiple shared locks can coexist            | Multi-reader scenarios             |\n| Transaction-exclusive | `xact=True`              | Automatically released when transaction ends | Transaction-scoped operations      |\n| Transaction-shared    | `shared=True, xact=True` | Shared locks within transaction              | Transactional read-heavy workloads |\n\n### Example: Transaction-Level Lock\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('postgresql://user:pass@localhost/db')\n\nwith engine.connect() as conn:\n    # Transaction-level lock - automatically released on commit/rollback\n    with create_sadlock(conn, 'my-key', xact=True) as lock:\n        conn.execute(text(\"INSERT INTO ...\"))\n        conn.commit()  # Lock is released here\n```\n\n### Example: Shared Lock for Read-Heavy Workloads\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('postgresql://user:pass@localhost/db')\n\n# Multiple readers can hold shared locks simultaneously\ndef read_resource(resource_id: str):\n    with engine.connect() as conn:\n        with create_sadlock(conn, f'resource:{resource_id}', shared=True):\n            return conn.execute(text(\"SELECT * FROM resources WHERE id = :id\"), {\"id\": resource_id})\n\n# Writers need exclusive locks\ndef write_resource(resource_id: str, data: dict):\n    with engine.connect() as conn:\n        # This will wait for all shared locks to be released\n        with create_sadlock(conn, f'resource:{resource_id}') as lock:\n            conn.execute(text(\"UPDATE resources SET ...\"))\n            conn.commit()\n```\n\n---\n\n## MSSQL Lock Types\n\nSQL Server's `sp_getapplock` supports multiple lock modes:\n\n| Lock Mode             | Parameters    | Description                                                  | Use Case                            |\n| --------------------- | ------------- | ------------------------------------------------------------ | ----------------------------------- |\n| `Exclusive` (default) | (default)     | Full exclusive access                                        | Write operations, critical sections |\n| `Shared`              | `shared=True` | Multiple readers can hold lock concurrently                  | Read-heavy workloads                |\n| `Update`              | `update=True` | Intended for update operations; compatible with Shared locks | Read-then-write patterns            |\n\n### Example: Exclusive Lock for Writing (Default)\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('mssql+pyodbc://user:pass@localhost/db')\n\nwith engine.connect() as conn:\n    # Exclusive lock for writing (default)\n    with create_sadlock(conn, 'my-resource') as lock:\n        conn.execute(text(\"UPDATE resources SET ...\"))\n```\n\n### Example: Shared Lock for Reading\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('mssql+pyodbc://user:pass@localhost/db')\n\n# Multiple readers can hold shared locks simultaneously\ndef read_resource(resource_id: str):\n    with engine.connect() as conn:\n        with create_sadlock(conn, f'resource:{resource_id}', shared=True):\n            return conn.execute(text(\"SELECT * FROM resources WHERE id = :id\"), {\"id\": resource_id})\n```\n\n### Example: Update Lock for Read-Then-Write Patterns\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('mssql+pyodbc://user:pass@localhost/db')\n\nwith engine.connect() as conn:\n    # Update lock - compatible with shared locks, used for read-then-write patterns\n    with create_sadlock(conn, 'my-resource', update=True) as lock:\n        data = conn.execute(text(\"SELECT * FROM resources WHERE id = :id\"), {\"id\": resource_id})\n        # Perform read operations\n        # Then upgrade to exclusive lock for writing\n        conn.execute(text(\"UPDATE resources SET ...\"))\n```\n\n---\n\n## Oracle Lock Types\n\nOracle's `DBMS_LOCK.REQUEST` supports 6 lock modes with different compatibility:\n\n| Lock Mode | Constant | Description | Use Case |\n|-----------|----------|-------------|----------|\n| `X` (default) | X_MODE | Exclusive - full exclusive access | Write operations, critical sections |\n| `S` | S_MODE | Shared - multiple readers | Read-heavy workloads |\n| `SS` | SS_MODE | Sub-Shared - share locks on subparts | Aggregate object read |\n| `SX` | SX_MODE | Sub-Exclusive (Row Exclusive) | Row-level updates |\n| `SSX` | SSX_MODE | Shared Sub-Exclusive | Read with pending write |\n| `NL` | NL_MODE | Null - no actual lock | Testing/coordination only |\n\n### Example: Exclusive Lock for Writing (Default)\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('oracle+oracledb://user:pass@localhost/db')\n\nwith engine.connect() as conn:\n    # Exclusive lock for writing (default)\n    with create_sadlock(conn, 'my-resource') as lock:\n        conn.execute(text(\"UPDATE resources SET ...\"))\n```\n\n### Example: Shared Lock for Reading\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('oracle+oracledb://user:pass@localhost/db')\n\n# Multiple readers can hold shared locks simultaneously\ndef read_resource(resource_id: str):\n    with engine.connect() as conn:\n        with create_sadlock(conn, f'resource:{resource_id}', lock_mode=\"S\"):\n            return conn.execute(text(\"SELECT * FROM resources WHERE id = :id\"), {\"id\": resource_id})\n```\n\n### Example: Transaction-Level Lock\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('oracle+oracledb://user:pass@localhost/db')\n\nwith engine.connect() as conn:\n    # Transaction-level lock - automatically released on commit/rollback\n    with create_sadlock(conn, 'my-resource', release_on_commit=True) as lock:\n        conn.execute(text(\"INSERT INTO ...\"))\n        conn.commit()  # Lock is released here\n```\n\n### Example: Using Integer Lock ID Directly\n\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_dlock import create_sadlock\n\nengine = create_engine('oracle+oracledb://user:pass@localhost/db')\n\nwith engine.connect() as conn:\n    # Direct integer lock ID (no hashing needed)\n    with create_sadlock(conn, 12345, lock_mode=\"X\") as lock:\n        # Direct lock ID usage\n        pass\n```\n\n**Note:** String keys are converted to integer IDs using blake2b hash (similar to PostgreSQL).\n\n---\n\n### Performance Considerations\n\n- Database lock operations require network round-trips and are slower than in-memory solutions like Redis\n- PostgreSQL timeout is implemented through polling and may have ~1 second variance\n- Consider your concurrency requirements before choosing database-based locks\n\n### Thread Safety\n\n- Lock objects are **thread-local** and cannot be safely passed between threads\n- Each thread must create its own lock instance\n- Cross-process/cross-server locking works normally\n\n### MySQL-Specific Behavior\n\n⚠️ **Warning:** MySQL allows acquiring the same named lock multiple times on the same connection. This can lead to unexpected cascading locks:\n\n```python\n# DANGER: On MySQL, the second acquisition succeeds immediately\nwith create_sadlock(conn, 'my-key') as lock1:\n    # This immediately returns without waiting - no real mutual exclusion!\n    with create_sadlock(conn, 'my-key') as lock2:\n        pass\n```\n\nTo avoid this, use separate connections or implement additional checking logic.\n\n### Lock Lifetime\n\n- Locks are tied to your database connection\n- Closing a connection releases all associated locks\n- Properly manage Connection/Session lifecycle to avoid accidental lock releases\n\n---\n\n## FAQ\n\n**Q: What happens if the database goes down?**\n\nA: Locks are automatically released when the database connection is lost. This is intentional behavior to prevent deadlocks.\n\n**Q: Can I pass a lock object between threads?**\n\nA: No. Lock objects are thread-local for safety reasons. Each thread should create its own lock instance pointing to the same lock key.\n\n**Q: How do I choose between MySQL and PostgreSQL?**\n\nA: Both are fully supported. PostgreSQL offers more lock types (shared/transaction-level), while MySQL's implementation is simpler.\n\n**Q: Are locks inherited by child processes?**\n\nA: No. Child processes must establish their own database connections and create new lock objects.\n\n**Q: How can I debug lock status?**\n\nA:\n- **MySQL:** `SELECT * FROM performance_schema.metadata_locks;`\n- **PostgreSQL:** `SELECT * FROM pg_locks WHERE locktype = 'advisory';`\n\n**Q: What's the maximum lock key size?**\n\nA:\n- **MySQL:** 64 characters\n- **MSSQL:** 255 characters\n- **PostgreSQL:** Keys are converted to 64-bit integers via BLAKE2b hash\n- **Oracle:** Keys are converted to integers (0-1073741823) via BLAKE2b hash\n\n**Q: Can I use this with SQLite?**\n\nA: No. SQLite does not support the same named/advisory lock mechanisms as MySQL or PostgreSQL.\n\n---\n\n## Testing\n\nThe following database drivers are tested:\n\n**MySQL:**\n- [mysqlclient](https://pypi.org/project/mysqlclient/) (synchronous)\n- [pymysql](https://pypi.org/project/pymysql/) (synchronous)\n- [aiomysql](https://pypi.org/project/aiomysql/) (asynchronous)\n\n**PostgreSQL:**\n- [psycopg2](https://pypi.org/project/psycopg2/) (synchronous)\n- [psycopg](https://pypi.org/project/psycopg/) (v3, synchronous and asynchronous)\n- [asyncpg](https://pypi.org/project/asyncpg/) (asynchronous)\n\n**MSSQL:**\n- [pyodbc](https://pypi.org/project/pyodbc/) (synchronous)\n- [pymssql](https://pypi.org/project/pymssql/) (synchronous)\n- [aioodbc](https://pypi.org/project/aioodbc/) (asynchronous)\n\n**Oracle:**\n- [oracledb](https://pypi.org/project/oracledb/) (synchronous \u0026 asynchronous)\n- [cx_Oracle](https://pypi.org/project/cx-Oracle/) (synchronous, legacy)\n\n### Running Tests Locally\n\n1. Install the project with development dependencies:\n\n```bash\nuv sync --group test\nuv pip install mysqlclient aiomysql psycopg2 asyncpg\n```\n\n2. Start MySQL and PostgreSQL services using Docker:\n\n```bash\ndocker compose -f db.docker-compose.yml up\n```\n\n3. Set environment variables for database connections (or use defaults):\n\n```bash\nexport TEST_URLS=\"mysql://test:test@127.0.0.1:3306/test postgresql://postgres:test@127.0.0.1:5432/\"\nexport TEST_ASYNC_URLS=\"mysql+aiomysql://test:test@127.0.0.1:3306/test postgresql+asyncpg://postgres:test@127.0.0.1:5432/\"\n```\n\n\u003e ℹ️ **Note:** Test cases also load environment variables from `tests/.env`.\n\n4. Run tests:\n\n```bash\npython -m unittest\n```\n\n### Running Tests with Docker Compose\n\nThe project includes a comprehensive test matrix across Python and SQLAlchemy versions:\n\n```bash\ncd tests\ndocker compose up --abort-on-container-exit\n```\n\n---\n\n## Documentation\n\nFull API documentation is available at [https://sqlalchemy-dlock.readthedocs.io/](https://sqlalchemy-dlock.readthedocs.io/en/latest/)\n\n---\n\n## Links\n\n- **Source Code:** [https://github.com/tanbro/sqlalchemy-dlock](https://github.com/tanbro/sqlalchemy-dlock)\n- **Issue Tracker:** [https://github.com/tanbro/sqlalchemy-dlock/issues](https://github.com/tanbro/sqlalchemy-dlock/issues)\n- **PyPI:** [https://pypi.org/project/sqlalchemy-dlock/](https://pypi.org/project/sqlalchemy-dlock/)\n\n[SQLAlchemy]: https://www.sqlalchemy.org/\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftanbro%2Fsqlalchemy-dlock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftanbro%2Fsqlalchemy-dlock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftanbro%2Fsqlalchemy-dlock/lists"}