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,205 @@
1
+ """Embedded queue development server helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import argparse
8
+ import contextlib
9
+ import cProfile
10
+ import importlib
11
+ import json
12
+ import signal
13
+ import threading
14
+ import time
15
+ from collections.abc import Callable, Iterator, Mapping
16
+ from dataclasses import dataclass
17
+
18
+ from ..embedded import create_embedded_queue_app
19
+ from .asgi import QueueClientAsgiApp, asgi_app
20
+ from .client import QueueClient
21
+ from .config import CURRENT_DEPLOYMENT, BaseUrl, DeploymentOption
22
+ from .embedded import EmbeddedQueueDevServer
23
+ from .http import AsyncHttpClientFactory
24
+ from .types import Duration
25
+
26
+
27
+ @dataclass(frozen=True, kw_only=True)
28
+ class QueueClientAsgiDevServer:
29
+ """Running queue client ASGI dev server."""
30
+
31
+ base_url: str
32
+ app: QueueClientAsgiApp
33
+ _server: Any
34
+ _thread: threading.Thread
35
+
36
+ def is_running(self) -> bool:
37
+ return self._thread.is_alive() and not self._server.should_exit
38
+
39
+
40
+ @contextlib.contextmanager
41
+ def embedded_queue_dev_server(
42
+ *,
43
+ host: str = "127.0.0.1",
44
+ port: int = 0,
45
+ manual_clock: bool = True,
46
+ profile: str | None = None,
47
+ ) -> Iterator[EmbeddedQueueDevServer]:
48
+ """Run the embedded queue server on a localhost HTTP port."""
49
+ uvicorn = _uvicorn()
50
+ app = create_embedded_queue_app(manual_clock=manual_clock)
51
+ config = uvicorn.Config(
52
+ app,
53
+ host=host,
54
+ port=port,
55
+ log_level="warning",
56
+ lifespan="off",
57
+ ws="none",
58
+ )
59
+ server = uvicorn.Server(config)
60
+ thread = threading.Thread(target=_profiled_server_run(server, profile), daemon=True)
61
+ thread.start()
62
+ _wait_for_server(server)
63
+ base_url = f"http://{host}:{_server_port(server)}"
64
+ try:
65
+ yield EmbeddedQueueDevServer(
66
+ state=app.state,
67
+ base_url=base_url,
68
+ app=app,
69
+ _thread=thread,
70
+ )
71
+ finally:
72
+ server.should_exit = True
73
+ thread.join(timeout=5)
74
+ if thread.is_alive():
75
+ raise RuntimeError("embedded queue dev server did not stop")
76
+ app.state.close()
77
+
78
+
79
+ @contextlib.contextmanager
80
+ def queue_client_asgi_dev_server( # noqa: PLR0913
81
+ *,
82
+ client: QueueClient | None = None,
83
+ host: str = "127.0.0.1",
84
+ port: int = 0,
85
+ token: str | None = None,
86
+ region: str | None = None,
87
+ base_url: BaseUrl | None = None,
88
+ deployment: DeploymentOption = CURRENT_DEPLOYMENT,
89
+ headers: Mapping[str, str] | None = None,
90
+ timeout: Duration | None = 10.0,
91
+ http_client_factory: AsyncHttpClientFactory | None = None,
92
+ profile: str | None = None,
93
+ ) -> Iterator[QueueClientAsgiDevServer]:
94
+ """Run a queue client ASGI callback app on a localhost HTTP port."""
95
+ uvicorn = _uvicorn()
96
+ app = asgi_app(
97
+ client=client,
98
+ token=token,
99
+ region=region,
100
+ base_url=base_url,
101
+ deployment=deployment,
102
+ headers=headers,
103
+ timeout=timeout,
104
+ http_client_factory=http_client_factory,
105
+ )
106
+ config = uvicorn.Config(
107
+ app,
108
+ host=host,
109
+ port=port,
110
+ log_level="warning",
111
+ lifespan="on",
112
+ ws="none",
113
+ )
114
+ server = uvicorn.Server(config)
115
+ thread = threading.Thread(target=_profiled_server_run(server, profile), daemon=True)
116
+ thread.start()
117
+ _wait_for_server(server, "queue client ASGI dev server")
118
+ try:
119
+ yield QueueClientAsgiDevServer(
120
+ base_url=f"http://{host}:{_server_port(server)}",
121
+ app=app,
122
+ _server=server,
123
+ _thread=thread,
124
+ )
125
+ finally:
126
+ server.should_exit = True
127
+ thread.join(timeout=5)
128
+ if thread.is_alive():
129
+ raise RuntimeError("queue client ASGI dev server did not stop")
130
+
131
+
132
+ def main(argv: list[str] | None = None) -> int:
133
+ """Run an embedded queue HTTP server for cross-runtime tests."""
134
+ parser = argparse.ArgumentParser()
135
+ parser.add_argument("--host", default="127.0.0.1", help="host interface to bind")
136
+ parser.add_argument(
137
+ "--port",
138
+ type=int,
139
+ default=0,
140
+ help="port to bind, or 0 for a random available port (default)",
141
+ )
142
+ args = parser.parse_args(argv)
143
+
144
+ stop = threading.Event()
145
+
146
+ def _handle_signal(_signum: int, _frame: object) -> None:
147
+ stop.set()
148
+
149
+ signal.signal(signal.SIGTERM, _handle_signal)
150
+ signal.signal(signal.SIGINT, _handle_signal)
151
+
152
+ with embedded_queue_dev_server(
153
+ host=args.host,
154
+ port=args.port,
155
+ manual_clock=False,
156
+ ) as server:
157
+ print(json.dumps({"baseUrl": server.base_url}), flush=True) # noqa: T201
158
+
159
+ while not stop.wait(0.1):
160
+ if not server.is_running():
161
+ raise RuntimeError("embedded queue server stopped unexpectedly")
162
+
163
+ return 0
164
+
165
+
166
+ def _uvicorn() -> Any:
167
+ try:
168
+ return importlib.import_module("uvicorn")
169
+ except ModuleNotFoundError as exc:
170
+ raise RuntimeError(
171
+ "Queue dev server support requires uvicorn. Install it with 'vercel-queue[devserver]'."
172
+ ) from exc
173
+
174
+
175
+ def _wait_for_server(server: Any, name: str = "embedded queue dev server") -> None:
176
+ deadline = time.monotonic() + 5
177
+ while not server.started:
178
+ if not server.should_exit and time.monotonic() < deadline:
179
+ time.sleep(0.01)
180
+ continue
181
+ raise RuntimeError(f"{name} did not start")
182
+
183
+
184
+ def _server_port(server: Any) -> int:
185
+ for asyncio_server in server.servers:
186
+ sockets = asyncio_server.sockets or ()
187
+ for sock in sockets:
188
+ address = sock.getsockname()
189
+ if isinstance(address, tuple):
190
+ return int(address[1])
191
+ raise RuntimeError("embedded queue dev server did not expose a TCP port")
192
+
193
+
194
+ def _profiled_server_run(server: Any, profile: str | None) -> Callable[[], None]:
195
+ if profile is None:
196
+ return server.run
197
+
198
+ def run() -> None:
199
+ profiler = cProfile.Profile()
200
+ try:
201
+ profiler.runcall(server.run)
202
+ finally:
203
+ profiler.dump_stats(profile)
204
+
205
+ return run