abysshub 0.0.1.dev14__tar.gz → 0.0.1.dev15__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 (21) hide show
  1. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/PKG-INFO +5 -3
  2. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/README.md +4 -2
  3. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/pyproject.toml +1 -1
  4. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/src/abysshub/_client.py +17 -7
  5. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/.gitignore +0 -0
  6. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/LICENSE +0 -0
  7. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/src/abysshub/__init__.py +0 -0
  8. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/src/abysshub/_error.py +0 -0
  9. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/src/abysshub/_files.py +0 -0
  10. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/src/abysshub/_http.py +0 -0
  11. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/src/abysshub/_run.py +0 -0
  12. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/src/abysshub/_types.py +0 -0
  13. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/src/abysshub/py.typed +0 -0
  14. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/tests/caller.py +0 -0
  15. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/tests/fake_server.py +0 -0
  16. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/tests/test_client.py +0 -0
  17. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/tests/test_files.py +0 -0
  18. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/tests/test_http.py +0 -0
  19. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/tests/test_package.py +0 -0
  20. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/tests/test_replay.py +0 -0
  21. {abysshub-0.0.1.dev14 → abysshub-0.0.1.dev15}/tests/test_timeout.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: abysshub
3
- Version: 0.0.1.dev14
3
+ Version: 0.0.1.dev15
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
@@ -68,7 +68,7 @@ run = abyss.run(widget, input, max_price=None, idempotency_key=None, timeout=Non
68
68
  - **`idempotency_key`:** each call presses with a fresh UUID `Idempotency-Key` unless you give one. Retries send the same one, so a run is never made or charged twice.
69
69
  - **`timeout`:** the seconds to wait at most (see [Timeouts](#timeouts)).
70
70
 
71
- The press holds until the run ends. When the hold ends early (a deploy cuts it, the API's holds are all taken, or an hour passes), `run()` re-attaches with `GET <Location>?wait=true` until the run ends. Re-attaching has no limit and is not a retry. Connecting has 10 s, and 30 s of silence counts as a cut.
71
+ The press holds until the run ends. When the hold ends early (a deploy cuts it, the network goes down, the API's holds are all taken, or an hour passes), `run()` re-attaches with `GET <Location>?wait=true` until the run ends. Re-attaching has no limit and is not a retry. Connecting has 10 s, and 30 s of silence counts as a cut.
72
72
 
73
73
  A **run** is a dataclass with `/v1`'s fields: `id`, `widget`, `status` (`queued`, `running`, `succeeded` or `failed`), `price`, `result` (the Widget's JSON, a `dict` when it wrote an object), `output_files`, `error`, `created_at`, `started_at` and `ended_at`.
74
74
 
@@ -146,7 +146,9 @@ A request is sent again, at most `max_retries` times (2 by default), only after:
146
146
  - **`429 rate_limited` and `409 idempotency_key_in_use`:** after the `Retry-After` seconds;
147
147
  - **a `5xx`:** a short backoff (0.5 s, then 1 s).
148
148
 
149
- When the last try fails too, it raises. Every other refusal raises at once, and re-attaching never counts as a retry.
149
+ When the last try fails too, it raises. Every other refusal raises at once.
150
+
151
+ **Re-attaching never counts as a retry.** A re-attach that drops, or meets a `429`, `409` or `5xx`, is tried again with no limit until the network is back, pausing 0.5 s and doubling to at most 10 s (or the `Retry-After` seconds). Only `timeout` stops it.
150
152
 
151
153
  ## Timeouts
152
154
 
@@ -51,7 +51,7 @@ run = abyss.run(widget, input, max_price=None, idempotency_key=None, timeout=Non
51
51
  - **`idempotency_key`:** each call presses with a fresh UUID `Idempotency-Key` unless you give one. Retries send the same one, so a run is never made or charged twice.
52
52
  - **`timeout`:** the seconds to wait at most (see [Timeouts](#timeouts)).
53
53
 
54
- The press holds until the run ends. When the hold ends early (a deploy cuts it, the API's holds are all taken, or an hour passes), `run()` re-attaches with `GET <Location>?wait=true` until the run ends. Re-attaching has no limit and is not a retry. Connecting has 10 s, and 30 s of silence counts as a cut.
54
+ The press holds until the run ends. When the hold ends early (a deploy cuts it, the network goes down, the API's holds are all taken, or an hour passes), `run()` re-attaches with `GET <Location>?wait=true` until the run ends. Re-attaching has no limit and is not a retry. Connecting has 10 s, and 30 s of silence counts as a cut.
55
55
 
56
56
  A **run** is a dataclass with `/v1`'s fields: `id`, `widget`, `status` (`queued`, `running`, `succeeded` or `failed`), `price`, `result` (the Widget's JSON, a `dict` when it wrote an object), `output_files`, `error`, `created_at`, `started_at` and `ended_at`.
57
57
 
@@ -129,7 +129,9 @@ A request is sent again, at most `max_retries` times (2 by default), only after:
129
129
  - **`429 rate_limited` and `409 idempotency_key_in_use`:** after the `Retry-After` seconds;
130
130
  - **a `5xx`:** a short backoff (0.5 s, then 1 s).
131
131
 
132
- When the last try fails too, it raises. Every other refusal raises at once, and re-attaching never counts as a retry.
132
+ When the last try fails too, it raises. Every other refusal raises at once.
133
+
134
+ **Re-attaching never counts as a retry.** A re-attach that drops, or meets a `429`, `409` or `5xx`, is tried again with no limit until the network is back, pausing 0.5 s and doubling to at most 10 s (or the `Retry-After` seconds). Only `timeout` stops it.
133
135
 
134
136
  ## Timeouts
135
137
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "abysshub"
7
- version = "0.0.1.dev14"
7
+ version = "0.0.1.dev15"
8
8
  description = "The official Python library for the Abyss API."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -31,6 +31,9 @@ REATTACH_PAUSE = 1.0
31
31
  RETRY_PAUSE = 0.5
32
32
  """Seconds before the first retry after a drop or a 5xx; the pause doubles with each retry."""
33
33
 
34
+ RETRY_PAUSE_CEILING = 10.0
35
+ """The longest pause between two tries, in seconds, which only re-attaching, with no limit, reaches."""
36
+
34
37
 
35
38
  def _version() -> str:
36
39
  try:
@@ -348,11 +351,13 @@ class Abyss:
348
351
  return _expect(self._send(call, "GET", url), is_, message)
349
352
 
350
353
  def _hold(self, call: _Call, url: str, done: Callable[[_Reply], bool]) -> _Reply:
351
- """Reads ``url`` until ``done``. Re-attaching has no limit and is not a retry."""
352
- reply = self._send(call, "GET", url)
354
+ """Reads ``url`` until ``done``. Re-attaching has no limit and is not a retry: a
355
+ re-attach that drops, or meets a 429, 409 or 5xx, is tried again until the network
356
+ is back, and only the call's ``timeout`` stops it."""
357
+ reply = self._send(call, "GET", url, retries=math.inf)
353
358
  while not done(reply):
354
359
  self._sleep(call, REATTACH_PAUSE)
355
- reply = self._send(call, "GET", url)
360
+ reply = self._send(call, "GET", url, retries=math.inf)
356
361
  return reply
357
362
 
358
363
  def _widget_url(self, widget: str) -> str:
@@ -375,9 +380,12 @@ class Abyss:
375
380
  url: str,
376
381
  headers: dict[str, str] | None = None,
377
382
  content: bytes | None = None,
383
+ retries: float | None = None,
378
384
  ) -> _Reply:
379
385
  """Sends one request to `/v1`. A drop before any headers, a 429, a 409 and a 5xx
380
- are sent again, at most ``max_retries`` times; every other refusal raises at once."""
386
+ are sent again, at most ``retries`` times (``max_retries`` when left out); every
387
+ other refusal raises at once."""
388
+ limit = self.max_retries if retries is None else retries
381
389
  sent_headers = {
382
390
  "Authorization": f"Bearer {self._api_key}",
383
391
  "Accept": "application/json",
@@ -393,7 +401,7 @@ class Abyss:
393
401
  raise self._timed_out(call) from None
394
402
  except httpx.TransportError as error:
395
403
  self._check(call)
396
- if attempt >= self.max_retries:
404
+ if attempt >= limit:
397
405
  raise AbyssError(
398
406
  "connection_error",
399
407
  "The connection dropped, or timed out, before the API answered.",
@@ -409,7 +417,7 @@ class Abyss:
409
417
  call.seen = run
410
418
  return _Reply(parsed, run, answer.text is None, answer.headers.get("location"), request_id)
411
419
  pause = _retry_pause(answer, attempt)
412
- if pause is None or attempt >= self.max_retries:
420
+ if pause is None or attempt >= limit:
413
421
  raise _refusal(answer.status, parsed, request_id)
414
422
  self._sleep(call, pause)
415
423
  attempt += 1
@@ -427,7 +435,9 @@ def _retry_pause(answer: Answer, attempt: int) -> float | None:
427
435
 
428
436
 
429
437
  def _backoff(attempt: int) -> float:
430
- return RETRY_PAUSE * 2**attempt
438
+ # The exponent stops growing once the ceiling is reached, so an unlimited re-attach
439
+ # never makes a float too large.
440
+ return min(RETRY_PAUSE * 2 ** min(attempt, 16), RETRY_PAUSE_CEILING)
431
441
 
432
442
 
433
443
  def _expect(reply: _Reply, is_: Callable[[Any], bool], message: str) -> Any:
File without changes