vercel-queue 0.8.0__tar.gz → 0.8.1__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.
Files changed (45) hide show
  1. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/PKG-INFO +6 -6
  2. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/README.md +1 -1
  3. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/devserver.py +71 -11
  4. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/version.py +1 -1
  5. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/.gitignore +0 -0
  6. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/LICENSE +0 -0
  7. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/_vercel_hatch_build.py +0 -0
  8. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/hatch_build.py +0 -0
  9. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/pyproject.toml +0 -0
  10. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/__init__.py +0 -0
  11. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/__main__.py +0 -0
  12. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/__init__.py +0 -0
  13. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/api_async.py +0 -0
  14. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/api_common.py +0 -0
  15. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/api_sync.py +0 -0
  16. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/asgi.py +0 -0
  17. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/asynctools.py +0 -0
  18. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/cli.py +0 -0
  19. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/client.py +0 -0
  20. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/client_sync.py +0 -0
  21. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/config.py +0 -0
  22. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/constants.py +0 -0
  23. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/embedded.py +0 -0
  24. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/errors.py +0 -0
  25. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/http.py +0 -0
  26. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/lease.py +0 -0
  27. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/log.py +0 -0
  28. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/messages.py +0 -0
  29. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/multipart.py +0 -0
  30. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/names.py +0 -0
  31. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/polling.py +0 -0
  32. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/push.py +0 -0
  33. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/response.py +0 -0
  34. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/retry.py +0 -0
  35. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/streams.py +0 -0
  36. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/subscribers.py +0 -0
  37. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/transports.py +0 -0
  38. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/_internal/types.py +0 -0
  39. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/devserver.py +0 -0
  40. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/embedded.py +0 -0
  41. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/py.typed +0 -0
  42. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/sync.py +0 -0
  43. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/testing/__init__.py +0 -0
  44. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/testing/pytest.py +0 -0
  45. {vercel_queue-0.8.0 → vercel_queue-0.8.1}/vercel/queue/testing/state.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: vercel-queue
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: Vercel Queue client for Python
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -9,9 +9,9 @@ Requires-Dist: anyio>=4.0.0
9
9
  Requires-Dist: httpx[http2]>=0.27.0
10
10
  Requires-Dist: python-multipart>=0.0.20
11
11
  Requires-Dist: typing-extensions>=4.0.0
12
- Requires-Dist: vercel-headers>=0.7.1
13
- Requires-Dist: vercel-internal-core<0.2.0,>=0.1.2
14
- Requires-Dist: vercel-oidc>=0.8.0
12
+ Requires-Dist: vercel-headers>=0.7.2
13
+ Requires-Dist: vercel-internal-core<0.2.0,>=0.1.3
14
+ Requires-Dist: vercel-oidc>=0.8.1
15
15
  Provides-Extra: devserver
16
16
  Requires-Dist: uvicorn; extra == 'devserver'
17
17
  Provides-Extra: trio
@@ -27,7 +27,7 @@ designed for seamless integration with Vercel deployments.
27
27
 
28
28
  ## Features
29
29
 
30
- - **Simple API**: `send`, `subscribe`, and `asgi_app` cover standard workflows.
30
+ - **Simple API**: `send`, `subscribe`, and `poll` cover standard workflows.
31
31
  - **Automatic Triggering on Vercel**: Vercel invokes your function when messages are ready.
32
32
  - **Works Anywhere**: `send`, decorated subscribers, and manual `poll` loops work on Vercel, self-hosted workers, and locally.
33
33
  - **Sync and Async Clients**: Prefer async for applications, use sync for scripts and blocking workers.
@@ -5,7 +5,7 @@ designed for seamless integration with Vercel deployments.
5
5
 
6
6
  ## Features
7
7
 
8
- - **Simple API**: `send`, `subscribe`, and `asgi_app` cover standard workflows.
8
+ - **Simple API**: `send`, `subscribe`, and `poll` cover standard workflows.
9
9
  - **Automatic Triggering on Vercel**: Vercel invokes your function when messages are ready.
10
10
  - **Works Anywhere**: `send`, decorated subscribers, and manual `poll` loops work on Vercel, self-hosted workers, and locally.
11
11
  - **Sync and Async Clients**: Prefer async for applications, use sync for scripts and blocking workers.
@@ -15,6 +15,9 @@ import time
15
15
  from collections.abc import Callable, Iterator, Mapping
16
16
  from dataclasses import dataclass
17
17
 
18
+ import anyio
19
+ from anyio import from_thread
20
+
18
21
  from ..embedded import create_embedded_queue_app
19
22
  from .asgi import QueueClientAsgiApp, asgi_app
20
23
  from .client import QueueClient
@@ -23,6 +26,9 @@ from .embedded import EmbeddedQueueDevServer
23
26
  from .http import AsyncHttpClientFactory
24
27
  from .types import Duration
25
28
 
29
+ _GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS = 5.0
30
+ _SERVER_STOP_TIMEOUT_MARGIN_SECONDS = 1.0
31
+
26
32
 
27
33
  @dataclass(frozen=True, kw_only=True)
28
34
  class QueueClientAsgiDevServer:
@@ -55,8 +61,9 @@ def embedded_queue_dev_server(
55
61
  log_level="warning",
56
62
  lifespan="off",
57
63
  ws="none",
64
+ timeout_graceful_shutdown=_GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS,
58
65
  )
59
- server = uvicorn.Server(config)
66
+ server = _cancellable_server(uvicorn, config)
60
67
  thread = threading.Thread(target=_profiled_server_run(server, profile), daemon=True)
61
68
  thread.start()
62
69
  _wait_for_server(server)
@@ -69,11 +76,15 @@ def embedded_queue_dev_server(
69
76
  _thread=thread,
70
77
  )
71
78
  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()
79
+ try:
80
+ _stop_server(
81
+ server,
82
+ thread,
83
+ "embedded queue dev server",
84
+ timeout=_GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS,
85
+ )
86
+ finally:
87
+ app.state.close()
77
88
 
78
89
 
79
90
  @contextlib.contextmanager
@@ -110,8 +121,9 @@ def queue_client_asgi_dev_server( # noqa: PLR0913
110
121
  log_level="warning",
111
122
  lifespan="on",
112
123
  ws="none",
124
+ timeout_graceful_shutdown=_GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS,
113
125
  )
114
- server = uvicorn.Server(config)
126
+ server = _cancellable_server(uvicorn, config)
115
127
  thread = threading.Thread(target=_profiled_server_run(server, profile), daemon=True)
116
128
  thread.start()
117
129
  _wait_for_server(server, "queue client ASGI dev server")
@@ -123,10 +135,12 @@ def queue_client_asgi_dev_server( # noqa: PLR0913
123
135
  _thread=thread,
124
136
  )
125
137
  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")
138
+ _stop_server(
139
+ server,
140
+ thread,
141
+ "queue client ASGI dev server",
142
+ timeout=_GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS,
143
+ )
130
144
 
131
145
 
132
146
  def main(argv: list[str] | None = None) -> int:
@@ -181,6 +195,52 @@ def _wait_for_server(server: Any, name: str = "embedded queue dev server") -> No
181
195
  raise RuntimeError(f"{name} did not start")
182
196
 
183
197
 
198
+ def _stop_server(
199
+ server: Any,
200
+ thread: threading.Thread,
201
+ name: str,
202
+ *,
203
+ timeout: float,
204
+ ) -> None:
205
+ server.should_exit = True
206
+ thread.join(timeout=timeout + _SERVER_STOP_TIMEOUT_MARGIN_SECONDS)
207
+ if thread.is_alive():
208
+ server.force_exit = True
209
+ server.cancel()
210
+ thread.join(timeout=timeout)
211
+ if thread.is_alive():
212
+ raise RuntimeError(f"{name} did not stop")
213
+
214
+
215
+ def _cancellable_server(uvicorn: Any, config: Any) -> Any:
216
+ class CancellableServer(uvicorn.Server):
217
+ _cancel_scope: anyio.CancelScope | None = None
218
+ _portal: from_thread.BlockingPortal | None = None
219
+
220
+ def run(self, sockets: list[Any] | None = None) -> None:
221
+ backend_options: dict[str, Any] = {}
222
+ get_loop_factory = getattr(self.config, "get_loop_factory", None)
223
+ if get_loop_factory is not None:
224
+ backend_options["loop_factory"] = get_loop_factory()
225
+ else:
226
+ self.config.setup_event_loop()
227
+ anyio.run(self._run, sockets, backend_options=backend_options)
228
+
229
+ async def _run(self, sockets: list[Any] | None) -> None:
230
+ async with from_thread.BlockingPortal() as self._portal:
231
+ with anyio.CancelScope() as self._cancel_scope:
232
+ await self.serve(sockets=sockets)
233
+
234
+ def cancel(self) -> None:
235
+ if self._portal is not None and self._cancel_scope is not None:
236
+ try:
237
+ self._portal.start_task_soon(self._cancel_scope.cancel)
238
+ except RuntimeError:
239
+ pass
240
+
241
+ return CancellableServer(config)
242
+
243
+
184
244
  def _server_port(server: Any) -> int:
185
245
  for asyncio_server in server.servers:
186
246
  sockets = asyncio_server.sockets or ()
@@ -1,3 +1,3 @@
1
1
  """Package version metadata."""
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.8.1"
File without changes
File without changes