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,432 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, TypeVar, overload
4
+
5
+ import concurrent.futures
6
+ from collections.abc import Iterator, Mapping
7
+ from types import TracebackType
8
+
9
+ from vercel.headers import HeadersContext
10
+
11
+ from .asynctools import iter_async_iterator, iter_coroutine
12
+ from .client import BaseQueueClient, Delivery
13
+ from .config import CURRENT_DEPLOYMENT, BaseUrl, DeploymentOption
14
+ from .http import (
15
+ AsyncQueueRuntime,
16
+ HttpResponse,
17
+ PushDeliveryBody,
18
+ SyncHttpClientFactory,
19
+ SyncQueueRuntime,
20
+ )
21
+ from .lease import (
22
+ LeaseRenewal,
23
+ finalize_payload_sync,
24
+ processing_lease_seconds,
25
+ retry_message_after_sync,
26
+ )
27
+ from .log import debug_log_for_msg
28
+ from .messages import sync_message_payload
29
+ from .names import SanitizedName
30
+ from .polling import run_poll_and_handle_sync, start_sync_polling_thread
31
+ from .push import accept_input_sync, parse_push_delivery_metadata
32
+ from .retry import retry_sync_follow_up
33
+ from .streams import SyncStreamPayload, SyncTextStreamPayload
34
+ from .subscribers import (
35
+ QueueSubscriber,
36
+ call_subscribers_sync,
37
+ infer_subscriber_transport,
38
+ reject_async_subscriber_for_sync,
39
+ )
40
+ from .types import (
41
+ Duration,
42
+ Handoff,
43
+ Message,
44
+ MessageID,
45
+ MessageMetadata,
46
+ RawHeaders,
47
+ RetryAfter,
48
+ StrContainer,
49
+ Topic,
50
+ Transport,
51
+ )
52
+
53
+ T = TypeVar("T")
54
+
55
+
56
+ class QueueClient(BaseQueueClient):
57
+ """Synchronous Vercel Queues client.
58
+
59
+ Args:
60
+ token: Vercel OIDC token. Defaults to environment/OIDC resolution.
61
+ region: Queue region, such as ``"iad1"``.
62
+ base_url: Custom service base URL or region resolver for tests or proxies.
63
+ deployment: Deployment partition selection. Use ``ALL_DEPLOYMENTS``
64
+ to omit ``Vqs-Deployment-Id``.
65
+ headers: Default custom headers for send requests.
66
+ timeout: HTTP timeout in seconds.
67
+ http_client_factory: Optional factory for the underlying HTTP client.
68
+
69
+ """
70
+
71
+ def __init__(
72
+ self,
73
+ *,
74
+ token: str | None = None,
75
+ region: str | None = None,
76
+ base_url: BaseUrl | None = None,
77
+ deployment: DeploymentOption = CURRENT_DEPLOYMENT,
78
+ headers: Mapping[str, str] | None = None,
79
+ timeout: Duration | None = 10.0,
80
+ http_client_factory: SyncHttpClientFactory | None = None,
81
+ ) -> None:
82
+ super().__init__(
83
+ runtime=SyncQueueRuntime(
84
+ timeout=timeout,
85
+ client_factory=http_client_factory,
86
+ ),
87
+ token=token,
88
+ region=region,
89
+ base_url=base_url,
90
+ deployment=deployment,
91
+ headers=headers,
92
+ timeout=timeout,
93
+ )
94
+
95
+ def send(
96
+ self,
97
+ topic: str | SanitizedName | Topic[T],
98
+ payload: T,
99
+ *,
100
+ idempotency_key: str | None = None,
101
+ retention: Duration | None = None,
102
+ delay: Duration | None = None,
103
+ deployment: DeploymentOption = CURRENT_DEPLOYMENT,
104
+ headers: Mapping[str, str] | None = None,
105
+ ) -> MessageID | None:
106
+ """Send a message to a topic."""
107
+ return iter_coroutine(
108
+ self._send(
109
+ topic,
110
+ payload,
111
+ idempotency_key=idempotency_key,
112
+ retention=retention,
113
+ delay=delay,
114
+ deployment=deployment,
115
+ headers=headers,
116
+ )
117
+ )
118
+
119
+ def _accept_impl(
120
+ self,
121
+ raw_body: PushDeliveryBody | HttpResponse,
122
+ headers: RawHeaders | None = None,
123
+ *,
124
+ transport: Transport[T] | None = None,
125
+ lease_duration: Duration | None = None,
126
+ ) -> Message[T]:
127
+ raw_body, headers = accept_input_sync(raw_body, headers)
128
+ message = iter_coroutine(
129
+ self._accept(
130
+ raw_body,
131
+ headers,
132
+ transport=transport,
133
+ lease_duration=lease_duration,
134
+ )
135
+ )
136
+ return sync_message_payload(message)
137
+
138
+ @overload
139
+ def accept_and_handle(
140
+ self,
141
+ raw_body: PushDeliveryBody,
142
+ headers: RawHeaders,
143
+ *,
144
+ lease_duration: Duration | None = None,
145
+ ) -> None: ...
146
+
147
+ @overload
148
+ def accept_and_handle(
149
+ self,
150
+ raw_body: HttpResponse,
151
+ headers: None = None,
152
+ *,
153
+ lease_duration: Duration | None = None,
154
+ ) -> None: ...
155
+
156
+ def accept_and_handle(
157
+ self,
158
+ raw_body: PushDeliveryBody | HttpResponse,
159
+ headers: RawHeaders | None = None,
160
+ *,
161
+ lease_duration: Duration | None = None,
162
+ ) -> None:
163
+ """Accept a push callback and dispatch matching subscribers."""
164
+ self._accept_and_handle(
165
+ raw_body,
166
+ headers,
167
+ lease_duration=lease_duration,
168
+ )
169
+
170
+ def _accept_and_handle(
171
+ self,
172
+ raw_body: PushDeliveryBody | HttpResponse,
173
+ headers: RawHeaders | None = None,
174
+ *,
175
+ transport: Transport[T] | None = None,
176
+ lease_duration: Duration | None = None,
177
+ ) -> None:
178
+ raw_body, headers = accept_input_sync(raw_body, headers)
179
+ processing_lease_duration = processing_lease_seconds(lease_duration)
180
+ if transport is None:
181
+ metadata = parse_push_delivery_metadata(headers)
182
+ transport = infer_subscriber_transport(metadata)
183
+ # Delivery headers carry the auth context for follow-up queue calls,
184
+ # including lease renewals running outside this call stack.
185
+ with HeadersContext(headers).use():
186
+ message = sync_message_payload(
187
+ iter_coroutine(
188
+ self._accept(
189
+ raw_body,
190
+ headers,
191
+ transport=transport,
192
+ lease_duration=processing_lease_duration,
193
+ )
194
+ )
195
+ )
196
+ lifecycle = _MessageLifecycle(
197
+ message,
198
+ client=self,
199
+ lease_duration=processing_lease_duration,
200
+ )
201
+ with lifecycle:
202
+ call_subscribers_sync(message)
203
+
204
+ @overload
205
+ def poll(
206
+ self,
207
+ topic: Topic[SyncStreamPayload],
208
+ consumer_group: str | SanitizedName,
209
+ *,
210
+ limit: int = 1,
211
+ lease_duration: Duration | None = None,
212
+ ) -> Iterator[Delivery[SyncStreamPayload]]: ...
213
+
214
+ @overload
215
+ def poll(
216
+ self,
217
+ topic: Topic[SyncTextStreamPayload],
218
+ consumer_group: str | SanitizedName,
219
+ *,
220
+ limit: int = 1,
221
+ lease_duration: Duration | None = None,
222
+ ) -> Iterator[Delivery[SyncTextStreamPayload]]: ...
223
+
224
+ @overload
225
+ def poll(
226
+ self,
227
+ topic: Topic[T],
228
+ consumer_group: str | SanitizedName,
229
+ *,
230
+ limit: int = 1,
231
+ lease_duration: Duration | None = None,
232
+ ) -> Iterator[Delivery[T]]: ...
233
+
234
+ @overload
235
+ def poll(
236
+ self,
237
+ topic: str,
238
+ consumer_group: str | SanitizedName,
239
+ *,
240
+ limit: int = 1,
241
+ lease_duration: Duration | None = None,
242
+ ) -> Iterator[Delivery[Any]]: ...
243
+
244
+ @overload
245
+ def poll(
246
+ self,
247
+ topic: SanitizedName,
248
+ consumer_group: str | SanitizedName,
249
+ *,
250
+ limit: int = 1,
251
+ lease_duration: Duration | None = None,
252
+ ) -> Iterator[Delivery[Any]]: ...
253
+
254
+ def poll(
255
+ self,
256
+ topic: str | SanitizedName | Topic[Any],
257
+ consumer_group: str | SanitizedName,
258
+ *,
259
+ limit: int = 1,
260
+ lease_duration: Duration | None = None,
261
+ ) -> Iterator[Delivery[Any]]:
262
+ """Poll available deliveries for a consumer group."""
263
+ messages = self._receive(
264
+ topic,
265
+ consumer_group,
266
+ limit=limit,
267
+ lease_duration=lease_duration,
268
+ )
269
+ for message in iter_async_iterator(messages):
270
+ yield Delivery(
271
+ sync_message_payload(message),
272
+ client=self,
273
+ lease_duration=lease_duration,
274
+ )
275
+
276
+ def poll_and_handle(
277
+ self,
278
+ subscriber: QueueSubscriber[..., Any],
279
+ *,
280
+ topics: StrContainer | None = None,
281
+ interval: Duration = 1.0,
282
+ limit: int | None = None,
283
+ lease_duration: Duration | None = None,
284
+ ) -> concurrent.futures.Future[None]:
285
+ """Start a background thread that polls and dispatches one subscriber."""
286
+ reject_async_subscriber_for_sync(subscriber)
287
+ return start_sync_polling_thread(
288
+ lambda stop: run_poll_and_handle_sync(
289
+ poll=self.poll,
290
+ subscriber=subscriber,
291
+ topics=topics,
292
+ interval=interval,
293
+ limit=limit,
294
+ lease_duration=lease_duration,
295
+ stop=stop,
296
+ ),
297
+ name="vercel-queue-poll-and-handle",
298
+ )
299
+
300
+ def acknowledge(self, message: Message[T] | MessageMetadata) -> None:
301
+ """Acknowledge a received message.
302
+
303
+ This is a lower-level API for integrations that manage message
304
+ lifecycles manually. Application code should usually process messages
305
+ through ``poll`` delivery context managers or ``accept_and_handle`` so
306
+ acknowledgement happens automatically.
307
+ """
308
+ retry_sync_follow_up(
309
+ lambda: iter_coroutine(self._acknowledge(message)),
310
+ event_prefix="ack",
311
+ )
312
+
313
+ def extend_lease(
314
+ self,
315
+ message: Message[T] | MessageMetadata,
316
+ duration: Duration,
317
+ ) -> None:
318
+ """Extend message processing.
319
+
320
+ This is a lower-level API for integrations that manage message
321
+ lifecycles manually. Application code should usually rely on delivery
322
+ context managers or ``LeaseRenewal`` to keep leases alive while a
323
+ handler runs.
324
+ """
325
+ retry_sync_follow_up(
326
+ lambda: iter_coroutine(self._extend_lease(message, duration)),
327
+ event_prefix="visibility",
328
+ )
329
+
330
+ def retry_after(
331
+ self,
332
+ message: Message[T] | MessageMetadata,
333
+ delay: Duration,
334
+ ) -> None:
335
+ """Request redelivery of a received message after ``delay``.
336
+
337
+ This is a lower-level API for integrations that manage message
338
+ lifecycles manually. Application code should usually raise
339
+ ``RetryAfter`` from a handler instead of calling this directly.
340
+
341
+ Unlike ``extend_lease``, this is a settlement-style follow-up:
342
+ transient failures are retried, and a lease that no longer exists is
343
+ tolerated with a warning because the message will be redelivered
344
+ anyway.
345
+ """
346
+ retry_message_after_sync(message, delay, self._extend_lease_sync)
347
+
348
+ def _extend_lease_sync(
349
+ self,
350
+ message: Message[Any] | MessageMetadata,
351
+ duration: Duration,
352
+ ) -> None:
353
+ iter_coroutine(self._extend_lease(message, duration))
354
+
355
+ async def _renew_lease(self, message: Message[Any], duration: Duration) -> None:
356
+ runtime = AsyncQueueRuntime(timeout=self.timeout)
357
+ runtime.configure_base_url(self.base_url)
358
+ await self._extend_lease(message, duration, runtime=runtime)
359
+
360
+
361
+ class _MessageLifecycle:
362
+ def __init__(
363
+ self,
364
+ message: Message[Any],
365
+ *,
366
+ client: QueueClient,
367
+ lease_duration: Duration | None = None,
368
+ ) -> None:
369
+ self._message = message
370
+ self._client = client
371
+ self._renewal = LeaseRenewal(
372
+ message,
373
+ client=client,
374
+ lease_duration=processing_lease_seconds(lease_duration),
375
+ )
376
+
377
+ def __enter__(self) -> LeaseRenewal:
378
+ self._renewal.__enter__()
379
+ return self._renewal
380
+
381
+ def __exit__(
382
+ self,
383
+ exc_type: type[BaseException] | None,
384
+ exc: BaseException | None,
385
+ traceback: TracebackType | None,
386
+ ) -> bool | None:
387
+ # ACK, Handoff, and RetryAfter deliberately use different renewal shutdown
388
+ # semantics. Server-side, background renewal and final directives are
389
+ # all conditional writes against the same active lease record. If a
390
+ # renewal races with ACK, the final state is safe either way: renewal
391
+ # first still leaves the same receipt handle valid for ACK, and ACK
392
+ # first makes the later renewal fail with a 4xx because the message is
393
+ # no longer INFLIGHT. Waiting for cancellation in the success path only
394
+ # adds tail latency without improving correctness.
395
+ #
396
+ # Handoff leaves the delivery open for another owner, so a best-effort
397
+ # stop failure can keep the hidden renewal alive past handoff.
398
+ #
399
+ # RetryAfter is different because it is itself a visibility update. A
400
+ # late automatic renewal can overwrite the shorter retry delay the
401
+ # handler requested, delaying redelivery until the normal processing
402
+ # lease. For that path we wait for the renewal worker to finish or
403
+ # cancel any in-flight extension before applying the RetryAfter
404
+ # visibility change. If stopping times out, we still apply the
405
+ # directive: the worst remaining case is the pre-existing race, while
406
+ # refusing to apply RetryAfter would always leave the long lease behind.
407
+ wait_for_renewal_stop = isinstance(exc, (Handoff, RetryAfter))
408
+ try:
409
+ self._renewal.stop(wait=wait_for_renewal_stop)
410
+ finally:
411
+ finalize_payload_sync(self._message.payload)
412
+
413
+ if isinstance(exc, Handoff):
414
+ debug_log_for_msg("message.handoff", self._message)
415
+ return True
416
+ if isinstance(exc, RetryAfter):
417
+ self._renewal.extend(exc.timeout_seconds)
418
+ debug_log_for_msg(
419
+ "message.retry_after",
420
+ self._message,
421
+ retry_after_seconds=exc.timeout_seconds,
422
+ )
423
+ return True
424
+ if exc is None:
425
+ # acknowledge() already retries transient failures internally.
426
+ self._client.acknowledge(self._message)
427
+ debug_log_for_msg("message.ack", self._message)
428
+ return None
429
+
430
+
431
+ # Only add public symbols to __all__; internal helpers must stay unexported.
432
+ __all__ = ("LeaseRenewal", "QueueClient")
@@ -0,0 +1,160 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Final, TypeAlias, final
4
+
5
+ import os
6
+ import re
7
+ from collections.abc import Callable, Mapping
8
+
9
+ from vercel.oidc import get_vercel_oidc_token
10
+ from vercel.oidc.aio import get_vercel_oidc_token as get_vercel_oidc_token_async
11
+
12
+ from .constants import (
13
+ HEADER_AUTHORIZATION,
14
+ HEADER_CONTENT_TYPE,
15
+ VQS_HEADER_DELAY_SECONDS,
16
+ VQS_HEADER_DEPLOYMENT_ID,
17
+ VQS_HEADER_IDEMPOTENCY_KEY,
18
+ VQS_HEADER_MAX_MESSAGES,
19
+ VQS_HEADER_RETENTION_SECONDS,
20
+ VQS_HEADER_VISIBILITY_TIMEOUT_SECONDS,
21
+ )
22
+ from .errors import DeploymentResolutionError, TokenResolutionError
23
+
24
+ DeploymentID: TypeAlias = str
25
+ BaseUrl: TypeAlias = str | Callable[[str], str]
26
+
27
+
28
+ @final
29
+ class CurrentDeployment:
30
+ pass
31
+
32
+
33
+ @final
34
+ class AllDeployments:
35
+ pass
36
+
37
+
38
+ CURRENT_DEPLOYMENT: Final = CurrentDeployment()
39
+ ALL_DEPLOYMENTS: Final = AllDeployments()
40
+ DeploymentOption: TypeAlias = DeploymentID | CurrentDeployment | AllDeployments
41
+ REGION_PATTERN = re.compile(r"^[a-z]{2,5}[0-9]{1,2}$")
42
+
43
+ PROTECTED_HEADERS = {
44
+ HEADER_AUTHORIZATION.lower(),
45
+ HEADER_CONTENT_TYPE.lower(),
46
+ VQS_HEADER_DELAY_SECONDS.lower(),
47
+ VQS_HEADER_DEPLOYMENT_ID.lower(),
48
+ VQS_HEADER_IDEMPOTENCY_KEY.lower(),
49
+ VQS_HEADER_MAX_MESSAGES.lower(),
50
+ VQS_HEADER_RETENTION_SECONDS.lower(),
51
+ VQS_HEADER_VISIBILITY_TIMEOUT_SECONDS.lower(),
52
+ }
53
+
54
+
55
+ def resolve_region(region: str | None = None) -> str:
56
+ resolved = region or os.environ.get("VERCEL_REGION")
57
+ if resolved is None:
58
+ raise ValueError("Queue region is required. Provide 'region' or set VERCEL_REGION.")
59
+ if not REGION_PATTERN.fullmatch(resolved):
60
+ raise ValueError(f"Invalid queue region: {resolved!r}")
61
+ return resolved
62
+
63
+
64
+ def validate_region(region: str | None) -> str | None:
65
+ if region is None:
66
+ return None
67
+ if not REGION_PATTERN.fullmatch(region):
68
+ raise ValueError(f"Invalid queue region: {region!r}")
69
+ return region
70
+
71
+
72
+ def resolve_base_url(base_url: BaseUrl | None = None, *, region: str | None = None) -> str:
73
+ resolved_region = resolve_region(region)
74
+ if base_url is not None:
75
+ if isinstance(base_url, str):
76
+ resolved = base_url.format(region=resolved_region)
77
+ else:
78
+ resolved = base_url(resolved_region)
79
+ return resolved.rstrip("/")
80
+ env_base_url = os.environ.get("VERCEL_QUEUE_BASE_URL")
81
+ if env_base_url:
82
+ return env_base_url.format(region=resolved_region).rstrip("/")
83
+ return f"https://{resolved_region}.vercel-queue.com"
84
+
85
+
86
+ def resolve_deployment(deployment: DeploymentOption = CURRENT_DEPLOYMENT) -> str | None:
87
+ if deployment is ALL_DEPLOYMENTS:
88
+ return None
89
+ if isinstance(deployment, str):
90
+ return deployment
91
+ env_deployment = os.environ.get("VERCEL_DEPLOYMENT_ID")
92
+ if env_deployment:
93
+ return env_deployment
94
+ raise DeploymentResolutionError(
95
+ "Failed to resolve queue deployment ID. Provide 'deployment', "
96
+ "set VERCEL_DEPLOYMENT_ID, or pass deployment=ALL_DEPLOYMENTS "
97
+ "to explicitly omit it."
98
+ )
99
+
100
+
101
+ def resolve_token(token: str | None = None) -> str:
102
+ if token:
103
+ return token
104
+ env_token = os.environ.get("VERCEL_QUEUE_TOKEN")
105
+ if env_token:
106
+ return env_token
107
+ try:
108
+ resolved = get_vercel_oidc_token()
109
+ except Exception as exc:
110
+ raise TokenResolutionError(
111
+ "Failed to resolve queue token. Provide 'token', set VERCEL_QUEUE_TOKEN, "
112
+ "or make a Vercel OIDC token available."
113
+ ) from exc
114
+ if not resolved:
115
+ raise TokenResolutionError("Failed to resolve queue token: OIDC returned an empty token")
116
+ return resolved
117
+
118
+
119
+ async def resolve_token_async(token: str | None = None) -> str:
120
+ if token:
121
+ return token
122
+ env_token = os.environ.get("VERCEL_QUEUE_TOKEN")
123
+ if env_token:
124
+ return env_token
125
+ try:
126
+ resolved = await get_vercel_oidc_token_async()
127
+ except Exception as exc:
128
+ raise TokenResolutionError(
129
+ "Failed to resolve queue token. Provide 'token', set VERCEL_QUEUE_TOKEN, "
130
+ "or make a Vercel OIDC token available."
131
+ ) from exc
132
+ if not resolved:
133
+ raise TokenResolutionError("Failed to resolve queue token: OIDC returned an empty token")
134
+ return resolved
135
+
136
+
137
+ def apply_custom_headers(headers: dict[str, str], custom_headers: Mapping[str, str] | None) -> None:
138
+ for key, value in (custom_headers or {}).items():
139
+ if key.lower() in PROTECTED_HEADERS or key.lower().startswith("vqs-"):
140
+ continue
141
+ headers[str(key)] = str(value)
142
+
143
+
144
+ # Only add public symbols to __all__; internal helpers must stay unexported.
145
+ __all__ = (
146
+ "ALL_DEPLOYMENTS",
147
+ "CURRENT_DEPLOYMENT",
148
+ "AllDeployments",
149
+ "BaseUrl",
150
+ "CurrentDeployment",
151
+ "DeploymentID",
152
+ "DeploymentOption",
153
+ "apply_custom_headers",
154
+ "resolve_base_url",
155
+ "resolve_deployment",
156
+ "resolve_region",
157
+ "resolve_token",
158
+ "resolve_token_async",
159
+ "validate_region",
160
+ )
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ import platform
4
+ import sys
5
+
6
+ from ..version import __version__ as _ver
7
+
8
+ CONTENT_TYPE_JSON = "application/json"
9
+ CONTENT_TYPE_NDJSON = "application/x-ndjson"
10
+ CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"
11
+ CONTENT_TYPE_TEXT = "text/plain; charset=utf-8"
12
+ CONTENT_TYPE_MULTIPART_MIXED = "multipart/mixed"
13
+
14
+ HEADER_ACCEPT = "Accept"
15
+ HEADER_AUTHORIZATION = "Authorization"
16
+ HEADER_CONTENT_TYPE = "Content-Type"
17
+ HEADER_RETRY_AFTER = "Retry-After"
18
+ HEADER_USER_AGENT = "User-Agent"
19
+
20
+ VQS_NAME_PATTERN = r"^[A-Za-z0-9_-]+$"
21
+
22
+ DEFAULT_RETRY_AFTER_SECONDS = 60
23
+
24
+ VQS_HEADER_DELAY_SECONDS = "Vqs-Delay-Seconds"
25
+ VQS_HEADER_CLIENT_TS = "Vqs-Client-Ts"
26
+ VQS_HEADER_DELIVERY_COUNT = "Vqs-Delivery-Count"
27
+ VQS_HEADER_DEPLOYMENT_ID = "Vqs-Deployment-Id"
28
+ VQS_HEADER_EXPIRES_AT = "Vqs-Expires-At"
29
+ VQS_HEADER_IDEMPOTENCY_KEY = "Vqs-Idempotency-Key"
30
+ VQS_HEADER_MAX_MESSAGES = "Vqs-Max-Messages"
31
+ VQS_HEADER_MESSAGE_ID = "Vqs-Message-Id"
32
+ VQS_HEADER_RECEIPT_HANDLE = "Vqs-Receipt-Handle"
33
+ VQS_HEADER_RETENTION_SECONDS = "Vqs-Retention-Seconds"
34
+ VQS_HEADER_TIMESTAMP = "Vqs-Timestamp"
35
+ VQS_HEADER_VISIBILITY_TIMEOUT_SECONDS = "Vqs-Visibility-Timeout-Seconds"
36
+
37
+ CLOUD_EVENT_TYPE_V2BETA = "com.vercel.queue.v2beta"
38
+ CLOUD_EVENT_HEADER_TYPE = "ce-type"
39
+ CLOUD_EVENT_HEADER_VQS_CONSUMER_GROUP = "ce-vqsconsumergroup"
40
+ CLOUD_EVENT_HEADER_VQS_CREATED_AT = "ce-vqscreatedat"
41
+ CLOUD_EVENT_HEADER_VQS_DELIVERY_COUNT = "ce-vqsdeliverycount"
42
+ CLOUD_EVENT_HEADER_VQS_EXPIRES_AT = "ce-vqsexpiresat"
43
+ CLOUD_EVENT_HEADER_VQS_MESSAGE_ID = "ce-vqsmessageid"
44
+ CLOUD_EVENT_HEADER_VQS_TOPIC = "ce-vqsqueuename"
45
+ CLOUD_EVENT_HEADER_VQS_RECEIPT_HANDLE = "ce-vqsreceipthandle"
46
+ CLOUD_EVENT_HEADER_VQS_REGION = "ce-vqsregion"
47
+ CLOUD_EVENT_HEADER_VQS_VISIBILITY_DEADLINE = "ce-vqsvisibilitydeadline"
48
+
49
+ PLATFORM = platform.uname()
50
+ USER_AGENT = f"vercel/queue/{_ver} (Python/{sys.version}; {PLATFORM.system}/{PLATFORM.machine})"