pyobs-core 2.0.0.dev94__py3-none-any.whl → 2.0.0.dev97__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.
Files changed (27) hide show
  1. pyobs/modules/robotic/scheduler.py +1 -1
  2. pyobs/robotic/scripts/calibration/darkbias.py +8 -4
  3. pyobs/robotic/scripts/calibration/pointing.py +4 -2
  4. pyobs/robotic/scripts/calibration/skyflats.py +27 -11
  5. pyobs/robotic/scripts/control/cases.py +6 -2
  6. pyobs/robotic/scripts/control/conditional.py +7 -3
  7. pyobs/robotic/scripts/control/parallel.py +7 -2
  8. pyobs/robotic/scripts/control/selector.py +4 -2
  9. pyobs/robotic/scripts/control/sequential.py +6 -2
  10. pyobs/robotic/scripts/imaging/autofocus.py +11 -5
  11. pyobs/robotic/scripts/imaging/imaging.py +56 -21
  12. pyobs/robotic/scripts/script.py +3 -1
  13. pyobs/robotic/scripts/utils/callmodule.py +6 -4
  14. pyobs/robotic/scripts/utils/debugtrigger.py +3 -1
  15. pyobs/robotic/scripts/utils/log.py +3 -1
  16. pyobs/robotic/storage/portal/__init__.py +4 -0
  17. pyobs/robotic/storage/{backend → portal}/observationarchive.py +13 -13
  18. pyobs/robotic/storage/{backend → portal}/taskarchive.py +16 -16
  19. pyobs/robotic/task.py +3 -3
  20. pyobs/robotic/utils/skyflats/priorities/archive.py +6 -5
  21. pyobs/robotic/utils/skyflats/priorities/const.py +5 -1
  22. {pyobs_core-2.0.0.dev94.dist-info → pyobs_core-2.0.0.dev97.dist-info}/METADATA +1 -1
  23. {pyobs_core-2.0.0.dev94.dist-info → pyobs_core-2.0.0.dev97.dist-info}/RECORD +26 -26
  24. pyobs/robotic/storage/backend/__init__.py +0 -4
  25. {pyobs_core-2.0.0.dev94.dist-info → pyobs_core-2.0.0.dev97.dist-info}/WHEEL +0 -0
  26. {pyobs_core-2.0.0.dev94.dist-info → pyobs_core-2.0.0.dev97.dist-info}/entry_points.txt +0 -0
  27. {pyobs_core-2.0.0.dev94.dist-info → pyobs_core-2.0.0.dev97.dist-info}/licenses/LICENSE +0 -0
@@ -102,7 +102,7 @@ class Scheduler(Module, IStartStop, IRunnable):
102
102
 
103
103
  # get scheduler
104
104
  self._task_archive = self.add_child_object(tasks, TaskArchive, on_tasks_changed=self._update_schedule)
105
- # BackendObservationArchive declares auto_update and gates its own polling loop on it --
105
+ # PortalObservationArchive declares auto_update and gates its own polling loop on it --
106
106
  # since we already drive updates via on_tasks_changed above, that poller must stay off.
107
107
  # Other ObservationArchive implementations (e.g. LcoObservationArchive) don't declare it at
108
108
  # all, so it must not be injected unconditionally.
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import TYPE_CHECKING, Annotated
5
5
 
6
+ from pydantic import Field
7
+
6
8
  from pyobs.interfaces import (
7
9
  IBinning,
8
10
  IData,
@@ -23,10 +25,12 @@ log = logging.getLogger(__name__)
23
25
  class DarkBiasScript(Script):
24
26
  """Script for running darks or biases."""
25
27
 
26
- camera: Annotated[str, IData, IBinning, IWindow, IExposureTime, IImageType]
27
- count: int = 20
28
- exptime: float = 0
29
- binning: tuple[int, int] = (1, 1)
28
+ camera: Annotated[str, IData, IBinning, IWindow, IExposureTime, IImageType] = Field(
29
+ description="Name of the camera module to expose with."
30
+ )
31
+ count: int = Field(default=20, description="Number of exposures to take.")
32
+ exptime: float = Field(default=0, description="Exposure time in seconds. 0 takes a bias, anything else a dark.")
33
+ binning: tuple[int, int] = Field(default=(1, 1), description="Detector binning as (x, y).")
30
34
 
31
35
  async def can_run(self, data: TaskData | None) -> bool:
32
36
  """Whether this config can currently run.
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import TYPE_CHECKING, Annotated
5
5
 
6
+ from pydantic import Field
7
+
6
8
  from pyobs.interfaces import IPointingAltAz, IReady
7
9
  from pyobs.robotic.scripts import Script
8
10
  from pyobs.robotic.utils.skyflats.pointing import SkyFlatsBasePointing
@@ -17,8 +19,8 @@ log = logging.getLogger(__name__)
17
19
  class PointingScript(Script):
18
20
  """Script for pointing the telescope for flats."""
19
21
 
20
- telescope: Annotated[str, IPointingAltAz, IReady]
21
- pointing: SkyFlatsBasePointing
22
+ telescope: Annotated[str, IPointingAltAz, IReady] = Field(description="Name of the telescope module to point.")
23
+ pointing: SkyFlatsBasePointing = Field(description="Strategy used to compute the flat-field pointing.")
22
24
 
23
25
  async def can_run(self, data: TaskData | None) -> bool:
24
26
  """Whether this config can currently run.
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import TYPE_CHECKING, Annotated
5
5
 
6
+ from pydantic import Field
7
+
6
8
  from pyobs.interfaces import IBinning, IFilters, IFlatField, IReady, IRoof, ITelescope
7
9
  from pyobs.robotic.scripts import Script
8
10
  from pyobs.robotic.utils.skyflats.priorities.base import SkyflatPriorities
@@ -21,17 +23,31 @@ FlatFunctions = str | dict[str, str | dict[str, str]]
21
23
  class SkyFlatsScript(Script):
22
24
  """Script for scheduling and running skyflats using an IFlatField module."""
23
25
 
24
- roof: Annotated[str, IRoof]
25
- telescope: Annotated[str, ITelescope]
26
- flatfield: Annotated[str, IBinning, IFilters, IFlatField]
27
- functions: FlatFunctions = {}
28
- priorities: SkyflatPriorities
29
- min_exptime: float = 0.5
30
- max_exptime: float = 5
31
- timespan: float = 7200
32
- filter_change: float = 30
33
- count: int = 20
34
- readout: dict[str, float] | None = None
26
+ roof: Annotated[str, IRoof] = Field(description="Name of the roof module, checked to be open before starting.")
27
+ telescope: Annotated[str, ITelescope] = Field(
28
+ description="Name of the telescope module, checked to be ready before starting."
29
+ )
30
+ flatfield: Annotated[str, IBinning, IFilters, IFlatField] = Field(
31
+ description="Name of the module that takes the flat-field exposures."
32
+ )
33
+ functions: FlatFunctions = Field(
34
+ default={},
35
+ description=(
36
+ "Exposure-time function(s) of solar altitude `h` in degrees, e.g. 'exp(-0.9*(h+3.9))'. "
37
+ "Either a single expression for all filters/binnings, a dict keyed by binning ('1x1') or "
38
+ "filter, or a nested dict of binning -> filter -> expression."
39
+ ),
40
+ )
41
+ priorities: SkyflatPriorities = Field(description="Strategy for prioritizing filter/binning combinations.")
42
+ min_exptime: float = Field(default=0.5, description="Minimum acceptable exposure time in seconds.")
43
+ max_exptime: float = Field(default=5, description="Maximum acceptable exposure time in seconds.")
44
+ timespan: float = Field(default=7200, description="Time span in seconds to schedule flats over.")
45
+ filter_change: float = Field(default=30, description="Estimated time in seconds to change a filter.")
46
+ count: int = Field(default=20, description="Number of exposures to take per filter/binning combination.")
47
+ readout: dict[str, float] | None = Field(
48
+ default=None,
49
+ description='Readout time in seconds per binning, keyed as "BxB" (e.g. "2x2"). Assumes 0s if unset.',
50
+ )
35
51
 
36
52
  async def can_run(self, data: TaskData | None) -> bool:
37
53
  """Whether this config can currently run.
@@ -4,6 +4,8 @@ import logging
4
4
  from datetime import UTC, datetime
5
5
  from typing import TYPE_CHECKING
6
6
 
7
+ from pydantic import Field
8
+
7
9
  if TYPE_CHECKING:
8
10
  from pyobs.robotic.task import TaskData
9
11
  from pyobs.utils.time import Time
@@ -16,8 +18,10 @@ log = logging.getLogger(__name__)
16
18
  class CasesRunner(Script):
17
19
  """Script for distinguishing cases."""
18
20
 
19
- expression: str
20
- cases: dict[str | int | float, Script]
21
+ expression: str = Field(description="Python expression to evaluate; its value selects which script to run.")
22
+ cases: dict[str | int | float, Script] = Field(
23
+ description='Scripts to run, keyed by the value of `expression`. Use key "else" for a fallback.'
24
+ )
21
25
 
22
26
  def __get_script(self) -> Script:
23
27
  # evaluate condition
@@ -4,6 +4,8 @@ import logging
4
4
  from datetime import UTC, datetime
5
5
  from typing import TYPE_CHECKING
6
6
 
7
+ from pydantic import Field
8
+
7
9
  if TYPE_CHECKING:
8
10
  from pyobs.robotic.task import TaskData
9
11
  from pyobs.utils.time import Time
@@ -16,9 +18,11 @@ log = logging.getLogger(__name__)
16
18
  class ConditionalRunner(Script):
17
19
  """Script for running an if condition."""
18
20
 
19
- condition: str
20
- true: Script
21
- false: Script | None = None
21
+ condition: str = Field(description="Python expression to evaluate; run `true` if truthy, `false` otherwise.")
22
+ true: Script = Field(description="Script to run if `condition` is truthy.")
23
+ false: Script | None = Field(
24
+ default=None, description="Script to run if `condition` is falsy. Does nothing if unset."
25
+ )
22
26
 
23
27
  def __get_script(self) -> Script | None:
24
28
  # evaluate condition
@@ -4,6 +4,8 @@ import asyncio
4
4
  import logging
5
5
  from typing import TYPE_CHECKING
6
6
 
7
+ from pydantic import Field
8
+
7
9
  if TYPE_CHECKING:
8
10
  from pyobs.robotic.task import TaskData
9
11
  from pyobs.utils.time import Time
@@ -22,8 +24,11 @@ async def _run_script(script: Script, data: TaskData | None) -> None:
22
24
  class ParallelRunner(Script):
23
25
  """Script for running other scripts in parallel."""
24
26
 
25
- scripts: list[Script]
26
- check_all_can_run: bool = True
27
+ scripts: list[Script] = Field(description="Scripts to run concurrently.")
28
+ check_all_can_run: bool = Field(
29
+ default=True,
30
+ description="If set, all scripts must be able to run for this script to run; otherwise, any one suffices.",
31
+ )
27
32
 
28
33
  async def can_run(self, data: TaskData | None) -> bool:
29
34
  results = [await s.can_run(data) for s in self.scripts]
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import TYPE_CHECKING, Annotated
5
5
 
6
+ from pydantic import Field
7
+
6
8
  from pyobs.interfaces import IMotion
7
9
  from pyobs.interfaces.IMode import IMode
8
10
  from pyobs.robotic.scripts import Script
@@ -18,8 +20,8 @@ log = logging.getLogger(__name__)
18
20
  class SelectorScript(Script):
19
21
  """Script for running Mode Selection."""
20
22
 
21
- mode: str
22
- selector: Annotated[str, IMode, IMotion]
23
+ mode: str = Field(description="Name of the mode to select.")
24
+ selector: Annotated[str, IMode, IMotion] = Field(description="Name of the mode-selection module.")
23
25
 
24
26
  async def can_run(self, data: TaskData | None) -> bool:
25
27
  """Whether this config can currently run.
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
5
5
 
6
6
  import astropy.units as u
7
7
  from astropy.time import TimeDelta
8
+ from pydantic import Field
8
9
 
9
10
  if TYPE_CHECKING:
10
11
  from pyobs.robotic.task import TaskData
@@ -17,8 +18,11 @@ log = logging.getLogger(__name__)
17
18
  class SequentialRunner(Script):
18
19
  """Script for running a sequence of other scripts."""
19
20
 
20
- scripts: list[Script]
21
- check_all_can_run: bool = True
21
+ scripts: list[Script] = Field(description="Scripts to run in sequence, each only if it can run.")
22
+ check_all_can_run: bool = Field(
23
+ default=True,
24
+ description="If set, all scripts must be able to run for this script to run; otherwise, only the first must.",
25
+ )
22
26
 
23
27
  async def can_run(self, data: TaskData | None) -> bool:
24
28
  if self.check_all_can_run:
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import TYPE_CHECKING, Annotated
5
5
 
6
+ from pydantic import Field
7
+
6
8
  from pyobs.interfaces import IAutoFocus, IMotion, IPointingRaDec, IReady, ITelescope
7
9
  from pyobs.robotic.scripts import Script
8
10
  from pyobs.utils.time import Time
@@ -16,11 +18,15 @@ log = logging.getLogger(__name__)
16
18
  class AutoFocusScript(Script):
17
19
  """Script for running autofocus series."""
18
20
 
19
- autofocus: Annotated[str, IAutoFocus] = "autofocus"
20
- telescope: Annotated[str, ITelescope, IPointingRaDec] = "telescope"
21
- count: int = 5
22
- step: float = 0.1
23
- exposure_time: float = 2.0
21
+ autofocus: Annotated[str, IAutoFocus] = Field(
22
+ default="autofocus", description="Name of the auto-focus module to run the series with."
23
+ )
24
+ telescope: Annotated[str, ITelescope, IPointingRaDec] = Field(
25
+ default="telescope", description="Name of the telescope module, moved to the target and stopped afterwards."
26
+ )
27
+ count: int = Field(default=5, description="Number of focus steps to take in the series.")
28
+ step: float = Field(default=0.1, description="Focus step size.")
29
+ exposure_time: float = Field(default=2.0, description="Exposure time in seconds for each focus step.")
24
30
 
25
31
  async def can_run(self, data: TaskData | None) -> bool:
26
32
  """Whether this config can currently run.
@@ -38,26 +38,42 @@ log = logging.getLogger(__name__)
38
38
  class AcquisitionConfig(BaseModel):
39
39
  model_config = ConfigDict(extra="forbid")
40
40
 
41
- enabled: bool = True
42
- optional: bool = False
41
+ enabled: bool = Field(
42
+ default=True, description="Whether to perform acquisition before starting this configuration."
43
+ )
44
+ optional: bool = Field(
45
+ default=False, description="If acquisition fails, continue without it instead of aborting the script."
46
+ )
43
47
 
44
48
 
45
49
  class GuidingConfig(BaseModel):
46
50
  model_config = ConfigDict(extra="forbid")
47
51
 
48
- enabled: bool = True
49
- optional: bool = False
52
+ enabled: bool = Field(default=True, description="Whether to start auto-guiding before starting this configuration.")
53
+ optional: bool = Field(
54
+ default=False, description="If starting auto-guiding fails, continue without it instead of aborting the script."
55
+ )
50
56
 
51
57
 
52
58
  class InstrumentConfig(BaseModel):
53
59
  model_config = ConfigDict(extra="forbid")
54
60
 
55
- exposure_time: float | ExposureTimeProvider = 0.0
56
- count: int = 1
57
- image_type: ImageType = ImageType.OBJECT
58
- binning: tuple[int, int] = (1, 1)
59
- window: tuple[int, int, int, int] | None = None
60
- optical_filter: str | None = None
61
+ exposure_time: float | ExposureTimeProvider = Field(
62
+ default=0.0, description="Exposure time in seconds, or a provider that computes it dynamically."
63
+ )
64
+ count: int = Field(default=1, description="Number of exposures to take with this configuration.")
65
+ image_type: ImageType = Field(
66
+ default=ImageType.OBJECT,
67
+ description="Type of image to take: OBJECT (science), BIAS, DARK, SKYFLAT, FOCUS, ACQUISITION, or GUIDING.",
68
+ )
69
+ binning: tuple[int, int] = Field(default=(1, 1), description="Detector binning as (x, y).")
70
+ window: tuple[int, int, int, int] | None = Field(
71
+ default=None,
72
+ description="Detector sub-window as (left, top, width, height). Uses the full frame if unset.",
73
+ )
74
+ optical_filter: str | None = Field(
75
+ default=None, description="Name of the filter to use. Uses the camera's current filter if unset."
76
+ )
61
77
 
62
78
  async def get_exposure_time(self) -> float:
63
79
  """Return the exposure time, computing it dynamically if needed."""
@@ -69,22 +85,41 @@ class InstrumentConfig(BaseModel):
69
85
  class Configuration(BaseModel):
70
86
  model_config = ConfigDict(extra="forbid")
71
87
 
72
- acquisition_config: AcquisitionConfig = Field(default_factory=AcquisitionConfig)
73
- guiding_config: GuidingConfig = Field(default_factory=GuidingConfig)
74
- instrument_configs: list[InstrumentConfig] = Field(default_factory=lambda: [InstrumentConfig()])
75
- repeats: int = 1
88
+ acquisition_config: AcquisitionConfig = Field(
89
+ default_factory=AcquisitionConfig, description="Settings for acquiring the target before exposing."
90
+ )
91
+ guiding_config: GuidingConfig = Field(
92
+ default_factory=GuidingConfig, description="Settings for auto-guiding while exposing."
93
+ )
94
+ instrument_configs: list[InstrumentConfig] = Field(
95
+ default_factory=lambda: [InstrumentConfig()],
96
+ description="Sequence of instrument configurations to run, in order, once per repeat.",
97
+ )
98
+ repeats: int = Field(
99
+ default=1, description="Number of times to repeat the full sequence of instrument configurations."
100
+ )
76
101
 
77
102
 
78
103
  class ImagingScript(Script):
79
104
  """Default script for imaging configs."""
80
105
 
81
- configuration: Configuration = Field(default_factory=Configuration)
82
-
83
- camera: Annotated[str, ICamera, IBinning, IWindow, IExposureTime, IImageType]
84
- telescope: Annotated[str | None, ITelescope, IPointingRaDec] = None
85
- filters: Annotated[str | None, IFilters] = None
86
- autoguider: Annotated[str | None, IAutoGuiding] = None
87
- acquisition: Annotated[str | None, IAcquisition] = None
106
+ configuration: Configuration = Field(default_factory=Configuration, description="What to expose and how.")
107
+
108
+ camera: Annotated[str, ICamera, IBinning, IWindow, IExposureTime, IImageType] = Field(
109
+ description="Name of the camera module to expose with."
110
+ )
111
+ telescope: Annotated[str | None, ITelescope, IPointingRaDec] = Field(
112
+ default=None, description="Name of the telescope module to point at the target. Required for OBJECT exposures."
113
+ )
114
+ filters: Annotated[str | None, IFilters] = Field(
115
+ default=None, description="Name of the filter wheel module. Required if any instrument config sets a filter."
116
+ )
117
+ autoguider: Annotated[str | None, IAutoGuiding] = Field(
118
+ default=None, description="Name of the auto-guiding module. Required if guiding is enabled."
119
+ )
120
+ acquisition: Annotated[str | None, IAcquisition] = Field(
121
+ default=None, description="Name of the acquisition module. Required if acquisition is enabled."
122
+ )
88
123
 
89
124
  _object_name: str | None = PrivateAttr(default=None)
90
125
 
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ from pydantic import Field
7
+
6
8
  from pyobs.interfaces import FitsHeaderEntry
7
9
  from pyobs.utils.serialization import PolymorphicBaseModel
8
10
  from pyobs.utils.time import Time
@@ -14,7 +16,7 @@ log = logging.getLogger(__name__)
14
16
 
15
17
 
16
18
  class Script(PolymorphicBaseModel):
17
- exptime_done: float = 0.0
19
+ exptime_done: float = Field(default=0.0, description="Exposure time accumulated so far while running this script.")
18
20
 
19
21
  _cant_run_reason: str | None = None
20
22
 
@@ -41,10 +41,12 @@ def _build_params_model(method, provided_keys):
41
41
  class CallModuleScript(Script):
42
42
  """Script for calling a method on a module."""
43
43
 
44
- module: str
45
- interface: str
46
- method: str
47
- params: dict[str, str | int | float] = Field(default_factory=dict)
44
+ module: str = Field(description="Name of the module to call the method on.")
45
+ interface: str = Field(description="Full class path of the interface that declares `method`.")
46
+ method: str = Field(description="Name of the method to call.")
47
+ params: dict[str, str | int | float] = Field(
48
+ default_factory=dict, description="Keyword arguments to pass to the method, validated against its signature."
49
+ )
48
50
 
49
51
  @model_validator(mode="after")
50
52
  def _validate_params(self) -> CallModuleScript:
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ from pydantic import Field
7
+
6
8
  if TYPE_CHECKING:
7
9
  from pyobs.robotic.task import TaskData
8
10
  from pyobs.robotic.scripts import Script
@@ -13,7 +15,7 @@ log = logging.getLogger(__name__)
13
15
  class DebugTriggerScript(Script):
14
16
  """Script for a debug trigger."""
15
17
 
16
- triggered: bool = False
18
+ triggered: bool = Field(default=False, description="Set to True once this script has run.")
17
19
 
18
20
  async def can_run(self, data: TaskData | None) -> bool:
19
21
  return True
@@ -4,6 +4,8 @@ import logging
4
4
  from datetime import UTC, datetime
5
5
  from typing import TYPE_CHECKING
6
6
 
7
+ from pydantic import Field
8
+
7
9
  if TYPE_CHECKING:
8
10
  from pyobs.robotic.task import TaskData
9
11
  from pyobs.robotic.scripts import Script
@@ -14,7 +16,7 @@ log = logging.getLogger(__name__)
14
16
  class LogScript(Script):
15
17
  """Script for logging something."""
16
18
 
17
- expression: str
19
+ expression: str = Field(description="Python expression to evaluate and log the value of.")
18
20
 
19
21
  async def can_run(self, data: TaskData | None) -> bool:
20
22
  return True
@@ -0,0 +1,4 @@
1
+ from .observationarchive import PortalObservationArchive
2
+ from .taskarchive import PortalTaskArchive
3
+
4
+ __all__ = ["PortalObservationArchive", "PortalTaskArchive"]
@@ -15,8 +15,8 @@ from pyobs.utils.time import Time
15
15
  log = logging.getLogger(__name__)
16
16
 
17
17
 
18
- class BackendObservationArchive(ObservationArchive):
19
- """Observation archive based on pyobs-robotic-backend."""
18
+ class PortalObservationArchive(ObservationArchive):
19
+ """Observation archive based on pyobs-portal."""
20
20
 
21
21
  def __init__(
22
22
  self,
@@ -39,12 +39,12 @@ class BackendObservationArchive(ObservationArchive):
39
39
  self.add_background_task(self._check_for_changes)
40
40
 
41
41
  async def open(self) -> None:
42
- """Opens the backend observation archive."""
42
+ """Opens the portal observation archive."""
43
43
  self._aiohttp_session = aiohttp.ClientSession(headers={"Authorization": f"Token {self._token}"})
44
44
  await ObservationArchive.open(self)
45
45
 
46
46
  async def close(self) -> None:
47
- """Closes the backend observation archive."""
47
+ """Closes the portal observation archive."""
48
48
  await ObservationArchive.close(self)
49
49
  if self._aiohttp_session is not None:
50
50
  await self._aiohttp_session.close()
@@ -57,20 +57,20 @@ class BackendObservationArchive(ObservationArchive):
57
57
  return self._aiohttp_session
58
58
 
59
59
  async def _check_for_changes(self) -> None:
60
- """Update schedule in background, gated on the backend's update marker."""
60
+ """Update schedule in background, gated on the portal's update marker."""
61
61
 
62
62
  while True:
63
63
  try:
64
64
  await self._poll()
65
65
  except Exception as e:
66
- log.error("Failed to update observations from backend: %s", e)
66
+ log.error("Failed to update observations from portal: %s", e)
67
67
  await asyncio.sleep(5)
68
68
 
69
69
  async def _poll(self) -> None:
70
- """Re-download the schedule when the backend's ``last_observation_update`` marker moved.
70
+ """Re-download the schedule when the portal's ``last_observation_update`` marker moved.
71
71
 
72
- The marker is a DB-derived ``Max(updated_at)`` (pyobs-robotic-backend#84), truthful across
73
- gunicorn workers, so it is a safe refresh gate; see ``BackendTaskArchive._poll``. The
72
+ The marker is a DB-derived ``Max(updated_at)`` (pyobs-portal#84), truthful across
73
+ gunicorn workers, so it is a safe refresh gate; see ``PortalTaskArchive._poll``. The
74
74
  content comparison in :meth:`_update` still decides whether to fire ``on_tasks_changed``.
75
75
  """
76
76
  last_update = await self.last_update_time()
@@ -79,12 +79,12 @@ class BackendObservationArchive(ObservationArchive):
79
79
  self._last_marker = last_update
80
80
 
81
81
  async def _update(self) -> None:
82
- """Fetch the schedule from the backend and apply it if anything changed.
82
+ """Fetch the schedule from the portal and apply it if anything changed.
83
83
 
84
- Called by :meth:`_poll` after the backend marker moved (or on the first poll). Changes are
84
+ Called by :meth:`_poll` after the portal marker moved (or on the first poll). Changes are
85
85
  detected by comparing full observation contents via ``model_dump(use_task_id=True)``,
86
86
  keyed by observation ID so a stable reordering of the same items is not mistaken for a
87
- change: the backend serializes ``task`` as a plain FK ID, while cached copies get their
87
+ change: the portal serializes ``task`` as a plain FK ID, while cached copies get their
88
88
  ``task`` replaced by a full ``Task`` when the mastermind calls ``fetch_task()``, so the ID
89
89
  normalization keeps both sides comparable. The comparison includes ``state``
90
90
  (``Observation.__eq__`` ignores it), which matters for in-set transitions such as
@@ -265,4 +265,4 @@ class BackendObservationArchive(ObservationArchive):
265
265
  return ObservationList([self.pyobs_model_validate(Observation, obs) for obs in observations])
266
266
 
267
267
 
268
- __all__ = ["BackendObservationArchive"]
268
+ __all__ = ["PortalObservationArchive"]
@@ -13,14 +13,14 @@ from pyobs.utils.time import Time
13
13
  log = logging.getLogger(__name__)
14
14
 
15
15
 
16
- class BackendTaskArchive(TaskArchive):
17
- """Task archive based on pyobs-robotic-backend."""
16
+ class PortalTaskArchive(TaskArchive):
17
+ """Task archive based on pyobs-portal."""
18
18
 
19
19
  def __init__(self, url: str, token: str, auto_update: bool = True, **kwargs: Any):
20
20
  """Creates a new task archive.
21
21
 
22
22
  Args:
23
- url: URL of pyobs-robotic-backend.
23
+ url: URL of pyobs-portal.
24
24
  token: Auth token.
25
25
  """
26
26
  TaskArchive.__init__(self, **kwargs)
@@ -36,12 +36,12 @@ class BackendTaskArchive(TaskArchive):
36
36
  self.add_background_task(self._check_for_changes)
37
37
 
38
38
  async def open(self) -> None:
39
- """Opens the backend task archive."""
39
+ """Opens the portal task archive."""
40
40
  self._aiohttp_session = aiohttp.ClientSession(headers={"Authorization": f"Token {self._token}"})
41
41
  await TaskArchive.open(self)
42
42
 
43
43
  async def close(self) -> None:
44
- """Closes the backend observation archive."""
44
+ """Closes the portal task archive."""
45
45
  await TaskArchive.close(self)
46
46
  if self._aiohttp_session is not None:
47
47
  await self._aiohttp_session.close()
@@ -54,18 +54,18 @@ class BackendTaskArchive(TaskArchive):
54
54
  return self._aiohttp_session
55
55
 
56
56
  async def _check_for_changes(self) -> None:
57
- """Update tasks in background, gated on the backend's update marker."""
57
+ """Update tasks in background, gated on the portal's update marker."""
58
58
  while True:
59
59
  try:
60
60
  await self._poll()
61
61
  except Exception as e:
62
- log.error("Failed to update tasks from backend: %s", e)
62
+ log.error("Failed to update tasks from portal: %s", e)
63
63
  await asyncio.sleep(5)
64
64
 
65
65
  async def _poll(self) -> None:
66
- """Re-download tasks/projects when the backend's ``last_task_update`` marker moved.
66
+ """Re-download tasks/projects when the portal's ``last_task_update`` marker moved.
67
67
 
68
- The marker is a DB-derived ``Max(updated_at)`` (pyobs-robotic-backend#84), truthful across
68
+ The marker is a DB-derived ``Max(updated_at)`` (pyobs-portal#84), truthful across
69
69
  gunicorn workers, so it is a safe refresh gate. Without it the archive re-downloaded (and
70
70
  re-compared) on every poll, and the content comparison misfired whenever runtime code
71
71
  mutated a serialized task field (e.g. ``DynamicTarget.resolve()`` overwriting ``name``),
@@ -78,14 +78,14 @@ class BackendTaskArchive(TaskArchive):
78
78
  self._last_marker = last_update
79
79
 
80
80
  async def _update(self) -> None:
81
- """Fetch tasks/projects from the backend and apply them if anything changed.
81
+ """Fetch tasks/projects from the portal and apply them if anything changed.
82
82
 
83
- Called by :meth:`_poll` after the backend marker moved (or on the first poll); applies the
83
+ Called by :meth:`_poll` after the portal marker moved (or on the first poll); applies the
84
84
  download only when the content actually differs from the cached copy. The comparison uses
85
85
  ``model_dump()`` rather than pydantic ``==``, which also compares runtime attributes (e.g.
86
86
  ``Task._cant_run_reason`` set by ``can_run()``) and would flag unchanged tasks as changed
87
87
  on every poll; it is keyed by ID so that a stable reordering of the same items (e.g. an
88
- unordered backend queryset) is not mistaken for a change.
88
+ unordered portal queryset) is not mistaken for a change.
89
89
  """
90
90
  projects = await self._get_projects()
91
91
  tasks = await self._get_tasks()
@@ -105,12 +105,12 @@ class BackendTaskArchive(TaskArchive):
105
105
  return Time(res["last_task_update"])
106
106
 
107
107
  async def _get_projects(self) -> list[Project]:
108
- """Fetch projects from backend."""
108
+ """Fetch projects from portal."""
109
109
  projects = await http_request_paginated(self._session, urljoin(self._url, "/api/projects/"), strict=True)
110
110
  return [self.pyobs_model_validate(Project, project) for project in projects]
111
111
 
112
112
  async def _get_tasks(self) -> list[Task]:
113
- """Fetch tasks from backend."""
113
+ """Fetch tasks from portal."""
114
114
  tasks = await http_request_paginated(self._session, urljoin(self._url, "/api/tasks/"), strict=True)
115
115
  return [self.pyobs_model_validate(Task, task) for task in tasks]
116
116
 
@@ -118,7 +118,7 @@ class BackendTaskArchive(TaskArchive):
118
118
  """Returns time when last time any tasks changed (as observed by this archive).
119
119
 
120
120
  This is the local time at which the last content change was detected by the polling loop,
121
- not the backend's marker timestamp -- the marker is per-process and unreliable.
121
+ not the portal's marker timestamp -- the marker is per-process and unreliable.
122
122
  """
123
123
  return self._last_update
124
124
 
@@ -151,4 +151,4 @@ class BackendTaskArchive(TaskArchive):
151
151
  return None
152
152
 
153
153
 
154
- __all__ = ["BackendTaskArchive"]
154
+ __all__ = ["PortalTaskArchive"]
pyobs/robotic/task.py CHANGED
@@ -46,9 +46,9 @@ class Task(PolymorphicBaseModel):
46
46
  script: dict[str, Any] = Field(default_factory=dict)
47
47
  active: bool = True
48
48
  updated_at: str | None = None
49
- """Last-modification timestamp as emitted by pyobs-robotic-backend (DB-derived ``updated_at``,
50
- added in pyobs-robotic-backend#84). Carried for round-trip fidelity so the strict ``Task``
51
- model (``extra="forbid"``) accepts the field the backend now sends; not used by pyobs itself."""
49
+ """Last-modification timestamp as emitted by pyobs-portal (DB-derived ``updated_at``,
50
+ added in pyobs-portal#84). Carried for round-trip fidelity so the strict ``Task``
51
+ model (``extra="forbid"``) accepts the field the portal now sends; not used by pyobs itself."""
52
52
 
53
53
  _resolved_target: Target | None = PrivateAttr(default=None)
54
54
  _cant_run_reason: str | None = None
@@ -1,5 +1,6 @@
1
1
  import astropy.units as u
2
2
  from astropy.time import TimeDelta
3
+ from pydantic import Field
3
4
 
4
5
  from pyobs.robotic.utils.archive import Archive
5
6
  from pyobs.utils.enums import ImageType
@@ -11,11 +12,11 @@ from .base import SkyflatPriorities
11
12
  class ArchiveSkyflatPriorities(SkyflatPriorities):
12
13
  """Calculate flat priorities from an archive."""
13
14
 
14
- archive: Archive
15
- site: str
16
- instrument: str
17
- filter_names: list[str]
18
- binnings: list[int]
15
+ archive: Archive = Field(description="Archive to query for the timestamp of the last flat per filter/binning.")
16
+ site: str = Field(description="Site code to filter archive frames by.")
17
+ instrument: str = Field(description="Instrument code to filter archive frames by.")
18
+ filter_names: list[str] = Field(description="Filters to compute priorities for.")
19
+ binnings: list[int] = Field(description="Binnings (as N for NxN) to compute priorities for.")
19
20
 
20
21
  model_config = {"arbitrary_types_allowed": True}
21
22
 
@@ -1,10 +1,14 @@
1
+ from pydantic import Field
2
+
1
3
  from .base import SkyflatPriorities
2
4
 
3
5
 
4
6
  class ConstSkyflatPriorities(SkyflatPriorities):
5
7
  """Constant flat priorities."""
6
8
 
7
- priorities: dict[tuple[str, tuple[int, int]], float]
9
+ priorities: dict[tuple[str, tuple[int, int]], float] = Field(
10
+ description="Fixed priority per (filter, binning) combination."
11
+ )
8
12
 
9
13
  async def __call__(self) -> dict[tuple[str, tuple[int, int]], float]:
10
14
  return self.priorities
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyobs-core
3
- Version: 2.0.0.dev94
3
+ Version: 2.0.0.dev97
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -238,7 +238,7 @@ pyobs/modules/pointing/guidingstatistics/uptime.py,sha256=1X2DFASvcSFsAqSmDofI-o
238
238
  pyobs/modules/robotic/__init__.py,sha256=awLUDoOQpmANpI_0Gnvgb-2w3L2yLIMSpCnUXkPvza0,296
239
239
  pyobs/modules/robotic/mastermind.py,sha256=4meuktgbaR7aGFHKwVdlp2AJjZ_d2SNfULpZJu2zr4c,10172
240
240
  pyobs/modules/robotic/pointing.py,sha256=ncIJsA4IGhmtBhrCH5bUjGJKmFAlb6iB5F8G7b15GWs,4239
241
- pyobs/modules/robotic/scheduler.py,sha256=KCouCOOjhUhqEnQ9zCC9VxrA-k6iYLPS8lzPlhz0Js8,16076
241
+ pyobs/modules/robotic/scheduler.py,sha256=K8-fOeP1fco4kdSWIpV8CvE3M0qiyyAHhEf813qsSJ4,16075
242
242
  pyobs/modules/robotic/scriptrunner.py,sha256=u0oGRPp5KpcyWqG83wMSJwqcEr-ENp4HNFCr_cGSkAw,1888
243
243
  pyobs/modules/roof/__init__.py,sha256=lARN3N3iqY_nb41Jaq6lE3UKhXuUvAnFeUqSq5HMQX4,209
244
244
  pyobs/modules/roof/basedome.py,sha256=HMAI7Sve90uRSRfP3yVKNhPVNRUC-bDOtuYQ1p3cQLI,1374
@@ -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=11mio1FoIvbDzktrq_swIVj4ggmShJ73kSh2itOt2ro,5892
271
+ pyobs/robotic/task.py,sha256=PNikHtoA7ppe_vbDdP4ZHtWsEpGPXzlQn_49lUhoMgw,5873
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
@@ -306,31 +306,28 @@ pyobs/robotic/scheduler/targets/picker/csvpicker.py,sha256=RaiN_MSshnBtKGn3pS1md
306
306
  pyobs/robotic/scheduler/targets/picker/picker.py,sha256=RMt41DbvGPMqm4XGULWNH3YuDEuFMLewxUQWsQ-9xGk,599
307
307
  pyobs/robotic/scripts/__init__.py,sha256=gTHfsrK18YxadGpESalfnWOVFSq9nAdMrPag2TnXPXs,100
308
308
  pyobs/robotic/scripts/exceptions.py,sha256=_i1X_rZEgEiy1YbIO3iH-cymKiyTk4KfdnZfdfppiDQ,393
309
- pyobs/robotic/scripts/script.py,sha256=EVpWLOGYTLkK0pHMyOW00T8YWEsoDwKfhJoTcFQwuqQ,1547
309
+ pyobs/robotic/scripts/script.py,sha256=Brnze9JxC-zk-A5s7mNLrNxriICNHf0oQAwGscvOPKw,1665
310
310
  pyobs/robotic/scripts/calibration/__init__.py,sha256=JvcFQ3UbSSAiPszLAtBo8541cqY9dKYKAIcRdcg5cpI,177
311
- pyobs/robotic/scripts/calibration/darkbias.py,sha256=QUptI3hAU9GZoTsovhgMaHAzX_MlMxVXxZ237uU0Yws,2980
312
- pyobs/robotic/scripts/calibration/pointing.py,sha256=nAvyjHvui23ihib2dqjyHhf0F-qpeeU7j357YvcQvBA,1850
313
- pyobs/robotic/scripts/calibration/skyflats.py,sha256=ho9BA_xeY-6KAVpXsDHLkX2kDu5uzqpcFH_wcjVunfc,4530
311
+ pyobs/robotic/scripts/calibration/darkbias.py,sha256=ReWF30oRp_OIZ2C-BuoNM4yl4UzLk2ktun-S8KON0Qc,3298
312
+ pyobs/robotic/scripts/calibration/pointing.py,sha256=IbTjqYd7xdQLpYYYg9c-DoSqHsg8fQUzNlT-B0NqRXU,2013
313
+ pyobs/robotic/scripts/calibration/skyflats.py,sha256=b25hoMKMyOrEu0woNDCiyxyWI94Af_PVvqGXM7bLDTM,5790
314
314
  pyobs/robotic/scripts/control/__init__.py,sha256=wji-Aa2m-na63QULzckJEoeoajr-pnz0bojBTO-B7qw,293
315
- pyobs/robotic/scripts/control/cases.py,sha256=8cnoZGie2IQtMAAKDBCgAMkVzdDc6kSlT_P6EeLl0Lk,1908
316
- pyobs/robotic/scripts/control/conditional.py,sha256=Mssgs7imMaV92UIC4rY7ajCTTomaTn751ONxvANf9Jg,2051
317
- pyobs/robotic/scripts/control/parallel.py,sha256=YAEDUCchIOzph0AlsCO4MfTGxnVRE1fmp_tQICmAgKs,1609
318
- pyobs/robotic/scripts/control/selector.py,sha256=-ZIBcdTHji0PyTALMeMHA3sVooWSNyIN3hh3eBrRD2U,1956
319
- pyobs/robotic/scripts/control/sequential.py,sha256=Zg9e_l3brVhvWwRkGgUqLc4VP70bhUnhL3jTz7fb7As,1776
315
+ pyobs/robotic/scripts/control/cases.py,sha256=GHm8NV8Pv-viLCzx2fWJb0nr3FsLDVSKD_e5XbAUDKA,2149
316
+ pyobs/robotic/scripts/control/conditional.py,sha256=82d5mMhwAPV6_-CNDGU3C3xwkbOeiWI13cdFESMvmS4,2343
317
+ pyobs/robotic/scripts/control/parallel.py,sha256=hK7KGyOoDOKP6LFrnTNlmnz76weoartXMmVGXcmFeU4,1835
318
+ pyobs/robotic/scripts/control/selector.py,sha256=Dk6R3rAFFYX-5NckVtHDRt0bntvowU1iy2DDNK59YQo,2093
319
+ pyobs/robotic/scripts/control/sequential.py,sha256=U7rvadQ5etO0AIlnlFL5r0HgmiOVViPOIvdM-JWblcg,2028
320
320
  pyobs/robotic/scripts/imaging/__init__.py,sha256=v92rykVP9bn0sgMf6Wt_GX17iGM4PmRDbNUEYjUbpSE,195
321
- pyobs/robotic/scripts/imaging/autofocus.py,sha256=tWrwSJo7AXGthUgPx0tiZi2JPFgZyHEC8s7G00LV8Cs,3139
322
- pyobs/robotic/scripts/imaging/imaging.py,sha256=laUWq60YHBpVSZmax0wCowLE8SrUDiAlmiRAmZXqZWo,12768
321
+ pyobs/robotic/scripts/imaging/autofocus.py,sha256=u9jmuoMvamYjAD5dnoGVMLOxbN8Y7BNhtWPBKaGNy9E,3581
322
+ pyobs/robotic/scripts/imaging/imaging.py,sha256=e_99sokFb9rIj2zhrcdPGkxxyD6D2aXBPR-aFcd--B8,14844
323
323
  pyobs/robotic/scripts/imaging/transitimaging.py,sha256=GXW-xq_e3BkJ74n6YvCUsWJN3mIFj0x94rR8giAQ4yw,4485
324
324
  pyobs/robotic/scripts/utils/__init__.py,sha256=XLda2nbNDv0lTkd6R64yPRxTpjkVb3Ytuk1c8oPcpOM,180
325
- pyobs/robotic/scripts/utils/callmodule.py,sha256=XbbFmrWxENf9z-4zg3FqriotQHKwgOiCkrEib5Us-IU,2673
326
- pyobs/robotic/scripts/utils/debugtrigger.py,sha256=_JmZsDfw83zoB4ncomUcjCtpuXrTXG8mDGCOiqXMOQI,529
327
- pyobs/robotic/scripts/utils/log.py,sha256=ffPm7zbgRRPrcR3CHVhSbd44fbMT2PgZHFsF0IW8YxY,647
325
+ pyobs/robotic/scripts/utils/callmodule.py,sha256=a0948Nm4zEUpH9mhbke_9cSSDTAx4TxVO0G1QY289Gw,2972
326
+ pyobs/robotic/scripts/utils/debugtrigger.py,sha256=9SW7JfON0eRxsDRdNOVYq7ocRoARzydIVOEkdMr9Nu4,625
327
+ pyobs/robotic/scripts/utils/log.py,sha256=jtElmP1tlOH-AST_9J9WwsN10M-OC1YdzbluF2sOa3Y,750
328
328
  pyobs/robotic/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
329
329
  pyobs/robotic/storage/observationarchive.py,sha256=GlCp1GuFZo9_1HyRDg12ubhJP-cyKVnTpdykhabzFV4,3388
330
330
  pyobs/robotic/storage/taskarchive.py,sha256=qdbd5b7D3i3tTs8R2lhrf7yiiqYDF6IB0UxusI_uA1I,1225
331
- pyobs/robotic/storage/backend/__init__.py,sha256=sW20klOE0SH4RbVKOcHsmV1H3Wu0kaSPD0RFKPd2aIU,165
332
- pyobs/robotic/storage/backend/observationarchive.py,sha256=iYpKfr4NyW-yJkpY-4_Jr4sYvMydcW7Ikafzw_eOCrg,10494
333
- pyobs/robotic/storage/backend/taskarchive.py,sha256=wouaSF7MbElUyMhA4g-cymF6JePq7uOxjzaM9HwYkgE,6155
334
331
  pyobs/robotic/storage/filesystem/__init__.py,sha256=OKG6UdaQUlErxeVcXbD71VKUhBOCDcfAbIDW-JN3QTc,153
335
332
  pyobs/robotic/storage/filesystem/observationarchive.py,sha256=woy6n9ih4gI9Aw2zEx7F2yUga3WFcVnuhNpuoxnI1l8,9102
336
333
  pyobs/robotic/storage/filesystem/taskarchive.py,sha256=vxhUJcbxKyaIj7G3XNCYwuTcrNJj0va8x0y7_WsyHIY,2214
@@ -351,6 +348,9 @@ pyobs/robotic/storage/lco/scripts/script.py,sha256=HOBL0J2W1Q5_4zfqwBnHEIRTTXoZW
351
348
  pyobs/robotic/storage/memory/__init__.py,sha256=SurkIJRT-Zik5brmrTr0Pp0jwiexIs7ckj1ZtKednOU,161
352
349
  pyobs/robotic/storage/memory/observationarchive.py,sha256=FzfwQe4vDQN-2n69eAMqY3TldQPZuJcC8wjpgyC14sw,4657
353
350
  pyobs/robotic/storage/memory/taskarchive.py,sha256=xP-5YgbQo9_1aHbrUos5cClzCgUmpuTsMlxsKGCqMeE,1807
351
+ pyobs/robotic/storage/portal/__init__.py,sha256=vMDjcTtBGfQs0Kb6kzt-8TKkRvg73A1fvtiiupL4h8I,161
352
+ pyobs/robotic/storage/portal/observationarchive.py,sha256=dD39meFk5b2XCPnRO6zEaII3X_DbsIgWiOiCnL4xhSU,10465
353
+ pyobs/robotic/storage/portal/taskarchive.py,sha256=LbFQSRiE6um_uNBo4iLmLnZSnyo56LfgTbZqOhIei9U,6108
354
354
  pyobs/robotic/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
355
355
  pyobs/robotic/utils/archive/__init__.py,sha256=cLeBhOAd1oHiq9i1uNDyYiwUIuF4C2yMr7S3rnvczig,243
356
356
  pyobs/robotic/utils/archive/archive.py,sha256=Zzbfy5yH2iCms7uylRxWnROszioHxFgVedHTlDhghNU,1985
@@ -367,9 +367,9 @@ pyobs/robotic/utils/skyflats/pointing/__init__.py,sha256=v7BVB-B_bTSSaKsgJJoZ4d1
367
367
  pyobs/robotic/utils/skyflats/pointing/base.py,sha256=qfDEmXfTZ4ekaABElhpE71zsQoZxoBpZK8AGMaxzgbc,565
368
368
  pyobs/robotic/utils/skyflats/pointing/static.py,sha256=GJjZqMW6Vmh2s56ScqdTjIKrWROFexg2xq1RSV_xd3o,1751
369
369
  pyobs/robotic/utils/skyflats/priorities/__init__.py,sha256=Tcc9G20VvPd-S_DWnhcZTSlsVPMqbjtV2YUFt8fAhsw,272
370
- pyobs/robotic/utils/skyflats/priorities/archive.py,sha256=6EBziHem5vq7w8grWmARdqLeUdRZDZVFKLW-kgBlpVk,1457
370
+ pyobs/robotic/utils/skyflats/priorities/archive.py,sha256=nBk4S7i3HCYEQp8F3_I1VgORKniAglpqk5TYfYnVPxU,1841
371
371
  pyobs/robotic/utils/skyflats/priorities/base.py,sha256=4zjY713YB6I5SfBnxx4OZ72wseA76Pgtdc_RQWb243Y,347
372
- pyobs/robotic/utils/skyflats/priorities/const.py,sha256=zgtHHyYjkbvZS1kqswqpO8PdYGffpez_qMRNEJpYjKc,326
372
+ pyobs/robotic/utils/skyflats/priorities/const.py,sha256=8bBuEqL1v9_wykUN0ol_3JJ0EZbQ27Zz1XgtiaHYHTk,441
373
373
  pyobs/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
374
374
  pyobs/utils/average.py,sha256=bQMgKm2pAUd282fcnVMlpClIVSYq5AvfjtL78AdiiSU,1336
375
375
  pyobs/utils/cache.py,sha256=C-_hKIV8GEoIy6orci1nqqjwbOupzG9QGWFr0UY40M4,2866
@@ -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.dev94.dist-info/METADATA,sha256=xGwxZTDWSVAAX0kLEhxnaONWqKiqDpv7GKy50sYOkbU,1990
447
- pyobs_core-2.0.0.dev94.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
448
- pyobs_core-2.0.0.dev94.dist-info/entry_points.txt,sha256=0ceIn3dTDwWdGdJ5VdTvYim7vKkdTe6aqqpoaG065UY,109
449
- pyobs_core-2.0.0.dev94.dist-info/licenses/LICENSE,sha256=gC8djBliCENDmuRvzCoejpWbsn8eqKx61Hc8bmDZ2LI,1099
450
- pyobs_core-2.0.0.dev94.dist-info/RECORD,,
446
+ pyobs_core-2.0.0.dev97.dist-info/METADATA,sha256=29V_lbN_fwktG29IgE4XOYMo97gnk8hUzJp3Vc1z63s,1990
447
+ pyobs_core-2.0.0.dev97.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
448
+ pyobs_core-2.0.0.dev97.dist-info/entry_points.txt,sha256=0ceIn3dTDwWdGdJ5VdTvYim7vKkdTe6aqqpoaG065UY,109
449
+ pyobs_core-2.0.0.dev97.dist-info/licenses/LICENSE,sha256=gC8djBliCENDmuRvzCoejpWbsn8eqKx61Hc8bmDZ2LI,1099
450
+ pyobs_core-2.0.0.dev97.dist-info/RECORD,,
@@ -1,4 +0,0 @@
1
- from .observationarchive import BackendObservationArchive
2
- from .taskarchive import BackendTaskArchive
3
-
4
- __all__ = ["BackendObservationArchive", "BackendTaskArchive"]