sqlalchemy-dlock 0.4__tar.gz → 0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/CHANGELOG.md +9 -1
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/MANIFEST.in +4 -1
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/PKG-INFO +60 -51
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/README.md +40 -47
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/pyproject.toml +8 -11
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/__init__.py +1 -1
- sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/asyncio/__init__.py +1 -0
- sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/asyncio/functions.py → sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/asyncio/factory.py +8 -5
- sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/asyncio/baselock.py → sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/asyncio/lock/base.py +17 -4
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/asyncio/lock/mysql.py +10 -16
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/asyncio/lock/postgresql.py +25 -31
- sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/factory.py +55 -0
- sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/lock/base.py +185 -0
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/lock/mysql.py +26 -11
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/lock/postgresql.py +58 -45
- sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/statement/mysql.py +4 -0
- sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/statement/postgresql.py +38 -0
- sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/utils.py +101 -0
- sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/version.py +16 -0
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock.egg-info/PKG-INFO +60 -51
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock.egg-info/SOURCES.txt +4 -4
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock.egg-info/requires.txt +8 -8
- sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/asyncio/__init__.py +0 -1
- sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/baselock.py +0 -153
- sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/functions.py +0 -46
- sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/statement/mysql.py +0 -20
- sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/statement/postgresql.py +0 -75
- sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/utils.py +0 -32
- sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/version.py +0 -4
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/AUTHORS.md +0 -0
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/LICENSE +0 -0
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/setup.cfg +0 -0
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/asyncio/lock/__init__.py +0 -0
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/exceptions.py +0 -0
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/lock/__init__.py +0 -0
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/statement/__init__.py +0 -0
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock.egg-info/dependency_links.txt +0 -0
- {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## v0.5
|
|
4
|
+
|
|
5
|
+
Date: 2023-12-06
|
|
6
|
+
|
|
7
|
+
- New:
|
|
8
|
+
- `contextual_timeout` parameter for “with” statement
|
|
9
|
+
- Support Python 3.12
|
|
10
|
+
|
|
3
11
|
## v0.4
|
|
4
12
|
|
|
5
13
|
Date: 2023-06-17
|
|
@@ -95,7 +103,7 @@ Date: 2021-03-09
|
|
|
95
103
|
|
|
96
104
|
- `sadlock` -> `create_sadlock`
|
|
97
105
|
- `asyncio.sadlock` -> `asyncio.create_async_sadlock`
|
|
98
|
-
|
|
106
|
+
|
|
99
107
|
and some other ...
|
|
100
108
|
|
|
101
109
|
## v0.2a1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sqlalchemy-dlock
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5
|
|
4
4
|
Summary: A distributed lock implementation based on SQLAlchemy
|
|
5
5
|
Author-email: liu xue yan <liu_xue_yan@foxmail.com>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -14,33 +14,49 @@ Classifier: Intended Audience :: Developers
|
|
|
14
14
|
Classifier: Programming Language :: Python
|
|
15
15
|
Requires-Python: >=3.7
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
License-File: AUTHORS.md
|
|
19
|
+
Requires-Dist: SQLAlchemy<3.0,>=1.4
|
|
17
20
|
Provides-Extra: asyncio
|
|
21
|
+
Requires-Dist: SQLAlchemy[asyncio]<3.0,>=1.4; extra == "asyncio"
|
|
18
22
|
Provides-Extra: mysqlclient
|
|
23
|
+
Requires-Dist: mysqlclient; extra == "mysqlclient"
|
|
19
24
|
Provides-Extra: pymysql
|
|
25
|
+
Requires-Dist: pymysql; extra == "pymysql"
|
|
20
26
|
Provides-Extra: aiomysql
|
|
27
|
+
Requires-Dist: SQLAlchemy[asyncio]<3.0,>=1.4; extra == "aiomysql"
|
|
28
|
+
Requires-Dist: aiomysql; extra == "aiomysql"
|
|
21
29
|
Provides-Extra: psycopg2
|
|
30
|
+
Requires-Dist: psycopg2>=2.8; extra == "psycopg2"
|
|
22
31
|
Provides-Extra: psycopg2-binary
|
|
32
|
+
Requires-Dist: psycopg2-binary>=2.8; extra == "psycopg2-binary"
|
|
23
33
|
Provides-Extra: psycopg3
|
|
34
|
+
Requires-Dist: SQLAlchemy<3.0,>=2.0; extra == "psycopg3"
|
|
35
|
+
Requires-Dist: psycopg; extra == "psycopg3"
|
|
24
36
|
Provides-Extra: psycopg3-binary
|
|
37
|
+
Requires-Dist: SQLAlchemy<3.0,>=2.0; extra == "psycopg3-binary"
|
|
38
|
+
Requires-Dist: psycopg[binary]; extra == "psycopg3-binary"
|
|
25
39
|
Provides-Extra: psycopg3-c
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
40
|
+
Requires-Dist: SQLAlchemy<3.0,>=2.0; extra == "psycopg3-c"
|
|
41
|
+
Requires-Dist: psycopg[c]; extra == "psycopg3-c"
|
|
42
|
+
Provides-Extra: asyncpg
|
|
43
|
+
Requires-Dist: SQLAlchemy[asyncio]<3.0,>=1.4; extra == "asyncpg"
|
|
44
|
+
Requires-Dist: asyncpg; extra == "asyncpg"
|
|
29
45
|
|
|
30
|
-
#
|
|
46
|
+
# sqlalchemy-dlock
|
|
31
47
|
|
|
32
48
|
[](https://github.com/tanbro/sqlalchemy-dlock/actions/workflows/python-package.yml)
|
|
33
49
|
[](https://pypi.org/project/sqlalchemy-dlock/)
|
|
34
|
-
[](https://sqlalchemy-dlock.readthedocs.io/en/latest
|
|
35
|
-
[](https://sqlalchemy-dlock.readthedocs.io/en/latest/)
|
|
51
|
+
[](https://codecov.io/gh/tanbro/sqlalchemy-dlock)
|
|
36
52
|
|
|
37
|
-
|
|
53
|
+
`sqlalchemy-dlock` is a distributed-lock library based on Database and [SQLAlchemy][].
|
|
38
54
|
|
|
39
55
|
It currently supports below locks:
|
|
40
56
|
|
|
41
57
|
Database | Lock
|
|
42
58
|
---------- | ---------------------------------------------------------------------------------------------
|
|
43
|
-
MySQL | [named lock](https://dev.mysql.com/doc/refman/
|
|
59
|
+
MySQL | [named lock](https://dev.mysql.com/doc/refman/en/locking-functions.html)
|
|
44
60
|
PostgreSQL | [advisory lock](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS)
|
|
45
61
|
|
|
46
62
|
## Install
|
|
@@ -51,7 +67,7 @@ pip install sqlalchemy-dlock
|
|
|
51
67
|
|
|
52
68
|
## Usage
|
|
53
69
|
|
|
54
|
-
- Work with [SQLAlchemy][] `Connection
|
|
70
|
+
- Work with [SQLAlchemy][] [`Connection`](https://docs.sqlalchemy.org/20/core/connections.html):
|
|
55
71
|
|
|
56
72
|
```python
|
|
57
73
|
from sqlalchemy import create_engine
|
|
@@ -110,7 +126,7 @@ pip install sqlalchemy-dlock
|
|
|
110
126
|
assert not lock2.locked
|
|
111
127
|
```
|
|
112
128
|
|
|
113
|
-
- Work with [SQLAlchemy][] `ORM` session:
|
|
129
|
+
- Work with [SQLAlchemy][] [`ORM` `Session`](https://docs.sqlalchemy.org/en/20/orm/session.html):
|
|
114
130
|
|
|
115
131
|
```python
|
|
116
132
|
from sqlalchemy import create_engine
|
|
@@ -130,10 +146,10 @@ pip install sqlalchemy-dlock
|
|
|
130
146
|
|
|
131
147
|
- Asynchronous I/O Support
|
|
132
148
|
|
|
133
|
-
> **
|
|
149
|
+
> **NOTE**
|
|
134
150
|
>
|
|
135
|
-
> - [SQLAlchemy][] `1.x
|
|
136
|
-
> - [SQLAlchemy][] `2.x
|
|
151
|
+
> - [SQLAlchemy][] `1.x`'s asynchronous I/O: <https://docs.sqlalchemy.org/14/orm/extensions/asyncio.html>
|
|
152
|
+
> - [SQLAlchemy][] `2.x`'s asynchronous I/O: <https://docs.sqlalchemy.org/20/orm/extensions/asyncio.html>
|
|
137
153
|
|
|
138
154
|
```python
|
|
139
155
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
@@ -143,7 +159,7 @@ pip install sqlalchemy-dlock
|
|
|
143
159
|
|
|
144
160
|
engine = create_async_engine('postgresql+asyncpg://scott:tiger@127.0.0.1/')
|
|
145
161
|
|
|
146
|
-
async with engine.
|
|
162
|
+
async with engine.connect() as conn:
|
|
147
163
|
async with create_async_sadlock(conn, key) as lock:
|
|
148
164
|
assert lock.locked
|
|
149
165
|
await lock.release()
|
|
@@ -152,22 +168,20 @@ pip install sqlalchemy-dlock
|
|
|
152
168
|
assert not lock.locked
|
|
153
169
|
```
|
|
154
170
|
|
|
155
|
-
> **
|
|
171
|
+
> **NOTE**
|
|
156
172
|
>
|
|
157
|
-
> [aiomysql][], [asyncpg][] and [psycopg][] are tested asynchronous drivers
|
|
173
|
+
> [aiomysql][], [asyncpg][] and [psycopg][] are tested asynchronous drivers.
|
|
158
174
|
>
|
|
159
|
-
> We can install asynchronous DB libraries
|
|
175
|
+
> We can install it with asynchronous DB libraries:
|
|
160
176
|
>
|
|
161
177
|
> - ```bash
|
|
162
|
-
> pip install sqlalchemy-dlock
|
|
178
|
+
> pip install SQLAlchemy[asyncio] aiomysql sqlalchemy-dlock
|
|
163
179
|
> ```
|
|
164
180
|
>
|
|
165
|
-
>
|
|
166
|
-
> pip install sqlalchemy-dlock[asyncio] aiomysql
|
|
167
|
-
> ```
|
|
181
|
+
> or
|
|
168
182
|
>
|
|
169
183
|
> - ```bash
|
|
170
|
-
> pip install
|
|
184
|
+
> pip install SQLAlchemy[asyncio] asyncpg sqlalchemy-dlock
|
|
171
185
|
> ```
|
|
172
186
|
|
|
173
187
|
## Test
|
|
@@ -179,7 +193,7 @@ Following drivers are tested:
|
|
|
179
193
|
- [pymysql][] (synchronous)
|
|
180
194
|
- [aiomysql][] (asynchronous)
|
|
181
195
|
- Postgres:
|
|
182
|
-
- [psycopg2][] (
|
|
196
|
+
- [psycopg2][] (synchronous)
|
|
183
197
|
- [asyncpg][] (asynchronous)
|
|
184
198
|
- [psycopg][] (synchronous and asynchronous)
|
|
185
199
|
|
|
@@ -187,15 +201,15 @@ You can run unit-tests
|
|
|
187
201
|
|
|
188
202
|
- on local environment:
|
|
189
203
|
|
|
190
|
-
1. Install the project
|
|
204
|
+
1. Install the project in editable mode with `asyncio` optional dependencies, and libraries/drivers needed in test. A virtual environment ([venv][]) is strongly advised:
|
|
191
205
|
|
|
192
206
|
```bash
|
|
193
|
-
pip install -e .[
|
|
207
|
+
pip install -e .[asyncio] -r tests/requirements.txt
|
|
194
208
|
```
|
|
195
209
|
|
|
196
|
-
1. start up mysql and postgresql
|
|
210
|
+
1. start up mysql and postgresql service
|
|
197
211
|
|
|
198
|
-
There is a docker [compose][] file `db.docker-compose.yml` in
|
|
212
|
+
There is a docker [compose][] file `db.docker-compose.yml` in project's top directory,
|
|
199
213
|
which can be used to run mysql and postgresql develop environment conveniently:
|
|
200
214
|
|
|
201
215
|
```bash
|
|
@@ -204,48 +218,43 @@ You can run unit-tests
|
|
|
204
218
|
|
|
205
219
|
1. set environment variables `TEST_URLS` and `TEST_ASYNC_URLS` for sync and async database connection url.
|
|
206
220
|
Multiple connections separated by space.
|
|
207
|
-
The test cases load environment variables in `tests/.env`.
|
|
208
221
|
|
|
209
|
-
eg (
|
|
222
|
+
eg: (following values are also the defaults, and can be omitted)
|
|
210
223
|
|
|
211
224
|
```ini
|
|
212
225
|
TEST_URLS=mysql://test:test@127.0.0.1/test postgresql://postgres:test@127.0.0.1/
|
|
213
226
|
TEST_ASYNC_URLS=mysql+aiomysql://test:test@127.0.0.1/test postgresql+asyncpg://postgres:test@127.0.0.1/
|
|
214
227
|
```
|
|
215
228
|
|
|
229
|
+
> **NOTE**
|
|
230
|
+
>
|
|
231
|
+
> The test cases would load environment variables from dot-env file `tests/.env`.
|
|
232
|
+
|
|
216
233
|
1. run unit-test
|
|
217
234
|
|
|
218
235
|
```bash
|
|
219
236
|
python -m unittest
|
|
220
237
|
```
|
|
221
238
|
|
|
222
|
-
- or on docker
|
|
239
|
+
- or on docker [compose][]:
|
|
223
240
|
|
|
224
|
-
|
|
241
|
+
Name of [compose][] services for Python and [SQLAlchemy][] version matrix defined in [compose][] file `docker-compose.yml` has such format:
|
|
225
242
|
|
|
226
|
-
|
|
227
|
-
python -m pip install build && python -m build -w
|
|
228
|
-
```
|
|
243
|
+
python{{X.Y}}-sqlalchemy{{X}}
|
|
229
244
|
|
|
230
|
-
1. run unit-
|
|
245
|
+
For example, if want to run tests for Python `3.8` and [SQLAlchemy][] `1.x`, we shall up to run unit-tests as below:
|
|
231
246
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
For example, if want to take a test for Python `3.9` and [SQLAlchemy][] `2.x`, we shall up to run unit-tests as below:
|
|
237
|
-
|
|
238
|
-
```bash
|
|
239
|
-
cd tests
|
|
240
|
-
docker compose up python3.9-sqlalchemy2
|
|
241
|
-
```
|
|
247
|
+
```bash
|
|
248
|
+
cd tests
|
|
249
|
+
docker compose up python3.8-sqlalchemy1
|
|
250
|
+
```
|
|
242
251
|
|
|
243
|
-
|
|
252
|
+
For Python `3.10` and [SQLAlchemy][] `2.x`:
|
|
244
253
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
254
|
+
```bash
|
|
255
|
+
cd tests
|
|
256
|
+
docker compose up python3.10-sqlalchemy2
|
|
257
|
+
```
|
|
249
258
|
|
|
250
259
|
[SQLAlchemy]: https://www.sqlalchemy.org/ "The Python SQL Toolkit and Object Relational Mapper"
|
|
251
260
|
[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. "
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# sqlalchemy-dlock
|
|
2
2
|
|
|
3
3
|
[](https://github.com/tanbro/sqlalchemy-dlock/actions/workflows/python-package.yml)
|
|
4
4
|
[](https://pypi.org/project/sqlalchemy-dlock/)
|
|
5
|
-
[](https://sqlalchemy-dlock.readthedocs.io/en/latest
|
|
6
|
-
[](https://sqlalchemy-dlock.readthedocs.io/en/latest/)
|
|
6
|
+
[](https://codecov.io/gh/tanbro/sqlalchemy-dlock)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
`sqlalchemy-dlock` is a distributed-lock library based on Database and [SQLAlchemy][].
|
|
9
9
|
|
|
10
10
|
It currently supports below locks:
|
|
11
11
|
|
|
12
12
|
Database | Lock
|
|
13
13
|
---------- | ---------------------------------------------------------------------------------------------
|
|
14
|
-
MySQL | [named lock](https://dev.mysql.com/doc/refman/
|
|
14
|
+
MySQL | [named lock](https://dev.mysql.com/doc/refman/en/locking-functions.html)
|
|
15
15
|
PostgreSQL | [advisory lock](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS)
|
|
16
16
|
|
|
17
17
|
## Install
|
|
@@ -22,7 +22,7 @@ pip install sqlalchemy-dlock
|
|
|
22
22
|
|
|
23
23
|
## Usage
|
|
24
24
|
|
|
25
|
-
- Work with [SQLAlchemy][] `Connection
|
|
25
|
+
- Work with [SQLAlchemy][] [`Connection`](https://docs.sqlalchemy.org/20/core/connections.html):
|
|
26
26
|
|
|
27
27
|
```python
|
|
28
28
|
from sqlalchemy import create_engine
|
|
@@ -81,7 +81,7 @@ pip install sqlalchemy-dlock
|
|
|
81
81
|
assert not lock2.locked
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
- Work with [SQLAlchemy][] `ORM` session:
|
|
84
|
+
- Work with [SQLAlchemy][] [`ORM` `Session`](https://docs.sqlalchemy.org/en/20/orm/session.html):
|
|
85
85
|
|
|
86
86
|
```python
|
|
87
87
|
from sqlalchemy import create_engine
|
|
@@ -101,10 +101,10 @@ pip install sqlalchemy-dlock
|
|
|
101
101
|
|
|
102
102
|
- Asynchronous I/O Support
|
|
103
103
|
|
|
104
|
-
> **
|
|
104
|
+
> **NOTE**
|
|
105
105
|
>
|
|
106
|
-
> - [SQLAlchemy][] `1.x
|
|
107
|
-
> - [SQLAlchemy][] `2.x
|
|
106
|
+
> - [SQLAlchemy][] `1.x`'s asynchronous I/O: <https://docs.sqlalchemy.org/14/orm/extensions/asyncio.html>
|
|
107
|
+
> - [SQLAlchemy][] `2.x`'s asynchronous I/O: <https://docs.sqlalchemy.org/20/orm/extensions/asyncio.html>
|
|
108
108
|
|
|
109
109
|
```python
|
|
110
110
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
@@ -114,7 +114,7 @@ pip install sqlalchemy-dlock
|
|
|
114
114
|
|
|
115
115
|
engine = create_async_engine('postgresql+asyncpg://scott:tiger@127.0.0.1/')
|
|
116
116
|
|
|
117
|
-
async with engine.
|
|
117
|
+
async with engine.connect() as conn:
|
|
118
118
|
async with create_async_sadlock(conn, key) as lock:
|
|
119
119
|
assert lock.locked
|
|
120
120
|
await lock.release()
|
|
@@ -123,22 +123,20 @@ pip install sqlalchemy-dlock
|
|
|
123
123
|
assert not lock.locked
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
> **
|
|
126
|
+
> **NOTE**
|
|
127
127
|
>
|
|
128
|
-
> [aiomysql][], [asyncpg][] and [psycopg][] are tested asynchronous drivers
|
|
128
|
+
> [aiomysql][], [asyncpg][] and [psycopg][] are tested asynchronous drivers.
|
|
129
129
|
>
|
|
130
|
-
> We can install asynchronous DB libraries
|
|
130
|
+
> We can install it with asynchronous DB libraries:
|
|
131
131
|
>
|
|
132
132
|
> - ```bash
|
|
133
|
-
> pip install sqlalchemy-dlock
|
|
133
|
+
> pip install SQLAlchemy[asyncio] aiomysql sqlalchemy-dlock
|
|
134
134
|
> ```
|
|
135
135
|
>
|
|
136
|
-
>
|
|
137
|
-
> pip install sqlalchemy-dlock[asyncio] aiomysql
|
|
138
|
-
> ```
|
|
136
|
+
> or
|
|
139
137
|
>
|
|
140
138
|
> - ```bash
|
|
141
|
-
> pip install
|
|
139
|
+
> pip install SQLAlchemy[asyncio] asyncpg sqlalchemy-dlock
|
|
142
140
|
> ```
|
|
143
141
|
|
|
144
142
|
## Test
|
|
@@ -150,7 +148,7 @@ Following drivers are tested:
|
|
|
150
148
|
- [pymysql][] (synchronous)
|
|
151
149
|
- [aiomysql][] (asynchronous)
|
|
152
150
|
- Postgres:
|
|
153
|
-
- [psycopg2][] (
|
|
151
|
+
- [psycopg2][] (synchronous)
|
|
154
152
|
- [asyncpg][] (asynchronous)
|
|
155
153
|
- [psycopg][] (synchronous and asynchronous)
|
|
156
154
|
|
|
@@ -158,15 +156,15 @@ You can run unit-tests
|
|
|
158
156
|
|
|
159
157
|
- on local environment:
|
|
160
158
|
|
|
161
|
-
1. Install the project
|
|
159
|
+
1. Install the project in editable mode with `asyncio` optional dependencies, and libraries/drivers needed in test. A virtual environment ([venv][]) is strongly advised:
|
|
162
160
|
|
|
163
161
|
```bash
|
|
164
|
-
pip install -e .[
|
|
162
|
+
pip install -e .[asyncio] -r tests/requirements.txt
|
|
165
163
|
```
|
|
166
164
|
|
|
167
|
-
1. start up mysql and postgresql
|
|
165
|
+
1. start up mysql and postgresql service
|
|
168
166
|
|
|
169
|
-
There is a docker [compose][] file `db.docker-compose.yml` in
|
|
167
|
+
There is a docker [compose][] file `db.docker-compose.yml` in project's top directory,
|
|
170
168
|
which can be used to run mysql and postgresql develop environment conveniently:
|
|
171
169
|
|
|
172
170
|
```bash
|
|
@@ -175,48 +173,43 @@ You can run unit-tests
|
|
|
175
173
|
|
|
176
174
|
1. set environment variables `TEST_URLS` and `TEST_ASYNC_URLS` for sync and async database connection url.
|
|
177
175
|
Multiple connections separated by space.
|
|
178
|
-
The test cases load environment variables in `tests/.env`.
|
|
179
176
|
|
|
180
|
-
eg (
|
|
177
|
+
eg: (following values are also the defaults, and can be omitted)
|
|
181
178
|
|
|
182
179
|
```ini
|
|
183
180
|
TEST_URLS=mysql://test:test@127.0.0.1/test postgresql://postgres:test@127.0.0.1/
|
|
184
181
|
TEST_ASYNC_URLS=mysql+aiomysql://test:test@127.0.0.1/test postgresql+asyncpg://postgres:test@127.0.0.1/
|
|
185
182
|
```
|
|
186
183
|
|
|
184
|
+
> **NOTE**
|
|
185
|
+
>
|
|
186
|
+
> The test cases would load environment variables from dot-env file `tests/.env`.
|
|
187
|
+
|
|
187
188
|
1. run unit-test
|
|
188
189
|
|
|
189
190
|
```bash
|
|
190
191
|
python -m unittest
|
|
191
192
|
```
|
|
192
193
|
|
|
193
|
-
- or on docker
|
|
194
|
-
|
|
195
|
-
1. build the project
|
|
194
|
+
- or on docker [compose][]:
|
|
196
195
|
|
|
197
|
-
|
|
198
|
-
python -m pip install build && python -m build -w
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
1. run unit-test
|
|
196
|
+
Name of [compose][] services for Python and [SQLAlchemy][] version matrix defined in [compose][] file `docker-compose.yml` has such format:
|
|
202
197
|
|
|
203
|
-
|
|
198
|
+
python{{X.Y}}-sqlalchemy{{X}}
|
|
204
199
|
|
|
205
|
-
|
|
200
|
+
For example, if want to run tests for Python `3.8` and [SQLAlchemy][] `1.x`, we shall up to run unit-tests as below:
|
|
206
201
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
docker compose up python3.9-sqlalchemy2
|
|
212
|
-
```
|
|
202
|
+
```bash
|
|
203
|
+
cd tests
|
|
204
|
+
docker compose up python3.8-sqlalchemy1
|
|
205
|
+
```
|
|
213
206
|
|
|
214
|
-
|
|
207
|
+
For Python `3.10` and [SQLAlchemy][] `2.x`:
|
|
215
208
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
209
|
+
```bash
|
|
210
|
+
cd tests
|
|
211
|
+
docker compose up python3.10-sqlalchemy2
|
|
212
|
+
```
|
|
220
213
|
|
|
221
214
|
[SQLAlchemy]: https://www.sqlalchemy.org/ "The Python SQL Toolkit and Object Relational Mapper"
|
|
222
215
|
[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. "
|
|
@@ -23,27 +23,27 @@ dynamic = ["version"]
|
|
|
23
23
|
requires-python = ">=3.7"
|
|
24
24
|
# requires
|
|
25
25
|
dependencies = [
|
|
26
|
-
"SQLAlchemy>=1.4
|
|
26
|
+
"SQLAlchemy>=1.4,<3.0",
|
|
27
27
|
]
|
|
28
28
|
# extra requires
|
|
29
29
|
[project.optional-dependencies]
|
|
30
30
|
# SQLAlchemy with asyncio, no engines
|
|
31
|
-
asyncio = ["SQLAlchemy[asyncio]"]
|
|
31
|
+
asyncio = ["SQLAlchemy[asyncio]>=1.4,<3.0"]
|
|
32
32
|
# MySQL
|
|
33
33
|
mysqlclient = ["mysqlclient"]
|
|
34
34
|
pymysql = ["pymysql"]
|
|
35
35
|
# MySQL asyncio
|
|
36
|
-
aiomysql = ["SQLAlchemy[asyncio]", "aiomysql"]
|
|
36
|
+
aiomysql = ["SQLAlchemy[asyncio]>=1.4,<3.0", "aiomysql"]
|
|
37
37
|
# Postgres
|
|
38
38
|
# psycopg2: sync
|
|
39
39
|
psycopg2 = ["psycopg2>=2.8"] # psycopg2 compiling needed when install
|
|
40
40
|
psycopg2-binary = ["psycopg2-binary>=2.8"] # psycopg2 with pre-compiled C library
|
|
41
41
|
# psycopg3: both sync and asyncio
|
|
42
|
-
psycopg3 = ["SQLAlchemy>=2.0", "psycopg"] # psycopg3 dynamik link to libpq
|
|
43
|
-
psycopg3-binary = ["SQLAlchemy>=2.0", "psycopg[binary]"] # psycopg3 with pre-compiled C library
|
|
44
|
-
psycopg3-c = ["SQLAlchemy>=2.0", "psycopg[c]"] # psycopg3 compiling needed when install
|
|
42
|
+
psycopg3 = ["SQLAlchemy>=2.0,<3.0", "psycopg"] # psycopg3 dynamik link to libpq
|
|
43
|
+
psycopg3-binary = ["SQLAlchemy>=2.0,<3.0", "psycopg[binary]"] # psycopg3 with pre-compiled C library
|
|
44
|
+
psycopg3-c = ["SQLAlchemy>=2.0,<3.0", "psycopg[c]"] # psycopg3 compiling needed when install
|
|
45
45
|
# Postgres asyncio
|
|
46
|
-
|
|
46
|
+
asyncpg = ["SQLAlchemy[asyncio]>=1.4,<3.0", "asyncpg"]
|
|
47
47
|
|
|
48
48
|
# Project links
|
|
49
49
|
[project.urls]
|
|
@@ -57,10 +57,7 @@ where = ["src"]
|
|
|
57
57
|
[tool.setuptools_scm]
|
|
58
58
|
write_to = "src/sqlalchemy_dlock/version.py"
|
|
59
59
|
|
|
60
|
-
[options.packages.find]
|
|
61
|
-
where = "src"
|
|
62
|
-
|
|
63
60
|
[tool.black]
|
|
64
|
-
line-length =
|
|
61
|
+
line-length = 128
|
|
65
62
|
[tool.isort]
|
|
66
63
|
profile = "black"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .factory import *
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
from importlib import import_module
|
|
2
|
+
from typing import Union
|
|
2
3
|
|
|
3
4
|
from sqlalchemy.engine import Connection
|
|
4
5
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
|
5
6
|
|
|
6
|
-
from ..utils import safe_name
|
|
7
|
-
from .
|
|
7
|
+
from ..utils import pascal_case, safe_name
|
|
8
|
+
from .lock.base import BaseAsyncSadLock, TAsyncConnectionOrSession
|
|
8
9
|
|
|
9
10
|
__all__ = ["create_async_sadlock"]
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
def create_async_sadlock(
|
|
13
|
+
def create_async_sadlock(
|
|
14
|
+
connection_or_session: TAsyncConnectionOrSession, key, contextual_timeout: Union[float, int, None] = None, **kwargs
|
|
15
|
+
) -> BaseAsyncSadLock:
|
|
13
16
|
if isinstance(connection_or_session, AsyncConnection):
|
|
14
17
|
sync_engine = connection_or_session.sync_engine
|
|
15
18
|
else:
|
|
@@ -23,5 +26,5 @@ def create_async_sadlock(connection_or_session: TAsyncConnectionOrSession, key,
|
|
|
23
26
|
mod = import_module(f"..lock.{engine_name}", __name__)
|
|
24
27
|
except ImportError as exception: # pragma: no cover
|
|
25
28
|
raise NotImplementedError(f"{engine_name}: {exception}")
|
|
26
|
-
|
|
27
|
-
return
|
|
29
|
+
clz = getattr(mod, f"{pascal_case(engine_name)}AsyncSadLock")
|
|
30
|
+
return clz(connection_or_session, key, contextual_timeout=contextual_timeout, **kwargs)
|
|
@@ -6,13 +6,24 @@ TAsyncConnectionOrSession = Union[AsyncConnection, AsyncSession, async_scoped_se
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class BaseAsyncSadLock:
|
|
9
|
-
def __init__(
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
connection_or_session: TAsyncConnectionOrSession,
|
|
12
|
+
key: Any,
|
|
13
|
+
/,
|
|
14
|
+
contextual_timeout: Union[float, int, None] = None,
|
|
15
|
+
**kwargs,
|
|
16
|
+
):
|
|
10
17
|
self._acquired = False
|
|
11
18
|
self._connection_or_session = connection_or_session
|
|
12
19
|
self._key = key
|
|
20
|
+
self._contextual_timeout = contextual_timeout
|
|
13
21
|
|
|
14
22
|
async def __aenter__(self):
|
|
15
|
-
|
|
23
|
+
if self._contextual_timeout is None:
|
|
24
|
+
await self.acquire()
|
|
25
|
+
elif not await self.acquire(timeout=self._contextual_timeout): # the timeout period has elapsed and not acquired
|
|
26
|
+
raise TimeoutError()
|
|
16
27
|
return self
|
|
17
28
|
|
|
18
29
|
async def __aexit__(self, exc_type, exc_value, exc_tb):
|
|
@@ -35,10 +46,12 @@ class BaseAsyncSadLock:
|
|
|
35
46
|
def locked(self) -> bool:
|
|
36
47
|
return self._acquired
|
|
37
48
|
|
|
38
|
-
async def acquire(
|
|
49
|
+
async def acquire(
|
|
50
|
+
self, block: bool = True, timeout: Union[float, int, None] = None, *args, **kwargs
|
|
51
|
+
) -> bool: # pragma: no cover
|
|
39
52
|
raise NotImplementedError()
|
|
40
53
|
|
|
41
|
-
async def release(self, *args, **kwargs):
|
|
54
|
+
async def release(self, *args, **kwargs): # pragma: no cover
|
|
42
55
|
raise NotImplementedError()
|
|
43
56
|
|
|
44
57
|
async def close(self, *args, **kwargs):
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from typing import Any, Callable, Optional, Union
|
|
2
2
|
|
|
3
3
|
from ...exceptions import SqlAlchemyDLockDatabaseError
|
|
4
|
-
from ...statement.mysql import
|
|
5
|
-
from
|
|
4
|
+
from ...statement.mysql import LOCK, UNLOCK
|
|
5
|
+
from .base import BaseAsyncSadLock, TAsyncConnectionOrSession
|
|
6
6
|
|
|
7
7
|
MYSQL_LOCK_NAME_MAX_LENGTH = 64
|
|
8
8
|
|
|
@@ -19,12 +19,9 @@ def default_convert(key: Union[bytearray, bytes, int, float]) -> str:
|
|
|
19
19
|
return result
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
class
|
|
22
|
+
class MysqlAsyncSadLock(BaseAsyncSadLock):
|
|
23
23
|
def __init__(
|
|
24
|
-
self,
|
|
25
|
-
connection_or_session: TAsyncConnectionOrSession,
|
|
26
|
-
key,
|
|
27
|
-
convert: Optional[TConvertFunction] = None,
|
|
24
|
+
self, connection_or_session: TAsyncConnectionOrSession, key, /, convert: Optional[TConvertFunction] = None, **kwargs
|
|
28
25
|
):
|
|
29
26
|
if convert:
|
|
30
27
|
key = convert(key)
|
|
@@ -35,7 +32,7 @@ class AsyncSadLock(BaseAsyncSadLock):
|
|
|
35
32
|
if len(key) > MYSQL_LOCK_NAME_MAX_LENGTH:
|
|
36
33
|
raise ValueError(f"MySQL enforces a maximum length on lock names of {MYSQL_LOCK_NAME_MAX_LENGTH} characters.")
|
|
37
34
|
#
|
|
38
|
-
super().__init__(connection_or_session, key)
|
|
35
|
+
super().__init__(connection_or_session, key, **kwargs)
|
|
39
36
|
|
|
40
37
|
async def acquire(self, block: bool = True, timeout: Union[float, int, None] = None) -> bool:
|
|
41
38
|
if self._acquired:
|
|
@@ -49,10 +46,8 @@ class AsyncSadLock(BaseAsyncSadLock):
|
|
|
49
46
|
timeout = 0
|
|
50
47
|
else:
|
|
51
48
|
timeout = 0
|
|
52
|
-
stmt =
|
|
53
|
-
|
|
54
|
-
ret_val = (await r.one())[0]
|
|
55
|
-
|
|
49
|
+
stmt = LOCK.params(str=self._key, timeout=timeout)
|
|
50
|
+
ret_val = (await self.connection_or_session.execute(stmt)).scalar_one()
|
|
56
51
|
if ret_val == 1:
|
|
57
52
|
self._acquired = True
|
|
58
53
|
elif ret_val == 0:
|
|
@@ -66,9 +61,8 @@ class AsyncSadLock(BaseAsyncSadLock):
|
|
|
66
61
|
async def release(self):
|
|
67
62
|
if not self._acquired:
|
|
68
63
|
raise ValueError("invoked on an unlocked lock")
|
|
69
|
-
stmt =
|
|
70
|
-
|
|
71
|
-
ret_val = (await r.one())[0]
|
|
64
|
+
stmt = UNLOCK.params(str=self._key)
|
|
65
|
+
ret_val = (await self.connection_or_session.execute(stmt)).scalar_one()
|
|
72
66
|
if ret_val == 1:
|
|
73
67
|
self._acquired = False
|
|
74
68
|
elif ret_val == 0: # pragma: no cover
|
|
@@ -83,5 +77,5 @@ class AsyncSadLock(BaseAsyncSadLock):
|
|
|
83
77
|
"was never obtained by a call to GET_LOCK(), "
|
|
84
78
|
"or has previously been released."
|
|
85
79
|
)
|
|
86
|
-
else:
|
|
80
|
+
else: # pragma: no cover
|
|
87
81
|
raise SqlAlchemyDLockDatabaseError(f"RELEASE_LOCK({self._key!r}) returns {ret_val}")
|