redis-message-queue 7.0.1__tar.gz → 8.0.1__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 (23) hide show
  1. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/PKG-INFO +77 -23
  2. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/README.md +76 -22
  3. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/pyproject.toml +1 -1
  4. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/_config.py +14 -0
  5. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/_redis_gateway.py +157 -25
  6. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/asyncio/_redis_gateway.py +140 -24
  7. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/asyncio/redis_message_queue.py +64 -34
  8. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/redis_message_queue.py +70 -42
  9. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/LICENSE +0 -0
  10. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/__init__.py +0 -0
  11. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/_abstract_redis_gateway.py +0 -0
  12. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/_callable_utils.py +0 -0
  13. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/_event.py +0 -0
  14. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/_exceptions.py +0 -0
  15. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/_queue_key_manager.py +0 -0
  16. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/_redis_cluster.py +0 -0
  17. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/_stored_message.py +0 -0
  18. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/asyncio/__init__.py +0 -0
  19. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/asyncio/_abstract_redis_gateway.py +0 -0
  20. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/interrupt_handler/__init__.py +0 -0
  21. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/interrupt_handler/_implementation.py +0 -0
  22. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/redis_message_queue/interrupt_handler/_interface.py +0 -0
  23. {redis_message_queue-7.0.1 → redis_message_queue-8.0.1}/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: 7.0.1
3
+ Version: 8.0.1
4
4
  Summary: Python message queuing with Redis and message deduplication
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -26,7 +26,7 @@ Description-Content-Type: text/markdown
26
26
 
27
27
  # redis-message-queue
28
28
 
29
- [![PyPI Version](https://img.shields.io/badge/v7.0.1-version?color=43cd0f&style=flat&label=pypi)](https://pypi.org/project/redis-message-queue)
29
+ [![PyPI Version](https://img.shields.io/badge/v8.0.1-version?color=43cd0f&style=flat&label=pypi)](https://pypi.org/project/redis-message-queue)
30
30
  [![PyPI Downloads](https://img.shields.io/pypi/dm/redis-message-queue?color=43cd0f&style=flat&label=downloads)](https://pypistats.org/packages/redis-message-queue)
31
31
  [![License: MIT](https://img.shields.io/badge/License-MIT-43cd0f.svg?style=flat&label=license)](LICENSE)
32
32
  [![Maintained: yes](https://img.shields.io/badge/yes-43cd0f.svg?style=flat&label=maintained)](https://github.com/Elijas/redis-message-queue/issues)
@@ -37,7 +37,7 @@ Description-Content-Type: text/markdown
37
37
  **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.
38
38
 
39
39
  ```bash
40
- pip install "redis-message-queue>=7.0.0,<8.0.0"
40
+ pip install "redis-message-queue>=8.0.0,<9.0.0"
41
41
  ```
42
42
 
43
43
  Requires Redis server >= 6.2.
@@ -52,11 +52,16 @@ from redis import Redis
52
52
  from redis_message_queue import RedisMessageQueue
53
53
 
54
54
  client = Redis.from_url("redis://localhost:6379/0", decode_responses=True)
55
- queue = RedisMessageQueue("quickstart", client=client, deduplication=True)
56
- queue.publish("hello")
55
+ queue = RedisMessageQueue(
56
+ "quickstart",
57
+ client=client,
58
+ deduplication=True,
59
+ get_deduplication_key=lambda msg: msg["id"],
60
+ )
61
+ queue.publish({"id": "msg-1", "text": "hello"})
57
62
  with queue.process_message() as message:
58
63
  if message is not None:
59
- print(f"got {message}")
64
+ print(f"got {message['text']}")
60
65
  # Expected output: got hello
61
66
  ```
62
67
 
@@ -72,10 +77,15 @@ from redis_message_queue.asyncio import RedisMessageQueue
72
77
 
73
78
  async def main():
74
79
  client = Redis.from_url("redis://localhost:6379/0", decode_responses=True)
75
- queue = RedisMessageQueue("quickstart", client=client, deduplication=True)
76
- await queue.publish("hello")
80
+ queue = RedisMessageQueue(
81
+ "quickstart",
82
+ client=client,
83
+ deduplication=True,
84
+ get_deduplication_key=lambda msg: msg["id"],
85
+ )
86
+ await queue.publish({"id": "msg-1", "text": "hello"})
77
87
  async with queue.process_message() as message:
78
- print(f"got {message}")
88
+ print(f"got {message['text']}")
79
89
  await client.aclose()
80
90
 
81
91
  asyncio.run(main()) # Expected output: got hello
@@ -89,7 +99,7 @@ asyncio.run(main()) # Expected output: got hello
89
99
 
90
100
  | Feature | Details |
91
101
  |---------|---------|
92
- | **Deduplicated publish** | Lua-scripted atomic SET NX + LPUSH prevents duplicate enqueues within a configurable TTL window (default: 1 hour), even with producer retries. Supports custom key functions for content-based deduplication. Note: deduplication is publish-side only and does not prevent duplicate *delivery* under at-least-once visibility-timeout reclaim |
102
+ | **Deduplicated publish** | Lua-scripted atomic SET NX + LPUSH prevents duplicate enqueues within a configurable TTL window (default: 1 hour), even with producer retries. Requires an explicit `get_deduplication_key` callable so your application defines what counts as a duplicate. Note: deduplication is publish-side only and does not prevent duplicate *delivery* under at-least-once visibility-timeout reclaim |
93
103
  | **Visibility-timeout redelivery** | Crashed or stalled consumers' messages are reclaimed and redelivered when a visibility timeout is configured |
94
104
  | **Success & failure logs** | Optional completed/failed queues for auditing and reprocessing, with configurable max length to prevent unbounded growth |
95
105
  | **Dead-letter queue** | Poison messages that exceed a configurable delivery count are automatically routed to a dead-letter queue instead of being redelivered indefinitely |
@@ -114,10 +124,7 @@ See [Crash recovery with visibility timeout](#crash-recovery-with-visibility-tim
114
124
  ### Deduplication
115
125
 
116
126
  ```python
117
- # Default: deduplicate by SHA-256 hash of canonical message content (1-hour TTL)
118
- queue = RedisMessageQueue("q", client=client, deduplication=True)
119
-
120
- # Custom dedup key (e.g., deduplicate by order ID only)
127
+ # Deduplicate by order ID for a 1-hour TTL
121
128
  queue = RedisMessageQueue(
122
129
  "q", client=client,
123
130
  deduplication=True,
@@ -128,12 +135,13 @@ queue = RedisMessageQueue(
128
135
  queue = RedisMessageQueue("q", client=client, deduplication=False)
129
136
  ```
130
137
 
131
- #### Custom dedup key callable must return a non-empty, high-cardinality, tenant-scoped string
138
+ #### Dedup key callable must return a non-empty, high-cardinality, tenant-scoped string
132
139
 
133
- When `get_deduplication_key` is a callable, it is called once per publish and
134
- must return a `str` that uniquely represents the deduplication scope for that
135
- message. Returning `None` or `""` raises `ConfigurationError` at publish time;
136
- returning a non-`str` value raises `TypeError`.
140
+ When `deduplication=True`, `get_deduplication_key` is required. The callable is
141
+ called once per publish and must return a `str` that uniquely represents the
142
+ deduplication scope for that message. Returning `None` or `""` raises
143
+ `ConfigurationError` at publish time; returning a non-`str` value raises
144
+ `TypeError`.
137
145
 
138
146
  Use stable, high-cardinality keys that include any tenant or account boundary
139
147
  needed by your system:
@@ -551,9 +559,9 @@ avoids the parent-construct hazard entirely.
551
559
  ### Redis memory sizing for deduplication and replay metadata
552
560
 
553
561
  When deduplication is enabled, each distinct dedup key creates one Redis string
554
- for `message_deduplication_log_ttl_seconds` (default: 3600 seconds). The default
555
- dedup key is a SHA-256 hash of the canonical message payload, so distinct
556
- payloads are distinct keys. Size Redis for:
562
+ for `message_deduplication_log_ttl_seconds` (default: 3600 seconds). The dedup
563
+ key is whatever your `get_deduplication_key` callable returns, so choose a
564
+ short, stable logical ID and size Redis for:
557
565
 
558
566
  ```text
559
567
  peak_unique_publish_rate_per_second
@@ -758,6 +766,52 @@ For a full analysis, see [docs/production-readiness.md](docs/production-readines
758
766
 
759
767
  ## Upgrading
760
768
 
769
+ ### v7 to v8 migration
770
+
771
+ v8.0.0 removes implicit dedup key generation. Deduplication is opt-in and
772
+ `deduplication=True` now requires `get_deduplication_key`. If you were relying
773
+ on v7's automatic content hash, provide the equivalent callable explicitly.
774
+
775
+ Before:
776
+
777
+ ```python
778
+ queue = RedisMessageQueue(
779
+ "orders",
780
+ client=client,
781
+ deduplication=True,
782
+ )
783
+ ```
784
+
785
+ After, prefer a stable logical ID:
786
+
787
+ ```python
788
+ queue = RedisMessageQueue(
789
+ "orders",
790
+ client=client,
791
+ deduplication=True,
792
+ get_deduplication_key=lambda msg: msg["order_id"],
793
+ )
794
+ ```
795
+
796
+ To preserve v7 content-hash behavior mechanically:
797
+
798
+ ```python
799
+ import hashlib
800
+ import json
801
+
802
+ queue = RedisMessageQueue(
803
+ "orders",
804
+ client=client,
805
+ deduplication=True,
806
+ get_deduplication_key=lambda msg: hashlib.sha256(
807
+ json.dumps(msg, sort_keys=True).encode()
808
+ ).hexdigest(),
809
+ )
810
+ ```
811
+
812
+ If you do not need publish-side deduplication, omit `deduplication` or set
813
+ `deduplication=False`.
814
+
761
815
  ### v6 to v7 migration
762
816
 
763
817
  v7.0.0 has four breaking changes to check during upgrade.
@@ -845,7 +899,7 @@ Users on redis-py 7.x and earlier are unaffected. If you installed a redis-py
845
899
  - **Do not toggle visibility timeout in either direction with messages in processing.** Messages claimed by non-VT consumers have no lease metadata, so VT-enabled consumers cannot reclaim them. Disabling VT later orphans existing lease deadline, lease token, and delivery count metadata and removes crash recovery for those in-flight messages. Drain the processing queue first.
846
900
  - **Reducing `max_delivery_count` retroactively DLQs messages.** The delivery count hash persists across restarts. Messages whose accumulated count exceeds the new limit are immediately dead-lettered on next claim.
847
901
  - **Changing `max_delivery_count` from a number to `None` leaves delivery metadata behind.** The delivery count hash continues to exist but is no longer consulted. Use this only after draining or after planning manual cleanup of the delivery-count hash.
848
- - **Changing `get_deduplication_key` changes the dedup keyspace.** Existing dedup records become inert for the duration of their TTL. Drain the queue or clear the old deduplication keys before switching between the default hash, explicit `None`, or a custom key function.
902
+ - **Changing `get_deduplication_key` changes the dedup keyspace.** Existing dedup records become inert for the duration of their TTL. Drain the queue or clear the old deduplication keys before switching key functions.
849
903
  - **Disabling `deduplication` has a retention-window overlap.** Existing dedup records remain in Redis until their TTL expires, but new publishes bypass them. Republishes that would have been suppressed under the old setting can enqueue during that window.
850
904
  - **Disabling `enable_failed_queue` stops recording handler failures.** Existing failed entries remain in Redis, but new failures are removed from `processing` without being appended to the failed queue. If `max_delivery_count=None` is also set, repeated handler failures can be dropped with no DLQ or failed-queue record; see [Dead-letter queue](#dead-letter-queue).
851
905
  - **Lowering `max_completed_length` or `max_failed_length` trims existing history.** The next completed or failed move calls `LTRIM`, so changing `None` to `N` or lowering `N` can immediately reduce historical entries to the new cap.
@@ -1,6 +1,6 @@
1
1
  # redis-message-queue
2
2
 
3
- [![PyPI Version](https://img.shields.io/badge/v7.0.1-version?color=43cd0f&style=flat&label=pypi)](https://pypi.org/project/redis-message-queue)
3
+ [![PyPI Version](https://img.shields.io/badge/v8.0.1-version?color=43cd0f&style=flat&label=pypi)](https://pypi.org/project/redis-message-queue)
4
4
  [![PyPI Downloads](https://img.shields.io/pypi/dm/redis-message-queue?color=43cd0f&style=flat&label=downloads)](https://pypistats.org/packages/redis-message-queue)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-43cd0f.svg?style=flat&label=license)](LICENSE)
6
6
  [![Maintained: yes](https://img.shields.io/badge/yes-43cd0f.svg?style=flat&label=maintained)](https://github.com/Elijas/redis-message-queue/issues)
@@ -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>=7.0.0,<8.0.0"
14
+ pip install "redis-message-queue>=8.0.0,<9.0.0"
15
15
  ```
16
16
 
17
17
  Requires Redis server >= 6.2.
@@ -26,11 +26,16 @@ from redis import Redis
26
26
  from redis_message_queue import RedisMessageQueue
27
27
 
28
28
  client = Redis.from_url("redis://localhost:6379/0", decode_responses=True)
29
- queue = RedisMessageQueue("quickstart", client=client, deduplication=True)
30
- queue.publish("hello")
29
+ queue = RedisMessageQueue(
30
+ "quickstart",
31
+ client=client,
32
+ deduplication=True,
33
+ get_deduplication_key=lambda msg: msg["id"],
34
+ )
35
+ queue.publish({"id": "msg-1", "text": "hello"})
31
36
  with queue.process_message() as message:
32
37
  if message is not None:
33
- print(f"got {message}")
38
+ print(f"got {message['text']}")
34
39
  # Expected output: got hello
35
40
  ```
36
41
 
@@ -46,10 +51,15 @@ from redis_message_queue.asyncio import RedisMessageQueue
46
51
 
47
52
  async def main():
48
53
  client = Redis.from_url("redis://localhost:6379/0", decode_responses=True)
49
- queue = RedisMessageQueue("quickstart", client=client, deduplication=True)
50
- await queue.publish("hello")
54
+ queue = RedisMessageQueue(
55
+ "quickstart",
56
+ client=client,
57
+ deduplication=True,
58
+ get_deduplication_key=lambda msg: msg["id"],
59
+ )
60
+ await queue.publish({"id": "msg-1", "text": "hello"})
51
61
  async with queue.process_message() as message:
52
- print(f"got {message}")
62
+ print(f"got {message['text']}")
53
63
  await client.aclose()
54
64
 
55
65
  asyncio.run(main()) # Expected output: got hello
@@ -63,7 +73,7 @@ asyncio.run(main()) # Expected output: got hello
63
73
 
64
74
  | Feature | Details |
65
75
  |---------|---------|
66
- | **Deduplicated publish** | Lua-scripted atomic SET NX + LPUSH prevents duplicate enqueues within a configurable TTL window (default: 1 hour), even with producer retries. Supports custom key functions for content-based deduplication. Note: deduplication is publish-side only and does not prevent duplicate *delivery* under at-least-once visibility-timeout reclaim |
76
+ | **Deduplicated publish** | Lua-scripted atomic SET NX + LPUSH prevents duplicate enqueues within a configurable TTL window (default: 1 hour), even with producer retries. Requires an explicit `get_deduplication_key` callable so your application defines what counts as a duplicate. Note: deduplication is publish-side only and does not prevent duplicate *delivery* under at-least-once visibility-timeout reclaim |
67
77
  | **Visibility-timeout redelivery** | Crashed or stalled consumers' messages are reclaimed and redelivered when a visibility timeout is configured |
68
78
  | **Success & failure logs** | Optional completed/failed queues for auditing and reprocessing, with configurable max length to prevent unbounded growth |
69
79
  | **Dead-letter queue** | Poison messages that exceed a configurable delivery count are automatically routed to a dead-letter queue instead of being redelivered indefinitely |
@@ -88,10 +98,7 @@ See [Crash recovery with visibility timeout](#crash-recovery-with-visibility-tim
88
98
  ### Deduplication
89
99
 
90
100
  ```python
91
- # Default: deduplicate by SHA-256 hash of canonical message content (1-hour TTL)
92
- queue = RedisMessageQueue("q", client=client, deduplication=True)
93
-
94
- # Custom dedup key (e.g., deduplicate by order ID only)
101
+ # Deduplicate by order ID for a 1-hour TTL
95
102
  queue = RedisMessageQueue(
96
103
  "q", client=client,
97
104
  deduplication=True,
@@ -102,12 +109,13 @@ queue = RedisMessageQueue(
102
109
  queue = RedisMessageQueue("q", client=client, deduplication=False)
103
110
  ```
104
111
 
105
- #### Custom dedup key callable must return a non-empty, high-cardinality, tenant-scoped string
112
+ #### Dedup key callable must return a non-empty, high-cardinality, tenant-scoped string
106
113
 
107
- When `get_deduplication_key` is a callable, it is called once per publish and
108
- must return a `str` that uniquely represents the deduplication scope for that
109
- message. Returning `None` or `""` raises `ConfigurationError` at publish time;
110
- returning a non-`str` value raises `TypeError`.
114
+ When `deduplication=True`, `get_deduplication_key` is required. The callable is
115
+ called once per publish and must return a `str` that uniquely represents the
116
+ deduplication scope for that message. Returning `None` or `""` raises
117
+ `ConfigurationError` at publish time; returning a non-`str` value raises
118
+ `TypeError`.
111
119
 
112
120
  Use stable, high-cardinality keys that include any tenant or account boundary
113
121
  needed by your system:
@@ -525,9 +533,9 @@ avoids the parent-construct hazard entirely.
525
533
  ### Redis memory sizing for deduplication and replay metadata
526
534
 
527
535
  When deduplication is enabled, each distinct dedup key creates one Redis string
528
- for `message_deduplication_log_ttl_seconds` (default: 3600 seconds). The default
529
- dedup key is a SHA-256 hash of the canonical message payload, so distinct
530
- payloads are distinct keys. Size Redis for:
536
+ for `message_deduplication_log_ttl_seconds` (default: 3600 seconds). The dedup
537
+ key is whatever your `get_deduplication_key` callable returns, so choose a
538
+ short, stable logical ID and size Redis for:
531
539
 
532
540
  ```text
533
541
  peak_unique_publish_rate_per_second
@@ -732,6 +740,52 @@ For a full analysis, see [docs/production-readiness.md](docs/production-readines
732
740
 
733
741
  ## Upgrading
734
742
 
743
+ ### v7 to v8 migration
744
+
745
+ v8.0.0 removes implicit dedup key generation. Deduplication is opt-in and
746
+ `deduplication=True` now requires `get_deduplication_key`. If you were relying
747
+ on v7's automatic content hash, provide the equivalent callable explicitly.
748
+
749
+ Before:
750
+
751
+ ```python
752
+ queue = RedisMessageQueue(
753
+ "orders",
754
+ client=client,
755
+ deduplication=True,
756
+ )
757
+ ```
758
+
759
+ After, prefer a stable logical ID:
760
+
761
+ ```python
762
+ queue = RedisMessageQueue(
763
+ "orders",
764
+ client=client,
765
+ deduplication=True,
766
+ get_deduplication_key=lambda msg: msg["order_id"],
767
+ )
768
+ ```
769
+
770
+ To preserve v7 content-hash behavior mechanically:
771
+
772
+ ```python
773
+ import hashlib
774
+ import json
775
+
776
+ queue = RedisMessageQueue(
777
+ "orders",
778
+ client=client,
779
+ deduplication=True,
780
+ get_deduplication_key=lambda msg: hashlib.sha256(
781
+ json.dumps(msg, sort_keys=True).encode()
782
+ ).hexdigest(),
783
+ )
784
+ ```
785
+
786
+ If you do not need publish-side deduplication, omit `deduplication` or set
787
+ `deduplication=False`.
788
+
735
789
  ### v6 to v7 migration
736
790
 
737
791
  v7.0.0 has four breaking changes to check during upgrade.
@@ -819,7 +873,7 @@ Users on redis-py 7.x and earlier are unaffected. If you installed a redis-py
819
873
  - **Do not toggle visibility timeout in either direction with messages in processing.** Messages claimed by non-VT consumers have no lease metadata, so VT-enabled consumers cannot reclaim them. Disabling VT later orphans existing lease deadline, lease token, and delivery count metadata and removes crash recovery for those in-flight messages. Drain the processing queue first.
820
874
  - **Reducing `max_delivery_count` retroactively DLQs messages.** The delivery count hash persists across restarts. Messages whose accumulated count exceeds the new limit are immediately dead-lettered on next claim.
821
875
  - **Changing `max_delivery_count` from a number to `None` leaves delivery metadata behind.** The delivery count hash continues to exist but is no longer consulted. Use this only after draining or after planning manual cleanup of the delivery-count hash.
822
- - **Changing `get_deduplication_key` changes the dedup keyspace.** Existing dedup records become inert for the duration of their TTL. Drain the queue or clear the old deduplication keys before switching between the default hash, explicit `None`, or a custom key function.
876
+ - **Changing `get_deduplication_key` changes the dedup keyspace.** Existing dedup records become inert for the duration of their TTL. Drain the queue or clear the old deduplication keys before switching key functions.
823
877
  - **Disabling `deduplication` has a retention-window overlap.** Existing dedup records remain in Redis until their TTL expires, but new publishes bypass them. Republishes that would have been suppressed under the old setting can enqueue during that window.
824
878
  - **Disabling `enable_failed_queue` stops recording handler failures.** Existing failed entries remain in Redis, but new failures are removed from `processing` without being appended to the failed queue. If `max_delivery_count=None` is also set, repeated handler failures can be dropped with no DLQ or failed-queue record; see [Dead-letter queue](#dead-letter-queue).
825
879
  - **Lowering `max_completed_length` or `max_failed_length` trims existing history.** The next completed or failed move calls `LTRIM`, so changing `None` to `N` or lowering `N` can immediately reduce historical entries to the new cap.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "redis-message-queue"
3
- version = "7.0.1"
3
+ version = "8.0.1"
4
4
  description = "Python message queuing with Redis and message deduplication"
5
5
  authors = ["Elijas <4084885+Elijas@users.noreply.github.com>"]
6
6
  readme = "README.md"
@@ -32,6 +32,11 @@ DEFAULT_PENDING_OVERLOAD_BLOCK_TIMEOUT_SECONDS = 1.0
32
32
  INTERRUPTIBLE_RETRY_SLEEP_POLL_SECONDS = 0.05
33
33
  PENDING_OVERLOAD_LUA_SENTINEL = -1
34
34
  PENDING_OVERLOAD_POLICIES = ("raise", "drop_oldest", "block")
35
+ DEDUPLICATION_REQUIRES_KEY_MESSAGE = (
36
+ "deduplication=True requires get_deduplication_key (callable returning a non-empty str). "
37
+ "Pass a callable like `lambda msg: msg['id']` (recommended: a stable logical ID), "
38
+ "or set deduplication=False."
39
+ )
35
40
 
36
41
 
37
42
  def is_redis_retryable_exception(exception):
@@ -301,6 +306,15 @@ def validate_gateway_parameters(
301
306
  )
302
307
 
303
308
 
309
+ def validate_dedup_configuration(
310
+ *,
311
+ deduplication: bool,
312
+ get_deduplication_key: object,
313
+ ) -> None:
314
+ if deduplication and get_deduplication_key is None:
315
+ raise ConfigurationError(DEDUPLICATION_REQUIRES_KEY_MESSAGE)
316
+
317
+
304
318
  def validate_pending_backpressure_parameters(
305
319
  max_pending_length: int | None,
306
320
  pending_overload_policy: str,