|
1 | | -# sqlalchemy-dlock |
2 | | - |
3 | | -A distributed lock implementation based on SQLAlchemy. |
4 | | - |
5 | | -## Commands |
6 | | - |
7 | | -### Development |
8 | | -```bash |
9 | | -# Install with all dependency groups |
10 | | -uv sync --all |
11 | | - |
12 | | -# Install with specific groups |
13 | | -uv sync --group test |
14 | | -uv sync --group typecheck |
15 | | -uv sync --group docs |
16 | | - |
17 | | -# Run tests (requires databases running) |
18 | | -python -m unittest |
19 | | - |
20 | | -# Type checking |
21 | | -uv run --no-dev mypy |
22 | | - |
23 | | -# Linting and formatting |
24 | | -ruff check . |
25 | | -ruff format . |
26 | | - |
27 | | -# Build package |
28 | | -uv build |
29 | | -``` |
30 | | - |
31 | | -### Testing with Docker |
32 | | -```bash |
33 | | -# Start test databases (MySQL, PostgreSQL, MSSQL, Oracle) |
34 | | -docker compose -f db.docker-compose.yml up |
35 | | - |
36 | | -# Run full test matrix across Python and SQLAlchemy versions |
37 | | -cd tests |
38 | | -docker compose up --abort-on-container-exit |
39 | | -``` |
40 | | - |
41 | | -> **Note:** Oracle testing requires **Oracle Database Enterprise/Standard Edition**. Oracle Database Free (23c/23ai) does NOT support `DBMS_LOCK.REQUEST` which is required for distributed lock functionality. This is a fundamental limitation of the Free/Express edition, not related to the container image flavor. |
42 | | -
|
43 | | -For local Oracle testing, ensure you have a full Oracle Database installation or use the official Oracle image: |
44 | | -```bash |
45 | | -docker compose -f db.docker-compose.yml up |
46 | | -``` |
47 | | - |
48 | | -### Pre-commit Hooks |
49 | | -```bash |
50 | | -# Install pre-commit hooks |
51 | | -pre-commit install |
52 | | - |
53 | | -# Run manually |
54 | | -pre-commit run --all-files |
55 | | -``` |
56 | | - |
57 | | -## Architecture |
58 | | - |
59 | | -**Entry Points:** `create_sadlock()`, `create_async_sadlock()` in [factory.py](src/sqlalchemy_dlock/factory.py) |
60 | | - |
61 | | -**Factory Pattern:** |
62 | | -1. Inspect SQLAlchemy engine name from connection/session |
63 | | -2. Look up lock class in [registry.py](src/sqlalchemy_dlock/registry.py) (`REGISTRY` for sync, `ASYNCIO_REGISTRY` for async) |
64 | | -3. Instantiate database-specific lock implementation |
65 | | - |
66 | | -**Key Directories:** |
67 | | -- [lock/](src/sqlalchemy_dlock/lock/) - Database-specific lock implementations |
68 | | - - [base.py](src/sqlalchemy_dlock/lock/base.py) - `BaseSadLock` (sync), `BaseAsyncSadLock` (async) |
69 | | - - [mysql.py](src/sqlalchemy_dlock/lock/mysql.py) - MySQL/MariaDB named locks |
70 | | - - [postgresql.py](src/sqlalchemy_dlock/lock/postgresql.py) - PostgreSQL advisory locks |
71 | | - - [mssql.py](src/sqlalchemy_dlock/lock/mssql.py) - MSSQL application locks |
72 | | - - [oracle.py](src/sqlalchemy_dlock/lock/oracle.py) - Oracle user locks |
73 | | -- [statement/](src/sqlalchemy_dlock/statement/) - SQL statement templates for each database |
74 | | -- [registry.py](src/sqlalchemy_dlock/registry.py) - Engine name to lock class mapping |
75 | | - |
76 | | -**Test Structure:** |
77 | | -- [tests/](tests/) - Synchronous tests |
78 | | -- [tests/asyncio/](tests/asyncio/) - Asynchronous tests |
79 | | -- [tests/engines.py](tests/engines.py) - Test database connection factory |
80 | | - |
81 | | -## Gotchas |
82 | | - |
83 | | -### Critical |
84 | | -- **Thread-local locks:** `BaseSadLock` extends `threading.local` - lock objects CANNOT be safely passed between threads. Each thread must create its own lock instance. |
85 | | -- **MySQL re-entrant behavior:** MySQL allows acquiring the same named lock multiple times on the same connection. This is NOT true mutual exclusion. |
86 | | -- **Lock lifetime:** Locks are tied to database connections. Closing a connection releases all associated locks. |
87 | | - |
88 | | -### Database-Specific |
89 | | -- **Key hashing:** PostgreSQL and Oracle convert string keys to 64-bit integers via BLAKE2b hash. |
90 | | -- **PostgreSQL timeout:** Implemented through polling, may have ~1 second variance. |
91 | | -- **Oracle lock ID range:** 0-1073741823 (uses `DBMS_LOCK.REQUEST`) |
92 | | -- **Oracle Free limitation:** Oracle Database Free (23c/23ai) does NOT support `DBMS_LOCK.REQUEST`. This is a fundamental limitation of the Free/Express edition. CI skips Oracle tests; test locally with `docker compose -f db.docker-compose.yml up` which uses the official Oracle image. |
93 | | -- **MSSQL driver:** Requires ODBC driver installation (`msodbcsql18` on Ubuntu) |
94 | | - |
95 | | -### Resource Requirements |
96 | | -- **MSSQL container:** Requires at least 2GB RAM |
97 | | -- **Oracle container:** Requires at least 2GB RAM |
98 | | - |
99 | | -### API Notes |
100 | | -- `contextual_timeout` parameter ONLY affects `with` statements, not direct `acquire()` calls |
101 | | -- Async lock classes are separate (`*AsyncSadLock`) but defined in same modules as sync variants |
102 | | - |
103 | | -## Environment |
104 | | - |
105 | | -**Python:** 3.9+ (CI tests 3.10-3.14) |
106 | | - |
107 | | -**Testing Databases:** Optional Docker services in [db.docker-compose.yml](db.docker-compose.yml) |
108 | | -- MySQL: `mysql://test:test@127.0.0.1:3306/test` |
109 | | -- PostgreSQL: `postgresql://postgres:test@127.0.0.1:5432/` |
110 | | -- MSSQL: `mssql+pyodbc://sa:YourStrongPassword123@127.0.0.1:1433/master` |
111 | | -- Oracle: `oracle+oracledb://sys:YourStrong@Passw0rd@127.0.0.1:1521/?service_name=FREEPDB1` |
112 | | - |
113 | | -**Environment Variables:** Test URLs can be set via `TEST_URLS` and `TEST_ASYNC_URLS`, or loaded from `tests/.env` |
114 | | - |
115 | | -## Code Style |
116 | | - |
117 | | -- **Formatter:** Ruff (line length: 128) |
118 | | -- **Linter:** Ruff with import sorting (`I`) |
119 | | -- **Type checker:** mypy |
120 | | -- **Source layout:** `src/` directory with setuptools |
121 | | -- **Config:** [.ruff.toml](.ruff.toml), [.mypy.ini](.mypy.ini) |
122 | | - |
123 | | -## Workflow |
124 | | - |
125 | | -When adding support for a new database: |
126 | | -1. Create lock implementation in [lock/](src/sqlalchemy_dlock/lock/) inheriting from `BaseSadLock` / `BaseAsyncSadLock` |
127 | | -2. Create SQL templates in [statement/](src/sqlalchemy_dlock/statement/) |
128 | | -3. Add entry to `REGISTRY` and `ASYNCIO_REGISTRY` in [registry.py](src/sqlalchemy_dlock/registry.py) |
129 | | -4. Add tests in [tests/](tests/) and [tests/asyncio/](tests/asyncio/) |
130 | | -5. Update [db.docker-compose.yml](db.docker-compose.yml) if testing locally |
| 1 | +See: (AGENTS.md)[AGENTS.md] |
0 commit comments