pyobs-core 2.0.0.dev83__py3-none-any.whl → 2.0.0.dev85__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.
- pyobs/robotic/task.py +1 -0
- pyobs/utils/http.py +38 -5
- {pyobs_core-2.0.0.dev83.dist-info → pyobs_core-2.0.0.dev85.dist-info}/METADATA +1 -1
- {pyobs_core-2.0.0.dev83.dist-info → pyobs_core-2.0.0.dev85.dist-info}/RECORD +7 -7
- {pyobs_core-2.0.0.dev83.dist-info → pyobs_core-2.0.0.dev85.dist-info}/WHEEL +0 -0
- {pyobs_core-2.0.0.dev83.dist-info → pyobs_core-2.0.0.dev85.dist-info}/entry_points.txt +0 -0
- {pyobs_core-2.0.0.dev83.dist-info → pyobs_core-2.0.0.dev85.dist-info}/licenses/LICENSE +0 -0
pyobs/robotic/task.py
CHANGED
pyobs/utils/http.py
CHANGED
|
@@ -14,6 +14,16 @@ from tenacity import (
|
|
|
14
14
|
log = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
class InvalidResponseError(RuntimeError):
|
|
18
|
+
"""Raised when the server returns an unexpected HTTP status. Carries the status and, if
|
|
19
|
+
available, the parsed JSON body, so callers can distinguish specific failure modes."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, status: int, body: Any = None):
|
|
22
|
+
self.status = status
|
|
23
|
+
self.body = body
|
|
24
|
+
super().__init__(f"Invalid response from server: HTTP {status}")
|
|
25
|
+
|
|
26
|
+
|
|
17
27
|
@retry(
|
|
18
28
|
retry=retry_if_exception_type((aiohttp.ClientError, asyncio.TimeoutError, RuntimeError)),
|
|
19
29
|
wait=wait_exponential(multiplier=1, min=1, max=30),
|
|
@@ -26,21 +36,44 @@ async def http_request_with_retries(
|
|
|
26
36
|
) -> dict[str, Any]:
|
|
27
37
|
async with session.request(method, url, **kwargs) as response:
|
|
28
38
|
if response.status != expected_status:
|
|
29
|
-
|
|
39
|
+
body = None
|
|
40
|
+
try:
|
|
41
|
+
body = await response.json()
|
|
42
|
+
except (aiohttp.ContentTypeError, ValueError):
|
|
43
|
+
pass
|
|
44
|
+
raise InvalidResponseError(response.status, body)
|
|
30
45
|
return cast(dict[str, Any], await response.json())
|
|
31
46
|
|
|
32
47
|
|
|
33
48
|
async def http_request_paginated(
|
|
34
49
|
session: aiohttp.ClientSession, url: str, method: str = "get", expected_status: int = 200, **kwargs: Any
|
|
35
50
|
) -> list[dict[str, Any]]:
|
|
36
|
-
"""Fetches all pages of a DRF-style paginated list endpoint and returns the combined results.
|
|
51
|
+
"""Fetches all pages of a DRF-style paginated list endpoint and returns the combined results.
|
|
52
|
+
|
|
53
|
+
If a page beyond the first becomes invalid mid-fetch (DRF's "Invalid page." 404, which can
|
|
54
|
+
happen when concurrent writes shift page boundaries on a live-growing dataset), pagination
|
|
55
|
+
stops early with whatever was already fetched instead of failing the whole request.
|
|
56
|
+
"""
|
|
37
57
|
results: list[dict[str, Any]] = []
|
|
38
58
|
next_url: str | None = url
|
|
59
|
+
is_first_page = True
|
|
39
60
|
while next_url is not None:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
61
|
+
try:
|
|
62
|
+
data = await http_request_with_retries(
|
|
63
|
+
session, next_url, method=method, expected_status=expected_status, **kwargs
|
|
64
|
+
)
|
|
65
|
+
except InvalidResponseError as e:
|
|
66
|
+
if (
|
|
67
|
+
not is_first_page
|
|
68
|
+
and e.status == 404
|
|
69
|
+
and isinstance(e.body, dict)
|
|
70
|
+
and e.body.get("detail") == "Invalid page."
|
|
71
|
+
):
|
|
72
|
+
log.warning("Pagination page became invalid mid-fetch, stopping early with %d result(s).", len(results))
|
|
73
|
+
break
|
|
74
|
+
raise
|
|
43
75
|
results.extend(data["results"])
|
|
44
76
|
next_url = data.get("next")
|
|
45
77
|
kwargs = {}
|
|
78
|
+
is_first_page = False
|
|
46
79
|
return results
|
|
@@ -268,7 +268,7 @@ pyobs/modules/weather/weather_api.py,sha256=OfvboDXajCMydTLFz3_s4Sl7CNBWe3IluebW
|
|
|
268
268
|
pyobs/modules/weather/weather_state.py,sha256=XzZiN-aM4yg-HEVeM3XmQXUshlG97Wr0Ka1vmL31vPw,736
|
|
269
269
|
pyobs/robotic/__init__.py,sha256=A0jlO6KelDFObBo74_qM9lXX952kYeoj-Z47D0XiIOQ,441
|
|
270
270
|
pyobs/robotic/observation.py,sha256=EvMDCz0QZmRAW4lwOFrxhwDTyO7NkNowaRwtLJfGBYU,4702
|
|
271
|
-
pyobs/robotic/task.py,sha256=
|
|
271
|
+
pyobs/robotic/task.py,sha256=ZXsxeyV41gNCnrUmOfU1i9XUH6Ez_MLO0-H_5LaX7l0,5472
|
|
272
272
|
pyobs/robotic/taskrunner.py,sha256=enT2GPbvL7jhWa1PHtFper2qCjDZ5wTDGIk27CmbPZw,1961
|
|
273
273
|
pyobs/robotic/scheduler/__init__.py,sha256=1doPZQUxGd-E9y8Wwo3YSw3LqjrBYzd5W29UAw7cYDY,268
|
|
274
274
|
pyobs/robotic/scheduler/_executor.py,sha256=M-0Km9vy_Hnr37e7JSFISapQnsXH7r2ZKO08QU0iM28,1164
|
|
@@ -380,7 +380,7 @@ pyobs/utils/curvefit.py,sha256=DIOPofmAP2GzAWCwV4L8h4mGK1GCvy1hyfGYE3Z78YI,1343
|
|
|
380
380
|
pyobs/utils/enums.py,sha256=vzmsyUEbQyBsZgcMwkgcEHZJIm25v6EIZauSnDfMeQM,5558
|
|
381
381
|
pyobs/utils/exceptions.py,sha256=7sgyVIwP40I1L14rSNgYFRB5IAc3W4RAmRbViWQa1TM,6923
|
|
382
382
|
pyobs/utils/fits.py,sha256=3Xy9a-3aPGLXeHhWmF4gtBaCv2jpmpsiN2dGWmcwylI,9213
|
|
383
|
-
pyobs/utils/http.py,sha256=
|
|
383
|
+
pyobs/utils/http.py,sha256=RsEH8dDjJ1kfliNR83x3d0_GWHha3MdImcWfwZVrsLg,2900
|
|
384
384
|
pyobs/utils/influxdb.py,sha256=0rd67kxslI9H_F_-sNFLTPMFl8hlSA0ihO6qoUGsyzw,2505
|
|
385
385
|
pyobs/utils/logger.py,sha256=3TMZ3DmapBMfWd0wX334VCr3uhmwzNl0Gy3-T6RnFB4,671
|
|
386
386
|
pyobs/utils/modulegui.py,sha256=k7JhACvBFWyqOxDfwCfU2v7Z-YmTN_QoaOEx2mmy2pw,1744
|
|
@@ -443,8 +443,8 @@ pyobs/vfs/vfs.py,sha256=3cXpRGbWh6m_lm0R7uZUNJcjppBaXSpQVMTRgHnv6Tk,11025
|
|
|
443
443
|
pyobs/vfs/filelists/__init__.py,sha256=NaTGFjLGh043j_F7kWEx_cXaII7t3CbYguRk2g5hEqw,111
|
|
444
444
|
pyobs/vfs/filelists/filelist.py,sha256=kCco_5G2gLH-nxWik-wcs6lGuc_-3LeeyR9L4_srf30,201
|
|
445
445
|
pyobs/vfs/filelists/testing.py,sha256=M8f2BULdjRCc7JFOKhAH7Kwre_uVQC0xLcv9xGTfNFw,425
|
|
446
|
-
pyobs_core-2.0.0.
|
|
447
|
-
pyobs_core-2.0.0.
|
|
448
|
-
pyobs_core-2.0.0.
|
|
449
|
-
pyobs_core-2.0.0.
|
|
450
|
-
pyobs_core-2.0.0.
|
|
446
|
+
pyobs_core-2.0.0.dev85.dist-info/METADATA,sha256=Rmhd6f2Xn7W7mvzfycgpzCaQcMVsDB28usco_L0O2ss,1990
|
|
447
|
+
pyobs_core-2.0.0.dev85.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
448
|
+
pyobs_core-2.0.0.dev85.dist-info/entry_points.txt,sha256=0ceIn3dTDwWdGdJ5VdTvYim7vKkdTe6aqqpoaG065UY,109
|
|
449
|
+
pyobs_core-2.0.0.dev85.dist-info/licenses/LICENSE,sha256=gC8djBliCENDmuRvzCoejpWbsn8eqKx61Hc8bmDZ2LI,1099
|
|
450
|
+
pyobs_core-2.0.0.dev85.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|