spooled 1.2.0__tar.gz → 1.2.2__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.
- {spooled-1.2.0 → spooled-1.2.2}/PKG-INFO +1 -1
- {spooled-1.2.0 → spooled-1.2.2}/pyproject.toml +1 -1
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/_version.py +1 -1
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/realtime/sse.py +42 -20
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/realtime/unified.py +22 -10
- {spooled-1.2.0 → spooled-1.2.2}/.gitignore +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/LICENSE +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/README.md +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/proto/spooled.proto +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/__init__.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/async_client.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/client.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/config.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/errors.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/grpc/__init__.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/grpc/client.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/grpc/stubs/__init__.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/grpc/stubs/spooled_pb2.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/grpc/stubs/spooled_pb2.pyi +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/grpc/stubs/spooled_pb2_grpc.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/py.typed +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/realtime/__init__.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/realtime/events.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/realtime/websocket.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/__init__.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/admin.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/api_keys.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/auth.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/base.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/billing.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/dashboard.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/health.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/ingest.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/jobs.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/metrics.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/organizations.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/queues.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/schedules.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/webhooks.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/workers.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/resources/workflows.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/__init__.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/admin.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/api_keys.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/auth.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/billing.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/common.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/dashboard.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/health.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/jobs.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/organizations.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/queues.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/schedules.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/webhooks.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/workers.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/types/workflows.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/utils/__init__.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/utils/async_http.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/utils/casing.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/utils/circuit_breaker.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/utils/http.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/utils/jwt.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/utils/retry.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/worker/__init__.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/worker/async_worker.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/worker/types.py +0 -0
- {spooled-1.2.0 → spooled-1.2.2}/src/spooled/worker/worker.py +0 -0
|
@@ -12,6 +12,7 @@ import time
|
|
|
12
12
|
from collections.abc import AsyncGenerator, Callable, Generator
|
|
13
13
|
from enum import Enum
|
|
14
14
|
from typing import Any
|
|
15
|
+
from urllib.parse import quote
|
|
15
16
|
|
|
16
17
|
import httpx
|
|
17
18
|
|
|
@@ -285,8 +286,11 @@ class SSEClient:
|
|
|
285
286
|
"""Build SSE endpoint URL."""
|
|
286
287
|
if self._job_id:
|
|
287
288
|
return f"{self._base_url}/api/v1/events/jobs/{self._job_id}"
|
|
289
|
+
# A queue subscription uses /events?queue=: /events/queues/{name} carries
|
|
290
|
+
# only queue.stats, so job events never arrived for a queue filter.
|
|
291
|
+
# Requires API 0.1.114+.
|
|
288
292
|
if self._queue:
|
|
289
|
-
return f"{self._base_url}/api/v1/events
|
|
293
|
+
return f"{self._base_url}/api/v1/events?queue={quote(self._queue, safe='')}"
|
|
290
294
|
return f"{self._base_url}/api/v1/events"
|
|
291
295
|
|
|
292
296
|
def connect(self) -> None:
|
|
@@ -300,16 +304,27 @@ class SSEClient:
|
|
|
300
304
|
|
|
301
305
|
try:
|
|
302
306
|
self._client = httpx.Client()
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
307
|
+
# send(stream=True) keeps the response open until close(). Entering
|
|
308
|
+
# client.stream(...) by hand dropped its context manager, which the
|
|
309
|
+
# garbage collector then exited — closing the stream before any
|
|
310
|
+
# event was read ("the stream has been closed").
|
|
311
|
+
self._response = self._client.send(
|
|
312
|
+
self._client.build_request(
|
|
313
|
+
"GET",
|
|
314
|
+
url,
|
|
315
|
+
headers={
|
|
316
|
+
"Authorization": f"Bearer {self._token}",
|
|
317
|
+
"Accept": "text/event-stream",
|
|
318
|
+
},
|
|
319
|
+
),
|
|
320
|
+
stream=True,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# sseclient needs raw byte chunks: it finds event boundaries by the
|
|
324
|
+
# blank line between events. iter_lines() yields str lines with the
|
|
325
|
+
# newlines stripped, so the first line raised TypeError and the
|
|
326
|
+
# stream never produced an event.
|
|
327
|
+
self._sse_client = sseclient.SSEClient(self._response.iter_bytes()) # type: ignore[arg-type]
|
|
313
328
|
self._set_state(SSEConnectionState.CONNECTED)
|
|
314
329
|
self._reconnect_attempts = 0
|
|
315
330
|
self._closed = False
|
|
@@ -629,8 +644,11 @@ class AsyncSSEClient:
|
|
|
629
644
|
"""Build SSE endpoint URL."""
|
|
630
645
|
if self._job_id:
|
|
631
646
|
return f"{self._base_url}/api/v1/events/jobs/{self._job_id}"
|
|
647
|
+
# A queue subscription uses /events?queue=: /events/queues/{name} carries
|
|
648
|
+
# only queue.stats, so job events never arrived for a queue filter.
|
|
649
|
+
# Requires API 0.1.114+.
|
|
632
650
|
if self._queue:
|
|
633
|
-
return f"{self._base_url}/api/v1/events
|
|
651
|
+
return f"{self._base_url}/api/v1/events?queue={quote(self._queue, safe='')}"
|
|
634
652
|
return f"{self._base_url}/api/v1/events"
|
|
635
653
|
|
|
636
654
|
async def connect(self) -> None:
|
|
@@ -643,14 +661,18 @@ class AsyncSSEClient:
|
|
|
643
661
|
|
|
644
662
|
try:
|
|
645
663
|
self._client = httpx.AsyncClient()
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
664
|
+
# See SSEClient.connect: keep the response open until close().
|
|
665
|
+
self._response = await self._client.send(
|
|
666
|
+
self._client.build_request(
|
|
667
|
+
"GET",
|
|
668
|
+
url,
|
|
669
|
+
headers={
|
|
670
|
+
"Authorization": f"Bearer {self._token}",
|
|
671
|
+
"Accept": "text/event-stream",
|
|
672
|
+
},
|
|
673
|
+
),
|
|
674
|
+
stream=True,
|
|
675
|
+
)
|
|
654
676
|
self._set_state(SSEConnectionState.CONNECTED)
|
|
655
677
|
self._reconnect_attempts = 0
|
|
656
678
|
self._closed = False
|
|
@@ -15,6 +15,7 @@ from collections.abc import Callable, Generator
|
|
|
15
15
|
from dataclasses import dataclass
|
|
16
16
|
from enum import Enum
|
|
17
17
|
from typing import Any, Literal
|
|
18
|
+
from urllib.parse import quote
|
|
18
19
|
|
|
19
20
|
from spooled.realtime.events import (
|
|
20
21
|
RealtimeEvent,
|
|
@@ -509,17 +510,24 @@ class SpooledRealtime:
|
|
|
509
510
|
|
|
510
511
|
try:
|
|
511
512
|
self._http_client = httpx.Client()
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
"
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
513
|
+
# send(stream=True) keeps the response open until disconnect(); a
|
|
514
|
+
# hand-entered client.stream(...) was closed by the GC (see sse.py).
|
|
515
|
+
self._sse_response = self._http_client.send(
|
|
516
|
+
self._http_client.build_request(
|
|
517
|
+
"GET",
|
|
518
|
+
url,
|
|
519
|
+
headers={
|
|
520
|
+
"Authorization": f"Bearer {self._options.token}",
|
|
521
|
+
"Accept": "text/event-stream",
|
|
522
|
+
},
|
|
523
|
+
),
|
|
524
|
+
stream=True,
|
|
525
|
+
)
|
|
520
526
|
|
|
521
527
|
if HAS_SSE:
|
|
522
|
-
|
|
528
|
+
# Raw byte chunks: sseclient splits events on the blank line,
|
|
529
|
+
# which iter_lines() strips (see realtime/sse.py).
|
|
530
|
+
self._sse_client = sseclient.SSEClient(self._sse_response.iter_bytes())
|
|
523
531
|
|
|
524
532
|
self._set_state(ConnectionState.CONNECTED)
|
|
525
533
|
self._reconnect_attempts = 0
|
|
@@ -578,8 +586,12 @@ class SpooledRealtime:
|
|
|
578
586
|
if filter:
|
|
579
587
|
if filter.job_id:
|
|
580
588
|
return f"{self._options.base_url}/api/v1/events/jobs/{filter.job_id}"
|
|
589
|
+
# /events/queues/{name} carries only queue.stats; job events for a
|
|
590
|
+
# queue come from /events?queue= (API 0.1.114+).
|
|
581
591
|
if filter.queue:
|
|
582
|
-
return
|
|
592
|
+
return (
|
|
593
|
+
f"{self._options.base_url}/api/v1/events?queue={quote(filter.queue, safe='')}"
|
|
594
|
+
)
|
|
583
595
|
|
|
584
596
|
return f"{self._options.base_url}/api/v1/events"
|
|
585
597
|
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|