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 CHANGED
@@ -149,6 +149,7 @@ class Project(BaseModel):
149
149
  code: str
150
150
  name: str = ""
151
151
  priority: float | None = Field(ge=0.0, le=9999.0, default=1.0)
152
+ users: list[str] = Field(default_factory=list)
152
153
 
153
154
 
154
155
  __all__ = ["Task", "TaskData", "Project"]
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
- raise RuntimeError(f"Invalid response from server: HTTP {response.status}")
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
- data = await http_request_with_retries(
41
- session, next_url, method=method, expected_status=expected_status, **kwargs
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyobs-core
3
- Version: 2.0.0.dev83
3
+ Version: 2.0.0.dev85
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -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=e8gZWnaDahWFpXIpn04o_4Q18UyAq5mjipP0Eb3AoCU,5421
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=2Rhw1EmPWx8p15_3pF0Ju3SKSF4aK4BUj_ZyAr2gG7A,1576
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.dev83.dist-info/METADATA,sha256=3vSeZzX93RmtWHfn4SbzqP-VlOAziDW-UX6cLGZb_vg,1990
447
- pyobs_core-2.0.0.dev83.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
448
- pyobs_core-2.0.0.dev83.dist-info/entry_points.txt,sha256=0ceIn3dTDwWdGdJ5VdTvYim7vKkdTe6aqqpoaG065UY,109
449
- pyobs_core-2.0.0.dev83.dist-info/licenses/LICENSE,sha256=gC8djBliCENDmuRvzCoejpWbsn8eqKx61Hc8bmDZ2LI,1099
450
- pyobs_core-2.0.0.dev83.dist-info/RECORD,,
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,,