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.
Files changed (43) hide show
  1. vercel/queue/__init__.py +8 -0
  2. vercel/queue/__main__.py +10 -0
  3. vercel/queue/_internal/__init__.py +1 -0
  4. vercel/queue/_internal/api_async.py +219 -0
  5. vercel/queue/_internal/api_common.py +104 -0
  6. vercel/queue/_internal/api_sync.py +137 -0
  7. vercel/queue/_internal/asgi.py +145 -0
  8. vercel/queue/_internal/asynctools.py +40 -0
  9. vercel/queue/_internal/cli.py +313 -0
  10. vercel/queue/_internal/client.py +1109 -0
  11. vercel/queue/_internal/client_sync.py +432 -0
  12. vercel/queue/_internal/config.py +160 -0
  13. vercel/queue/_internal/constants.py +50 -0
  14. vercel/queue/_internal/devserver.py +205 -0
  15. vercel/queue/_internal/embedded.py +1900 -0
  16. vercel/queue/_internal/errors.py +262 -0
  17. vercel/queue/_internal/http.py +634 -0
  18. vercel/queue/_internal/lease.py +1007 -0
  19. vercel/queue/_internal/log.py +143 -0
  20. vercel/queue/_internal/messages.py +122 -0
  21. vercel/queue/_internal/multipart.py +255 -0
  22. vercel/queue/_internal/names.py +101 -0
  23. vercel/queue/_internal/polling.py +200 -0
  24. vercel/queue/_internal/push.py +246 -0
  25. vercel/queue/_internal/response.py +111 -0
  26. vercel/queue/_internal/retry.py +86 -0
  27. vercel/queue/_internal/streams.py +313 -0
  28. vercel/queue/_internal/subscribers.py +1025 -0
  29. vercel/queue/_internal/transports.py +363 -0
  30. vercel/queue/_internal/types.py +300 -0
  31. vercel/queue/_internal/typeutils.py +203 -0
  32. vercel/queue/devserver.py +24 -0
  33. vercel/queue/embedded.py +50 -0
  34. vercel/queue/py.typed +1 -0
  35. vercel/queue/sync.py +8 -0
  36. vercel/queue/testing/__init__.py +14 -0
  37. vercel/queue/testing/pytest.py +42 -0
  38. vercel/queue/testing/state.py +32 -0
  39. vercel/queue/version.py +3 -0
  40. vercel_queue-0.7.0.dist-info/METADATA +681 -0
  41. vercel_queue-0.7.0.dist-info/RECORD +43 -0
  42. vercel_queue-0.7.0.dist-info/WHEEL +4 -0
  43. 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
+ )