pyobs-brot 2.0.0.dev13__tar.gz → 2.0.0.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 (27) hide show
  1. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/.github/dependabot.yml +5 -0
  2. pyobs_brot-2.0.0.dev15/.github/workflows/pyrefly.yml +23 -0
  3. pyobs_brot-2.0.0.dev15/.github/workflows/pytest.yml +25 -0
  4. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/PKG-INFO +3 -3
  5. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/pyobs_brot/brotdome.py +4 -0
  6. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/pyobs_brot/brotroof.py +4 -0
  7. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/pyobs_brot/brottelescope.py +12 -10
  8. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/pyproject.toml +8 -3
  9. pyobs_brot-2.0.0.dev15/specs/index.md +17 -0
  10. pyobs_brot-2.0.0.dev15/tests/test_brotroof.py +34 -0
  11. pyobs_brot-2.0.0.dev15/tests/test_brottelescope.py +129 -0
  12. pyobs_brot-2.0.0.dev15/tests/test_import.py +49 -0
  13. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/uv.lock +1048 -787
  14. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/.github/workflows/pypi.yml +0 -0
  15. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/.github/workflows/ruff.yml +0 -0
  16. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/.gitignore +0 -0
  17. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/.pre-commit-config.yaml +0 -0
  18. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/.readthedocs.yml +0 -0
  19. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/LICENSE +0 -0
  20. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/README.md +0 -0
  21. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/docs/Makefile +0 -0
  22. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/docs/make.bat +0 -0
  23. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/docs/requirements.txt +0 -0
  24. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/docs/source/_static/pyobs.gif +0 -0
  25. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/docs/source/conf.py +0 -0
  26. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/docs/source/index.rst +0 -0
  27. {pyobs_brot-2.0.0.dev13 → pyobs_brot-2.0.0.dev15}/pyobs_brot/__init__.py +0 -0
@@ -6,5 +6,10 @@ updates:
6
6
  interval: "weekly"
7
7
  day: "tuesday"
8
8
  target-branch: "develop"
9
+ groups:
10
+ patch-and-minor:
11
+ update-types:
12
+ - "minor"
13
+ - "patch"
9
14
  labels:
10
15
  - "dependencies"
@@ -0,0 +1,23 @@
1
+ name: pyrefly
2
+ on: push
3
+
4
+ jobs:
5
+ pyrefly:
6
+ runs-on: ubuntu-latest
7
+ timeout-minutes: 10
8
+
9
+ steps:
10
+ - name: Check out repository code
11
+ uses: actions/checkout@v4
12
+
13
+ - name: Set up Python
14
+ uses: astral-sh/setup-uv@v5
15
+ with:
16
+ enable-cache: true
17
+ python-version: "3.13"
18
+
19
+ - name: Install packages
20
+ run: uv sync --all-extras --dev
21
+
22
+ - name: Run pyrefly
23
+ run: uv run pyrefly check
@@ -0,0 +1,25 @@
1
+ name: PyTest
2
+ on: push
3
+
4
+ jobs:
5
+ test:
6
+ runs-on: ubuntu-latest
7
+ timeout-minutes: 10
8
+
9
+ steps:
10
+ - name: Check out repository code
11
+ uses: actions/checkout@v4
12
+
13
+ - name: Set up Python
14
+ uses: astral-sh/setup-uv@v5
15
+ with:
16
+ enable-cache: true
17
+ python-version: "3.13"
18
+
19
+ - name: Install packages
20
+ run: uv sync --all-extras --dev
21
+
22
+ - name: Run tests
23
+ run: uv run pytest tests
24
+ env:
25
+ CI: true
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: pyobs-brot
3
- Version: 2.0.0.dev13
3
+ Version: 2.0.0.dev15
4
4
  Summary: pyobs module for BROTlib telescopes
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>, Lukas Melzig <lukas.melzig@stud.uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -9,4 +9,4 @@ Requires-Python: >=3.11
9
9
  Requires-Dist: astropy<8,>=7.0.1
10
10
  Requires-Dist: paho-mqtt>=2.1.0
11
11
  Requires-Dist: pybrotlib>=1.1.5
12
- Requires-Dist: pyobs-core<3,>=2.0.0.dev20
12
+ Requires-Dist: pyobs-core<3,>=2.0.0.dev82
@@ -76,6 +76,10 @@ class BrotDome(BaseDome, IDome):
76
76
 
77
77
  @timeout(300)
78
78
  async def init(self, **kwargs: Any) -> None:
79
+ # weather?
80
+ if not self.is_weather_good():
81
+ raise exc.InitError("Weather seems to be bad.")
82
+
79
83
  log.info("Opening roof")
80
84
  if self.brot.dome.shutter == DomeShutterStatus.OPEN:
81
85
  return
@@ -60,6 +60,10 @@ class BrotRoof(BaseRoof):
60
60
 
61
61
  @timeout(300)
62
62
  async def init(self, **kwargs: Any) -> None:
63
+ # weather?
64
+ if not self.is_weather_good():
65
+ raise exc.InitError("Weather seems to be bad.")
66
+
63
67
  log.info("Opening roof")
64
68
  if self.brot.roof.status == RoofStatus.OPEN:
65
69
  return
@@ -56,11 +56,10 @@ class BrotBaseTelescope(
56
56
  dome: str = "None",
57
57
  **kwargs: Any,
58
58
  ):
59
- BaseTelescope.__init__(
60
- self,
61
- **kwargs,
59
+ super().__init__(
62
60
  motion_status_interfaces=["ITelescope", "IFocuser"],
63
61
  wait_for_dome=None if dome == "None" else dome,
62
+ **kwargs,
64
63
  )
65
64
 
66
65
  self.mqtt = MQTTTransport(host, port)
@@ -70,7 +69,6 @@ class BrotBaseTelescope(
70
69
  self.focus_offset = 0.0
71
70
  self._roof = roof
72
71
 
73
- FitsNamespaceMixin.__init__(self, **kwargs)
74
72
  self.add_background_task(self._update_task)
75
73
 
76
74
  @property
@@ -242,6 +240,10 @@ class BrotBaseTelescope(
242
240
 
243
241
  @timeout(120)
244
242
  async def init(self, **kwargs: Any) -> None:
243
+ # weather?
244
+ if not self.is_weather_good():
245
+ raise exc.InitError("Weather seems to be bad.")
246
+
245
247
  match self.brot.telescope.status:
246
248
  case TelescopeStatus.PARKED:
247
249
  pass
@@ -328,21 +330,21 @@ class BrotRaDecTelescope(BrotBaseTelescope, IOffsetsRaDec):
328
330
  await self.brot.telescope.set_offset_dec(ddec * 3600)
329
331
  MAX_TARGET_DISTANCE = 2.0 / 3600.0
330
332
  while (
331
- self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.HA.TARGETDISTANCE < MAX_TARGET_DISTANCE
332
- and self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.DEC.TARGETDISTANCE < MAX_TARGET_DISTANCE
333
+ self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.HA.TARGETDISTANCE > MAX_TARGET_DISTANCE
334
+ or self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.DEC.TARGETDISTANCE > MAX_TARGET_DISTANCE
333
335
  ):
334
336
  await asyncio.sleep(0.1)
335
337
  await self.comm.set_state(IOffsetsRaDec, RaDecOffsetState(ra=dra, dec=ddec))
336
338
 
337
339
  async def get_fits_header_before(
338
- self, namespaces: list[str] | None = None, **kwargs: Any
340
+ self, namespaces: list[str] | None = None, sender: str = "", **kwargs: Any
339
341
  ) -> dict[str, FitsHeaderEntry]:
340
342
  hdr = await BrotBaseTelescope.get_fits_header_before(self)
341
343
  tel = self.brot.telescope._telemetry
342
344
  hdr["TEL-FOCU"] = FitsHeaderEntry(self.brot.focus.position, "Focus position [mm]")
343
345
  hdr["HAOFF"] = FitsHeaderEntry(tel.POSITION.INSTRUMENTAL.HA.OFFSET, "Hour Angle offset")
344
346
  hdr["DECOFF"] = FitsHeaderEntry(tel.POSITION.INSTRUMENTAL.DEC.OFFSET, "Declination offset")
345
- return self._filter_fits_namespace(hdr, namespaces=namespaces, **kwargs)
347
+ return self._filter_fits_namespace(hdr, sender=sender, namespaces=namespaces, **kwargs)
346
348
 
347
349
  async def add_pointing_measurement(self, **kwargs: Any) -> None:
348
350
  telemetry = self.brot.telescope._telemetry
@@ -383,14 +385,14 @@ class BrotAltAzTelescope(BrotBaseTelescope, IOffsetsAltAz, IPointingSeries):
383
385
  await self.comm.set_state(IOffsetsAltAz, AltAzOffsetState(alt=dalt, az=daz))
384
386
 
385
387
  async def get_fits_header_before(
386
- self, namespaces: list[str] | None = None, **kwargs: Any
388
+ self, namespaces: list[str] | None = None, sender: str = "", **kwargs: Any
387
389
  ) -> dict[str, FitsHeaderEntry]:
388
390
  tel = self.brot.telescope._telemetry
389
391
  hdr = await BrotBaseTelescope.get_fits_header_before(self)
390
392
  hdr["TEL-FOCU"] = FitsHeaderEntry(self.brot.focus.position, "Focus position [mm]")
391
393
  hdr["HAOFF"] = FitsHeaderEntry(tel.POSITION.INSTRUMENTAL.HA.OFFSET, "Hour Angle offset")
392
394
  hdr["DECOFF"] = FitsHeaderEntry(tel.POSITION.INSTRUMENTAL.DEC.OFFSET, "Declination offset")
393
- return self._filter_fits_namespace(hdr, namespaces=namespaces, **kwargs)
395
+ return self._filter_fits_namespace(hdr, sender=sender, namespaces=namespaces, **kwargs)
394
396
 
395
397
  async def add_pointing_measurement(self, **kwargs: Any) -> None:
396
398
  telemetry = self.brot.telescope._telemetry
@@ -1,23 +1,25 @@
1
1
  [project]
2
2
  name = "pyobs-brot"
3
- version = "2.0.0.dev13"
3
+ version = "2.0.0.dev15"
4
4
  description = "pyobs module for BROTlib telescopes"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }, { name = "Lukas Melzig", email = "lukas.melzig@stud.uni-goettingen.de" }]
6
6
  requires-python = ">=3.11"
7
7
  license = "MIT"
8
8
  dependencies = [
9
9
  "astropy>=7.0.1,<8",
10
- "pyobs-core>=2.0.0.dev20,<3",
10
+ "pyobs-core>=2.0.0.dev82,<3",
11
11
  "paho-mqtt>=2.1.0",
12
12
  "pybrotlib>=1.1.5",
13
13
  ]
14
14
 
15
15
  [dependency-groups]
16
16
  dev = [
17
- "black>=23.7.0,<24",
17
+ "black>=23.7.0,<27",
18
18
  "ruff>=0.9.0",
19
19
  "pyrefly>=1.1.1",
20
20
  "pre-commit>=3.3.3,<5",
21
+ "pytest>=8.0.0",
22
+ "pytest-asyncio>=0.24.0",
21
23
  ]
22
24
 
23
25
  [build-system]
@@ -37,3 +39,6 @@ select = ["E", "F", "W", "I", "UP", "G"]
37
39
 
38
40
  [tool.pyrefly]
39
41
  python-version = "3.11"
42
+
43
+ [tool.pytest.ini_options]
44
+ asyncio_mode = "strict"
@@ -0,0 +1,17 @@
1
+ # Design/planning docs
2
+
3
+ This repo has no `specs/` structure of its own. Design docs, implementation plans, and ADRs that
4
+ concern `pyobs-brot` — including ones actually implemented here — live in `pyobs-core`'s `specs/`
5
+ tree instead (`specs/design/`, `specs/plans/`, `specs/adrs/`), each tagged with a `Repos:` line
6
+ naming every repo it concerns. See `pyobs-core/CLAUDE.md`'s "Cross-repo docs" section.
7
+
8
+ Relevant so far:
9
+
10
+ - `pyobs-core/specs/plans/ejabberd-throughput-benchmarking.md` — XMPP/ejabberd throughput
11
+ benchmarking, including a production incident investigation involving `BrotDome`/
12
+ `BrotRaDecTelescope` on `pyobs-iag50` (disco#info capability fetches silently timing out after a
13
+ few modules restart together) and the `pybrotlib` `MQTTTransport.run()` event-loop-starvation fix
14
+ found along the way.
15
+ - `pyobs-core/specs/plans/enforce-state-publishing.md` — the state-publishing convention every
16
+ `Module` follows; `BrotRaDecTelescope`'s `ITemperatures` state had the same gap this doc's
17
+ convention exists to catch (2026-07-27 addendum).
@@ -0,0 +1,34 @@
1
+ """Unit tests for the roof-status -> motion-status mapping in BrotRoof.
2
+
3
+ The BROT/MQTT backend is mocked out; only the state mapping is exercised.
4
+ """
5
+
6
+ from unittest.mock import MagicMock
7
+
8
+ import pytest
9
+ from pybrotlib.components.roof import RoofStatus
10
+ from pyobs.utils.enums import MotionStatus
11
+
12
+ from pyobs_brot import BrotRoof
13
+
14
+
15
+ @pytest.mark.parametrize(
16
+ ("status", "expected"),
17
+ [
18
+ (RoofStatus.ERROR, MotionStatus.ERROR),
19
+ (RoofStatus.CLOSED, MotionStatus.PARKED),
20
+ (RoofStatus.OPENING, MotionStatus.INITIALIZING),
21
+ (RoofStatus.CLOSING, MotionStatus.PARKING),
22
+ (RoofStatus.OPEN, MotionStatus.POSITIONED),
23
+ (RoofStatus.STOPPED, MotionStatus.IDLE),
24
+ ],
25
+ )
26
+ @pytest.mark.asyncio
27
+ async def test_update_status_mapping(status: RoofStatus, expected: MotionStatus) -> None:
28
+ roof = BrotRoof(host="localhost", name="roof")
29
+ roof.brot.roof = MagicMock()
30
+ roof.brot.roof.status = status
31
+
32
+ await roof._update_status()
33
+
34
+ assert roof.motion_status() == expected
@@ -0,0 +1,129 @@
1
+ """Unit tests for the non-hardware logic in the BROT telescope drivers: constructor
2
+ state, focus-offset composition, and the RA/Dec offset scaling + convergence wait.
3
+
4
+ The BROT/MQTT backend (self.brot) is mocked out; hardware I/O itself is out of scope.
5
+ """
6
+
7
+ from types import SimpleNamespace
8
+ from typing import Any
9
+ from unittest.mock import AsyncMock, MagicMock
10
+
11
+ import pytest
12
+ from pyobs.interfaces import IFocuser, IOffsetsRaDec
13
+
14
+ from pyobs_brot import BrotRaDecTelescope
15
+
16
+
17
+ def test_constructor_defaults() -> None:
18
+ telescope = BrotRaDecTelescope(host="localhost", name="telescope")
19
+ assert telescope.temperatures == {}
20
+ assert telescope.focus_offset == 0.0
21
+ assert telescope._roof == "None"
22
+
23
+
24
+ def test_constructor_threads_kwargs_cooperatively() -> None:
25
+ """Regression test for the cooperative-super()-chain fix: BrotBaseTelescope used to call
26
+ BaseTelescope.__init__() then a separate, redundant FitsNamespaceMixin.__init__(self,
27
+ **kwargs) -- BaseTelescope's own cooperative chain already reaches FitsNamespaceMixin (it's
28
+ the last base), so the second call re-threads the same kwargs (location, comm, ...) through
29
+ a chain that's now cooperative all the way to object.__init__(), which raises on any
30
+ leftover. This is exactly the shape deployed in pyobs-monet's telescope configs (host, name,
31
+ fits_headers, temperatures, comm, location, timezone all set)."""
32
+ telescope = BrotRaDecTelescope(
33
+ host="localhost",
34
+ name="telescope",
35
+ fits_headers={"TEL-FOCL": (8400.0, "Focal length")},
36
+ temperatures={"M1": "TEMP_M1"},
37
+ location={"longitude": 20.81, "latitude": -32.37, "elevation": 1798.0},
38
+ timezone="Africa/Johannesburg",
39
+ )
40
+ assert telescope.motion_status().name == "UNKNOWN"
41
+ assert telescope.temperatures == {"M1": "TEMP_M1"}
42
+
43
+
44
+ def test_constructor_temperatures_and_roof() -> None:
45
+ telescope = BrotRaDecTelescope(
46
+ host="localhost",
47
+ name="telescope",
48
+ temperatures={"ccd": "T1"},
49
+ roof="some-roof",
50
+ )
51
+ assert telescope.temperatures == {"ccd": "T1"}
52
+ assert telescope._roof == "some-roof"
53
+
54
+
55
+ @pytest.mark.asyncio
56
+ async def test_set_focus_offset_composes_and_publishes() -> None:
57
+ telescope = BrotRaDecTelescope(host="localhost", name="telescope")
58
+ telescope.brot.focus = MagicMock()
59
+ telescope.brot.focus.position = 10.0
60
+ telescope.brot.focus.set = AsyncMock()
61
+ telescope._wait_for_focus = AsyncMock() # type: ignore[method-assign]
62
+ telescope.comm.set_state = AsyncMock() # type: ignore[method-assign]
63
+
64
+ await telescope.set_focus_offset(0.25)
65
+
66
+ # the hardware call adds the offset onto the current position...
67
+ telescope.brot.focus.set.assert_awaited_once_with(10.25)
68
+ assert telescope.focus_offset == 0.25
69
+
70
+ # ...and the published focus subtracts the offset back out of the new position
71
+ assert telescope.comm.set_state.await_args is not None
72
+ interface, state = telescope.comm.set_state.await_args.args
73
+ assert interface is IFocuser
74
+ assert (state.focus, state.focus_offset) == (9.75, 0.25)
75
+
76
+
77
+ def _telemetry(ha_dist: float, dec_dist: float) -> Any:
78
+ return SimpleNamespace(
79
+ POSITION=SimpleNamespace(
80
+ INSTRUMENTAL=SimpleNamespace(
81
+ HA=SimpleNamespace(TARGETDISTANCE=ha_dist),
82
+ DEC=SimpleNamespace(TARGETDISTANCE=dec_dist),
83
+ )
84
+ )
85
+ )
86
+
87
+
88
+ @pytest.mark.asyncio
89
+ async def test_set_offsets_radec_sends_scaled_and_sign_flipped_offsets() -> None:
90
+ telescope = BrotRaDecTelescope(host="localhost", name="telescope")
91
+ telescope.brot.telescope.set_offset_ha = AsyncMock() # type: ignore[method-assign]
92
+ telescope.brot.telescope.set_offset_dec = AsyncMock() # type: ignore[method-assign]
93
+ telescope.brot.telescope._telemetry = _telemetry(0.0, 0.0) # already converged
94
+ telescope.comm.set_state = AsyncMock() # type: ignore[method-assign]
95
+
96
+ await telescope.set_offsets_radec(0.001, 0.002)
97
+
98
+ # HA is sign-flipped (RA east is HA west) and both are scaled deg -> arcsec
99
+ telescope.brot.telescope.set_offset_ha.assert_awaited_once_with(-3.6)
100
+ telescope.brot.telescope.set_offset_dec.assert_awaited_once_with(7.2)
101
+ assert telescope.comm.set_state.await_args is not None
102
+ interface, state = telescope.comm.set_state.await_args.args
103
+ assert interface is IOffsetsRaDec
104
+ assert (state.ra, state.dec) == (0.001, 0.002)
105
+
106
+
107
+ @pytest.mark.asyncio
108
+ async def test_set_offsets_radec_waits_until_both_axes_converge(monkeypatch: pytest.MonkeyPatch) -> None:
109
+ telescope = BrotRaDecTelescope(host="localhost", name="telescope")
110
+ telescope.brot.telescope.set_offset_ha = AsyncMock() # type: ignore[method-assign]
111
+ telescope.brot.telescope.set_offset_dec = AsyncMock() # type: ignore[method-assign]
112
+ telescope.comm.set_state = AsyncMock() # type: ignore[method-assign]
113
+
114
+ # still slewing for two polls (HA, then DEC, still off-target), converged on the third
115
+ readings = [(1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
116
+ telescope.brot.telescope._telemetry = _telemetry(*readings[0])
117
+
118
+ sleep_calls = 0
119
+
120
+ async def fake_sleep(_seconds: float) -> None:
121
+ nonlocal sleep_calls
122
+ sleep_calls += 1
123
+ telescope.brot.telescope._telemetry = _telemetry(*readings[sleep_calls])
124
+
125
+ monkeypatch.setattr("pyobs_brot.brottelescope.asyncio.sleep", fake_sleep)
126
+
127
+ await telescope.set_offsets_radec(0.001, 0.002)
128
+
129
+ assert sleep_calls == 2
@@ -0,0 +1,49 @@
1
+ """Smoke tests: import every public module and instantiate each driver class without
2
+ hardware, asserting the interfaces they claim.
3
+
4
+ pybrotlib is a pure-Python MQTT client that connects at connect() time, not at
5
+ construction, so instantiation is safe with no broker running.
6
+ """
7
+
8
+ from pyobs.interfaces import (
9
+ IDome,
10
+ IFocuser,
11
+ IOffsetsAltAz,
12
+ IOffsetsRaDec,
13
+ IPointingAltAz,
14
+ IPointingRaDec,
15
+ IRoof,
16
+ ITemperatures,
17
+ )
18
+ from pyobs.modules import Module
19
+
20
+ from pyobs_brot import BrotDome, BrotRaDecTelescope, BrotRoof
21
+ from pyobs_brot.brottelescope import BrotAltAzTelescope
22
+
23
+
24
+ def test_instantiate_dome() -> None:
25
+ dome = BrotDome(host="localhost", name="dome")
26
+ assert isinstance(dome, Module)
27
+ assert isinstance(dome, IDome)
28
+
29
+
30
+ def test_instantiate_roof() -> None:
31
+ roof = BrotRoof(host="localhost", name="roof")
32
+ assert isinstance(roof, Module)
33
+ assert isinstance(roof, IRoof)
34
+
35
+
36
+ def test_instantiate_radec_telescope() -> None:
37
+ telescope = BrotRaDecTelescope(host="localhost", name="telescope")
38
+ assert isinstance(telescope, Module)
39
+ assert isinstance(telescope, IFocuser)
40
+ assert isinstance(telescope, ITemperatures)
41
+ assert isinstance(telescope, IPointingRaDec)
42
+ assert isinstance(telescope, IPointingAltAz)
43
+ assert isinstance(telescope, IOffsetsRaDec)
44
+
45
+
46
+ def test_instantiate_altaz_telescope() -> None:
47
+ telescope = BrotAltAzTelescope(host="localhost", name="telescope")
48
+ assert isinstance(telescope, Module)
49
+ assert isinstance(telescope, IOffsetsAltAz)