abysshub 0.1.1.dev1__tar.gz → 0.1.1.dev3__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.
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/PKG-INFO +8 -4
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/README.md +7 -3
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/pyproject.toml +1 -1
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/src/abysshub/_async_client.py +9 -1
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/src/abysshub/_client.py +49 -7
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/src/abysshub/_error.py +14 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/src/abysshub/_http.py +9 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/tests/test_client.py +14 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/tests/test_timeout.py +2 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/.gitignore +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/LICENSE +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/src/abysshub/__init__.py +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/src/abysshub/_files.py +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/src/abysshub/_run.py +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/src/abysshub/_types.py +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/src/abysshub/py.typed +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/tests/caller.py +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/tests/fake_server.py +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/tests/test_files.py +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/tests/test_http.py +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/tests/test_package.py +0 -0
- {abysshub-0.1.1.dev1 → abysshub-0.1.1.dev3}/tests/test_replay.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: abysshub
|
|
3
|
-
Version: 0.1.1.
|
|
3
|
+
Version: 0.1.1.dev3
|
|
4
4
|
Summary: The official Python library for the Abyss API.
|
|
5
5
|
Project-URL: Homepage, https://abysshub.com
|
|
6
6
|
Project-URL: Source, https://github.com/Abysshub/abysshub-sdk
|
|
@@ -133,6 +133,9 @@ A refusal, a failed run, a timeout and a lost connection all raise one class, `A
|
|
|
133
133
|
| `param` | the Field, body key, query key or header at fault, else `None` |
|
|
134
134
|
| `doc_url` | the code on the API access page, else `None` |
|
|
135
135
|
| `request_id` | quote it to support, else `None` |
|
|
136
|
+
| `shortfall` | with `insufficient_funds`: how much Byssium the wallet is short, else `None` |
|
|
137
|
+
| `price` | with `price_above_max`: what the run costs now, else `None` |
|
|
138
|
+
| `run_id` | the id of the run the call started or read, once the press's `Location` or a run body named it, else `None` |
|
|
136
139
|
| `run` | the run, for a failed run or a timeout, else `None`; an `AsyncRun` from `AsyncAbyss` |
|
|
137
140
|
|
|
138
141
|
- **Refusals:** `invalid_api_key` (401), `not_found` (404), `invalid_request`, `invalid_input`, `held_input_*`, `input_unreachable` and `idempotency_key_reused` (422), `insufficient_funds` (402), `spend_cap_reached` and `price_above_max` (400), `idempotency_key_in_use` (409), `rate_limited` (429), `server_error` (500).
|
|
@@ -159,11 +162,12 @@ There is no timeout by default: `run()` waits as long as the run takes. `timeout
|
|
|
159
162
|
try:
|
|
160
163
|
run = abyss.run("widget_131", input, timeout=60)
|
|
161
164
|
except AbyssError as error:
|
|
162
|
-
if error.code == "timeout":
|
|
163
|
-
|
|
165
|
+
if error.code == "timeout" and error.run_id:
|
|
166
|
+
# The run goes on: wait for it again, without pressing (and paying) twice.
|
|
167
|
+
run = abyss.runs.get(error.run_id, wait=True)
|
|
164
168
|
```
|
|
165
169
|
|
|
166
|
-
It raises `AbyssError` with `code == "timeout"
|
|
170
|
+
It raises `AbyssError` with `code == "timeout"`, `run`, the run as last seen or `None`, and `run_id`, the run's id. The press answers its `Location` at once, so `run_id` is set as soon as the press has answered, even while no run has been seen yet; it is `None` only when the waiting stopped before that. Nothing is sent after the waiting stops.
|
|
167
171
|
|
|
168
172
|
## Async
|
|
169
173
|
|
|
@@ -116,6 +116,9 @@ A refusal, a failed run, a timeout and a lost connection all raise one class, `A
|
|
|
116
116
|
| `param` | the Field, body key, query key or header at fault, else `None` |
|
|
117
117
|
| `doc_url` | the code on the API access page, else `None` |
|
|
118
118
|
| `request_id` | quote it to support, else `None` |
|
|
119
|
+
| `shortfall` | with `insufficient_funds`: how much Byssium the wallet is short, else `None` |
|
|
120
|
+
| `price` | with `price_above_max`: what the run costs now, else `None` |
|
|
121
|
+
| `run_id` | the id of the run the call started or read, once the press's `Location` or a run body named it, else `None` |
|
|
119
122
|
| `run` | the run, for a failed run or a timeout, else `None`; an `AsyncRun` from `AsyncAbyss` |
|
|
120
123
|
|
|
121
124
|
- **Refusals:** `invalid_api_key` (401), `not_found` (404), `invalid_request`, `invalid_input`, `held_input_*`, `input_unreachable` and `idempotency_key_reused` (422), `insufficient_funds` (402), `spend_cap_reached` and `price_above_max` (400), `idempotency_key_in_use` (409), `rate_limited` (429), `server_error` (500).
|
|
@@ -142,11 +145,12 @@ There is no timeout by default: `run()` waits as long as the run takes. `timeout
|
|
|
142
145
|
try:
|
|
143
146
|
run = abyss.run("widget_131", input, timeout=60)
|
|
144
147
|
except AbyssError as error:
|
|
145
|
-
if error.code == "timeout":
|
|
146
|
-
|
|
148
|
+
if error.code == "timeout" and error.run_id:
|
|
149
|
+
# The run goes on: wait for it again, without pressing (and paying) twice.
|
|
150
|
+
run = abyss.runs.get(error.run_id, wait=True)
|
|
147
151
|
```
|
|
148
152
|
|
|
149
|
-
It raises `AbyssError` with `code == "timeout"
|
|
153
|
+
It raises `AbyssError` with `code == "timeout"`, `run`, the run as last seen or `None`, and `run_id`, the run's id. The press answers its `Location` at once, so `run_id` is set as soon as the press has answered, even while no run has been seen yet; it is `None` only when the waiting stopped before that. Nothing is sent after the waiting stops.
|
|
150
154
|
|
|
151
155
|
## Async
|
|
152
156
|
|
|
@@ -14,6 +14,7 @@ from ._client import (
|
|
|
14
14
|
_Call,
|
|
15
15
|
_expect,
|
|
16
16
|
_has_ended,
|
|
17
|
+
_heard,
|
|
17
18
|
_is_key,
|
|
18
19
|
_is_run,
|
|
19
20
|
_is_run_list,
|
|
@@ -260,7 +261,14 @@ class AsyncAbyss(_Base[AsyncRun]):
|
|
|
260
261
|
self._check(call)
|
|
261
262
|
try:
|
|
262
263
|
answer = await arequest(
|
|
263
|
-
self._http,
|
|
264
|
+
self._http,
|
|
265
|
+
method,
|
|
266
|
+
url,
|
|
267
|
+
sent_headers,
|
|
268
|
+
content,
|
|
269
|
+
self._timeouts(call),
|
|
270
|
+
call.deadline,
|
|
271
|
+
lambda headers: _heard(call, headers),
|
|
264
272
|
)
|
|
265
273
|
except Stopped:
|
|
266
274
|
raise self._timed_out(call) from None
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import math
|
|
5
5
|
import os
|
|
6
6
|
import platform
|
|
7
|
+
import re
|
|
7
8
|
import time
|
|
8
9
|
import uuid
|
|
9
10
|
from collections.abc import Callable, Mapping
|
|
@@ -25,6 +26,9 @@ API = "https://api.abysshub.com"
|
|
|
25
26
|
DEV_API = "https://api.dev.abysshub.com"
|
|
26
27
|
DEV_KEY_PREFIX = "abyss_sk_dev_"
|
|
27
28
|
|
|
29
|
+
RUN_LOCATION = re.compile(r"/v1/runs/([^/?#]+)/?(?:[?#]|$)")
|
|
30
|
+
"""A ``Location`` naming a run, ``…/v1/runs/{id}``, with the id as its group."""
|
|
31
|
+
|
|
28
32
|
REATTACH_PAUSE = 1.0
|
|
29
33
|
"""Seconds between two re-attaches that both ended without the run's ending."""
|
|
30
34
|
|
|
@@ -55,16 +59,21 @@ class _Reply:
|
|
|
55
59
|
"""Whether the connection was cut, or went silent, after the headers."""
|
|
56
60
|
location: str | None
|
|
57
61
|
request_id: str | None
|
|
62
|
+
run_id: str | None
|
|
63
|
+
"""The id of the call's run, as far as the call knew it when this reply arrived."""
|
|
58
64
|
|
|
59
65
|
|
|
60
66
|
@dataclass
|
|
61
67
|
class _Call:
|
|
62
|
-
"""One call's waiting: when it stops, and the run as last
|
|
68
|
+
"""One call's waiting: when it stops, and what a timeout carries: the run as last
|
|
69
|
+
seen, the run's id once known, and the latest ``Request-Id``."""
|
|
63
70
|
|
|
64
71
|
timeout: float | None
|
|
65
72
|
deadline: float | None
|
|
66
73
|
"""A ``time.monotonic()`` reading, or None to wait as long as it takes."""
|
|
67
74
|
seen: dict[str, Any] | None = None
|
|
75
|
+
run_id: str | None = None
|
|
76
|
+
request_id: str | None = None
|
|
68
77
|
|
|
69
78
|
def left(self) -> float | None:
|
|
70
79
|
return None if self.deadline is None else self.deadline - time.monotonic()
|
|
@@ -182,6 +191,8 @@ class _Base(Generic[R]):
|
|
|
182
191
|
return AbyssError(
|
|
183
192
|
"timeout",
|
|
184
193
|
f"Stopped waiting after {call.timeout} s. The run goes on.",
|
|
194
|
+
request_id=call.request_id,
|
|
195
|
+
run_id=call.run_id,
|
|
185
196
|
run=self._run(call.seen) if call.seen is not None else None,
|
|
186
197
|
)
|
|
187
198
|
|
|
@@ -268,6 +279,7 @@ class _Base(Generic[R]):
|
|
|
268
279
|
"unexpected_response",
|
|
269
280
|
"The press answered without a Location to read the run at.",
|
|
270
281
|
request_id=reply.request_id,
|
|
282
|
+
run_id=reply.run_id,
|
|
271
283
|
)
|
|
272
284
|
return str(httpx.URL(f"{self.base_url}/").join(location).copy_set_param("wait", "true"))
|
|
273
285
|
|
|
@@ -290,6 +302,7 @@ class _Base(Generic[R]):
|
|
|
290
302
|
raise AbyssError(
|
|
291
303
|
"connection_error",
|
|
292
304
|
"The connection dropped, or timed out, before the API answered.",
|
|
305
|
+
run_id=call.run_id,
|
|
293
306
|
) from error
|
|
294
307
|
return _backoff(attempt)
|
|
295
308
|
|
|
@@ -302,10 +315,12 @@ class _Base(Generic[R]):
|
|
|
302
315
|
run = parsed if _is_run(parsed) else None
|
|
303
316
|
if run is not None:
|
|
304
317
|
call.seen = run
|
|
305
|
-
|
|
318
|
+
call.run_id = run["id"]
|
|
319
|
+
location = answer.headers.get("location")
|
|
320
|
+
return _Reply(parsed, run, answer.text is None, location, request_id, call.run_id)
|
|
306
321
|
pause = _retry_pause(answer, attempt)
|
|
307
322
|
if pause is None or attempt >= limit:
|
|
308
|
-
raise _refusal(answer.status, parsed, request_id)
|
|
323
|
+
raise _refusal(answer.status, parsed, request_id, call.run_id)
|
|
309
324
|
return pause
|
|
310
325
|
|
|
311
326
|
def _ended(self, reply: _Reply) -> R:
|
|
@@ -314,7 +329,7 @@ class _Base(Generic[R]):
|
|
|
314
329
|
run = self._run(reply.run)
|
|
315
330
|
if run.status == "failed":
|
|
316
331
|
error = run.error or RunError("platform_fault", "The run failed.")
|
|
317
|
-
raise AbyssError(error.code, error.message, request_id=reply.request_id, run=run)
|
|
332
|
+
raise AbyssError(error.code, error.message, request_id=reply.request_id, run_id=run.id, run=run)
|
|
318
333
|
return run
|
|
319
334
|
|
|
320
335
|
|
|
@@ -481,7 +496,16 @@ class Abyss(_Base[Run]):
|
|
|
481
496
|
while True:
|
|
482
497
|
self._check(call)
|
|
483
498
|
try:
|
|
484
|
-
answer = request(
|
|
499
|
+
answer = request(
|
|
500
|
+
self._http,
|
|
501
|
+
method,
|
|
502
|
+
url,
|
|
503
|
+
sent_headers,
|
|
504
|
+
content,
|
|
505
|
+
self._timeouts(call),
|
|
506
|
+
call.deadline,
|
|
507
|
+
lambda headers: _heard(call, headers),
|
|
508
|
+
)
|
|
485
509
|
except Stopped:
|
|
486
510
|
raise self._timed_out(call) from None
|
|
487
511
|
except httpx.TransportError as error:
|
|
@@ -495,6 +519,14 @@ class Abyss(_Base[Run]):
|
|
|
495
519
|
attempt += 1
|
|
496
520
|
|
|
497
521
|
|
|
522
|
+
def _heard(call: _Call, headers: httpx.Headers) -> None:
|
|
523
|
+
"""Notes a response's headers as they arrive: its ``Request-Id``, and the run its ``Location`` names."""
|
|
524
|
+
call.request_id = headers.get("request-id", call.request_id)
|
|
525
|
+
match = RUN_LOCATION.search(headers.get("location", ""))
|
|
526
|
+
if match is not None:
|
|
527
|
+
call.run_id = match.group(1)
|
|
528
|
+
|
|
529
|
+
|
|
498
530
|
def _retry_pause(answer: Answer, attempt: int) -> float | None:
|
|
499
531
|
"""How long to wait before sending a refused request again, or None when it is not retried."""
|
|
500
532
|
if answer.status in (409, 429):
|
|
@@ -516,10 +548,10 @@ def _expect(reply: _Reply, is_: Callable[[Any], bool], message: str) -> Any:
|
|
|
516
548
|
"""The reply's body when it is what the route answers, else ``unexpected_response``."""
|
|
517
549
|
if is_(reply.body):
|
|
518
550
|
return reply.body
|
|
519
|
-
raise AbyssError("unexpected_response", message, request_id=reply.request_id)
|
|
551
|
+
raise AbyssError("unexpected_response", message, request_id=reply.request_id, run_id=reply.run_id)
|
|
520
552
|
|
|
521
553
|
|
|
522
|
-
def _refusal(status: int, body: Any, request_id: str | None) -> AbyssError:
|
|
554
|
+
def _refusal(status: int, body: Any, request_id: str | None, run_id: str | None) -> AbyssError:
|
|
523
555
|
error = body.get("error") if isinstance(body, dict) else None
|
|
524
556
|
if not isinstance(error, dict) or not isinstance(error.get("code"), str):
|
|
525
557
|
return AbyssError(
|
|
@@ -527,6 +559,7 @@ def _refusal(status: int, body: Any, request_id: str | None) -> AbyssError:
|
|
|
527
559
|
f"The API answered {status} without an error body.",
|
|
528
560
|
status=status,
|
|
529
561
|
request_id=request_id,
|
|
562
|
+
run_id=run_id,
|
|
530
563
|
)
|
|
531
564
|
body_request_id = body.get("request_id")
|
|
532
565
|
return AbyssError(
|
|
@@ -535,7 +568,10 @@ def _refusal(status: int, body: Any, request_id: str | None) -> AbyssError:
|
|
|
535
568
|
status=status,
|
|
536
569
|
param=_text(error.get("param")),
|
|
537
570
|
doc_url=_text(error.get("doc_url")),
|
|
571
|
+
shortfall=_number(error.get("shortfall")),
|
|
572
|
+
price=_number(error.get("price")),
|
|
538
573
|
request_id=body_request_id if isinstance(body_request_id, str) else request_id,
|
|
574
|
+
run_id=run_id,
|
|
539
575
|
)
|
|
540
576
|
|
|
541
577
|
|
|
@@ -543,6 +579,12 @@ def _text(value: Any) -> str | None:
|
|
|
543
579
|
return value if isinstance(value, str) else None
|
|
544
580
|
|
|
545
581
|
|
|
582
|
+
def _number(value: Any) -> float | None:
|
|
583
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
584
|
+
return None
|
|
585
|
+
return float(value)
|
|
586
|
+
|
|
587
|
+
|
|
546
588
|
def _parse(text: str | None) -> Any:
|
|
547
589
|
if text is None:
|
|
548
590
|
return None
|
|
@@ -19,6 +19,14 @@ class AbyssError(Exception):
|
|
|
19
19
|
param: str | None
|
|
20
20
|
doc_url: str | None
|
|
21
21
|
request_id: str | None
|
|
22
|
+
run_id: str | None
|
|
23
|
+
"""The id of the run the call started or read, once the call has learned it from a
|
|
24
|
+
``Location`` header or a run body; else None. A ``timeout`` carries it even when no
|
|
25
|
+
run body has arrived, so ``runs.get(run_id, wait=True)`` finds the run again."""
|
|
26
|
+
shortfall: float | None
|
|
27
|
+
"""With ``insufficient_funds``: how much Byssium the wallet is short; else None."""
|
|
28
|
+
price: float | None
|
|
29
|
+
"""With ``price_above_max``: what the run costs now; else None."""
|
|
22
30
|
run: Run | AsyncRun | None
|
|
23
31
|
"""The run, for a failed run or a timeout: an ``AsyncRun`` from ``AsyncAbyss``."""
|
|
24
32
|
|
|
@@ -31,6 +39,9 @@ class AbyssError(Exception):
|
|
|
31
39
|
param: str | None = None,
|
|
32
40
|
doc_url: str | None = None,
|
|
33
41
|
request_id: str | None = None,
|
|
42
|
+
run_id: str | None = None,
|
|
43
|
+
shortfall: float | None = None,
|
|
44
|
+
price: float | None = None,
|
|
34
45
|
run: Run | AsyncRun | None = None,
|
|
35
46
|
) -> None:
|
|
36
47
|
super().__init__(message)
|
|
@@ -40,6 +51,9 @@ class AbyssError(Exception):
|
|
|
40
51
|
self.param = param
|
|
41
52
|
self.doc_url = doc_url
|
|
42
53
|
self.request_id = request_id
|
|
54
|
+
self.run_id = run_id
|
|
55
|
+
self.shortfall = shortfall
|
|
56
|
+
self.price = price
|
|
43
57
|
self.run = run
|
|
44
58
|
|
|
45
59
|
def __repr__(self) -> str:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
|
|
6
7
|
import httpx
|
|
@@ -37,16 +38,21 @@ def request(
|
|
|
37
38
|
content: bytes | None = None,
|
|
38
39
|
timeout: httpx.Timeout | None = None,
|
|
39
40
|
deadline: float | None = None,
|
|
41
|
+
heard: Callable[[httpx.Headers], None] | None = None,
|
|
40
42
|
) -> Answer:
|
|
41
43
|
"""Sends one request and reads its whole body.
|
|
42
44
|
|
|
43
45
|
A failure before the headers raises ``httpx.TransportError``; a cut after them
|
|
44
46
|
answers ``text=None``, so the caller can re-attach. Past ``deadline`` (a
|
|
45
47
|
``time.monotonic()`` reading), a hold that still sends its spaces raises ``Stopped``.
|
|
48
|
+
``heard`` is handed the headers as soon as they arrive, so a stop during the body
|
|
49
|
+
still leaves them known.
|
|
46
50
|
"""
|
|
47
51
|
with client.stream(
|
|
48
52
|
method, url, headers=headers, content=content, timeout=timeout or client.timeout
|
|
49
53
|
) as response:
|
|
54
|
+
if heard is not None:
|
|
55
|
+
heard(response.headers)
|
|
50
56
|
chunks = []
|
|
51
57
|
try:
|
|
52
58
|
for chunk in response.iter_bytes():
|
|
@@ -66,11 +72,14 @@ async def arequest(
|
|
|
66
72
|
content: bytes | None = None,
|
|
67
73
|
timeout: httpx.Timeout | None = None,
|
|
68
74
|
deadline: float | None = None,
|
|
75
|
+
heard: Callable[[httpx.Headers], None] | None = None,
|
|
69
76
|
) -> Answer:
|
|
70
77
|
"""``request()`` on httpx's async client."""
|
|
71
78
|
async with client.stream(
|
|
72
79
|
method, url, headers=headers, content=content, timeout=timeout or client.timeout
|
|
73
80
|
) as response:
|
|
81
|
+
if heard is not None:
|
|
82
|
+
heard(response.headers)
|
|
74
83
|
chunks = []
|
|
75
84
|
try:
|
|
76
85
|
async for chunk in response.aiter_bytes():
|
|
@@ -67,3 +67,17 @@ def test_every_request_sends_the_user_agent() -> None:
|
|
|
67
67
|
assert server.problems == []
|
|
68
68
|
finally:
|
|
69
69
|
server.close()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_a_refusal_without_shortfall_or_price_carries_none_for_both() -> None:
|
|
73
|
+
recording = load_exchanges("run-refused-invalid-input.json")[0]
|
|
74
|
+
server = Replay(recording, "abyss_sk_x")
|
|
75
|
+
try:
|
|
76
|
+
with Abyss(api_key="abyss_sk_x", base_url=server.base) as abyss, pytest.raises(AbyssError) as raised:
|
|
77
|
+
abyss.run(recording["call"]["widget"], recording["call"]["input"])
|
|
78
|
+
assert raised.value.code == "invalid_input"
|
|
79
|
+
assert raised.value.shortfall is None
|
|
80
|
+
assert raised.value.price is None
|
|
81
|
+
assert server.problems == []
|
|
82
|
+
finally:
|
|
83
|
+
server.close()
|
|
@@ -33,6 +33,7 @@ def test_the_clients_timeout_applies_to_every_call() -> None:
|
|
|
33
33
|
abyss.run(WIDGET, INPUT)
|
|
34
34
|
assert raised.value.code == "timeout"
|
|
35
35
|
assert raised.value.run is not None and raised.value.run.id == ID
|
|
36
|
+
assert raised.value.run_id == ID
|
|
36
37
|
|
|
37
38
|
against(work)
|
|
38
39
|
|
|
@@ -62,5 +63,6 @@ def test_async_the_clients_timeout_applies_and_a_calls_overrides_it(client: floa
|
|
|
62
63
|
assert raised.value.code == "timeout"
|
|
63
64
|
error_run = raised.value.run
|
|
64
65
|
assert isinstance(error_run, AsyncRun) and error_run.id == ID and error_run.status == "queued"
|
|
66
|
+
assert raised.value.run_id == ID
|
|
65
67
|
|
|
66
68
|
against(lambda base: asyncio.run(run(base)))
|
|
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
|