redqueue 0.11.1__tar.gz → 0.11.2__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.
- {redqueue-0.11.1 → redqueue-0.11.2}/CHANGELOG.md +28 -11
- {redqueue-0.11.1 → redqueue-0.11.2}/PKG-INFO +1 -1
- {redqueue-0.11.1 → redqueue-0.11.2}/docs/API.md +6 -2
- {redqueue-0.11.1 → redqueue-0.11.2}/pyproject.toml +1 -1
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/_version.py +1 -1
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/async_client.py +107 -84
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/client.py +60 -47
- {redqueue-0.11.1 → redqueue-0.11.2}/tests/test_project_skeleton.py +187 -122
- {redqueue-0.11.1 → redqueue-0.11.2}/.github/workflows/ci.yml +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/.gitignore +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/CODE_OF_CONDUCT.md +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/CONTRIBUTING.md +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/LICENSE +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/NOTICE +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/README-zh-CN.md +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/README.md +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/docs/RELEASE.md +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/examples/README.md +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/examples/__init__.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/examples/async_list_queue.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/examples/common.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/examples/compatibility_check.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/examples/custom_serializer.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/examples/delayed_tasks.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/examples/monitoring_hooks.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/examples/stream_queue.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/examples/sync_list_queue.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/requirements.txt +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/scripts/check.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/__init__.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/backends/__init__.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/backends/async_delay.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/backends/async_list.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/backends/async_stream.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/backends/base.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/backends/delay.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/backends/list.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/backends/stream.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/compat.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/config.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/connection.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/exceptions.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/message.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/monitoring.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/src/redqueue/serialization.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/tests/README.md +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/tests/__init__.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/tests/fakes.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/tests/test_availability.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/tests/test_backend_contracts.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/tests/test_integration_redis.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/tests/test_performance.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/tests/test_real_redis_availability.py +0 -0
- {redqueue-0.11.1 → redqueue-0.11.2}/tests/test_real_redis_performance.py +0 -0
|
@@ -7,24 +7,41 @@ All notable public release changes are documented here.
|
|
|
7
7
|
Development versions are tracked separately from formal release versions.
|
|
8
8
|
开发版本与正式版本分开管理。
|
|
9
9
|
|
|
10
|
-
## [0.11.
|
|
10
|
+
## [0.11.2] - 2026-06-21
|
|
11
11
|
|
|
12
12
|
### Fixed
|
|
13
13
|
|
|
14
|
-
- Fixed
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
- Fixed cleanup for directly constructed sync clients when owned Redis backend
|
|
15
|
+
initialization fails.
|
|
16
|
+
- Fixed cleanup for directly constructed async clients when lazy backend
|
|
17
|
+
initialization fails.
|
|
18
|
+
- Made sync and async client `close()` idempotent for owned Redis clients.
|
|
19
19
|
|
|
20
20
|
### 修复
|
|
21
21
|
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
22
|
+
- 修复直接构造同步客户端时,如果 owned Redis 的后端初始化失败,Redis client
|
|
23
|
+
未释放的问题。
|
|
24
|
+
- 修复直接构造异步客户端时,如果懒加载后端初始化失败,Redis client 未释放的问题。
|
|
25
|
+
- 同步和异步客户端的 `close()` 对 owned Redis client 变为幂等。
|
|
26
26
|
|
|
27
|
-
## [0.11.
|
|
27
|
+
## [0.11.1] - 2026-06-21
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Fixed resource cleanup in sync and async `from_url()` when Redis capability
|
|
32
|
+
detection, configuration validation, or backend initialization fails after the
|
|
33
|
+
client created an owned Redis connection.
|
|
34
|
+
- Added explicit `owns_redis` override support to sync and async `from_url()`
|
|
35
|
+
for advanced ownership control.
|
|
36
|
+
|
|
37
|
+
### 修复
|
|
38
|
+
|
|
39
|
+
- 修复同步和异步 `from_url()` 在自动创建 Redis 连接后,如果 Redis 能力探测、
|
|
40
|
+
配置校验或后端初始化失败,已创建连接未释放的问题。
|
|
41
|
+
- 同步和异步 `from_url()` 新增显式 `owns_redis` 覆盖支持,用于高级资源所有权
|
|
42
|
+
控制。
|
|
43
|
+
|
|
44
|
+
## [0.11.0] - 2026-06-21
|
|
28
45
|
|
|
29
46
|
### Added
|
|
30
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redqueue
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.2
|
|
4
4
|
Summary: Redis-backed Python message queue library with List, Streams, delayed tasks, and monitoring.
|
|
5
5
|
Project-URL: Homepage, https://github.com/SpringMirror-pear/redqueue
|
|
6
6
|
Project-URL: Repository, https://github.com/SpringMirror-pear/redqueue.git
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# RedQueue API / RedQueue API 文档
|
|
2
2
|
|
|
3
|
-
This document describes the public API available in RedQueue `0.11.
|
|
3
|
+
This document describes the public API available in RedQueue `0.11.2`.
|
|
4
4
|
|
|
5
|
-
本文档描述 RedQueue `0.11.
|
|
5
|
+
本文档描述 RedQueue `0.11.2` 的公开 API。
|
|
6
6
|
|
|
7
7
|
## Clients / 客户端
|
|
8
8
|
|
|
@@ -25,6 +25,8 @@ client = QueueClient.from_url(
|
|
|
25
25
|
Methods / 方法:
|
|
26
26
|
|
|
27
27
|
- `from_url(url, *, queue, backend="list", connection_manager=None, **options) -> QueueClient`
|
|
28
|
+
- Advanced options include `pool_options`, injected `redis`, injected
|
|
29
|
+
`capabilities`, and `owns_redis`.
|
|
28
30
|
- `publish(payload, *, delay=None, headers=None, message_id=None) -> str`
|
|
29
31
|
- `consume(*, timeout=None, batch_size=1) -> Message | list[Message] | None`
|
|
30
32
|
- `ack(message) -> None`
|
|
@@ -57,6 +59,8 @@ client = await AsyncQueueClient.from_url(
|
|
|
57
59
|
Methods / 方法:
|
|
58
60
|
|
|
59
61
|
- `await from_url(url, *, queue, backend="list", connection_manager=None, **options) -> AsyncQueueClient`
|
|
62
|
+
- Advanced options include `pool_options`, injected `redis`, injected
|
|
63
|
+
`capabilities`, and `owns_redis`.
|
|
60
64
|
- `await publish(payload, *, delay=None, headers=None, message_id=None) -> str`
|
|
61
65
|
- `await consume(*, timeout=None, batch_size=1) -> Message | list[Message] | None`
|
|
62
66
|
- `await ack(message) -> None`
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "redqueue"
|
|
7
|
-
version = "0.11.
|
|
7
|
+
version = "0.11.2"
|
|
8
8
|
description = "Redis-backed Python message queue library with List, Streams, delayed tasks, and monitoring."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -56,11 +56,12 @@ class AsyncQueueClient:
|
|
|
56
56
|
"""
|
|
57
57
|
|
|
58
58
|
self.config = config
|
|
59
|
-
self.redis = redis
|
|
60
|
-
self.capabilities = capabilities
|
|
61
|
-
self._owns_redis = owns_redis
|
|
62
|
-
self.
|
|
63
|
-
self.
|
|
59
|
+
self.redis = redis
|
|
60
|
+
self.capabilities = capabilities
|
|
61
|
+
self._owns_redis = owns_redis
|
|
62
|
+
self._closed = False
|
|
63
|
+
self.backend: AsyncListBackend | AsyncStreamBackend | None = None
|
|
64
|
+
self.delay_backend: AsyncDelayBackend | None = None
|
|
64
65
|
self.config.monitoring.emit(
|
|
65
66
|
MonitoringEvent(
|
|
66
67
|
type=MonitoringEventType.CLIENT_CREATED,
|
|
@@ -88,7 +89,8 @@ class AsyncQueueClient:
|
|
|
88
89
|
connection_manager: Optional async connection manager used to create
|
|
89
90
|
a client from a shared pool.
|
|
90
91
|
**options: Additional ``QueueConfig`` options. Tests may also pass
|
|
91
|
-
``redis``, ``capabilities``,
|
|
92
|
+
``redis``, ``capabilities``, ``pool_options``, or
|
|
93
|
+
``owns_redis``.
|
|
92
94
|
|
|
93
95
|
Returns:
|
|
94
96
|
An initialized ``AsyncQueueClient`` with its primary backend ready.
|
|
@@ -99,50 +101,48 @@ class AsyncQueueClient:
|
|
|
99
101
|
QueueConfigError: If configuration values are invalid.
|
|
100
102
|
"""
|
|
101
103
|
|
|
102
|
-
redis = options.pop("redis", None)
|
|
103
|
-
pool_options = options.pop("pool_options", None) or {}
|
|
104
|
-
explicit_owns_redis = options.pop("owns_redis", None)
|
|
105
|
-
owns_redis = (
|
|
106
|
-
bool(explicit_owns_redis)
|
|
107
|
-
if explicit_owns_redis is not None
|
|
108
|
-
else False
|
|
109
|
-
)
|
|
110
|
-
if redis is None:
|
|
111
|
-
if connection_manager is not None:
|
|
112
|
-
redis = connection_manager.redis()
|
|
113
|
-
else:
|
|
114
|
-
redis = Redis.from_url(url, **pool_options)
|
|
115
|
-
owns_redis = (
|
|
116
|
-
True
|
|
117
|
-
if explicit_owns_redis is None
|
|
118
|
-
else bool(explicit_owns_redis)
|
|
104
|
+
redis = options.pop("redis", None)
|
|
105
|
+
pool_options = options.pop("pool_options", None) or {}
|
|
106
|
+
explicit_owns_redis = options.pop("owns_redis", None)
|
|
107
|
+
owns_redis = (
|
|
108
|
+
bool(explicit_owns_redis)
|
|
109
|
+
if explicit_owns_redis is not None
|
|
110
|
+
else False
|
|
111
|
+
)
|
|
112
|
+
if redis is None:
|
|
113
|
+
if connection_manager is not None:
|
|
114
|
+
redis = connection_manager.redis()
|
|
115
|
+
else:
|
|
116
|
+
redis = Redis.from_url(url, **pool_options)
|
|
117
|
+
owns_redis = (
|
|
118
|
+
True
|
|
119
|
+
if explicit_owns_redis is None
|
|
120
|
+
else bool(explicit_owns_redis)
|
|
121
|
+
)
|
|
122
|
+
capabilities = options.pop("capabilities", None)
|
|
123
|
+
if capabilities is None:
|
|
124
|
+
try:
|
|
125
|
+
capabilities = await detect_capabilities_async(
|
|
126
|
+
cast(AsyncRedisInfoClient, redis)
|
|
119
127
|
)
|
|
128
|
+
except Exception:
|
|
129
|
+
if owns_redis:
|
|
130
|
+
await cls._close_redis(redis)
|
|
131
|
+
raise
|
|
120
132
|
try:
|
|
121
|
-
capabilities = options.pop(
|
|
122
|
-
"capabilities",
|
|
123
|
-
None,
|
|
124
|
-
) or await detect_capabilities_async(cast(AsyncRedisInfoClient, redis))
|
|
125
133
|
config = QueueConfig(queue=queue, backend=backend, **options)
|
|
126
|
-
client = cls(
|
|
127
|
-
config=config,
|
|
128
|
-
redis=redis,
|
|
129
|
-
capabilities=capabilities,
|
|
130
|
-
owns_redis=owns_redis,
|
|
131
|
-
)
|
|
132
|
-
await client._ensure_backend()
|
|
133
|
-
return client
|
|
134
134
|
except Exception:
|
|
135
135
|
if owns_redis:
|
|
136
|
-
|
|
137
|
-
redis,
|
|
138
|
-
"close",
|
|
139
|
-
None,
|
|
140
|
-
)
|
|
141
|
-
if close is not None:
|
|
142
|
-
result = close()
|
|
143
|
-
if hasattr(result, "__await__"):
|
|
144
|
-
await result
|
|
136
|
+
await cls._close_redis(redis)
|
|
145
137
|
raise
|
|
138
|
+
client = cls(
|
|
139
|
+
config=config,
|
|
140
|
+
redis=redis,
|
|
141
|
+
capabilities=capabilities,
|
|
142
|
+
owns_redis=owns_redis,
|
|
143
|
+
)
|
|
144
|
+
await client._ensure_backend()
|
|
145
|
+
return client
|
|
146
146
|
|
|
147
147
|
async def publish(
|
|
148
148
|
self,
|
|
@@ -321,21 +321,20 @@ class AsyncQueueClient:
|
|
|
321
321
|
|
|
322
322
|
await (await self._ensure_backend()).requeue_dead(message)
|
|
323
323
|
|
|
324
|
-
async def close(self) -> None:
|
|
325
|
-
"""Close the async Redis client when this client owns it."""
|
|
326
|
-
|
|
327
|
-
if not self._owns_redis:
|
|
328
|
-
return
|
|
329
|
-
|
|
330
|
-
close = getattr(self.redis, "aclose", None) or getattr(
|
|
331
|
-
self.redis,
|
|
332
|
-
"close",
|
|
333
|
-
None,
|
|
334
|
-
)
|
|
335
|
-
if close is not None:
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
await result
|
|
324
|
+
async def close(self) -> None:
|
|
325
|
+
"""Close the async Redis client when this client owns it."""
|
|
326
|
+
|
|
327
|
+
if self._closed or not self._owns_redis:
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
close = getattr(self.redis, "aclose", None) or getattr(
|
|
331
|
+
self.redis,
|
|
332
|
+
"close",
|
|
333
|
+
None,
|
|
334
|
+
)
|
|
335
|
+
if close is not None:
|
|
336
|
+
await self._call_close(close)
|
|
337
|
+
self._closed = True
|
|
339
338
|
|
|
340
339
|
async def __aenter__(self) -> AsyncQueueClient:
|
|
341
340
|
"""Enter an asynchronous resource-management context."""
|
|
@@ -365,31 +364,55 @@ class AsyncQueueClient:
|
|
|
365
364
|
|
|
366
365
|
if self.backend is not None:
|
|
367
366
|
return self.backend
|
|
368
|
-
if self.config.backend_type is BackendType.LIST:
|
|
369
|
-
if self.redis is None:
|
|
370
|
-
raise TypeError("redis client is required for async List backend")
|
|
371
|
-
capabilities = self.capabilities
|
|
372
|
-
if capabilities is None:
|
|
373
|
-
raise TypeError("Redis capabilities are required before backend use")
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
capabilities
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
367
|
+
if self.config.backend_type is BackendType.LIST:
|
|
368
|
+
if self.redis is None:
|
|
369
|
+
raise TypeError("redis client is required for async List backend")
|
|
370
|
+
capabilities = self.capabilities
|
|
371
|
+
if capabilities is None:
|
|
372
|
+
raise TypeError("Redis capabilities are required before backend use")
|
|
373
|
+
try:
|
|
374
|
+
self.backend = AsyncListBackend(self.redis, self.config, capabilities)
|
|
375
|
+
return self.backend
|
|
376
|
+
except Exception:
|
|
377
|
+
await self.close()
|
|
378
|
+
raise
|
|
379
|
+
if self.config.backend_type is BackendType.STREAM:
|
|
380
|
+
if self.redis is None:
|
|
381
|
+
raise TypeError("redis client is required for async Streams backend")
|
|
382
|
+
capabilities = self.capabilities
|
|
383
|
+
if capabilities is None:
|
|
384
|
+
raise TypeError("Redis capabilities are required before backend use")
|
|
385
|
+
try:
|
|
386
|
+
self.backend = await AsyncStreamBackend.create(
|
|
387
|
+
self.redis,
|
|
388
|
+
self.config,
|
|
389
|
+
capabilities,
|
|
390
|
+
)
|
|
391
|
+
return self.backend
|
|
392
|
+
except Exception:
|
|
393
|
+
await self.close()
|
|
394
|
+
raise
|
|
395
|
+
raise NotImplementedError(
|
|
396
|
+
f"backend {self.config.backend_type.value!r} is not implemented"
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
@staticmethod
|
|
400
|
+
async def _close_redis(redis: Any) -> None:
|
|
401
|
+
"""Close an async Redis-like object if it exposes a close method."""
|
|
402
|
+
|
|
403
|
+
close = getattr(redis, "aclose", None) or getattr(redis, "close", None)
|
|
404
|
+
if close is not None:
|
|
405
|
+
await AsyncQueueClient._call_close(close)
|
|
406
|
+
|
|
407
|
+
@staticmethod
|
|
408
|
+
async def _call_close(close: Any) -> None:
|
|
409
|
+
"""Call a sync or async close method."""
|
|
410
|
+
|
|
411
|
+
result = close()
|
|
412
|
+
if hasattr(result, "__await__"):
|
|
413
|
+
await result
|
|
414
|
+
|
|
415
|
+
async def _ensure_delay_backend(self) -> AsyncDelayBackend:
|
|
393
416
|
"""Return the initialized async delay scheduler, creating it when needed."""
|
|
394
417
|
|
|
395
418
|
if self.delay_backend is not None:
|
|
@@ -61,15 +61,20 @@ class QueueClient:
|
|
|
61
61
|
RedisCompatibilityError: If Redis lacks a required command family.
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
|
-
self.config = config
|
|
65
|
-
self.redis = redis
|
|
66
|
-
self.capabilities = capabilities
|
|
67
|
-
self._owns_redis = owns_redis
|
|
68
|
-
self.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
self.config = config
|
|
65
|
+
self.redis = redis
|
|
66
|
+
self.capabilities = capabilities
|
|
67
|
+
self._owns_redis = owns_redis
|
|
68
|
+
self._closed = False
|
|
69
|
+
try:
|
|
70
|
+
self.backend = self._create_backend()
|
|
71
|
+
self.delay_backend = self._create_delay_backend()
|
|
72
|
+
except Exception:
|
|
73
|
+
self.close()
|
|
74
|
+
raise
|
|
75
|
+
self.config.monitoring.emit(
|
|
76
|
+
MonitoringEvent(
|
|
77
|
+
type=MonitoringEventType.CLIENT_CREATED,
|
|
73
78
|
queue=config.queue,
|
|
74
79
|
backend=config.backend_type.value,
|
|
75
80
|
)
|
|
@@ -94,8 +99,8 @@ class QueueClient:
|
|
|
94
99
|
connection_manager: Optional connection manager used to create a
|
|
95
100
|
client from a shared pool.
|
|
96
101
|
**options: Additional ``QueueConfig`` options. Tests may also pass
|
|
97
|
-
``redis``, ``capabilities``,
|
|
98
|
-
customize connection creation.
|
|
102
|
+
``redis``, ``capabilities``, ``pool_options``, or
|
|
103
|
+
``owns_redis`` to bypass or customize connection creation.
|
|
99
104
|
|
|
100
105
|
Returns:
|
|
101
106
|
A ready-to-use synchronous ``QueueClient``.
|
|
@@ -107,41 +112,48 @@ class QueueClient:
|
|
|
107
112
|
QueueConfigError: If configuration values are invalid.
|
|
108
113
|
"""
|
|
109
114
|
|
|
110
|
-
redis = options.pop("redis", None)
|
|
111
|
-
pool_options = options.pop("pool_options", None) or {}
|
|
112
|
-
explicit_owns_redis = options.pop("owns_redis", None)
|
|
113
|
-
owns_redis = (
|
|
114
|
-
bool(explicit_owns_redis)
|
|
115
|
-
if explicit_owns_redis is not None
|
|
116
|
-
else False
|
|
117
|
-
)
|
|
118
|
-
if redis is None:
|
|
119
|
-
if connection_manager is not None:
|
|
120
|
-
redis = connection_manager.redis()
|
|
121
|
-
else:
|
|
122
|
-
redis = Redis.from_url(url, **pool_options)
|
|
123
|
-
owns_redis = (
|
|
124
|
-
True
|
|
125
|
-
if explicit_owns_redis is None
|
|
126
|
-
else bool(explicit_owns_redis)
|
|
127
|
-
)
|
|
115
|
+
redis = options.pop("redis", None)
|
|
116
|
+
pool_options = options.pop("pool_options", None) or {}
|
|
117
|
+
explicit_owns_redis = options.pop("owns_redis", None)
|
|
118
|
+
owns_redis = (
|
|
119
|
+
bool(explicit_owns_redis)
|
|
120
|
+
if explicit_owns_redis is not None
|
|
121
|
+
else False
|
|
122
|
+
)
|
|
123
|
+
if redis is None:
|
|
124
|
+
if connection_manager is not None:
|
|
125
|
+
redis = connection_manager.redis()
|
|
126
|
+
else:
|
|
127
|
+
redis = Redis.from_url(url, **pool_options)
|
|
128
|
+
owns_redis = (
|
|
129
|
+
True
|
|
130
|
+
if explicit_owns_redis is None
|
|
131
|
+
else bool(explicit_owns_redis)
|
|
132
|
+
)
|
|
133
|
+
capabilities = options.pop("capabilities", None)
|
|
134
|
+
if capabilities is None:
|
|
135
|
+
try:
|
|
136
|
+
capabilities = detect_capabilities(cast(RedisInfoClient, redis))
|
|
137
|
+
except Exception:
|
|
138
|
+
if owns_redis:
|
|
139
|
+
close = getattr(redis, "close", None)
|
|
140
|
+
if close is not None:
|
|
141
|
+
close()
|
|
142
|
+
raise
|
|
128
143
|
try:
|
|
129
|
-
capabilities = options.pop("capabilities", None) or detect_capabilities(
|
|
130
|
-
cast(RedisInfoClient, redis)
|
|
131
|
-
)
|
|
132
144
|
config = QueueConfig(queue=queue, backend=backend, **options)
|
|
133
|
-
return cls(
|
|
134
|
-
config=config,
|
|
135
|
-
redis=redis,
|
|
136
|
-
capabilities=capabilities,
|
|
137
|
-
owns_redis=owns_redis,
|
|
138
|
-
)
|
|
139
145
|
except Exception:
|
|
140
146
|
if owns_redis:
|
|
141
147
|
close = getattr(redis, "close", None)
|
|
142
148
|
if close is not None:
|
|
143
149
|
close()
|
|
144
150
|
raise
|
|
151
|
+
return cls(
|
|
152
|
+
config=config,
|
|
153
|
+
redis=redis,
|
|
154
|
+
capabilities=capabilities,
|
|
155
|
+
owns_redis=owns_redis,
|
|
156
|
+
)
|
|
145
157
|
|
|
146
158
|
def publish(
|
|
147
159
|
self,
|
|
@@ -321,15 +333,16 @@ class QueueClient:
|
|
|
321
333
|
|
|
322
334
|
self.backend.requeue_dead(message)
|
|
323
335
|
|
|
324
|
-
def close(self) -> None:
|
|
325
|
-
"""Close the Redis client when this client owns it."""
|
|
326
|
-
|
|
327
|
-
if not self._owns_redis:
|
|
328
|
-
return
|
|
329
|
-
|
|
330
|
-
close = getattr(self.redis, "close", None)
|
|
331
|
-
if close is not None:
|
|
332
|
-
close()
|
|
336
|
+
def close(self) -> None:
|
|
337
|
+
"""Close the Redis client when this client owns it."""
|
|
338
|
+
|
|
339
|
+
if self._closed or not self._owns_redis:
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
close = getattr(self.redis, "close", None)
|
|
343
|
+
if close is not None:
|
|
344
|
+
close()
|
|
345
|
+
self._closed = True
|
|
333
346
|
|
|
334
347
|
def __enter__(self) -> QueueClient:
|
|
335
348
|
"""Enter a synchronous resource-management context."""
|
|
@@ -50,7 +50,7 @@ from tests.fakes import (
|
|
|
50
50
|
|
|
51
51
|
class ProjectSkeletonTests(unittest.TestCase):
|
|
52
52
|
def test_version_is_current_dev_version(self) -> None:
|
|
53
|
-
self.assertEqual(__version__, "0.11.
|
|
53
|
+
self.assertEqual(__version__, "0.11.2")
|
|
54
54
|
|
|
55
55
|
def test_queue_config_accepts_and_normalizes_backend(self) -> None:
|
|
56
56
|
config = QueueConfig(queue=" emails ", backend="stream")
|
|
@@ -592,7 +592,7 @@ class ProjectSkeletonTests(unittest.TestCase):
|
|
|
592
592
|
|
|
593
593
|
self.assertNotIn("close", redis.commands)
|
|
594
594
|
|
|
595
|
-
def test_sync_client_context_closes_owned_redis(self) -> None:
|
|
595
|
+
def test_sync_client_context_closes_owned_redis(self) -> None:
|
|
596
596
|
redis = FakeListRedis()
|
|
597
597
|
|
|
598
598
|
with QueueClient(
|
|
@@ -601,8 +601,37 @@ class ProjectSkeletonTests(unittest.TestCase):
|
|
|
601
601
|
capabilities=RedisCapabilities(RedisVersion(7, 0, 0)),
|
|
602
602
|
):
|
|
603
603
|
pass
|
|
604
|
-
|
|
605
|
-
self.assertIn("close", redis.commands)
|
|
604
|
+
|
|
605
|
+
self.assertIn("close", redis.commands)
|
|
606
|
+
|
|
607
|
+
def test_sync_client_close_is_idempotent(self) -> None:
|
|
608
|
+
redis = FakeListRedis()
|
|
609
|
+
client = QueueClient(
|
|
610
|
+
QueueConfig(queue="emails"),
|
|
611
|
+
redis=redis,
|
|
612
|
+
capabilities=RedisCapabilities(RedisVersion(7, 0, 0)),
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
client.close()
|
|
616
|
+
client.close()
|
|
617
|
+
|
|
618
|
+
self.assertEqual(redis.commands.count("close"), 1)
|
|
619
|
+
|
|
620
|
+
def test_sync_client_closes_owned_redis_when_backend_init_fails(self) -> None:
|
|
621
|
+
class ClosableStreamRedis(FakeStreamRedis):
|
|
622
|
+
def close(self) -> None:
|
|
623
|
+
self.commands.append("close")
|
|
624
|
+
|
|
625
|
+
redis = ClosableStreamRedis()
|
|
626
|
+
|
|
627
|
+
with self.assertRaises(RedisCompatibilityError):
|
|
628
|
+
QueueClient(
|
|
629
|
+
QueueConfig(queue="events", backend="stream"),
|
|
630
|
+
redis=redis,
|
|
631
|
+
capabilities=RedisCapabilities(RedisVersion(4, 0, 14)),
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
self.assertIn("close", redis.commands)
|
|
606
635
|
|
|
607
636
|
def test_async_client_can_leave_injected_redis_open(self) -> None:
|
|
608
637
|
async def run() -> FakeAsyncListRedis:
|
|
@@ -620,7 +649,7 @@ class ProjectSkeletonTests(unittest.TestCase):
|
|
|
620
649
|
|
|
621
650
|
self.assertNotIn("aclose", redis.commands)
|
|
622
651
|
|
|
623
|
-
def test_async_client_context_closes_owned_redis(self) -> None:
|
|
652
|
+
def test_async_client_context_closes_owned_redis(self) -> None:
|
|
624
653
|
async def run() -> FakeAsyncListRedis:
|
|
625
654
|
redis = FakeAsyncListRedis()
|
|
626
655
|
async with AsyncQueueClient(
|
|
@@ -632,8 +661,44 @@ class ProjectSkeletonTests(unittest.TestCase):
|
|
|
632
661
|
return redis
|
|
633
662
|
|
|
634
663
|
redis = asyncio.run(run())
|
|
635
|
-
|
|
636
|
-
self.assertIn("aclose", redis.commands)
|
|
664
|
+
|
|
665
|
+
self.assertIn("aclose", redis.commands)
|
|
666
|
+
|
|
667
|
+
def test_async_client_close_is_idempotent(self) -> None:
|
|
668
|
+
async def run() -> FakeAsyncListRedis:
|
|
669
|
+
redis = FakeAsyncListRedis()
|
|
670
|
+
client = AsyncQueueClient(
|
|
671
|
+
QueueConfig(queue="jobs"),
|
|
672
|
+
redis=redis,
|
|
673
|
+
capabilities=RedisCapabilities(RedisVersion(7, 0, 0)),
|
|
674
|
+
)
|
|
675
|
+
await client.close()
|
|
676
|
+
await client.close()
|
|
677
|
+
return redis
|
|
678
|
+
|
|
679
|
+
redis = asyncio.run(run())
|
|
680
|
+
|
|
681
|
+
self.assertEqual(redis.commands.count("aclose"), 1)
|
|
682
|
+
|
|
683
|
+
def test_async_client_closes_owned_redis_when_backend_init_fails(self) -> None:
|
|
684
|
+
async def run() -> FakeAsyncStreamRedis:
|
|
685
|
+
class ClosableAsyncStreamRedis(FakeAsyncStreamRedis):
|
|
686
|
+
async def aclose(self) -> None:
|
|
687
|
+
self.commands.append("aclose")
|
|
688
|
+
|
|
689
|
+
redis = ClosableAsyncStreamRedis()
|
|
690
|
+
client = AsyncQueueClient(
|
|
691
|
+
QueueConfig(queue="events", backend="stream"),
|
|
692
|
+
redis=redis,
|
|
693
|
+
capabilities=RedisCapabilities(RedisVersion(4, 0, 14)),
|
|
694
|
+
)
|
|
695
|
+
with self.assertRaises(RedisCompatibilityError):
|
|
696
|
+
await client.consume(timeout=1)
|
|
697
|
+
return redis
|
|
698
|
+
|
|
699
|
+
redis = asyncio.run(run())
|
|
700
|
+
|
|
701
|
+
self.assertIn("aclose", redis.commands)
|
|
637
702
|
|
|
638
703
|
def test_connection_managers_create_pooled_clients(self) -> None:
|
|
639
704
|
manager = RedisConnectionManager(
|
|
@@ -653,7 +718,7 @@ class ProjectSkeletonTests(unittest.TestCase):
|
|
|
653
718
|
with self.assertRaises(RuntimeError):
|
|
654
719
|
manager.redis()
|
|
655
720
|
|
|
656
|
-
def test_sync_from_url_accepts_connection_manager_and_pool_options(self) -> None:
|
|
721
|
+
def test_sync_from_url_accepts_connection_manager_and_pool_options(self) -> None:
|
|
657
722
|
redis = FakeListRedis()
|
|
658
723
|
client = QueueClient.from_url(
|
|
659
724
|
"redis://127.0.0.1:6379/0",
|
|
@@ -682,58 +747,58 @@ class ProjectSkeletonTests(unittest.TestCase):
|
|
|
682
747
|
self.assertIs(managed_client.redis.connection_pool, manager.pool)
|
|
683
748
|
managed_client.close()
|
|
684
749
|
self.assertIs(manager.redis().connection_pool, manager.pool)
|
|
685
|
-
finally:
|
|
686
|
-
manager.close()
|
|
687
|
-
|
|
688
|
-
def test_sync_from_url_closes_owned_redis_when_initialization_fails(self) -> None:
|
|
689
|
-
class OwnedRedis(FakeListRedis):
|
|
690
|
-
def info(self, section: str | None = None) -> dict[str, str]:
|
|
691
|
-
return {"redis_version": "7.0.0"}
|
|
692
|
-
|
|
693
|
-
redis = OwnedRedis()
|
|
694
|
-
|
|
695
|
-
with self.assertRaises(QueueConfigError):
|
|
696
|
-
QueueClient.from_url(
|
|
697
|
-
"redis://127.0.0.1:6379/0",
|
|
698
|
-
queue="bad queue",
|
|
699
|
-
redis=redis,
|
|
700
|
-
owns_redis=True,
|
|
701
|
-
)
|
|
702
|
-
|
|
703
|
-
self.assertIn("close", redis.commands)
|
|
704
|
-
|
|
705
|
-
def test_sync_from_url_leaves_injected_redis_open_when_init_fails(self) -> None:
|
|
706
|
-
class InjectedRedis(FakeListRedis):
|
|
707
|
-
def info(self, section: str | None = None) -> dict[str, str]:
|
|
708
|
-
return {"redis_version": "7.0.0"}
|
|
709
|
-
|
|
710
|
-
redis = InjectedRedis()
|
|
711
|
-
|
|
712
|
-
with self.assertRaises(QueueConfigError):
|
|
713
|
-
QueueClient.from_url(
|
|
714
|
-
"redis://127.0.0.1:6379/0",
|
|
715
|
-
queue="bad queue",
|
|
716
|
-
redis=redis,
|
|
717
|
-
)
|
|
718
|
-
|
|
719
|
-
self.assertNotIn("close", redis.commands)
|
|
720
|
-
|
|
721
|
-
def test_sync_from_url_closes_owned_redis_when_capability_probe_fails(self) -> None:
|
|
722
|
-
class BrokenInfoRedis(FakeListRedis):
|
|
723
|
-
def info(self, section: str | None = None) -> dict[str, str]:
|
|
724
|
-
raise TimeoutError("redis unavailable")
|
|
725
|
-
|
|
726
|
-
redis = BrokenInfoRedis()
|
|
727
|
-
|
|
728
|
-
with self.assertRaises(BackendUnavailableError):
|
|
729
|
-
QueueClient.from_url(
|
|
730
|
-
"redis://127.0.0.1:6379/0",
|
|
731
|
-
queue="emails",
|
|
732
|
-
redis=redis,
|
|
733
|
-
owns_redis=True,
|
|
734
|
-
)
|
|
735
|
-
|
|
736
|
-
self.assertIn("close", redis.commands)
|
|
750
|
+
finally:
|
|
751
|
+
manager.close()
|
|
752
|
+
|
|
753
|
+
def test_sync_from_url_closes_owned_redis_when_initialization_fails(self) -> None:
|
|
754
|
+
class OwnedRedis(FakeListRedis):
|
|
755
|
+
def info(self, section: str | None = None) -> dict[str, str]:
|
|
756
|
+
return {"redis_version": "7.0.0"}
|
|
757
|
+
|
|
758
|
+
redis = OwnedRedis()
|
|
759
|
+
|
|
760
|
+
with self.assertRaises(QueueConfigError):
|
|
761
|
+
QueueClient.from_url(
|
|
762
|
+
"redis://127.0.0.1:6379/0",
|
|
763
|
+
queue="bad queue",
|
|
764
|
+
redis=redis,
|
|
765
|
+
owns_redis=True,
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
self.assertIn("close", redis.commands)
|
|
769
|
+
|
|
770
|
+
def test_sync_from_url_leaves_injected_redis_open_when_init_fails(self) -> None:
|
|
771
|
+
class InjectedRedis(FakeListRedis):
|
|
772
|
+
def info(self, section: str | None = None) -> dict[str, str]:
|
|
773
|
+
return {"redis_version": "7.0.0"}
|
|
774
|
+
|
|
775
|
+
redis = InjectedRedis()
|
|
776
|
+
|
|
777
|
+
with self.assertRaises(QueueConfigError):
|
|
778
|
+
QueueClient.from_url(
|
|
779
|
+
"redis://127.0.0.1:6379/0",
|
|
780
|
+
queue="bad queue",
|
|
781
|
+
redis=redis,
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
self.assertNotIn("close", redis.commands)
|
|
785
|
+
|
|
786
|
+
def test_sync_from_url_closes_owned_redis_when_capability_probe_fails(self) -> None:
|
|
787
|
+
class BrokenInfoRedis(FakeListRedis):
|
|
788
|
+
def info(self, section: str | None = None) -> dict[str, str]:
|
|
789
|
+
raise TimeoutError("redis unavailable")
|
|
790
|
+
|
|
791
|
+
redis = BrokenInfoRedis()
|
|
792
|
+
|
|
793
|
+
with self.assertRaises(BackendUnavailableError):
|
|
794
|
+
QueueClient.from_url(
|
|
795
|
+
"redis://127.0.0.1:6379/0",
|
|
796
|
+
queue="emails",
|
|
797
|
+
redis=redis,
|
|
798
|
+
owns_redis=True,
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
self.assertIn("close", redis.commands)
|
|
737
802
|
|
|
738
803
|
def test_async_connection_manager_creates_pooled_clients(self) -> None:
|
|
739
804
|
async def run() -> None:
|
|
@@ -755,7 +820,7 @@ class ProjectSkeletonTests(unittest.TestCase):
|
|
|
755
820
|
|
|
756
821
|
asyncio.run(run())
|
|
757
822
|
|
|
758
|
-
def test_async_from_url_accepts_connection_manager_and_pool_options(self) -> None:
|
|
823
|
+
def test_async_from_url_accepts_connection_manager_and_pool_options(self) -> None:
|
|
759
824
|
async def run() -> None:
|
|
760
825
|
redis = FakeAsyncListRedis()
|
|
761
826
|
client = await AsyncQueueClient.from_url(
|
|
@@ -787,67 +852,67 @@ class ProjectSkeletonTests(unittest.TestCase):
|
|
|
787
852
|
self.assertIs(manager.redis().connection_pool, manager.pool)
|
|
788
853
|
finally:
|
|
789
854
|
await manager.close()
|
|
790
|
-
|
|
791
|
-
asyncio.run(run())
|
|
792
|
-
|
|
793
|
-
def test_async_from_url_closes_owned_redis_when_initialization_fails(self) -> None:
|
|
794
|
-
class OwnedAsyncRedis(FakeAsyncListRedis):
|
|
795
|
-
async def info(self, section: str | None = None) -> dict[str, str]:
|
|
796
|
-
return {"redis_version": "7.0.0"}
|
|
797
|
-
|
|
798
|
-
async def run() -> FakeAsyncListRedis:
|
|
799
|
-
redis = OwnedAsyncRedis()
|
|
800
|
-
with self.assertRaises(QueueConfigError):
|
|
801
|
-
await AsyncQueueClient.from_url(
|
|
802
|
-
"redis://127.0.0.1:6379/0",
|
|
803
|
-
queue="bad queue",
|
|
804
|
-
redis=redis,
|
|
805
|
-
owns_redis=True,
|
|
806
|
-
)
|
|
807
|
-
return redis
|
|
808
|
-
|
|
809
|
-
redis = asyncio.run(run())
|
|
810
|
-
|
|
811
|
-
self.assertIn("aclose", redis.commands)
|
|
812
|
-
|
|
813
|
-
def test_async_from_url_leaves_injected_redis_open_when_init_fails(self) -> None:
|
|
814
|
-
class InjectedAsyncRedis(FakeAsyncListRedis):
|
|
815
|
-
async def info(self, section: str | None = None) -> dict[str, str]:
|
|
816
|
-
return {"redis_version": "7.0.0"}
|
|
817
|
-
|
|
818
|
-
async def run() -> FakeAsyncListRedis:
|
|
819
|
-
redis = InjectedAsyncRedis()
|
|
820
|
-
with self.assertRaises(QueueConfigError):
|
|
821
|
-
await AsyncQueueClient.from_url(
|
|
822
|
-
"redis://127.0.0.1:6379/0",
|
|
823
|
-
queue="bad queue",
|
|
824
|
-
redis=redis,
|
|
825
|
-
)
|
|
826
|
-
return redis
|
|
827
|
-
|
|
828
|
-
redis = asyncio.run(run())
|
|
829
|
-
|
|
830
|
-
self.assertNotIn("aclose", redis.commands)
|
|
831
|
-
|
|
832
|
-
def test_async_from_url_closes_owned_redis_on_probe_failure(self) -> None:
|
|
833
|
-
class BrokenInfoAsyncRedis(FakeAsyncListRedis):
|
|
834
|
-
async def info(self, section: str | None = None) -> dict[str, str]:
|
|
835
|
-
raise TimeoutError("redis unavailable")
|
|
836
|
-
|
|
837
|
-
async def run() -> FakeAsyncListRedis:
|
|
838
|
-
redis = BrokenInfoAsyncRedis()
|
|
839
|
-
with self.assertRaises(BackendUnavailableError):
|
|
840
|
-
await AsyncQueueClient.from_url(
|
|
841
|
-
"redis://127.0.0.1:6379/0",
|
|
842
|
-
queue="jobs",
|
|
843
|
-
redis=redis,
|
|
844
|
-
owns_redis=True,
|
|
845
|
-
)
|
|
846
|
-
return redis
|
|
847
|
-
|
|
848
|
-
redis = asyncio.run(run())
|
|
849
|
-
|
|
850
|
-
self.assertIn("aclose", redis.commands)
|
|
855
|
+
|
|
856
|
+
asyncio.run(run())
|
|
857
|
+
|
|
858
|
+
def test_async_from_url_closes_owned_redis_when_initialization_fails(self) -> None:
|
|
859
|
+
class OwnedAsyncRedis(FakeAsyncListRedis):
|
|
860
|
+
async def info(self, section: str | None = None) -> dict[str, str]:
|
|
861
|
+
return {"redis_version": "7.0.0"}
|
|
862
|
+
|
|
863
|
+
async def run() -> FakeAsyncListRedis:
|
|
864
|
+
redis = OwnedAsyncRedis()
|
|
865
|
+
with self.assertRaises(QueueConfigError):
|
|
866
|
+
await AsyncQueueClient.from_url(
|
|
867
|
+
"redis://127.0.0.1:6379/0",
|
|
868
|
+
queue="bad queue",
|
|
869
|
+
redis=redis,
|
|
870
|
+
owns_redis=True,
|
|
871
|
+
)
|
|
872
|
+
return redis
|
|
873
|
+
|
|
874
|
+
redis = asyncio.run(run())
|
|
875
|
+
|
|
876
|
+
self.assertIn("aclose", redis.commands)
|
|
877
|
+
|
|
878
|
+
def test_async_from_url_leaves_injected_redis_open_when_init_fails(self) -> None:
|
|
879
|
+
class InjectedAsyncRedis(FakeAsyncListRedis):
|
|
880
|
+
async def info(self, section: str | None = None) -> dict[str, str]:
|
|
881
|
+
return {"redis_version": "7.0.0"}
|
|
882
|
+
|
|
883
|
+
async def run() -> FakeAsyncListRedis:
|
|
884
|
+
redis = InjectedAsyncRedis()
|
|
885
|
+
with self.assertRaises(QueueConfigError):
|
|
886
|
+
await AsyncQueueClient.from_url(
|
|
887
|
+
"redis://127.0.0.1:6379/0",
|
|
888
|
+
queue="bad queue",
|
|
889
|
+
redis=redis,
|
|
890
|
+
)
|
|
891
|
+
return redis
|
|
892
|
+
|
|
893
|
+
redis = asyncio.run(run())
|
|
894
|
+
|
|
895
|
+
self.assertNotIn("aclose", redis.commands)
|
|
896
|
+
|
|
897
|
+
def test_async_from_url_closes_owned_redis_on_probe_failure(self) -> None:
|
|
898
|
+
class BrokenInfoAsyncRedis(FakeAsyncListRedis):
|
|
899
|
+
async def info(self, section: str | None = None) -> dict[str, str]:
|
|
900
|
+
raise TimeoutError("redis unavailable")
|
|
901
|
+
|
|
902
|
+
async def run() -> FakeAsyncListRedis:
|
|
903
|
+
redis = BrokenInfoAsyncRedis()
|
|
904
|
+
with self.assertRaises(BackendUnavailableError):
|
|
905
|
+
await AsyncQueueClient.from_url(
|
|
906
|
+
"redis://127.0.0.1:6379/0",
|
|
907
|
+
queue="jobs",
|
|
908
|
+
redis=redis,
|
|
909
|
+
owns_redis=True,
|
|
910
|
+
)
|
|
911
|
+
return redis
|
|
912
|
+
|
|
913
|
+
redis = asyncio.run(run())
|
|
914
|
+
|
|
915
|
+
self.assertIn("aclose", redis.commands)
|
|
851
916
|
|
|
852
917
|
def test_async_list_backend_ack_uses_original_serialized_payload(self) -> None:
|
|
853
918
|
class NonDeterministicSerializer:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|