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,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
|