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.
Files changed (38) hide show
  1. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/CHANGELOG.md +9 -1
  2. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/MANIFEST.in +4 -1
  3. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/PKG-INFO +60 -51
  4. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/README.md +40 -47
  5. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/pyproject.toml +8 -11
  6. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/__init__.py +1 -1
  7. sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/asyncio/__init__.py +1 -0
  8. sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/asyncio/functions.py → sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/asyncio/factory.py +8 -5
  9. sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/asyncio/baselock.py → sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/asyncio/lock/base.py +17 -4
  10. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/asyncio/lock/mysql.py +10 -16
  11. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/asyncio/lock/postgresql.py +25 -31
  12. sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/factory.py +55 -0
  13. sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/lock/base.py +185 -0
  14. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/lock/mysql.py +26 -11
  15. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/lock/postgresql.py +58 -45
  16. sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/statement/mysql.py +4 -0
  17. sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/statement/postgresql.py +38 -0
  18. sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/utils.py +101 -0
  19. sqlalchemy-dlock-0.5/src/sqlalchemy_dlock/version.py +16 -0
  20. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock.egg-info/PKG-INFO +60 -51
  21. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock.egg-info/SOURCES.txt +4 -4
  22. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock.egg-info/requires.txt +8 -8
  23. sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/asyncio/__init__.py +0 -1
  24. sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/baselock.py +0 -153
  25. sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/functions.py +0 -46
  26. sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/statement/mysql.py +0 -20
  27. sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/statement/postgresql.py +0 -75
  28. sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/utils.py +0 -32
  29. sqlalchemy-dlock-0.4/src/sqlalchemy_dlock/version.py +0 -4
  30. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/AUTHORS.md +0 -0
  31. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/LICENSE +0 -0
  32. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/setup.cfg +0 -0
  33. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/asyncio/lock/__init__.py +0 -0
  34. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/exceptions.py +0 -0
  35. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/lock/__init__.py +0 -0
  36. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock/statement/__init__.py +0 -0
  37. {sqlalchemy-dlock-0.4 → sqlalchemy-dlock-0.5}/src/sqlalchemy_dlock.egg-info/dependency_links.txt +0 -0
  38. {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,8 +1,11 @@
1
1
  exclude .*
2
2
  exclude *.docker-compose.yml
3
+ exclude *.requirements.txt
3
4
  exclude codecov.yml
5
+ exclude coverage.*
4
6
 
5
7
  prune .*
6
8
  prune docs
7
- prune tests
8
9
  prune scripts
10
+ prune tests
11
+ prune htmlcov
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sqlalchemy-dlock
3
- Version: 0.4
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
- Provides-Extra: asyncpg3
27
- License-File: LICENSE
28
- License-File: AUTHORS.md
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
- # SQLAlchemy-DLock
46
+ # sqlalchemy-dlock
31
47
 
32
48
  [![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)
33
49
  [![PyPI](https://img.shields.io/pypi/v/sqlalchemy-dlock)](https://pypi.org/project/sqlalchemy-dlock/)
34
- [![Documentation Status](https://readthedocs.org/projects/sqlalchemy-dlock/badge/?version=latest)](https://sqlalchemy-dlock.readthedocs.io/en/latest/?badge=latest)
35
- [![codecov](https://codecov.io/gh/tanbro/sqlalchemy-dlock/branch/main/graph/badge.svg?token=GfcDT1ckFX)](https://codecov.io/gh/tanbro/sqlalchemy-dlock)
50
+ [![Documentation Status](https://readthedocs.org/projects/sqlalchemy-dlock/badge/?version=latest)](https://sqlalchemy-dlock.readthedocs.io/en/latest/)
51
+ [![codecov](https://codecov.io/gh/tanbro/sqlalchemy-dlock/branch/main/graph/badge.svg)](https://codecov.io/gh/tanbro/sqlalchemy-dlock)
36
52
 
37
- Distributed lock based on Database and [SQLAlchemy][].
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/8.0/en/locking-functions.html)
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
- > **Note**:
149
+ > **NOTE**
134
150
  >
135
- > - [SQLAlchemy][] `1.x`: <https://docs.sqlalchemy.org/14/orm/extensions/asyncio.html>
136
- > - [SQLAlchemy][] `2.x`: <https://docs.sqlalchemy.org/20/orm/extensions/asyncio.html>
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.begin() as conn:
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
- > **Note**:
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 like one of blows:
175
+ > We can install it with asynchronous DB libraries:
160
176
  >
161
177
  > - ```bash
162
- > pip install sqlalchemy-dlock[asyncpg]
178
+ > pip install SQLAlchemy[asyncio] aiomysql sqlalchemy-dlock
163
179
  > ```
164
180
  >
165
- > - ```bash
166
- > pip install sqlalchemy-dlock[asyncio] aiomysql
167
- > ```
181
+ > or
168
182
  >
169
183
  > - ```bash
170
- > pip install SQLALchemy[asyncio] sqlalchemy-dlock psycopg
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][] (asynchronous)
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 with drivers and `asyncio` extra requires, a virtual environment ([venv][]) is strongly advised:
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 .[mysqlclient psycopg2-binary aiomysql asyncpg]
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 the project dir,
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 (and also the defaults):
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-compose:
239
+ - or on docker [compose][]:
223
240
 
224
- 1. build the project
241
+ Name of [compose][] services for Python and [SQLAlchemy][] version matrix defined in [compose][] file `docker-compose.yml` has such format:
225
242
 
226
- ```bash
227
- python -m pip install build && python -m build -w
228
- ```
243
+ python{{X.Y}}-sqlalchemy{{X}}
229
244
 
230
- 1. run unit-test
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
- Name of services for Python and [SQLAlchemy][] version matrix in the [compose][] file has such format:
233
-
234
- python{{X.Y}}-sqlalchemy{{X}}
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
- For Python `3.8` and [SQLAlchemy][] `1.x`:
252
+ For Python `3.10` and [SQLAlchemy][] `2.x`:
244
253
 
245
- ```bash
246
- cd tests
247
- docker compose up python3.8-sqlalchemy1
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
- # SQLAlchemy-DLock
1
+ # sqlalchemy-dlock
2
2
 
3
3
  [![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)
4
4
  [![PyPI](https://img.shields.io/pypi/v/sqlalchemy-dlock)](https://pypi.org/project/sqlalchemy-dlock/)
5
- [![Documentation Status](https://readthedocs.org/projects/sqlalchemy-dlock/badge/?version=latest)](https://sqlalchemy-dlock.readthedocs.io/en/latest/?badge=latest)
6
- [![codecov](https://codecov.io/gh/tanbro/sqlalchemy-dlock/branch/main/graph/badge.svg?token=GfcDT1ckFX)](https://codecov.io/gh/tanbro/sqlalchemy-dlock)
5
+ [![Documentation Status](https://readthedocs.org/projects/sqlalchemy-dlock/badge/?version=latest)](https://sqlalchemy-dlock.readthedocs.io/en/latest/)
6
+ [![codecov](https://codecov.io/gh/tanbro/sqlalchemy-dlock/branch/main/graph/badge.svg)](https://codecov.io/gh/tanbro/sqlalchemy-dlock)
7
7
 
8
- Distributed lock based on Database and [SQLAlchemy][].
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/8.0/en/locking-functions.html)
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
- > **Note**:
104
+ > **NOTE**
105
105
  >
106
- > - [SQLAlchemy][] `1.x`: <https://docs.sqlalchemy.org/14/orm/extensions/asyncio.html>
107
- > - [SQLAlchemy][] `2.x`: <https://docs.sqlalchemy.org/20/orm/extensions/asyncio.html>
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.begin() as conn:
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
- > **Note**:
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 like one of blows:
130
+ > We can install it with asynchronous DB libraries:
131
131
  >
132
132
  > - ```bash
133
- > pip install sqlalchemy-dlock[asyncpg]
133
+ > pip install SQLAlchemy[asyncio] aiomysql sqlalchemy-dlock
134
134
  > ```
135
135
  >
136
- > - ```bash
137
- > pip install sqlalchemy-dlock[asyncio] aiomysql
138
- > ```
136
+ > or
139
137
  >
140
138
  > - ```bash
141
- > pip install SQLALchemy[asyncio] sqlalchemy-dlock psycopg
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][] (asynchronous)
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 with drivers and `asyncio` extra requires, a virtual environment ([venv][]) is strongly advised:
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 .[mysqlclient psycopg2-binary aiomysql asyncpg]
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 the project dir,
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 (and also the defaults):
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-compose:
194
-
195
- 1. build the project
194
+ - or on docker [compose][]:
196
195
 
197
- ```bash
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
- Name of services for Python and [SQLAlchemy][] version matrix in the [compose][] file has such format:
198
+ python{{X.Y}}-sqlalchemy{{X}}
204
199
 
205
- python{{X.Y}}-sqlalchemy{{X}}
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
- 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:
208
-
209
- ```bash
210
- cd tests
211
- docker compose up python3.9-sqlalchemy2
212
- ```
202
+ ```bash
203
+ cd tests
204
+ docker compose up python3.8-sqlalchemy1
205
+ ```
213
206
 
214
- For Python `3.8` and [SQLAlchemy][] `1.x`:
207
+ For Python `3.10` and [SQLAlchemy][] `2.x`:
215
208
 
216
- ```bash
217
- cd tests
218
- docker compose up python3.8-sqlalchemy1
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.3,<3.0",
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
- asyncpg3 = ["SQLAlchemy[asyncio]", "asyncpg"]
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 = 127
61
+ line-length = 128
65
62
  [tool.isort]
66
63
  profile = "black"
@@ -3,5 +3,5 @@ Distributed lock based on Database and SQLAlchemy
3
3
  """
4
4
 
5
5
  from .exceptions import *
6
- from .functions import *
6
+ from .factory import *
7
7
  from .version import version as __version__
@@ -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 .baselock import BaseAsyncSadLock, TAsyncConnectionOrSession
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(connection_or_session: TAsyncConnectionOrSession, key, *args, **kwargs) -> BaseAsyncSadLock:
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
- lock_cls = getattr(mod, "AsyncSadLock")
27
- return lock_cls(connection_or_session, key, *args, **kwargs)
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__(self, connection_or_session: TAsyncConnectionOrSession, key: Any, *args, **kwargs):
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
- await self.acquire()
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(self, block: bool = True, timeout: Union[float, int, None] = None, *args, **kwargs) -> bool:
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 STATEMENTS
5
- from ..baselock import BaseAsyncSadLock, TAsyncConnectionOrSession
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 AsyncSadLock(BaseAsyncSadLock):
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 = STATEMENTS["lock"].params(str=self._key, timeout=timeout)
53
- r = await self.connection_or_session.stream(stmt)
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 = STATEMENTS["unlock"].params(str=self._key)
70
- r = await self.connection_or_session.stream(stmt)
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}")