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,313 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Concatenate, Generic, ParamSpec, TypeVar
|
|
4
|
+
|
|
5
|
+
import codecs
|
|
6
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping
|
|
7
|
+
from functools import wraps
|
|
8
|
+
|
|
9
|
+
import anyio
|
|
10
|
+
from anyio.abc import ByteReceiveStream
|
|
11
|
+
from anyio.streams.buffered import BufferedByteReceiveStream
|
|
12
|
+
|
|
13
|
+
from .asynctools import iter_async_iterator
|
|
14
|
+
|
|
15
|
+
_DEFAULT_READ_SIZE = 64 * 1024
|
|
16
|
+
_DEFAULT_DELIMITER_LIMIT = 64 * 1024
|
|
17
|
+
|
|
18
|
+
_P = ParamSpec("_P")
|
|
19
|
+
_R = TypeVar("_R")
|
|
20
|
+
_T = TypeVar("_T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AsyncIteratorByteReceiveStream(ByteReceiveStream):
|
|
24
|
+
"""Adapt an async byte iterator to AnyIO's byte receive stream protocol."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, stream: AsyncIterator[bytes]) -> None:
|
|
27
|
+
self._stream = stream
|
|
28
|
+
self._buffer = bytearray()
|
|
29
|
+
self._closed = False
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def extra_attributes(self) -> Mapping[Any, Any]:
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
async def receive(self, max_bytes: int = 65536) -> bytes:
|
|
36
|
+
if self._closed:
|
|
37
|
+
raise anyio.ClosedResourceError
|
|
38
|
+
if max_bytes < 1:
|
|
39
|
+
raise ValueError("max_bytes must be greater than zero")
|
|
40
|
+
if self._buffer:
|
|
41
|
+
chunk = bytes(self._buffer[:max_bytes])
|
|
42
|
+
del self._buffer[:max_bytes]
|
|
43
|
+
return chunk
|
|
44
|
+
try:
|
|
45
|
+
chunk = await anext(self._stream)
|
|
46
|
+
except StopAsyncIteration:
|
|
47
|
+
raise anyio.EndOfStream from None
|
|
48
|
+
if not chunk:
|
|
49
|
+
return await self.receive(max_bytes)
|
|
50
|
+
if len(chunk) > max_bytes:
|
|
51
|
+
self._buffer.extend(chunk[max_bytes:])
|
|
52
|
+
return chunk[:max_bytes]
|
|
53
|
+
return chunk
|
|
54
|
+
|
|
55
|
+
async def aclose(self) -> None:
|
|
56
|
+
self._closed = True
|
|
57
|
+
close = getattr(self._stream, "aclose", None)
|
|
58
|
+
if close is not None:
|
|
59
|
+
await close()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def buffered_byte_receive_stream(
|
|
63
|
+
stream: AsyncIterator[bytes],
|
|
64
|
+
) -> BufferedByteReceiveStream:
|
|
65
|
+
return BufferedByteReceiveStream(AsyncIteratorByteReceiveStream(stream))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class _SyncStreamPayloadBase(Generic[_T]):
|
|
69
|
+
def __init__(self, stream: Iterator[_T]) -> None:
|
|
70
|
+
self._stream = stream
|
|
71
|
+
self._consumed = False
|
|
72
|
+
|
|
73
|
+
def __iter__(self) -> Iterator[_T]:
|
|
74
|
+
if self._consumed:
|
|
75
|
+
return iter(())
|
|
76
|
+
self._consumed = True
|
|
77
|
+
return self._stream
|
|
78
|
+
|
|
79
|
+
def finalize(self) -> None:
|
|
80
|
+
for _ in self._stream:
|
|
81
|
+
pass
|
|
82
|
+
self._consumed = True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class SyncStreamPayload(_SyncStreamPayloadBase[bytes]):
|
|
86
|
+
"""One-shot synchronous byte stream for a message body.
|
|
87
|
+
|
|
88
|
+
Multipart receive responses expose part bodies as streams. The sync client
|
|
89
|
+
wraps the async body iterator so callers can process large payloads without
|
|
90
|
+
requiring the SDK to buffer them first.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class _ReadGuard:
|
|
95
|
+
def __init__(self) -> None:
|
|
96
|
+
self._reading = False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
_S = TypeVar("_S", bound=_ReadGuard)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _guarded_read(
|
|
103
|
+
func: Callable[Concatenate[_S, _P], Awaitable[_R]],
|
|
104
|
+
) -> Callable[Concatenate[_S, _P], Awaitable[_R]]:
|
|
105
|
+
@wraps(func)
|
|
106
|
+
async def wrapper(self: _S, *args: _P.args, **kwargs: _P.kwargs) -> _R:
|
|
107
|
+
if self._reading:
|
|
108
|
+
raise anyio.BusyResourceError(f"reading from {type(self).__name__}")
|
|
109
|
+
self._reading = True
|
|
110
|
+
try:
|
|
111
|
+
return await func(self, *args, **kwargs)
|
|
112
|
+
finally:
|
|
113
|
+
self._reading = False
|
|
114
|
+
|
|
115
|
+
return wrapper
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _guarded_iter(
|
|
119
|
+
func: Callable[Concatenate[_S, _P], AsyncIterator[_T]],
|
|
120
|
+
) -> Callable[Concatenate[_S, _P], AsyncIterator[_T]]:
|
|
121
|
+
@wraps(func)
|
|
122
|
+
async def wrapper(
|
|
123
|
+
self: _S,
|
|
124
|
+
*args: _P.args,
|
|
125
|
+
**kwargs: _P.kwargs,
|
|
126
|
+
) -> AsyncIterator[_T]:
|
|
127
|
+
if self._reading:
|
|
128
|
+
raise anyio.BusyResourceError(f"reading from {type(self).__name__}")
|
|
129
|
+
self._reading = True
|
|
130
|
+
try:
|
|
131
|
+
async for item in func(self, *args, **kwargs):
|
|
132
|
+
yield item
|
|
133
|
+
finally:
|
|
134
|
+
self._reading = False
|
|
135
|
+
|
|
136
|
+
return wrapper
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class AsyncStreamPayload(_ReadGuard):
|
|
140
|
+
"""One-shot asynchronous byte stream for a message body.
|
|
141
|
+
|
|
142
|
+
Iteration and reader methods share a single cursor over the underlying
|
|
143
|
+
stream. Concurrent reads or iteration are unsupported.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
stream: AsyncIterator[bytes],
|
|
149
|
+
*,
|
|
150
|
+
on_close: Callable[[], Awaitable[None]] | None = None,
|
|
151
|
+
) -> None:
|
|
152
|
+
super().__init__()
|
|
153
|
+
self._stream = buffered_byte_receive_stream(stream)
|
|
154
|
+
self._on_close = on_close
|
|
155
|
+
self._closed = False
|
|
156
|
+
|
|
157
|
+
def __aiter__(self) -> AsyncIterator[bytes]:
|
|
158
|
+
return self._iterate()
|
|
159
|
+
|
|
160
|
+
def to_sync(self) -> SyncStreamPayload:
|
|
161
|
+
return SyncStreamPayload(iter_async_iterator(aiter(self)))
|
|
162
|
+
|
|
163
|
+
@_guarded_read
|
|
164
|
+
async def read(self, n: int = -1) -> bytes:
|
|
165
|
+
try:
|
|
166
|
+
if n < 0:
|
|
167
|
+
return await self._read_all()
|
|
168
|
+
if n == 0:
|
|
169
|
+
return b""
|
|
170
|
+
return await self._stream.receive(n)
|
|
171
|
+
except anyio.EndOfStream:
|
|
172
|
+
return b""
|
|
173
|
+
|
|
174
|
+
@_guarded_read
|
|
175
|
+
async def readline(self) -> bytes:
|
|
176
|
+
try:
|
|
177
|
+
return await self._stream.receive_until(b"\n", _DEFAULT_DELIMITER_LIMIT) + b"\n"
|
|
178
|
+
except anyio.IncompleteRead:
|
|
179
|
+
partial = self._stream.buffer
|
|
180
|
+
self._stream.feed_data(b"")
|
|
181
|
+
await self._discard_buffer()
|
|
182
|
+
return partial
|
|
183
|
+
|
|
184
|
+
async def _read_all(self) -> bytes:
|
|
185
|
+
chunks: list[bytes] = []
|
|
186
|
+
while True:
|
|
187
|
+
try:
|
|
188
|
+
chunk = await self._stream.receive(_DEFAULT_READ_SIZE)
|
|
189
|
+
except anyio.EndOfStream:
|
|
190
|
+
break
|
|
191
|
+
chunks.append(chunk)
|
|
192
|
+
return b"".join(chunks)
|
|
193
|
+
|
|
194
|
+
@_guarded_read
|
|
195
|
+
async def readexactly(self, n: int) -> bytes:
|
|
196
|
+
if n < 0:
|
|
197
|
+
raise ValueError("readexactly size can not be less than zero")
|
|
198
|
+
return await self._stream.receive_exactly(n)
|
|
199
|
+
|
|
200
|
+
@_guarded_read
|
|
201
|
+
async def readuntil(self, separator: bytes = b"\n") -> bytes:
|
|
202
|
+
if not separator:
|
|
203
|
+
raise ValueError("Separator should be at least one-byte string")
|
|
204
|
+
return await self._stream.receive_until(separator, _DEFAULT_DELIMITER_LIMIT) + separator
|
|
205
|
+
|
|
206
|
+
@_guarded_read
|
|
207
|
+
async def afinalize(self) -> None:
|
|
208
|
+
if self._closed:
|
|
209
|
+
return
|
|
210
|
+
try:
|
|
211
|
+
while True:
|
|
212
|
+
try:
|
|
213
|
+
await self._stream.receive(_DEFAULT_READ_SIZE)
|
|
214
|
+
except anyio.EndOfStream:
|
|
215
|
+
break
|
|
216
|
+
finally:
|
|
217
|
+
self._closed = True
|
|
218
|
+
if self._on_close is not None:
|
|
219
|
+
await self._on_close()
|
|
220
|
+
|
|
221
|
+
@_guarded_iter
|
|
222
|
+
async def _iterate(self) -> AsyncIterator[bytes]:
|
|
223
|
+
while True:
|
|
224
|
+
try:
|
|
225
|
+
yield await self._stream.receive(_DEFAULT_READ_SIZE)
|
|
226
|
+
except anyio.EndOfStream:
|
|
227
|
+
break
|
|
228
|
+
|
|
229
|
+
async def _discard_buffer(self) -> None:
|
|
230
|
+
if not self._stream.buffer:
|
|
231
|
+
return
|
|
232
|
+
await self._stream.receive(len(self._stream.buffer))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class SyncTextStreamPayload(_SyncStreamPayloadBase[str]):
|
|
236
|
+
"""One-shot synchronous text stream for a UTF-8 message body."""
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class AsyncTextStreamPayload(_ReadGuard):
|
|
240
|
+
"""One-shot asynchronous text stream for a UTF-8 message body."""
|
|
241
|
+
|
|
242
|
+
def __init__(
|
|
243
|
+
self,
|
|
244
|
+
stream: AsyncIterator[bytes] | AsyncStreamPayload,
|
|
245
|
+
*,
|
|
246
|
+
on_close: Callable[[], Awaitable[None]] | None = None,
|
|
247
|
+
) -> None:
|
|
248
|
+
if isinstance(stream, AsyncStreamPayload):
|
|
249
|
+
self._stream = stream
|
|
250
|
+
else:
|
|
251
|
+
self._stream = AsyncStreamPayload(stream, on_close=on_close)
|
|
252
|
+
super().__init__()
|
|
253
|
+
self._decoder = codecs.getincrementaldecoder("utf-8")()
|
|
254
|
+
self._buffer = ""
|
|
255
|
+
self._eof = False
|
|
256
|
+
self._closed = False
|
|
257
|
+
|
|
258
|
+
def __aiter__(self) -> AsyncIterator[str]:
|
|
259
|
+
return self._iterate()
|
|
260
|
+
|
|
261
|
+
def to_sync(self) -> SyncTextStreamPayload:
|
|
262
|
+
return SyncTextStreamPayload(iter_async_iterator(aiter(self)))
|
|
263
|
+
|
|
264
|
+
@_guarded_read
|
|
265
|
+
async def read(self, n: int = -1) -> str:
|
|
266
|
+
if n == 0:
|
|
267
|
+
return ""
|
|
268
|
+
if n < 0:
|
|
269
|
+
return await self._read_all()
|
|
270
|
+
while len(self._buffer) < n:
|
|
271
|
+
text = await self._read_next_text()
|
|
272
|
+
if not text:
|
|
273
|
+
break
|
|
274
|
+
self._buffer += text
|
|
275
|
+
data = self._buffer[:n]
|
|
276
|
+
self._buffer = self._buffer[n:]
|
|
277
|
+
return data
|
|
278
|
+
|
|
279
|
+
async def afinalize(self) -> None:
|
|
280
|
+
if self._closed:
|
|
281
|
+
return
|
|
282
|
+
try:
|
|
283
|
+
await self._stream.afinalize()
|
|
284
|
+
finally:
|
|
285
|
+
self._closed = True
|
|
286
|
+
|
|
287
|
+
@_guarded_iter
|
|
288
|
+
async def _iterate(self) -> AsyncIterator[str]:
|
|
289
|
+
if self._buffer:
|
|
290
|
+
yield self._read_buffered_text()
|
|
291
|
+
while text := await self._read_next_text():
|
|
292
|
+
yield text
|
|
293
|
+
|
|
294
|
+
async def _read_all(self) -> str:
|
|
295
|
+
chunks = [self._read_buffered_text()] if self._buffer else []
|
|
296
|
+
while text := await self._read_next_text():
|
|
297
|
+
chunks.append(text)
|
|
298
|
+
return "".join(chunks)
|
|
299
|
+
|
|
300
|
+
def _read_buffered_text(self) -> str:
|
|
301
|
+
text = self._buffer
|
|
302
|
+
self._buffer = ""
|
|
303
|
+
return text
|
|
304
|
+
|
|
305
|
+
async def _read_next_text(self) -> str:
|
|
306
|
+
if self._eof:
|
|
307
|
+
return ""
|
|
308
|
+
while chunk := await self._stream.read(_DEFAULT_READ_SIZE):
|
|
309
|
+
text = self._decoder.decode(chunk)
|
|
310
|
+
if text:
|
|
311
|
+
return text
|
|
312
|
+
self._eof = True
|
|
313
|
+
return self._decoder.decode(b"", final=True)
|