redis-message-queue 8.2.8__tar.gz → 8.2.9__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.
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/PKG-INFO +23 -15
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/README.md +22 -14
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/pyproject.toml +2 -2
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/asyncio/redis_message_queue.py +10 -6
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/redis_message_queue.py +14 -6
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/.gitignore +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/LICENSE +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/__init__.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_abstract_redis_gateway.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_callable_utils.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_config.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_event.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_exceptions.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_payload_limits.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_queue_key_manager.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_redis_cluster.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_redis_gateway.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_stored_message.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/asyncio/__init__.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/asyncio/_abstract_redis_gateway.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/asyncio/_redis_gateway.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/interrupt_handler/__init__.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/interrupt_handler/_event_driven.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/interrupt_handler/_implementation.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/interrupt_handler/_interface.py +0 -0
- {redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redis-message-queue
|
|
3
|
-
Version: 8.2.
|
|
3
|
+
Version: 8.2.9
|
|
4
4
|
Summary: Python message queuing with Redis and message deduplication
|
|
5
5
|
Project-URL: Homepage, https://github.com/Elijas/redis-message-queue
|
|
6
6
|
Project-URL: Repository, https://github.com/Elijas/redis-message-queue
|
|
@@ -34,7 +34,7 @@ Description-Content-Type: text/markdown
|
|
|
34
34
|
**Lightweight Python message queuing with Redis and built-in publish-side deduplication.** Deduplicate publishes within a TTL window, with optional crash recovery — across any number of producers and consumers.
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
pip install "redis-message-queue>=8.2.
|
|
37
|
+
pip install "redis-message-queue>=8.2.9,<9.0.0"
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Requires Redis server >= 6.2.
|
|
@@ -848,6 +848,11 @@ Pre-commit and mid-flight exceptions:
|
|
|
848
848
|
exceptions. Under an ambiguous lost response, Redis may have committed
|
|
849
849
|
despite the exception. Treat them as "operation did not succeed from the
|
|
850
850
|
caller's perspective", not "Redis did not commit".
|
|
851
|
+
- Visibility-timeout claim-store write failures raise
|
|
852
|
+
`ClaimStoreFailedError` and emit `claim/failure`. When the compensating
|
|
853
|
+
return-to-pending write succeeds, the payload is back in pending; if
|
|
854
|
+
return-to-pending also fails, the payload remains in processing so there is
|
|
855
|
+
still a live queue copy.
|
|
851
856
|
|
|
852
857
|
#### Drain events
|
|
853
858
|
|
|
@@ -875,11 +880,6 @@ The following operations have no `on_event` surface by design:
|
|
|
875
880
|
preserves queue safety on Cluster `CROSSSLOT` rejection but cannot be
|
|
876
881
|
observed through `on_event`. Operators watching key-TTL behavior or Redis
|
|
877
882
|
slow logs can detect orphans.
|
|
878
|
-
- **VT claim-store OOM compensation:** if the visibility-timeout Lua script
|
|
879
|
-
cannot store the claim result, it removes the message from processing, pushes
|
|
880
|
-
it back to pending, and returns `false`. Python translates that into
|
|
881
|
-
`claim_empty/skipped`, the same shape as an empty poll. This is intentional
|
|
882
|
-
fail-safe behavior; the message is not lost.
|
|
883
883
|
- **`drop_oldest` evictions:** when publish backpressure uses
|
|
884
884
|
`pending_overload_policy="drop_oldest"`, the oldest pending message is
|
|
885
885
|
discarded before the new message is enqueued. The successful enqueue emits
|
|
@@ -890,14 +890,22 @@ The following operations have no `on_event` surface by design:
|
|
|
890
890
|
terminal operation's failure event. There is no per-attempt event for those
|
|
891
891
|
paths.
|
|
892
892
|
|
|
893
|
-
The public exception hierarchy is rooted at `RedisMessageQueueError`.
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
`
|
|
898
|
-
`
|
|
899
|
-
|
|
900
|
-
|
|
893
|
+
The public exception hierarchy is rooted at `RedisMessageQueueError`. The
|
|
894
|
+
current exported queue-owned exception classes are:
|
|
895
|
+
|
|
896
|
+
- `RedisMessageQueueError` (base)
|
|
897
|
+
- `ClaimStoreFailedError` - visibility-timeout claim metadata could not be stored
|
|
898
|
+
- `ConfigurationError` - invalid constructor args; also a `ValueError`
|
|
899
|
+
- `DrainFailedError` - drain pending-claim recovery failed
|
|
900
|
+
- `GatewayContractError` - custom gateway protocol violation; also a `TypeError`
|
|
901
|
+
- `LuaScriptError` - Lua `redis.error_reply(...)`; also a redis-py `ResponseError`
|
|
902
|
+
- `MalformedStoredMessageError` - stored value is not a valid RMQ envelope
|
|
903
|
+
- `PayloadTooLargeError` - serialized payload exceeds `max_payload_bytes`; also a `ValueError`
|
|
904
|
+
- `PayloadTooDeepError` - payload nesting exceeds `max_payload_depth`; also a `ValueError`
|
|
905
|
+
- `QueueBackpressureError` - `pending_overload_policy="raise"` rejected enqueue
|
|
906
|
+
- `QueueDrainedError` - `publish()` called after explicit drain/aclose
|
|
907
|
+
- `CleanupFailedError` - cleanup after handler completion failed
|
|
908
|
+
- `RetryBudgetExhaustedError` - Redis retry budget exhausted; also a redis-py `RedisError`
|
|
901
909
|
|
|
902
910
|
## Known limitations
|
|
903
911
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
**Lightweight Python message queuing with Redis and built-in publish-side deduplication.** Deduplicate publishes within a TTL window, with optional crash recovery — across any number of producers and consumers.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
pip install "redis-message-queue>=8.2.
|
|
14
|
+
pip install "redis-message-queue>=8.2.9,<9.0.0"
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Requires Redis server >= 6.2.
|
|
@@ -825,6 +825,11 @@ Pre-commit and mid-flight exceptions:
|
|
|
825
825
|
exceptions. Under an ambiguous lost response, Redis may have committed
|
|
826
826
|
despite the exception. Treat them as "operation did not succeed from the
|
|
827
827
|
caller's perspective", not "Redis did not commit".
|
|
828
|
+
- Visibility-timeout claim-store write failures raise
|
|
829
|
+
`ClaimStoreFailedError` and emit `claim/failure`. When the compensating
|
|
830
|
+
return-to-pending write succeeds, the payload is back in pending; if
|
|
831
|
+
return-to-pending also fails, the payload remains in processing so there is
|
|
832
|
+
still a live queue copy.
|
|
828
833
|
|
|
829
834
|
#### Drain events
|
|
830
835
|
|
|
@@ -852,11 +857,6 @@ The following operations have no `on_event` surface by design:
|
|
|
852
857
|
preserves queue safety on Cluster `CROSSSLOT` rejection but cannot be
|
|
853
858
|
observed through `on_event`. Operators watching key-TTL behavior or Redis
|
|
854
859
|
slow logs can detect orphans.
|
|
855
|
-
- **VT claim-store OOM compensation:** if the visibility-timeout Lua script
|
|
856
|
-
cannot store the claim result, it removes the message from processing, pushes
|
|
857
|
-
it back to pending, and returns `false`. Python translates that into
|
|
858
|
-
`claim_empty/skipped`, the same shape as an empty poll. This is intentional
|
|
859
|
-
fail-safe behavior; the message is not lost.
|
|
860
860
|
- **`drop_oldest` evictions:** when publish backpressure uses
|
|
861
861
|
`pending_overload_policy="drop_oldest"`, the oldest pending message is
|
|
862
862
|
discarded before the new message is enqueued. The successful enqueue emits
|
|
@@ -867,14 +867,22 @@ The following operations have no `on_event` surface by design:
|
|
|
867
867
|
terminal operation's failure event. There is no per-attempt event for those
|
|
868
868
|
paths.
|
|
869
869
|
|
|
870
|
-
The public exception hierarchy is rooted at `RedisMessageQueueError`.
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
`
|
|
875
|
-
`
|
|
876
|
-
|
|
877
|
-
|
|
870
|
+
The public exception hierarchy is rooted at `RedisMessageQueueError`. The
|
|
871
|
+
current exported queue-owned exception classes are:
|
|
872
|
+
|
|
873
|
+
- `RedisMessageQueueError` (base)
|
|
874
|
+
- `ClaimStoreFailedError` - visibility-timeout claim metadata could not be stored
|
|
875
|
+
- `ConfigurationError` - invalid constructor args; also a `ValueError`
|
|
876
|
+
- `DrainFailedError` - drain pending-claim recovery failed
|
|
877
|
+
- `GatewayContractError` - custom gateway protocol violation; also a `TypeError`
|
|
878
|
+
- `LuaScriptError` - Lua `redis.error_reply(...)`; also a redis-py `ResponseError`
|
|
879
|
+
- `MalformedStoredMessageError` - stored value is not a valid RMQ envelope
|
|
880
|
+
- `PayloadTooLargeError` - serialized payload exceeds `max_payload_bytes`; also a `ValueError`
|
|
881
|
+
- `PayloadTooDeepError` - payload nesting exceeds `max_payload_depth`; also a `ValueError`
|
|
882
|
+
- `QueueBackpressureError` - `pending_overload_policy="raise"` rejected enqueue
|
|
883
|
+
- `QueueDrainedError` - `publish()` called after explicit drain/aclose
|
|
884
|
+
- `CleanupFailedError` - cleanup after handler completion failed
|
|
885
|
+
- `RetryBudgetExhaustedError` - Redis retry budget exhausted; also a redis-py `RedisError`
|
|
878
886
|
|
|
879
887
|
## Known limitations
|
|
880
888
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "redis-message-queue"
|
|
3
|
-
version = "8.2.
|
|
3
|
+
version = "8.2.9"
|
|
4
4
|
description = "Python message queuing with Redis and message deduplication"
|
|
5
5
|
authors = [{ name = "Elijas", email = "4084885+Elijas@users.noreply.github.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -48,7 +48,7 @@ default-groups = ["dev", "test"]
|
|
|
48
48
|
##############################
|
|
49
49
|
|
|
50
50
|
[tool.bumpversion]
|
|
51
|
-
current_version = "8.2.
|
|
51
|
+
current_version = "8.2.9"
|
|
52
52
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
53
53
|
serialize = ["{major}.{minor}.{patch}"]
|
|
54
54
|
search = "{current_version}"
|
|
@@ -87,6 +87,12 @@ def _warning_exception_name(exc: BaseException) -> str:
|
|
|
87
87
|
return type(exc).__name__
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
def _warn_runtime_warning(message: str, *, stacklevel: int) -> None:
|
|
91
|
+
with warnings.catch_warnings():
|
|
92
|
+
warnings.simplefilter("always", RuntimeWarning)
|
|
93
|
+
warnings.warn(message, RuntimeWarning, stacklevel=stacklevel + 1)
|
|
94
|
+
|
|
95
|
+
|
|
90
96
|
def _find_non_string_dict_keys(value: object) -> list[object]:
|
|
91
97
|
non_str_keys: list[object] = []
|
|
92
98
|
seen: set[int] = set()
|
|
@@ -1273,7 +1279,7 @@ class RedisMessageQueue:
|
|
|
1273
1279
|
message_id=message_id,
|
|
1274
1280
|
lease_token_hash=lease_token_hash,
|
|
1275
1281
|
)
|
|
1276
|
-
|
|
1282
|
+
_warn_runtime_warning(_STALE_LEASE_NACK_WARNING, stacklevel=2)
|
|
1277
1283
|
except BaseException as cleanup_exc:
|
|
1278
1284
|
# The handler exception is the user-visible failure; cleanup failure is secondary.
|
|
1279
1285
|
logger.exception("Failed to clean up message from processing queue")
|
|
@@ -1286,10 +1292,9 @@ class RedisMessageQueue:
|
|
|
1286
1292
|
error=cleanup_exc,
|
|
1287
1293
|
duration_ms=_duration_ms(cleanup_started_at),
|
|
1288
1294
|
)
|
|
1289
|
-
|
|
1295
|
+
_warn_runtime_warning(
|
|
1290
1296
|
f"Cleanup raised after handler exception ({_warning_exception_name(cleanup_exc)}); "
|
|
1291
1297
|
"see logs for both tracebacks",
|
|
1292
|
-
RuntimeWarning,
|
|
1293
1298
|
stacklevel=2,
|
|
1294
1299
|
)
|
|
1295
1300
|
raise
|
|
@@ -1349,7 +1354,7 @@ class RedisMessageQueue:
|
|
|
1349
1354
|
message_id=message_id,
|
|
1350
1355
|
lease_token_hash=lease_token_hash,
|
|
1351
1356
|
)
|
|
1352
|
-
|
|
1357
|
+
_warn_runtime_warning(_STALE_LEASE_ACK_WARNING, stacklevel=2)
|
|
1353
1358
|
finished_without_error = True
|
|
1354
1359
|
finally:
|
|
1355
1360
|
if lease_heartbeat is not None:
|
|
@@ -1438,9 +1443,8 @@ class RedisMessageQueue:
|
|
|
1438
1443
|
exception_type=type(exc).__name__,
|
|
1439
1444
|
error=exc,
|
|
1440
1445
|
)
|
|
1441
|
-
|
|
1446
|
+
_warn_runtime_warning(
|
|
1442
1447
|
f"Failed to trim queue {destination_queue} ({type(exc).__name__}); list may exceed max_*_length",
|
|
1443
|
-
RuntimeWarning,
|
|
1444
1448
|
stacklevel=3,
|
|
1445
1449
|
)
|
|
1446
1450
|
|
{redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/redis_message_queue.py
RENAMED
|
@@ -100,6 +100,12 @@ def _warning_exception_name(exc: BaseException) -> str:
|
|
|
100
100
|
return type(exc).__name__
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
def _warn_runtime_warning(message: str, *, stacklevel: int) -> None:
|
|
104
|
+
with warnings.catch_warnings():
|
|
105
|
+
warnings.simplefilter("always", RuntimeWarning)
|
|
106
|
+
warnings.warn(message, RuntimeWarning, stacklevel=stacklevel + 1)
|
|
107
|
+
|
|
108
|
+
|
|
103
109
|
def _find_non_string_dict_keys(value: object) -> list[object]:
|
|
104
110
|
non_str_keys: list[object] = []
|
|
105
111
|
seen: set[int] = set()
|
|
@@ -339,12 +345,16 @@ def _close_or_cancel_awaitable(awaitable: object) -> None:
|
|
|
339
345
|
try:
|
|
340
346
|
close()
|
|
341
347
|
return
|
|
348
|
+
except asyncio.CancelledError:
|
|
349
|
+
pass
|
|
342
350
|
except Exception:
|
|
343
351
|
pass
|
|
344
352
|
cancel = getattr(awaitable, "cancel", None)
|
|
345
353
|
if callable(cancel):
|
|
346
354
|
try:
|
|
347
355
|
cancel()
|
|
356
|
+
except asyncio.CancelledError:
|
|
357
|
+
pass
|
|
348
358
|
except Exception:
|
|
349
359
|
pass
|
|
350
360
|
|
|
@@ -1235,7 +1245,7 @@ class RedisMessageQueue:
|
|
|
1235
1245
|
message_id=message_id,
|
|
1236
1246
|
lease_token_hash=lease_token_hash,
|
|
1237
1247
|
)
|
|
1238
|
-
|
|
1248
|
+
_warn_runtime_warning(_STALE_LEASE_NACK_WARNING, stacklevel=2)
|
|
1239
1249
|
except BaseException as cleanup_exc:
|
|
1240
1250
|
# The handler exception is the user-visible failure; cleanup failure is secondary.
|
|
1241
1251
|
logger.exception("Failed to clean up message from processing queue")
|
|
@@ -1248,10 +1258,9 @@ class RedisMessageQueue:
|
|
|
1248
1258
|
error=cleanup_exc,
|
|
1249
1259
|
duration_ms=_duration_ms(cleanup_started_at),
|
|
1250
1260
|
)
|
|
1251
|
-
|
|
1261
|
+
_warn_runtime_warning(
|
|
1252
1262
|
f"Cleanup raised after handler exception ({_warning_exception_name(cleanup_exc)}); "
|
|
1253
1263
|
"see logs for both tracebacks",
|
|
1254
|
-
RuntimeWarning,
|
|
1255
1264
|
stacklevel=2,
|
|
1256
1265
|
)
|
|
1257
1266
|
raise
|
|
@@ -1310,7 +1319,7 @@ class RedisMessageQueue:
|
|
|
1310
1319
|
message_id=message_id,
|
|
1311
1320
|
lease_token_hash=lease_token_hash,
|
|
1312
1321
|
)
|
|
1313
|
-
|
|
1322
|
+
_warn_runtime_warning(_STALE_LEASE_ACK_WARNING, stacklevel=2)
|
|
1314
1323
|
finally:
|
|
1315
1324
|
if lease_heartbeat is not None:
|
|
1316
1325
|
lease_heartbeat.stop()
|
|
@@ -1400,9 +1409,8 @@ class RedisMessageQueue:
|
|
|
1400
1409
|
exception_type=type(exc).__name__,
|
|
1401
1410
|
error=exc,
|
|
1402
1411
|
)
|
|
1403
|
-
|
|
1412
|
+
_warn_runtime_warning(
|
|
1404
1413
|
f"Failed to trim queue {destination_queue} ({type(exc).__name__}); list may exceed max_*_length",
|
|
1405
|
-
RuntimeWarning,
|
|
1406
1414
|
stacklevel=3,
|
|
1407
1415
|
)
|
|
1408
1416
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_callable_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_payload_limits.py
RENAMED
|
File without changes
|
{redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_queue_key_manager.py
RENAMED
|
File without changes
|
{redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_redis_cluster.py
RENAMED
|
File without changes
|
{redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_redis_gateway.py
RENAMED
|
File without changes
|
{redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/_stored_message.py
RENAMED
|
File without changes
|
{redis_message_queue-8.2.8 → redis_message_queue-8.2.9}/redis_message_queue/asyncio/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|