asynk 0.1.0__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.
- asynk-0.1.0/LICENSE +21 -0
- asynk-0.1.0/PKG-INFO +109 -0
- asynk-0.1.0/README.md +82 -0
- asynk-0.1.0/pyproject.toml +82 -0
- asynk-0.1.0/src/asynk/__init__.py +78 -0
- asynk-0.1.0/src/asynk/_extras.py +28 -0
- asynk-0.1.0/src/asynk/_redis.py +39 -0
- asynk-0.1.0/src/asynk/app.py +297 -0
- asynk-0.1.0/src/asynk/cli.py +162 -0
- asynk-0.1.0/src/asynk/codec/__init__.py +48 -0
- asynk-0.1.0/src/asynk/codec/json.py +89 -0
- asynk-0.1.0/src/asynk/cron.py +261 -0
- asynk-0.1.0/src/asynk/envelope.py +125 -0
- asynk-0.1.0/src/asynk/errors.py +62 -0
- asynk-0.1.0/src/asynk/hooks.py +182 -0
- asynk-0.1.0/src/asynk/logging.py +25 -0
- asynk-0.1.0/src/asynk/options.py +47 -0
- asynk-0.1.0/src/asynk/probes.py +95 -0
- asynk-0.1.0/src/asynk/py.typed +0 -0
- asynk-0.1.0/src/asynk/recorder.py +103 -0
- asynk-0.1.0/src/asynk/results.py +178 -0
- asynk-0.1.0/src/asynk/retry/__init__.py +103 -0
- asynk-0.1.0/src/asynk/retry/backoff.py +82 -0
- asynk-0.1.0/src/asynk/runner.py +66 -0
- asynk-0.1.0/src/asynk/scheduled_task.py +109 -0
- asynk-0.1.0/src/asynk/scheduler.py +312 -0
- asynk-0.1.0/src/asynk/signals.py +34 -0
- asynk-0.1.0/src/asynk/store/__init__.py +68 -0
- asynk-0.1.0/src/asynk/store/memory.py +93 -0
- asynk-0.1.0/src/asynk/store/null.py +35 -0
- asynk-0.1.0/src/asynk/store/polling.py +33 -0
- asynk-0.1.0/src/asynk/store/redis/__init__.py +9 -0
- asynk-0.1.0/src/asynk/store/redis/store.py +85 -0
- asynk-0.1.0/src/asynk/task.py +131 -0
- asynk-0.1.0/src/asynk/transport/__init__.py +128 -0
- asynk-0.1.0/src/asynk/transport/memory.py +164 -0
- asynk-0.1.0/src/asynk/transport/redis/__init__.py +9 -0
- asynk-0.1.0/src/asynk/transport/redis/transport.py +353 -0
- asynk-0.1.0/src/asynk/worker.py +387 -0
asynk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Carlos Marques
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
asynk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asynk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Asyncio task framework with decoupled producers and consumers
|
|
5
|
+
Keywords: asyncio,task-queue,tasks,workers,scheduler
|
|
6
|
+
Author: Carlos Marques
|
|
7
|
+
Author-email: Carlos Marques <chmarques93@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Dist: redis>=5.0.1 ; extra == 'redis'
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Project-URL: Issues, https://github.com/marquesch/asynk/issues
|
|
24
|
+
Project-URL: Repository, https://github.com/marquesch/asynk
|
|
25
|
+
Provides-Extra: redis
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# asynk
|
|
29
|
+
|
|
30
|
+
asynk is a lightweight asyncio task framework for Python 3.11+, built on the
|
|
31
|
+
standard library only. Define tasks with a decorator, publish them with
|
|
32
|
+
`delay`, and run them in workers, behind any broker you plug in.
|
|
33
|
+
|
|
34
|
+
**Documentation:** <https://marquesch.github.io/asynk/>
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install asynk
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Example
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
"""The smallest asynk app: one task, published and run in the same process."""
|
|
46
|
+
|
|
47
|
+
import asyncio
|
|
48
|
+
|
|
49
|
+
from asynk import App, MemoryStore, Worker
|
|
50
|
+
from asynk.transport.memory import memory_transport
|
|
51
|
+
|
|
52
|
+
producer, consumer = memory_transport()
|
|
53
|
+
app = App("first-app", producer=producer, consumer=consumer, store=MemoryStore())
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@app.task()
|
|
57
|
+
async def add(a: int, b: int) -> int:
|
|
58
|
+
return a + b
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def main() -> None:
|
|
62
|
+
worker = Worker(app)
|
|
63
|
+
run = asyncio.create_task(worker.run())
|
|
64
|
+
|
|
65
|
+
handle = await add.delay(2, b=3)
|
|
66
|
+
print("2 + 3 =", await handle.result(timeout=5))
|
|
67
|
+
|
|
68
|
+
worker.request_shutdown()
|
|
69
|
+
await run
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
asyncio.run(main())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
With a broker transport, producers and workers run in separate processes; start
|
|
77
|
+
a worker with `asynk worker tasks:app`.
|
|
78
|
+
|
|
79
|
+
## Why asynk
|
|
80
|
+
|
|
81
|
+
- **Decoupled producers and consumers**: the core only knows an envelope, a
|
|
82
|
+
codec and two small protocols, so any broker can sit behind them.
|
|
83
|
+
- **Pluggable transport and store**: bring your own broker and result store; an
|
|
84
|
+
in-memory transport and store ship for development and tests, and a
|
|
85
|
+
[Redis store](https://marquesch.github.io/asynk/guides/redis/) and a
|
|
86
|
+
[Redis transport](https://marquesch.github.io/asynk/guides/redis-transport/)
|
|
87
|
+
come with `asynk[redis]`.
|
|
88
|
+
- **Type-checked `delay`**: calls are checked against the task's signature.
|
|
89
|
+
- **Cron scheduled tasks** with per-slot dedupe when schedulers share a store.
|
|
90
|
+
- **Task results** through a store, with states and awaitable handles.
|
|
91
|
+
- **Retries with backoff**: a retry policy per app or per task, with exponential
|
|
92
|
+
backoff and an exception filter.
|
|
93
|
+
- **Lifecycle hooks** shared by workers and the scheduler.
|
|
94
|
+
- **Middleware** around every task: timing, metrics, vetoes, cleanup.
|
|
95
|
+
- **Health probes and graceful shutdown**, ready for Kubernetes.
|
|
96
|
+
|
|
97
|
+
## Learn more
|
|
98
|
+
|
|
99
|
+
- [Getting started](https://marquesch.github.io/asynk/getting-started/)
|
|
100
|
+
- [Concepts](https://marquesch.github.io/asynk/concepts/)
|
|
101
|
+
- [Guides](https://marquesch.github.io/asynk/guides/scheduled-tasks/) and
|
|
102
|
+
[advanced topics](https://marquesch.github.io/asynk/advanced/lifecycle-hooks/)
|
|
103
|
+
- [API reference](https://marquesch.github.io/asynk/reference/)
|
|
104
|
+
- [Release notes](https://marquesch.github.io/asynk/release-notes/) and the
|
|
105
|
+
[changelog](CHANGELOG.md)
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT, see [LICENSE](LICENSE).
|
asynk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# asynk
|
|
2
|
+
|
|
3
|
+
asynk is a lightweight asyncio task framework for Python 3.11+, built on the
|
|
4
|
+
standard library only. Define tasks with a decorator, publish them with
|
|
5
|
+
`delay`, and run them in workers, behind any broker you plug in.
|
|
6
|
+
|
|
7
|
+
**Documentation:** <https://marquesch.github.io/asynk/>
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install asynk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Example
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
"""The smallest asynk app: one task, published and run in the same process."""
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
|
|
22
|
+
from asynk import App, MemoryStore, Worker
|
|
23
|
+
from asynk.transport.memory import memory_transport
|
|
24
|
+
|
|
25
|
+
producer, consumer = memory_transport()
|
|
26
|
+
app = App("first-app", producer=producer, consumer=consumer, store=MemoryStore())
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.task()
|
|
30
|
+
async def add(a: int, b: int) -> int:
|
|
31
|
+
return a + b
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def main() -> None:
|
|
35
|
+
worker = Worker(app)
|
|
36
|
+
run = asyncio.create_task(worker.run())
|
|
37
|
+
|
|
38
|
+
handle = await add.delay(2, b=3)
|
|
39
|
+
print("2 + 3 =", await handle.result(timeout=5))
|
|
40
|
+
|
|
41
|
+
worker.request_shutdown()
|
|
42
|
+
await run
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
asyncio.run(main())
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
With a broker transport, producers and workers run in separate processes; start
|
|
50
|
+
a worker with `asynk worker tasks:app`.
|
|
51
|
+
|
|
52
|
+
## Why asynk
|
|
53
|
+
|
|
54
|
+
- **Decoupled producers and consumers**: the core only knows an envelope, a
|
|
55
|
+
codec and two small protocols, so any broker can sit behind them.
|
|
56
|
+
- **Pluggable transport and store**: bring your own broker and result store; an
|
|
57
|
+
in-memory transport and store ship for development and tests, and a
|
|
58
|
+
[Redis store](https://marquesch.github.io/asynk/guides/redis/) and a
|
|
59
|
+
[Redis transport](https://marquesch.github.io/asynk/guides/redis-transport/)
|
|
60
|
+
come with `asynk[redis]`.
|
|
61
|
+
- **Type-checked `delay`**: calls are checked against the task's signature.
|
|
62
|
+
- **Cron scheduled tasks** with per-slot dedupe when schedulers share a store.
|
|
63
|
+
- **Task results** through a store, with states and awaitable handles.
|
|
64
|
+
- **Retries with backoff**: a retry policy per app or per task, with exponential
|
|
65
|
+
backoff and an exception filter.
|
|
66
|
+
- **Lifecycle hooks** shared by workers and the scheduler.
|
|
67
|
+
- **Middleware** around every task: timing, metrics, vetoes, cleanup.
|
|
68
|
+
- **Health probes and graceful shutdown**, ready for Kubernetes.
|
|
69
|
+
|
|
70
|
+
## Learn more
|
|
71
|
+
|
|
72
|
+
- [Getting started](https://marquesch.github.io/asynk/getting-started/)
|
|
73
|
+
- [Concepts](https://marquesch.github.io/asynk/concepts/)
|
|
74
|
+
- [Guides](https://marquesch.github.io/asynk/guides/scheduled-tasks/) and
|
|
75
|
+
[advanced topics](https://marquesch.github.io/asynk/advanced/lifecycle-hooks/)
|
|
76
|
+
- [API reference](https://marquesch.github.io/asynk/reference/)
|
|
77
|
+
- [Release notes](https://marquesch.github.io/asynk/release-notes/) and the
|
|
78
|
+
[changelog](CHANGELOG.md)
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "asynk"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Asyncio task framework with decoupled producers and consumers"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Carlos Marques", email = "chmarques93@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
keywords = ["asyncio", "task-queue", "tasks", "workers", "scheduler"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Framework :: AsyncIO",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
dependencies = []
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
redis = ["redis>=5.0.1"]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Repository = "https://github.com/marquesch/asynk"
|
|
33
|
+
Issues = "https://github.com/marquesch/asynk/issues"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
asynk = "asynk.cli:main"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["uv_build==0.9.9"]
|
|
40
|
+
build-backend = "uv_build"
|
|
41
|
+
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
dev = [
|
|
44
|
+
"asynk[redis]",
|
|
45
|
+
"fakeredis>=2.40.0",
|
|
46
|
+
"mypy>=2.4.1",
|
|
47
|
+
"pytest>=9.1.1",
|
|
48
|
+
"pytest-asyncio>=1.4.0",
|
|
49
|
+
"pytest-cov>=7.1.0",
|
|
50
|
+
"ruff>=0.17.3",
|
|
51
|
+
]
|
|
52
|
+
docs = [
|
|
53
|
+
"mkdocstrings-python>=2.0.10",
|
|
54
|
+
"zensical==0.1.0",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
asyncio_mode = "auto"
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
addopts = "--cov"
|
|
61
|
+
pythonpath = ["."]
|
|
62
|
+
|
|
63
|
+
[tool.coverage.run]
|
|
64
|
+
source = ["src/asynk"]
|
|
65
|
+
branch = true
|
|
66
|
+
|
|
67
|
+
[tool.coverage.report]
|
|
68
|
+
show_missing = true
|
|
69
|
+
skip_covered = true
|
|
70
|
+
|
|
71
|
+
[tool.mypy]
|
|
72
|
+
strict = true
|
|
73
|
+
python_version = "3.11"
|
|
74
|
+
files = ["src", "tests", "examples"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff]
|
|
77
|
+
line-length = 100
|
|
78
|
+
|
|
79
|
+
[tool.ruff.lint]
|
|
80
|
+
select = ["E", "F", "I", "UP", "B", "ASYNC", "SIM"]
|
|
81
|
+
# Public APIs take a plain `timeout` argument by design.
|
|
82
|
+
ignore = ["ASYNC109"]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""asynk: an asyncio task framework."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
from asynk.app import App
|
|
6
|
+
from asynk.codec import Codec
|
|
7
|
+
from asynk.codec.json import JsonCodec
|
|
8
|
+
from asynk.envelope import Envelope
|
|
9
|
+
from asynk.errors import (
|
|
10
|
+
AsynkError,
|
|
11
|
+
CronError,
|
|
12
|
+
DecodeError,
|
|
13
|
+
DeliveryFailedError,
|
|
14
|
+
EncodeError,
|
|
15
|
+
InvalidEnvelopeError,
|
|
16
|
+
NotConfiguredError,
|
|
17
|
+
TaskError,
|
|
18
|
+
TaskNotFoundError,
|
|
19
|
+
)
|
|
20
|
+
from asynk.hooks import Next, TaskCall, TaskResult, TaskStatus
|
|
21
|
+
from asynk.options import TaskOptions
|
|
22
|
+
from asynk.results import TaskHandle, TaskState
|
|
23
|
+
from asynk.retry import Backoff, Exponential, RetryPolicy
|
|
24
|
+
from asynk.runner import Runner
|
|
25
|
+
from asynk.scheduled_task import ScheduledTask
|
|
26
|
+
from asynk.scheduler import Scheduler
|
|
27
|
+
from asynk.store import Store
|
|
28
|
+
from asynk.store.memory import MemoryStore
|
|
29
|
+
from asynk.store.null import NullStore
|
|
30
|
+
from asynk.task import Task
|
|
31
|
+
from asynk.transport import Consumer, Delivery, Producer, PublishOptions
|
|
32
|
+
from asynk.worker import Worker
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
__version__ = version("asynk")
|
|
36
|
+
except PackageNotFoundError:
|
|
37
|
+
# Imported from a source tree without an installed distribution.
|
|
38
|
+
__version__ = "0+unknown"
|
|
39
|
+
"""The installed asynk version."""
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"__version__",
|
|
43
|
+
"App",
|
|
44
|
+
"AsynkError",
|
|
45
|
+
"Backoff",
|
|
46
|
+
"Codec",
|
|
47
|
+
"Consumer",
|
|
48
|
+
"CronError",
|
|
49
|
+
"DecodeError",
|
|
50
|
+
"Delivery",
|
|
51
|
+
"DeliveryFailedError",
|
|
52
|
+
"Envelope",
|
|
53
|
+
"InvalidEnvelopeError",
|
|
54
|
+
"JsonCodec",
|
|
55
|
+
"EncodeError",
|
|
56
|
+
"Exponential",
|
|
57
|
+
"NotConfiguredError",
|
|
58
|
+
"Next",
|
|
59
|
+
"Producer",
|
|
60
|
+
"PublishOptions",
|
|
61
|
+
"RetryPolicy",
|
|
62
|
+
"Runner",
|
|
63
|
+
"Task",
|
|
64
|
+
"TaskCall",
|
|
65
|
+
"TaskResult",
|
|
66
|
+
"TaskStatus",
|
|
67
|
+
"ScheduledTask",
|
|
68
|
+
"Scheduler",
|
|
69
|
+
"Worker",
|
|
70
|
+
"Store",
|
|
71
|
+
"MemoryStore",
|
|
72
|
+
"NullStore",
|
|
73
|
+
"TaskHandle",
|
|
74
|
+
"TaskOptions",
|
|
75
|
+
"TaskState",
|
|
76
|
+
"TaskError",
|
|
77
|
+
"TaskNotFoundError",
|
|
78
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Import-time guard shared by the packages that need an optional extra."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def require_extra(package: str, extra: str) -> None:
|
|
9
|
+
"""Imports `package`, or raises an ImportError that names the extra to install.
|
|
10
|
+
|
|
11
|
+
Call it at the top of the package that needs the extra.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
package: Top-level name of the third-party package the extra installs.
|
|
15
|
+
extra: Name of the extra, as in `pip install 'asynk[<extra>]'`.
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
ImportError: If `package` is not installed. An error raised by one of
|
|
19
|
+
its own dependencies is passed on unchanged.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
importlib.import_module(package)
|
|
23
|
+
except ImportError as exc:
|
|
24
|
+
if exc.name != package:
|
|
25
|
+
raise
|
|
26
|
+
raise ImportError(
|
|
27
|
+
f"asynk[{extra}] needs the {package} package: pip install 'asynk[{extra}]'"
|
|
28
|
+
) from exc
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""The Redis client a Redis store or transport uses, and who closes it."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from asynk._extras import require_extra
|
|
6
|
+
|
|
7
|
+
require_extra("redis", "redis")
|
|
8
|
+
|
|
9
|
+
from redis.asyncio import Redis # noqa: E402
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RedisConnection:
|
|
13
|
+
"""A Redis client, either given by the caller or created from a URL."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, url: str | None, client: Redis | None, *, owner: str) -> None:
|
|
16
|
+
"""Initializes the connection.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
url: Redis URL; the connection creates its own client and closes it.
|
|
20
|
+
client: Redis client the caller owns and closes.
|
|
21
|
+
owner: Name of the class using the connection, for error messages.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ValueError: If not exactly one of `url` and `client` is given.
|
|
25
|
+
"""
|
|
26
|
+
if (url is None) == (client is None):
|
|
27
|
+
raise ValueError(f"{owner} needs exactly one of url and client")
|
|
28
|
+
if client is None:
|
|
29
|
+
assert url is not None
|
|
30
|
+
client = Redis.from_url(url, socket_timeout=5.0, socket_connect_timeout=5.0)
|
|
31
|
+
self._owns_client = True
|
|
32
|
+
else:
|
|
33
|
+
self._owns_client = False
|
|
34
|
+
self.client = client
|
|
35
|
+
|
|
36
|
+
async def close(self) -> None:
|
|
37
|
+
"""Closes the client if the connection created it."""
|
|
38
|
+
if self._owns_client:
|
|
39
|
+
await self.client.aclose()
|