vercel-queue-bundle 0.7.3__tar.gz → 0.8.0__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.
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/PKG-INFO +1 -1
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/api_common.py +2 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/subscribers.py +72 -22
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/transports.py +17 -4
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/types.py +81 -20
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/version.py +1 -1
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/LICENSE +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/README.md +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/_vercel_hatch_build.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/hatch_build.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/pyproject.toml +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/__init__.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/__main__.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/__init__.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/api_async.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/api_sync.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/asgi.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/asynctools.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/cli.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/client.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/client_sync.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/config.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/constants.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/devserver.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/embedded.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/errors.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/http.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/lease.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/log.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/messages.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/multipart.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/names.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/polling.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/push.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/response.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/retry.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/streams.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/LICENSE.python-multipart.txt +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/__init__.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/multipart/__init__.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/multipart/decoders.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/multipart/exceptions.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/multipart/multipart.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/python_multipart/__init__.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/python_multipart/decoders.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/python_multipart/exceptions.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/python_multipart/multipart.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/devserver.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/embedded.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/py.typed +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/sync.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/testing/__init__.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/testing/pytest.py +0 -0
- {vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/testing/state.py +0 -0
{vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/api_common.py
RENAMED
|
@@ -35,6 +35,7 @@ StrContainer = _types.StrContainer
|
|
|
35
35
|
TextBufferTransport = _transports.TextBufferTransport
|
|
36
36
|
TextStreamTransport = _transports.TextStreamTransport
|
|
37
37
|
Topic = _types.Topic
|
|
38
|
+
TopicPattern = _types.TopicPattern
|
|
38
39
|
TypedJsonTransport = _transports.TypedJsonTransport
|
|
39
40
|
__version__ = _version.__version__
|
|
40
41
|
asgi_app = _asgi.asgi_app
|
|
@@ -93,6 +94,7 @@ __all__ = (
|
|
|
93
94
|
"ThrottledError",
|
|
94
95
|
"TokenResolutionError",
|
|
95
96
|
"Topic",
|
|
97
|
+
"TopicPattern",
|
|
96
98
|
"TypedJsonTransport",
|
|
97
99
|
"UnauthorizedError",
|
|
98
100
|
"UnhandledMessageError",
|
{vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/subscribers.py
RENAMED
|
@@ -40,9 +40,11 @@ from .transports import (
|
|
|
40
40
|
is_untyped_payload_annotation,
|
|
41
41
|
payload_transport_kind,
|
|
42
42
|
reject_invalid_payload_annotation,
|
|
43
|
+
topic_payload_annotation,
|
|
43
44
|
transport_for_kind,
|
|
44
45
|
)
|
|
45
46
|
from .types import (
|
|
47
|
+
BaseTopic,
|
|
46
48
|
Duration,
|
|
47
49
|
Message,
|
|
48
50
|
MessageMetadata,
|
|
@@ -50,12 +52,15 @@ from .types import (
|
|
|
50
52
|
RetryAfter,
|
|
51
53
|
StrContainer,
|
|
52
54
|
Topic,
|
|
55
|
+
TopicPattern,
|
|
53
56
|
Transport,
|
|
54
57
|
duration_to_seconds,
|
|
55
58
|
)
|
|
56
59
|
|
|
57
60
|
_Subscriber: TypeAlias = Callable[..., Any | Awaitable[Any]]
|
|
58
61
|
_SubscriberRef: TypeAlias = weakref.ReferenceType[_Subscriber]
|
|
62
|
+
_SubscriptionTopic: TypeAlias = "str | SanitizedName | Topic[Any] | TopicPattern[Any]"
|
|
63
|
+
"""Anything ``subscribe()`` accepts: one topic, or a pattern matching several."""
|
|
59
64
|
P = ParamSpec("P")
|
|
60
65
|
R = TypeVar("R")
|
|
61
66
|
R_co = TypeVar("R_co", covariant=True)
|
|
@@ -66,6 +71,14 @@ class PayloadAdapter(Protocol):
|
|
|
66
71
|
def validate_python(self, value: Any, /) -> Any: ...
|
|
67
72
|
|
|
68
73
|
|
|
74
|
+
class _TransportPayloadAdapter:
|
|
75
|
+
def __init__(self, validate_payload: Callable[[Any], Any]) -> None:
|
|
76
|
+
self._validate_payload = validate_payload
|
|
77
|
+
|
|
78
|
+
def validate_python(self, value: Any, /) -> Any:
|
|
79
|
+
return self._validate_payload(value)
|
|
80
|
+
|
|
81
|
+
|
|
69
82
|
class EmbeddedDispatcher(Protocol):
|
|
70
83
|
def register_subscription(
|
|
71
84
|
self,
|
|
@@ -107,6 +120,7 @@ class InvocationPlan:
|
|
|
107
120
|
payload_adapter: PayloadAdapter | None
|
|
108
121
|
mode: InvocationMode
|
|
109
122
|
transport_kind: TransportKind
|
|
123
|
+
transport: Transport[Any] | None = None
|
|
110
124
|
|
|
111
125
|
def prepare_payload(self, payload: Any) -> Any:
|
|
112
126
|
if self.payload_adapter is None:
|
|
@@ -120,6 +134,33 @@ class InvocationPlan:
|
|
|
120
134
|
raise
|
|
121
135
|
|
|
122
136
|
|
|
137
|
+
class _TransportOrKind:
|
|
138
|
+
def __init__(self, plan: InvocationPlan) -> None:
|
|
139
|
+
self.transport_kind = plan.transport_kind
|
|
140
|
+
self.transport = plan.transport
|
|
141
|
+
|
|
142
|
+
def __hash__(self) -> int:
|
|
143
|
+
return hash(self.transport_kind) if self.transport is None else id(self.transport)
|
|
144
|
+
|
|
145
|
+
def __eq__(self, other: object) -> bool:
|
|
146
|
+
if not isinstance(other, _TransportOrKind):
|
|
147
|
+
return NotImplemented
|
|
148
|
+
if self.transport is None and other.transport is None:
|
|
149
|
+
return self.transport_kind == other.transport_kind
|
|
150
|
+
# Two instances of the same class can decode differently
|
|
151
|
+
return self.transport is other.transport
|
|
152
|
+
|
|
153
|
+
def __repr__(self) -> str:
|
|
154
|
+
if self.transport is None:
|
|
155
|
+
return self.transport_kind
|
|
156
|
+
return repr(self.transport)
|
|
157
|
+
|
|
158
|
+
def get_transport(self) -> Transport[Any]:
|
|
159
|
+
if self.transport is None:
|
|
160
|
+
return _transport_for_kind(self.transport_kind)
|
|
161
|
+
return self.transport
|
|
162
|
+
|
|
163
|
+
|
|
123
164
|
@dataclass(frozen=True, kw_only=True)
|
|
124
165
|
class _Subscription:
|
|
125
166
|
func_ref: _SubscriberRef
|
|
@@ -314,23 +355,17 @@ def _message_payload_annotation(annotation: Any) -> Any:
|
|
|
314
355
|
return message_args[0]
|
|
315
356
|
|
|
316
357
|
|
|
317
|
-
def
|
|
318
|
-
if not isinstance(topic, Topic):
|
|
319
|
-
return inspect.Signature.empty
|
|
320
|
-
if getattr(type(topic), "__topic_origin__", None) is not Topic:
|
|
321
|
-
return inspect.Signature.empty
|
|
322
|
-
return type(topic).__topic_payload_type__
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
def _normalize_subscription_topic(topic: str | SanitizedName | Topic[Any]) -> str:
|
|
358
|
+
def _normalize_subscription_topic(topic: _SubscriptionTopic) -> str:
|
|
326
359
|
if isinstance(topic, SanitizedName):
|
|
327
360
|
return str(topic)
|
|
328
361
|
if isinstance(topic, str):
|
|
329
362
|
validate_subscription_pattern(topic)
|
|
330
363
|
return topic
|
|
364
|
+
if isinstance(topic, TopicPattern):
|
|
365
|
+
return str(topic.name)
|
|
331
366
|
if isinstance(topic, Topic):
|
|
332
367
|
return validate_topic_name(topic)
|
|
333
|
-
raise TypeError("topic must be a string or
|
|
368
|
+
raise TypeError("topic must be a string, Topic, or TopicPattern")
|
|
334
369
|
|
|
335
370
|
|
|
336
371
|
def _resolve_invocation_payload_annotation(
|
|
@@ -356,7 +391,11 @@ def _payload_adapter(
|
|
|
356
391
|
annotation: Any,
|
|
357
392
|
*,
|
|
358
393
|
localns: dict[str, Any] | None = None,
|
|
394
|
+
transport: Transport[Any] | None = None,
|
|
359
395
|
) -> PayloadAdapter | None:
|
|
396
|
+
validate_payload = getattr(transport, "validate_payload", None)
|
|
397
|
+
if callable(validate_payload):
|
|
398
|
+
return _TransportPayloadAdapter(validate_payload)
|
|
360
399
|
annotation = strip_annotated(annotation)
|
|
361
400
|
if is_untyped_payload_annotation(annotation):
|
|
362
401
|
return None
|
|
@@ -407,6 +446,7 @@ def _build_invocation_plan(
|
|
|
407
446
|
func: _Subscriber,
|
|
408
447
|
*,
|
|
409
448
|
topic_payload_annotation: Any = inspect.Signature.empty,
|
|
449
|
+
transport: Transport[Any] | None = None,
|
|
410
450
|
) -> InvocationPlan:
|
|
411
451
|
signature = inspect.signature(func)
|
|
412
452
|
input_params: list[inspect.Parameter] = []
|
|
@@ -446,9 +486,11 @@ def _build_invocation_plan(
|
|
|
446
486
|
payload_adapter=_payload_adapter(
|
|
447
487
|
payload_annotation,
|
|
448
488
|
localns=resolved_annotation.localns,
|
|
489
|
+
transport=transport,
|
|
449
490
|
),
|
|
450
491
|
mode=mode,
|
|
451
492
|
transport_kind=_transport_kind(payload_annotation),
|
|
493
|
+
transport=transport,
|
|
452
494
|
)
|
|
453
495
|
|
|
454
496
|
|
|
@@ -593,13 +635,13 @@ def infer_subscriber_transport(metadata: MessageMetadata) -> Transport[Any]:
|
|
|
593
635
|
if not matching:
|
|
594
636
|
raise _no_matching_subscriptions_error(metadata.topic)
|
|
595
637
|
|
|
596
|
-
|
|
597
|
-
if len(
|
|
638
|
+
tks = {_TransportOrKind(matched.subscription.invocation) for matched in matching}
|
|
639
|
+
if len(tks) != 1:
|
|
598
640
|
raise SubscriptionError(
|
|
599
641
|
"matching queue subscribers require incompatible payload transports: "
|
|
600
|
-
+ ", ".join(sorted(
|
|
642
|
+
+ ", ".join(sorted(map(repr, tks)))
|
|
601
643
|
)
|
|
602
|
-
return
|
|
644
|
+
return tks.pop().get_transport()
|
|
603
645
|
|
|
604
646
|
|
|
605
647
|
async def _maybe_await_result(result: Any) -> Any:
|
|
@@ -702,14 +744,14 @@ def _register_subscription(
|
|
|
702
744
|
func: Callable[P, R],
|
|
703
745
|
*,
|
|
704
746
|
consumer_group: str | SanitizedName | None = None,
|
|
705
|
-
topic:
|
|
747
|
+
topic: _SubscriptionTopic,
|
|
706
748
|
retry_after: Duration | None = None,
|
|
707
749
|
initial_delay: Duration | None = None,
|
|
708
750
|
max_concurrency: int | None = None,
|
|
709
751
|
max_attempts: int | None = None,
|
|
710
752
|
) -> QueueSubscriber[P, R]:
|
|
711
753
|
topic_name = _normalize_subscription_topic(topic)
|
|
712
|
-
|
|
754
|
+
payload_annotation = topic_payload_annotation(topic)
|
|
713
755
|
|
|
714
756
|
resolved_consumer_group = (
|
|
715
757
|
_default_consumer_group(func)
|
|
@@ -725,7 +767,10 @@ def _register_subscription(
|
|
|
725
767
|
consumer_group=resolved_consumer_group,
|
|
726
768
|
invocation=_build_invocation_plan(
|
|
727
769
|
cast("_Subscriber", func),
|
|
728
|
-
topic_payload_annotation=
|
|
770
|
+
topic_payload_annotation=payload_annotation,
|
|
771
|
+
# This is not receive_transport_for_topic() because we are doing
|
|
772
|
+
# more than that in _build_invocation_plan()
|
|
773
|
+
transport=topic.transport if isinstance(topic, BaseTopic) else None,
|
|
729
774
|
),
|
|
730
775
|
topic=topic_name,
|
|
731
776
|
retry_after_seconds=_optional_bounded_duration(
|
|
@@ -769,7 +814,12 @@ def subscribe(
|
|
|
769
814
|
|
|
770
815
|
|
|
771
816
|
@overload
|
|
772
|
-
def subscribe(
|
|
817
|
+
def subscribe(
|
|
818
|
+
func: Callable[[T], R],
|
|
819
|
+
/,
|
|
820
|
+
*,
|
|
821
|
+
topic: Topic[T] | TopicPattern[T],
|
|
822
|
+
) -> QueueSubscriber[[T], R]: ...
|
|
773
823
|
|
|
774
824
|
|
|
775
825
|
@overload
|
|
@@ -777,7 +827,7 @@ def subscribe(
|
|
|
777
827
|
func: Callable[[Message[T]], R],
|
|
778
828
|
/,
|
|
779
829
|
*,
|
|
780
|
-
topic: Topic[T],
|
|
830
|
+
topic: Topic[T] | TopicPattern[T],
|
|
781
831
|
) -> QueueSubscriber[[Message[T]], R]: ...
|
|
782
832
|
|
|
783
833
|
|
|
@@ -808,7 +858,7 @@ def subscribe(
|
|
|
808
858
|
@overload
|
|
809
859
|
def subscribe(
|
|
810
860
|
*,
|
|
811
|
-
topic: Topic[T],
|
|
861
|
+
topic: Topic[T] | TopicPattern[T],
|
|
812
862
|
consumer_group: str | SanitizedName | None = None,
|
|
813
863
|
retry_after: Duration | None = None,
|
|
814
864
|
initial_delay: Duration | None = None,
|
|
@@ -850,7 +900,7 @@ def subscribe(
|
|
|
850
900
|
func: None,
|
|
851
901
|
/,
|
|
852
902
|
*,
|
|
853
|
-
topic: Topic[T],
|
|
903
|
+
topic: Topic[T] | TopicPattern[T],
|
|
854
904
|
consumer_group: str | SanitizedName | None = None,
|
|
855
905
|
retry_after: Duration | None = None,
|
|
856
906
|
initial_delay: Duration | None = None,
|
|
@@ -863,7 +913,7 @@ def subscribe(
|
|
|
863
913
|
func: _Subscriber | None = None,
|
|
864
914
|
/,
|
|
865
915
|
*,
|
|
866
|
-
topic:
|
|
916
|
+
topic: _SubscriptionTopic,
|
|
867
917
|
consumer_group: str | SanitizedName | None = None,
|
|
868
918
|
retry_after: Duration | None = None,
|
|
869
919
|
initial_delay: Duration | None = None,
|
{vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/transports.py
RENAMED
|
@@ -27,8 +27,10 @@ from .streams import (
|
|
|
27
27
|
SyncTextStreamPayload,
|
|
28
28
|
)
|
|
29
29
|
from .types import (
|
|
30
|
+
BaseTopic,
|
|
30
31
|
RequestContent,
|
|
31
32
|
Topic,
|
|
33
|
+
TopicPattern,
|
|
32
34
|
Transport,
|
|
33
35
|
)
|
|
34
36
|
|
|
@@ -304,14 +306,24 @@ def receive_transport_for_annotation(annotation: Any) -> Transport[Any]:
|
|
|
304
306
|
return transport_for_kind(kind)
|
|
305
307
|
|
|
306
308
|
|
|
309
|
+
def topic_payload_annotation(topic: object) -> Any:
|
|
310
|
+
"""Return the payload type ``topic`` was specialized with, if any.
|
|
311
|
+
|
|
312
|
+
A bare name, an unspecialized topic, and a user subclass all report "no
|
|
313
|
+
annotation": none of them carries a payload type we put there.
|
|
314
|
+
"""
|
|
315
|
+
origin = getattr(type(topic), "__topic_origin__", None)
|
|
316
|
+
if origin is not Topic and origin is not TopicPattern:
|
|
317
|
+
return inspect.Signature.empty
|
|
318
|
+
return getattr(type(topic), "__topic_payload_type__", inspect.Signature.empty)
|
|
319
|
+
|
|
320
|
+
|
|
307
321
|
def receive_transport_for_topic(topic: object) -> Transport[Any]:
|
|
308
|
-
if not isinstance(topic,
|
|
322
|
+
if not isinstance(topic, BaseTopic):
|
|
309
323
|
return RawJsonTransport[Any]()
|
|
310
324
|
if topic.transport is not None:
|
|
311
325
|
return topic.transport
|
|
312
|
-
|
|
313
|
-
return RawJsonTransport[Any]()
|
|
314
|
-
return receive_transport_for_annotation(type(topic).__topic_payload_type__)
|
|
326
|
+
return receive_transport_for_annotation(topic_payload_annotation(topic))
|
|
315
327
|
|
|
316
328
|
|
|
317
329
|
def send_transport_for_topic(topic: object) -> Transport[Any] | None:
|
|
@@ -360,5 +372,6 @@ __all__ = (
|
|
|
360
372
|
"receive_transport_for_topic",
|
|
361
373
|
"reject_invalid_payload_annotation",
|
|
362
374
|
"send_transport_for_topic",
|
|
375
|
+
"topic_payload_annotation",
|
|
363
376
|
"transport_for_kind",
|
|
364
377
|
)
|
|
@@ -12,8 +12,15 @@ from collections.abc import (
|
|
|
12
12
|
from dataclasses import dataclass
|
|
13
13
|
from datetime import datetime, timedelta
|
|
14
14
|
|
|
15
|
+
from vercel._internal.core.polyfills import Self
|
|
16
|
+
|
|
15
17
|
from .constants import DEFAULT_RETRY_AFTER_SECONDS
|
|
16
|
-
from .names import
|
|
18
|
+
from .names import (
|
|
19
|
+
SanitizedName,
|
|
20
|
+
validate_name,
|
|
21
|
+
validate_subscription_pattern,
|
|
22
|
+
validate_topic_name,
|
|
23
|
+
)
|
|
17
24
|
|
|
18
25
|
T = TypeVar("T")
|
|
19
26
|
_TYPE_VAR_TYPE = type(T)
|
|
@@ -62,47 +69,62 @@ def duration_to_float_seconds(duration: Duration) -> float:
|
|
|
62
69
|
|
|
63
70
|
|
|
64
71
|
@dataclass(frozen=True, kw_only=True, eq=False)
|
|
65
|
-
class
|
|
66
|
-
"""
|
|
72
|
+
class BaseTopic(Generic[T]):
|
|
73
|
+
"""Shared behaviour of :class:`Topic` and :class:`TopicPattern`.
|
|
67
74
|
|
|
68
|
-
|
|
75
|
+
Use ``Topic`` to name one topic and ``TopicPattern`` to match several; the
|
|
76
|
+
two are separate types so that a pattern cannot reach an operation that
|
|
77
|
+
needs a single, concrete topic.
|
|
69
78
|
"""
|
|
70
79
|
|
|
71
|
-
name: SanitizedName
|
|
72
|
-
"""
|
|
80
|
+
name: SanitizedName | str
|
|
81
|
+
"""The topic name, or the pattern that selects topic names.
|
|
82
|
+
|
|
83
|
+
A pattern is a plain ``str``: ``SanitizedName`` means "safe to put in a
|
|
84
|
+
request path", which a pattern is not.
|
|
85
|
+
"""
|
|
73
86
|
|
|
74
87
|
transport: Transport[Any] | None = None
|
|
75
|
-
"""Optional transport used when sending to or
|
|
88
|
+
"""Optional transport used when sending to, polling, or subscribing to this topic."""
|
|
76
89
|
|
|
77
|
-
__topic_origin__: ClassVar[type[
|
|
90
|
+
__topic_origin__: ClassVar[type[BaseTopic[Any]] | None] = None
|
|
78
91
|
__topic_payload_type__: ClassVar[Any] = None
|
|
79
|
-
_specializations: ClassVar[dict[Any, type[
|
|
80
|
-
|
|
81
|
-
def
|
|
92
|
+
_specializations: ClassVar[dict[Any, type[BaseTopic[Any]]]] = {}
|
|
93
|
+
|
|
94
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
95
|
+
super().__init_subclass__(**kwargs)
|
|
96
|
+
# `Topic` and `TopicPattern` each need their own cache, or the first
|
|
97
|
+
# `Topic[bytes]` would be handed back for `TopicPattern[bytes]`. A
|
|
98
|
+
# specialization (the only thing carrying `__topic_origin__` in its
|
|
99
|
+
# own namespace) keeps sharing the cache of the class it came from.
|
|
100
|
+
if "__topic_origin__" not in cls.__dict__:
|
|
101
|
+
cls._specializations = {}
|
|
102
|
+
|
|
103
|
+
def __class_getitem__(cls, params: Any) -> type[Self]:
|
|
82
104
|
if isinstance(params, tuple):
|
|
83
105
|
if len(params) != 1:
|
|
84
|
-
raise TypeError("
|
|
106
|
+
raise TypeError(f"{cls.__name__} expects exactly one type argument")
|
|
85
107
|
params = params[0]
|
|
86
108
|
if isinstance(params, _TYPE_VAR_TYPE):
|
|
87
|
-
return
|
|
109
|
+
return cls
|
|
88
110
|
|
|
89
111
|
try:
|
|
90
|
-
return cls._specializations[params]
|
|
112
|
+
return cast("type[Self]", cls._specializations[params])
|
|
91
113
|
except KeyError:
|
|
92
114
|
pass
|
|
93
115
|
|
|
94
116
|
payload_repr = _topic_payload_type_repr(params)
|
|
95
117
|
specialization = type(
|
|
96
|
-
f"
|
|
118
|
+
f"{cls.__name__}[{payload_repr}]",
|
|
97
119
|
(cls,),
|
|
98
120
|
{
|
|
99
121
|
"__module__": cls.__module__,
|
|
100
|
-
"__topic_origin__":
|
|
122
|
+
"__topic_origin__": cls,
|
|
101
123
|
"__topic_payload_type__": params,
|
|
102
124
|
},
|
|
103
125
|
)
|
|
104
126
|
cls._specializations[params] = specialization
|
|
105
|
-
return cast("type[
|
|
127
|
+
return cast("type[Self]", specialization)
|
|
106
128
|
|
|
107
129
|
def __init__(
|
|
108
130
|
self,
|
|
@@ -110,14 +132,22 @@ class Topic(Generic[T]):
|
|
|
110
132
|
*,
|
|
111
133
|
transport: Transport[Any] | None = None,
|
|
112
134
|
) -> None:
|
|
113
|
-
object.__setattr__(self, "name",
|
|
135
|
+
object.__setattr__(self, "name", self._validated_name(name))
|
|
114
136
|
object.__setattr__(self, "transport", transport)
|
|
115
137
|
|
|
138
|
+
@staticmethod
|
|
139
|
+
def _validated_name(name: str | SanitizedName) -> SanitizedName | str:
|
|
140
|
+
raise NotImplementedError
|
|
141
|
+
|
|
116
142
|
def __repr__(self) -> str:
|
|
117
|
-
|
|
143
|
+
# Report the class the user named, not the synthesized specialization.
|
|
144
|
+
cls = type(self)
|
|
145
|
+
return f"{(cls.__topic_origin__ or cls).__name__}(name={self.name!r})"
|
|
118
146
|
|
|
119
147
|
def __eq__(self, other: object) -> bool:
|
|
120
|
-
|
|
148
|
+
# A pattern always ends in `*`, which a topic name can never contain,
|
|
149
|
+
# so comparing names is enough to keep the two kinds apart.
|
|
150
|
+
if not isinstance(other, BaseTopic):
|
|
121
151
|
return NotImplemented
|
|
122
152
|
return self.name == other.name
|
|
123
153
|
|
|
@@ -125,6 +155,36 @@ class Topic(Generic[T]):
|
|
|
125
155
|
return hash(self.name)
|
|
126
156
|
|
|
127
157
|
|
|
158
|
+
class Topic(BaseTopic[T]):
|
|
159
|
+
"""A named Vercel Queues topic.
|
|
160
|
+
|
|
161
|
+
Topics identify the stream that messages are sent to and received from.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
name: SanitizedName
|
|
165
|
+
"""Topic name to send to, poll, or subscribe to."""
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def _validated_name(name: str | SanitizedName) -> SanitizedName | str:
|
|
169
|
+
return SanitizedName(validate_topic_name(name))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TopicPattern(BaseTopic[T]):
|
|
173
|
+
"""A pattern selecting every Vercel Queues topic it matches.
|
|
174
|
+
|
|
175
|
+
``"*"`` matches every topic and a prefix ending in ``*`` matches by
|
|
176
|
+
prefix. A pattern can only be subscribed to: sending and polling name one
|
|
177
|
+
topic, so they take a :class:`Topic`.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
name: str
|
|
181
|
+
"""The pattern this subscription matches topics with."""
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def _validated_name(name: str | SanitizedName) -> SanitizedName | str:
|
|
185
|
+
return validate_subscription_pattern(str(name))
|
|
186
|
+
|
|
187
|
+
|
|
128
188
|
def _topic_payload_type_repr(payload_type: object) -> str:
|
|
129
189
|
name = getattr(payload_type, "__qualname__", None)
|
|
130
190
|
if isinstance(name, str):
|
|
@@ -297,4 +357,5 @@ __all__: tuple[str, ...] = (
|
|
|
297
357
|
"RetryAfter",
|
|
298
358
|
"StrContainer",
|
|
299
359
|
"Topic",
|
|
360
|
+
"TopicPattern",
|
|
300
361
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/asynctools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_internal/client_sync.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/multipart/__init__.py
RENAMED
|
File without changes
|
{vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/multipart/decoders.py
RENAMED
|
File without changes
|
{vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/multipart/exceptions.py
RENAMED
|
File without changes
|
{vercel_queue_bundle-0.7.3 → vercel_queue_bundle-0.8.0}/vercel/queue/_vendor/multipart/multipart.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|