vercel-queue 0.7.0__py3-none-any.whl
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.
- vercel/queue/__init__.py +8 -0
- vercel/queue/__main__.py +10 -0
- vercel/queue/_internal/__init__.py +1 -0
- vercel/queue/_internal/api_async.py +219 -0
- vercel/queue/_internal/api_common.py +104 -0
- vercel/queue/_internal/api_sync.py +137 -0
- vercel/queue/_internal/asgi.py +145 -0
- vercel/queue/_internal/asynctools.py +40 -0
- vercel/queue/_internal/cli.py +313 -0
- vercel/queue/_internal/client.py +1109 -0
- vercel/queue/_internal/client_sync.py +432 -0
- vercel/queue/_internal/config.py +160 -0
- vercel/queue/_internal/constants.py +50 -0
- vercel/queue/_internal/devserver.py +205 -0
- vercel/queue/_internal/embedded.py +1900 -0
- vercel/queue/_internal/errors.py +262 -0
- vercel/queue/_internal/http.py +634 -0
- vercel/queue/_internal/lease.py +1007 -0
- vercel/queue/_internal/log.py +143 -0
- vercel/queue/_internal/messages.py +122 -0
- vercel/queue/_internal/multipart.py +255 -0
- vercel/queue/_internal/names.py +101 -0
- vercel/queue/_internal/polling.py +200 -0
- vercel/queue/_internal/push.py +246 -0
- vercel/queue/_internal/response.py +111 -0
- vercel/queue/_internal/retry.py +86 -0
- vercel/queue/_internal/streams.py +313 -0
- vercel/queue/_internal/subscribers.py +1025 -0
- vercel/queue/_internal/transports.py +363 -0
- vercel/queue/_internal/types.py +300 -0
- vercel/queue/_internal/typeutils.py +203 -0
- vercel/queue/devserver.py +24 -0
- vercel/queue/embedded.py +50 -0
- vercel/queue/py.typed +1 -0
- vercel/queue/sync.py +8 -0
- vercel/queue/testing/__init__.py +14 -0
- vercel/queue/testing/pytest.py +42 -0
- vercel/queue/testing/state.py +32 -0
- vercel/queue/version.py +3 -0
- vercel_queue-0.7.0.dist-info/METADATA +681 -0
- vercel_queue-0.7.0.dist-info/RECORD +43 -0
- vercel_queue-0.7.0.dist-info/WHEEL +4 -0
- vercel_queue-0.7.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
from .types import MessageID
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class QueueError(Exception):
|
|
9
|
+
"""Base exception for Vercel Queue errors."""
|
|
10
|
+
|
|
11
|
+
status_code: int | None = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RetryableError(QueueError):
|
|
15
|
+
"""Mixin for errors that follow-up operations may retry."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def retryable(self) -> bool:
|
|
19
|
+
return True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BadRequestError(QueueError, ValueError):
|
|
23
|
+
status_code = 400
|
|
24
|
+
default_message: Final[str] = "Invalid parameters"
|
|
25
|
+
|
|
26
|
+
def __init__(self, message: str | None = None) -> None:
|
|
27
|
+
super().__init__(message or self.default_message)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UnauthorizedError(QueueError, PermissionError):
|
|
31
|
+
status_code = 401
|
|
32
|
+
default_message: Final[str] = "Missing or invalid authentication token"
|
|
33
|
+
|
|
34
|
+
def __init__(self, message: str | None = None) -> None:
|
|
35
|
+
super().__init__(message or self.default_message)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ForbiddenError(QueueError, PermissionError):
|
|
39
|
+
status_code = 403
|
|
40
|
+
default_message: Final[str] = "Queue environment does not match token environment"
|
|
41
|
+
|
|
42
|
+
def __init__(self, message: str | None = None) -> None:
|
|
43
|
+
super().__init__(message or self.default_message)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class CommunicationError(RetryableError, ConnectionError):
|
|
47
|
+
"""Network-level failure while communicating with the queue service.
|
|
48
|
+
|
|
49
|
+
Wraps the underlying HTTP library's transport errors (connect failures,
|
|
50
|
+
resets, TLS errors, timeouts) so callers do not need to depend on it.
|
|
51
|
+
Subclasses the built-in :class:`ConnectionError` so generic
|
|
52
|
+
connection-failure handling catches it without knowing about this package.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ProtocolError(QueueError):
|
|
57
|
+
"""The queue service returned malformed or incomplete protocol metadata."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PayloadValidationError(QueueError, ValueError):
|
|
61
|
+
"""Raised when queue payload validation rejects a message."""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SubscriptionError(QueueError, TypeError):
|
|
65
|
+
"""Raised when a queue subscriber cannot be registered."""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class DuplicateSubscriptionError(SubscriptionError):
|
|
69
|
+
"""Raised when a queue subscriber overlaps an existing local registration."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ServiceError(QueueError):
|
|
73
|
+
status_code: int
|
|
74
|
+
default_message = "Unexpected queue response"
|
|
75
|
+
|
|
76
|
+
def __init__(self, status_code: int, message: str | None = None) -> None:
|
|
77
|
+
self.status_code = int(status_code)
|
|
78
|
+
super().__init__(message or f"{self.default_message}: {self.status_code}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class InternalServerError(ServiceError):
|
|
82
|
+
status_code = 500
|
|
83
|
+
default_message = "Unexpected server error"
|
|
84
|
+
|
|
85
|
+
def __init__(self, message: str | None = None, status_code: int = 500) -> None:
|
|
86
|
+
super().__init__(status_code, message or self.default_message)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TokenResolutionError(QueueError):
|
|
90
|
+
status_code = 500
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class DeploymentResolutionError(QueueError):
|
|
94
|
+
status_code = 500
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class DuplicateIdempotencyKeyError(QueueError):
|
|
98
|
+
status_code = 409
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ConsumerDiscoveryError(QueueError):
|
|
102
|
+
status_code = 502
|
|
103
|
+
|
|
104
|
+
def __init__(self, message: str | None = None) -> None:
|
|
105
|
+
super().__init__(message or "Failed to discover queue consumer")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ConsumerRegistryNotConfiguredError(QueueError):
|
|
109
|
+
status_code = 503
|
|
110
|
+
|
|
111
|
+
def __init__(self, message: str | None = None) -> None:
|
|
112
|
+
super().__init__(message or "Queue consumer registry is not configured")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class InvalidLimitError(QueueError, ValueError):
|
|
116
|
+
status_code = 400
|
|
117
|
+
|
|
118
|
+
def __init__(self, limit: int, min_value: int = 1, max_value: int = 10) -> None:
|
|
119
|
+
self.limit = int(limit)
|
|
120
|
+
self.min = int(min_value)
|
|
121
|
+
self.max = int(max_value)
|
|
122
|
+
super().__init__(
|
|
123
|
+
f"Invalid limit: {self.limit}. Limit must be between {self.min} and {self.max}."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class MessageNotFoundError(QueueError, LookupError):
|
|
128
|
+
status_code = 404
|
|
129
|
+
|
|
130
|
+
def __init__(self, message_id: MessageID) -> None:
|
|
131
|
+
self.message_id = message_id
|
|
132
|
+
super().__init__(f"Message {message_id} not found")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class MessageAlreadyProcessedError(QueueError):
|
|
136
|
+
status_code = 410
|
|
137
|
+
|
|
138
|
+
def __init__(self, message_id: MessageID) -> None:
|
|
139
|
+
self.message_id = message_id
|
|
140
|
+
super().__init__(f"Message {message_id} has already been processed")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class MessageUnavailableError(QueueError):
|
|
144
|
+
status_code = 409
|
|
145
|
+
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
message_id: MessageID,
|
|
149
|
+
reason: str | None = None,
|
|
150
|
+
*,
|
|
151
|
+
original_message_id: MessageID | None = None,
|
|
152
|
+
) -> None:
|
|
153
|
+
self.message_id = message_id
|
|
154
|
+
self.reason = reason
|
|
155
|
+
self.original_message_id = original_message_id
|
|
156
|
+
suffix = f": {reason}" if reason else ""
|
|
157
|
+
super().__init__(f"Message {message_id} not available for processing{suffix}")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class MessageCorruptedError(QueueError):
|
|
161
|
+
status_code = 500
|
|
162
|
+
|
|
163
|
+
def __init__(self, message_id: MessageID, reason: str) -> None:
|
|
164
|
+
self.message_id = message_id
|
|
165
|
+
self.reason = reason
|
|
166
|
+
super().__init__(f"Message {message_id} is corrupted: {reason}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class MessageLockedError(QueueError):
|
|
170
|
+
status_code = 409
|
|
171
|
+
|
|
172
|
+
def __init__(
|
|
173
|
+
self,
|
|
174
|
+
message_id: MessageID,
|
|
175
|
+
retry_after: int | None = None,
|
|
176
|
+
reason: str | None = None,
|
|
177
|
+
) -> None:
|
|
178
|
+
self.message_id = message_id
|
|
179
|
+
self.retry_after = retry_after
|
|
180
|
+
self.reason = reason
|
|
181
|
+
reason_msg = f" {reason}." if reason else ""
|
|
182
|
+
retry_msg = (
|
|
183
|
+
f" Retry after {retry_after} seconds."
|
|
184
|
+
if retry_after is not None
|
|
185
|
+
else " Try again later."
|
|
186
|
+
)
|
|
187
|
+
super().__init__(f"Message {message_id} is temporarily locked.{reason_msg}{retry_msg}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class MessageNotInFlightError(MessageLockedError):
|
|
191
|
+
def __init__(self, message_id: MessageID, retry_after: int | None = None) -> None:
|
|
192
|
+
super().__init__(message_id, retry_after, "Message is not currently in-flight")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class MessageLeaseExpiredError(MessageLockedError):
|
|
196
|
+
def __init__(self, message_id: MessageID, retry_after: int | None = None) -> None:
|
|
197
|
+
super().__init__(message_id, retry_after, "Message lease has expired")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class ReceiptHandleMismatchError(MessageLockedError):
|
|
201
|
+
def __init__(self, message_id: MessageID, retry_after: int | None = None) -> None:
|
|
202
|
+
super().__init__(
|
|
203
|
+
message_id,
|
|
204
|
+
retry_after,
|
|
205
|
+
"Receipt handle does not match current lease holder",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class UnhandledMessageError(QueueError):
|
|
210
|
+
status_code = 500
|
|
211
|
+
|
|
212
|
+
def __init__(self, topic: str | None, consumer_group: str | None = None) -> None:
|
|
213
|
+
self.topic = topic
|
|
214
|
+
self.consumer_group = consumer_group
|
|
215
|
+
suffix = f" and consumer group {consumer_group!r}" if consumer_group is not None else ""
|
|
216
|
+
super().__init__(f"No queue subscribers found for topic {topic!r}{suffix}.")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class ThrottledError(RetryableError):
|
|
220
|
+
status_code = 429
|
|
221
|
+
|
|
222
|
+
def __init__(self, retry_after: int | None = None) -> None:
|
|
223
|
+
self.retry_after = retry_after
|
|
224
|
+
suffix = f", Retry-After={retry_after}" if retry_after is not None else ""
|
|
225
|
+
super().__init__(f"Throttled by queue service{suffix}")
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def retryable(self) -> bool:
|
|
229
|
+
return self.retry_after is not None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# Only add public symbols to __all__; internal helpers must stay unexported.
|
|
233
|
+
__all__: tuple[str, ...] = (
|
|
234
|
+
"BadRequestError",
|
|
235
|
+
"CommunicationError",
|
|
236
|
+
"ConsumerDiscoveryError",
|
|
237
|
+
"ConsumerRegistryNotConfiguredError",
|
|
238
|
+
"DeploymentResolutionError",
|
|
239
|
+
"DuplicateIdempotencyKeyError",
|
|
240
|
+
"DuplicateSubscriptionError",
|
|
241
|
+
"ForbiddenError",
|
|
242
|
+
"InternalServerError",
|
|
243
|
+
"InvalidLimitError",
|
|
244
|
+
"MessageAlreadyProcessedError",
|
|
245
|
+
"MessageCorruptedError",
|
|
246
|
+
"MessageLeaseExpiredError",
|
|
247
|
+
"MessageLockedError",
|
|
248
|
+
"MessageNotFoundError",
|
|
249
|
+
"MessageNotInFlightError",
|
|
250
|
+
"MessageUnavailableError",
|
|
251
|
+
"PayloadValidationError",
|
|
252
|
+
"ProtocolError",
|
|
253
|
+
"QueueError",
|
|
254
|
+
"ReceiptHandleMismatchError",
|
|
255
|
+
"RetryableError",
|
|
256
|
+
"ServiceError",
|
|
257
|
+
"SubscriptionError",
|
|
258
|
+
"ThrottledError",
|
|
259
|
+
"TokenResolutionError",
|
|
260
|
+
"UnauthorizedError",
|
|
261
|
+
"UnhandledMessageError",
|
|
262
|
+
)
|