pyobs-alpaca 2.0.0.dev3__tar.gz → 2.0.0.dev5__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.
- pyobs_alpaca-2.0.0.dev5/.flake8 +14 -0
- pyobs_alpaca-2.0.0.dev5/.github/dependabot.yml +15 -0
- pyobs_alpaca-2.0.0.dev5/.github/workflows/dependabot-automerge.yml +24 -0
- pyobs_alpaca-2.0.0.dev5/.github/workflows/pyrefly.yml +23 -0
- pyobs_alpaca-2.0.0.dev5/.github/workflows/pytest.yml +25 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/PKG-INFO +3 -3
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/pyobs_alpaca/device.py +13 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/pyobs_alpaca/dome.py +14 -9
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/pyobs_alpaca/focuser.py +13 -11
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/pyobs_alpaca/telescope.py +16 -9
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/pyproject.toml +8 -3
- pyobs_alpaca-2.0.0.dev5/tests/test_cooperative_init.py +64 -0
- pyobs_alpaca-2.0.0.dev5/tests/test_device.py +68 -0
- pyobs_alpaca-2.0.0.dev5/tests/test_import.py +47 -0
- pyobs_alpaca-2.0.0.dev5/uv.lock +2604 -0
- pyobs_alpaca-2.0.0.dev3/uv.lock +0 -2107
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/.github/workflows/pypi.yml +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/.github/workflows/ruff.yml +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/.pre-commit-config.yaml +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/.readthedocs.yml +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/CHANGELOG.rst +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/LICENSE +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/README.md +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/docs/Makefile +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/docs/make.bat +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/docs/requirements.txt +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/docs/source/_static/pyobs.gif +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/docs/source/conf.py +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/docs/source/index.rst +0 -0
- {pyobs_alpaca-2.0.0.dev3 → pyobs_alpaca-2.0.0.dev5}/pyobs_alpaca/__init__.py +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[flake8]
|
|
2
|
+
ignore =
|
|
3
|
+
E203, E266, E501, W503, F403,
|
|
4
|
+
# E704: Multiple statements on one line (def)
|
|
5
|
+
E704,
|
|
6
|
+
# E722: do not use bare 'except'
|
|
7
|
+
E722
|
|
8
|
+
# E701: multiple statements on one line (colon)
|
|
9
|
+
E701
|
|
10
|
+
per-file-ignores = __init__.py:F401
|
|
11
|
+
max-line-length = 120
|
|
12
|
+
max-complexity = 30
|
|
13
|
+
select = B, C, E, F, W, T4, B9
|
|
14
|
+
exclude = .git,.venv,__pycache__,docs,build,dist
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Dependabot auto-merge
|
|
2
|
+
|
|
3
|
+
on: pull_request
|
|
4
|
+
|
|
5
|
+
permissions:
|
|
6
|
+
contents: write
|
|
7
|
+
pull-requests: write
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
dependabot:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}
|
|
13
|
+
steps:
|
|
14
|
+
- name: Fetch Dependabot metadata
|
|
15
|
+
id: metadata
|
|
16
|
+
uses: dependabot/fetch-metadata@v2
|
|
17
|
+
with:
|
|
18
|
+
compat-lookup: true
|
|
19
|
+
- name: Enable auto-merge for patch and minor updates
|
|
20
|
+
if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' }}
|
|
21
|
+
run: gh pr merge --auto --merge "$PR_URL"
|
|
22
|
+
env:
|
|
23
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
24
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -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,10 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: pyobs-alpaca
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev5
|
|
4
4
|
Summary: pyobs module for ASCOM Alpaca
|
|
5
5
|
Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
9
|
Requires-Dist: numpy<3,>=2.2.5
|
|
10
|
-
Requires-Dist: pyobs-core<3,>=2.0.0.
|
|
10
|
+
Requires-Dist: pyobs-core<3,>=2.0.0.dev82
|
|
@@ -23,6 +23,19 @@ class ServerGetResponse(NamedTuple):
|
|
|
23
23
|
Value: Any
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
DEVICE_INIT_KWARGS = {"server", "port", "device_type", "device", "version", "alive_parameter"}
|
|
27
|
+
"""Names of AlpacaDevice's own constructor params -- callers that also thread **kwargs through
|
|
28
|
+
their own cooperative super().__init__() chain must exclude these first, since nothing in that
|
|
29
|
+
chain (Module/mixins) recognizes them and they'd otherwise leak through to object.__init__()."""
|
|
30
|
+
|
|
31
|
+
OBJECT_SHARED_KWARGS = {"vfs", "comm", "timezone", "location", "observer"}
|
|
32
|
+
"""Names of pyobs.object.Object's own constructor params. AlpacaDevice forwards anything beyond
|
|
33
|
+
its own params (see DEVICE_INIT_KWARGS) straight to Object cooperatively, so callers building an
|
|
34
|
+
AlpacaDevice from a module's **kwargs must restrict to DEVICE_INIT_KWARGS | OBJECT_SHARED_KWARGS --
|
|
35
|
+
any module/mixin-specific kwarg (e.g. fits_namespaces, motion_status_interfaces) would otherwise
|
|
36
|
+
leak through to object.__init__() the same way."""
|
|
37
|
+
|
|
38
|
+
|
|
26
39
|
class AlpacaDevice(Object):
|
|
27
40
|
def __init__(
|
|
28
41
|
self,
|
|
@@ -13,12 +13,12 @@ from pyobs.utils.enums import MotionStatus
|
|
|
13
13
|
from pyobs.utils.parallel import event_wait
|
|
14
14
|
from pyobs.utils.threads import LockWithAbort
|
|
15
15
|
|
|
16
|
-
from .device import AlpacaDevice
|
|
16
|
+
from .device import DEVICE_INIT_KWARGS, OBJECT_SHARED_KWARGS, AlpacaDevice
|
|
17
17
|
|
|
18
18
|
log = logging.getLogger("pyobs")
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class AlpacaDome(
|
|
21
|
+
class AlpacaDome(BaseDome, FollowMixin):
|
|
22
22
|
__module__ = "pyobs_alpaca"
|
|
23
23
|
|
|
24
24
|
def __init__(self, tolerance: float = 3, park_az: float = 180, follow: str | None = None, **kwargs: Any):
|
|
@@ -29,10 +29,20 @@ class AlpacaDome(FollowMixin, BaseDome):
|
|
|
29
29
|
park_az: Azimuth for park position.
|
|
30
30
|
follow: Name of other device (e.g. telescope) to follow.
|
|
31
31
|
"""
|
|
32
|
-
|
|
32
|
+
super().__init__(
|
|
33
|
+
motion_status_interfaces=["IDome"],
|
|
34
|
+
device=follow,
|
|
35
|
+
interval=10,
|
|
36
|
+
tolerance=tolerance,
|
|
37
|
+
mode=IPointingAltAz,
|
|
38
|
+
only_follow_when_ready=False,
|
|
39
|
+
**{k: v for k, v in kwargs.items() if k not in DEVICE_INIT_KWARGS},
|
|
40
|
+
)
|
|
33
41
|
|
|
34
42
|
# device
|
|
35
|
-
self._device = self.add_child_object(
|
|
43
|
+
self._device = self.add_child_object(
|
|
44
|
+
AlpacaDevice, **{k: v for k, v in kwargs.items() if k in DEVICE_INIT_KWARGS | OBJECT_SHARED_KWARGS}
|
|
45
|
+
)
|
|
36
46
|
|
|
37
47
|
# store
|
|
38
48
|
self._tolerance = tolerance
|
|
@@ -53,11 +63,6 @@ class AlpacaDome(FollowMixin, BaseDome):
|
|
|
53
63
|
# start thread
|
|
54
64
|
self.add_background_task(self._update_status)
|
|
55
65
|
|
|
56
|
-
# mixins
|
|
57
|
-
FollowMixin.__init__(
|
|
58
|
-
self, device=follow, interval=10, tolerance=tolerance, mode=IPointingAltAz, only_follow_when_ready=False
|
|
59
|
-
)
|
|
60
|
-
|
|
61
66
|
async def open(self) -> None:
|
|
62
67
|
"""Open module."""
|
|
63
68
|
await BaseDome.open(self)
|
|
@@ -2,26 +2,31 @@ import asyncio
|
|
|
2
2
|
import logging
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from pyobs.interfaces import FocuserState, IFitsHeaderBefore, IFocuser, IReady, ReadyState
|
|
5
|
+
from pyobs.interfaces import FitsHeaderEntry, FocuserState, IFitsHeaderBefore, IFocuser, IReady, ReadyState
|
|
6
6
|
from pyobs.mixins import MotionStatusMixin
|
|
7
7
|
from pyobs.modules import Module, timeout
|
|
8
8
|
from pyobs.utils import exceptions as exc
|
|
9
9
|
from pyobs.utils.enums import MotionStatus
|
|
10
10
|
from pyobs.utils.threads import LockWithAbort
|
|
11
11
|
|
|
12
|
-
from .device import AlpacaDevice
|
|
12
|
+
from .device import DEVICE_INIT_KWARGS, OBJECT_SHARED_KWARGS, AlpacaDevice
|
|
13
13
|
|
|
14
14
|
log = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class AlpacaFocuser(MotionStatusMixin, IFocuser, IFitsHeaderBefore
|
|
17
|
+
class AlpacaFocuser(Module, MotionStatusMixin, IFocuser, IFitsHeaderBefore):
|
|
18
18
|
__module__ = "pyobs_alpaca"
|
|
19
19
|
|
|
20
20
|
def __init__(self, **kwargs: Any):
|
|
21
|
-
|
|
21
|
+
super().__init__(
|
|
22
|
+
motion_status_interfaces=["IFocuser"],
|
|
23
|
+
**{k: v for k, v in kwargs.items() if k not in DEVICE_INIT_KWARGS},
|
|
24
|
+
)
|
|
22
25
|
|
|
23
26
|
# device
|
|
24
|
-
self._device = self.add_child_object(
|
|
27
|
+
self._device = self.add_child_object(
|
|
28
|
+
AlpacaDevice, **{k: v for k, v in kwargs.items() if k in DEVICE_INIT_KWARGS | OBJECT_SHARED_KWARGS}
|
|
29
|
+
)
|
|
25
30
|
|
|
26
31
|
# variables
|
|
27
32
|
self._focus_offset = 0.0
|
|
@@ -30,11 +35,8 @@ class AlpacaFocuser(MotionStatusMixin, IFocuser, IFitsHeaderBefore, Module):
|
|
|
30
35
|
self._lock_motion = asyncio.Lock()
|
|
31
36
|
self._abort_motion = asyncio.Event()
|
|
32
37
|
|
|
33
|
-
# init mixins
|
|
34
|
-
MotionStatusMixin.__init__(self, motion_status_interfaces=["IFocuser"])
|
|
35
|
-
|
|
36
38
|
# register exception
|
|
37
|
-
|
|
39
|
+
self._register_exception(exc.MotionError, 3, timespan=600, callback=self._default_remote_error_callback)
|
|
38
40
|
|
|
39
41
|
async def open(self) -> None:
|
|
40
42
|
"""Open module."""
|
|
@@ -57,13 +59,13 @@ class AlpacaFocuser(MotionStatusMixin, IFocuser, IFitsHeaderBefore, Module):
|
|
|
57
59
|
|
|
58
60
|
async def get_fits_header_before(
|
|
59
61
|
self, namespaces: list[str] | None = None, **kwargs: Any
|
|
60
|
-
) -> dict[str,
|
|
62
|
+
) -> dict[str, FitsHeaderEntry]:
|
|
61
63
|
"""Returns FITS header for the current status of this module."""
|
|
62
64
|
|
|
63
65
|
try:
|
|
64
66
|
pos = await self._device.get("Position")
|
|
65
67
|
step = await self._device.get("StepSize") * 1000.0
|
|
66
|
-
return {"TEL-FOCU": (pos / step, "Focus of telescope [mm]")}
|
|
68
|
+
return {"TEL-FOCU": FitsHeaderEntry(pos / step, "Focus of telescope [mm]")}
|
|
67
69
|
except ConnectionError as e:
|
|
68
70
|
log.warning("Could not determine focus position: %s", e)
|
|
69
71
|
return {}
|
|
@@ -6,6 +6,7 @@ import numpy as np
|
|
|
6
6
|
from pyobs.events import OffsetsRaDecEvent
|
|
7
7
|
from pyobs.interfaces import (
|
|
8
8
|
AltAzState,
|
|
9
|
+
FitsHeaderEntry,
|
|
9
10
|
IFitsHeaderBefore,
|
|
10
11
|
IOffsetsRaDec,
|
|
11
12
|
IPointingAltAz,
|
|
@@ -24,7 +25,7 @@ from pyobs.utils.enums import MotionStatus
|
|
|
24
25
|
from pyobs.utils.parallel import acquire_lock, event_wait
|
|
25
26
|
from pyobs.utils.threads import LockWithAbort
|
|
26
27
|
|
|
27
|
-
from .device import AlpacaDevice
|
|
28
|
+
from .device import DEVICE_INIT_KWARGS, OBJECT_SHARED_KWARGS, AlpacaDevice
|
|
28
29
|
|
|
29
30
|
log = logging.getLogger("pyobs")
|
|
30
31
|
|
|
@@ -39,10 +40,15 @@ class AlpacaTelescope(BaseTelescope, FitsNamespaceMixin, IFitsHeaderBefore, IOff
|
|
|
39
40
|
settle_time: Time in seconds to wait after slew before finishing.
|
|
40
41
|
park_position: Alt/Az park position.
|
|
41
42
|
"""
|
|
42
|
-
|
|
43
|
+
super().__init__(
|
|
44
|
+
motion_status_interfaces=["ITelescope"],
|
|
45
|
+
**{k: v for k, v in kwargs.items() if k not in DEVICE_INIT_KWARGS},
|
|
46
|
+
)
|
|
43
47
|
|
|
44
48
|
# device
|
|
45
|
-
self._device = self.add_child_object(
|
|
49
|
+
self._device = self.add_child_object(
|
|
50
|
+
AlpacaDevice, **{k: v for k, v in kwargs.items() if k in DEVICE_INIT_KWARGS | OBJECT_SHARED_KWARGS}
|
|
51
|
+
)
|
|
46
52
|
|
|
47
53
|
# variables
|
|
48
54
|
self._settle_time = settle_time
|
|
@@ -56,9 +62,6 @@ class AlpacaTelescope(BaseTelescope, FitsNamespaceMixin, IFitsHeaderBefore, IOff
|
|
|
56
62
|
self._cached_ra: float | None = None
|
|
57
63
|
self._cached_dec: float | None = None
|
|
58
64
|
|
|
59
|
-
# mixins
|
|
60
|
-
FitsNamespaceMixin.__init__(self, **kwargs)
|
|
61
|
-
|
|
62
65
|
# background position polling
|
|
63
66
|
self.add_background_task(self._update_position)
|
|
64
67
|
|
|
@@ -219,6 +222,10 @@ class AlpacaTelescope(BaseTelescope, FitsNamespaceMixin, IFitsHeaderBefore, IOff
|
|
|
219
222
|
await self.stop_motion()
|
|
220
223
|
raise exc.MoveError("Could not move telescope to RA/Dec.")
|
|
221
224
|
|
|
225
|
+
async def _set_tracking_rate(self, ra_rate: float, dec_rate: float) -> None:
|
|
226
|
+
"""Tracking rates are not supported by the Alpaca telescope driver."""
|
|
227
|
+
raise NotImplementedError
|
|
228
|
+
|
|
222
229
|
@timeout(10000)
|
|
223
230
|
async def set_offsets_radec(self, dra: float, ddec: float, **kwargs: Any) -> None:
|
|
224
231
|
"""Move an RA/Dec offset."""
|
|
@@ -295,14 +302,14 @@ class AlpacaTelescope(BaseTelescope, FitsNamespaceMixin, IFitsHeaderBefore, IOff
|
|
|
295
302
|
|
|
296
303
|
async def get_fits_header_before(
|
|
297
304
|
self, namespaces: list[str] | None = None, **kwargs: Any
|
|
298
|
-
) -> dict[str,
|
|
305
|
+
) -> dict[str, FitsHeaderEntry]:
|
|
299
306
|
"""Returns FITS header for the current status of this module."""
|
|
300
307
|
|
|
301
308
|
hdr = await BaseTelescope.get_fits_header_before(self)
|
|
302
309
|
|
|
303
310
|
try:
|
|
304
|
-
hdr["RAOFF"] = (self._offset_ra, "RA offset [deg]")
|
|
305
|
-
hdr["DECOFF"] = (self._offset_dec, "Dec offset [deg]")
|
|
311
|
+
hdr["RAOFF"] = FitsHeaderEntry(self._offset_ra, "RA offset [deg]")
|
|
312
|
+
hdr["DECOFF"] = FitsHeaderEntry(self._offset_dec, "Dec offset [deg]")
|
|
306
313
|
return self._filter_fits_namespace(hdr, namespaces=namespaces, **kwargs)
|
|
307
314
|
except ConnectionError:
|
|
308
315
|
return {}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyobs-alpaca"
|
|
3
|
-
version = "2.0.0.
|
|
3
|
+
version = "2.0.0.dev5"
|
|
4
4
|
description = "pyobs module for ASCOM Alpaca"
|
|
5
5
|
authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
7
|
license = "MIT"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"numpy>=2.2.5,<3",
|
|
10
|
-
"pyobs-core>=2.0.0.
|
|
10
|
+
"pyobs-core>=2.0.0.dev82,<3",
|
|
11
11
|
]
|
|
12
12
|
|
|
13
13
|
[dependency-groups]
|
|
14
14
|
dev = [
|
|
15
|
-
"black>=25.1.0,<
|
|
15
|
+
"black>=25.1.0,<27",
|
|
16
16
|
"ruff>=0.9.0",
|
|
17
17
|
"pyrefly>=1.1.1",
|
|
18
18
|
"pre-commit>=4.2.0,<5",
|
|
19
|
+
"pytest>=8.0.0",
|
|
20
|
+
"pytest-asyncio>=0.24.0",
|
|
19
21
|
]
|
|
20
22
|
|
|
21
23
|
[build-system]
|
|
@@ -35,3 +37,6 @@ select = ["E", "F", "W", "I", "UP", "G"]
|
|
|
35
37
|
|
|
36
38
|
[tool.pyrefly]
|
|
37
39
|
python-version = "3.11"
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
asyncio_mode = "strict"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Regression tests for the cooperative-super()-chain conversion of AlpacaDome, AlpacaFocuser,
|
|
2
|
+
and AlpacaTelescope.
|
|
3
|
+
|
|
4
|
+
Each of these classes used to make separate, explicit calls to its Module/Base and mixin
|
|
5
|
+
__init__ methods with unfiltered kwargs, instead of a single cooperative super().__init__()
|
|
6
|
+
call. Against pyobs-core's cooperative __init__ chains, that pattern either drops kwargs a
|
|
7
|
+
sibling mixin declares (leaking them to object.__init__()) or re-threads already-consumed
|
|
8
|
+
kwargs through a second, redundant call -- both raise TypeError. If that happens here, these
|
|
9
|
+
constructor calls themselves raise instead of returning.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import inspect
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from pyobs.mixins.follow import FollowMixin
|
|
17
|
+
from pyobs.object import Object
|
|
18
|
+
from pyobs.utils.enums import MotionStatus
|
|
19
|
+
|
|
20
|
+
from pyobs_alpaca import AlpacaDome, AlpacaFocuser, AlpacaTelescope
|
|
21
|
+
from pyobs_alpaca.device import DEVICE_INIT_KWARGS, OBJECT_SHARED_KWARGS, AlpacaDevice
|
|
22
|
+
|
|
23
|
+
_DEVICE_ARGS = dict(server="localhost", port=11111, device_type="dome", device=0)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _param_names(func: Callable[..., Any]) -> set[str]:
|
|
27
|
+
return {name for name in inspect.signature(func).parameters if name not in ("self", "kwargs")}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_device_init_kwargs_matches_alpaca_device_signature() -> None:
|
|
31
|
+
"""Keeps DEVICE_INIT_KWARGS honest: if AlpacaDevice's constructor ever gains/loses a param
|
|
32
|
+
without updating this set, callers threading a module's **kwargs through both AlpacaDevice
|
|
33
|
+
and the module's own super().__init__() chain would silently start leaking kwargs to
|
|
34
|
+
object.__init__() again -- exactly the bug this whole conversion fixes."""
|
|
35
|
+
assert DEVICE_INIT_KWARGS == _param_names(AlpacaDevice.__init__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_object_shared_kwargs_matches_object_signature() -> None:
|
|
39
|
+
"""Same invariant as above, for the Object-level kwargs AlpacaDevice forwards cooperatively
|
|
40
|
+
(and that the module chain also needs, e.g. comm/vfs)."""
|
|
41
|
+
assert OBJECT_SHARED_KWARGS == _param_names(Object.__init__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_dome_constructor_threads_kwargs_cooperatively() -> None:
|
|
45
|
+
dome = AlpacaDome(tolerance=5, follow="telescope", **_DEVICE_ARGS)
|
|
46
|
+
assert dome.motion_status() == MotionStatus.UNKNOWN
|
|
47
|
+
assert isinstance(dome, FollowMixin)
|
|
48
|
+
assert dome.is_following is True
|
|
49
|
+
assert dome._tolerance == 5
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_focuser_constructor_threads_kwargs_cooperatively() -> None:
|
|
53
|
+
focuser = AlpacaFocuser(**{**_DEVICE_ARGS, "device_type": "focuser"})
|
|
54
|
+
assert focuser.motion_status() == MotionStatus.UNKNOWN
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_telescope_constructor_threads_kwargs_cooperatively() -> None:
|
|
58
|
+
telescope = AlpacaTelescope(
|
|
59
|
+
settle_time=1.5,
|
|
60
|
+
fits_namespaces={"instrument": None},
|
|
61
|
+
**{**_DEVICE_ARGS, "device_type": "telescope"},
|
|
62
|
+
)
|
|
63
|
+
assert telescope.motion_status() == MotionStatus.UNKNOWN
|
|
64
|
+
assert telescope._FitsNamespaceMixin__namespaces == {"instrument": None} # type: ignore[attr-defined]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Unit tests for the pure, non-hardware logic in AlpacaDevice.
|
|
2
|
+
|
|
3
|
+
Hardware I/O (open/close, talking to the ASCOM server) is out of scope here; these cover
|
|
4
|
+
URL construction, version validation, the connection guard, and the connected-state
|
|
5
|
+
transition logic.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from pyobs_alpaca.device import AlpacaDevice
|
|
13
|
+
|
|
14
|
+
_ARGS = dict(server="myserver", port=11111, device_type="telescope", device=3)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_build_alpaca_url() -> None:
|
|
18
|
+
device = AlpacaDevice(**_ARGS)
|
|
19
|
+
assert device._build_alpaca_url("RightAscension") == ("http://myserver:11111/api/v1/telescope/3/rightascension")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_only_v1_supported() -> None:
|
|
23
|
+
with pytest.raises(ValueError):
|
|
24
|
+
AlpacaDevice(**_ARGS, version="v2")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_starts_disconnected() -> None:
|
|
28
|
+
device = AlpacaDevice(**_ARGS)
|
|
29
|
+
assert device.connected is False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.asyncio
|
|
33
|
+
async def test_get_requires_connection() -> None:
|
|
34
|
+
device = AlpacaDevice(**_ARGS)
|
|
35
|
+
with pytest.raises(ConnectionError):
|
|
36
|
+
await device.get("RightAscension")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.asyncio
|
|
40
|
+
async def test_put_requires_connection() -> None:
|
|
41
|
+
device = AlpacaDevice(**_ARGS)
|
|
42
|
+
with pytest.raises(ConnectionError):
|
|
43
|
+
await device.put("Tracking", Tracking=False)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.asyncio
|
|
47
|
+
async def test_check_connected_sets_flag_on_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
48
|
+
device = AlpacaDevice(**_ARGS)
|
|
49
|
+
|
|
50
|
+
async def fake_get(name: str) -> Any:
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
monkeypatch.setattr(device, "_get", fake_get)
|
|
54
|
+
await device._check_connected()
|
|
55
|
+
assert device.connected is True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_check_connected_clears_flag_on_connection_error(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
60
|
+
device = AlpacaDevice(**_ARGS)
|
|
61
|
+
device._connected = True
|
|
62
|
+
|
|
63
|
+
async def fake_get(name: str) -> Any:
|
|
64
|
+
raise ConnectionError
|
|
65
|
+
|
|
66
|
+
monkeypatch.setattr(device, "_get", fake_get)
|
|
67
|
+
await device._check_connected()
|
|
68
|
+
assert device.connected is False
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Smoke tests: import every public module and instantiate each driver class with a
|
|
2
|
+
missing/mocked hardware connection, asserting it loads and advertises the interfaces it
|
|
3
|
+
claims.
|
|
4
|
+
|
|
5
|
+
No real hardware or ASCOM server is involved: instantiation only registers background
|
|
6
|
+
tasks, which never start until open() is called.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pyobs.interfaces import (
|
|
10
|
+
IDome,
|
|
11
|
+
IFitsHeaderBefore,
|
|
12
|
+
IFocuser,
|
|
13
|
+
IOffsetsRaDec,
|
|
14
|
+
ISyncTarget,
|
|
15
|
+
ITelescope,
|
|
16
|
+
)
|
|
17
|
+
from pyobs.object import Object
|
|
18
|
+
|
|
19
|
+
from pyobs_alpaca import AlpacaDome, AlpacaFocuser, AlpacaTelescope
|
|
20
|
+
from pyobs_alpaca.device import AlpacaDevice
|
|
21
|
+
|
|
22
|
+
_TELESCOPE_ARGS = dict(server="localhost", port=11111, device_type="telescope", device=0)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_import_device_and_instantiate() -> None:
|
|
26
|
+
device = AlpacaDevice(**_TELESCOPE_ARGS)
|
|
27
|
+
assert isinstance(device, Object)
|
|
28
|
+
assert not device.connected
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_import_telescope_and_instantiate() -> None:
|
|
32
|
+
telescope = AlpacaTelescope(**_TELESCOPE_ARGS)
|
|
33
|
+
assert isinstance(telescope, ITelescope)
|
|
34
|
+
assert isinstance(telescope, IFitsHeaderBefore)
|
|
35
|
+
assert isinstance(telescope, IOffsetsRaDec)
|
|
36
|
+
assert isinstance(telescope, ISyncTarget)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_import_focuser_and_instantiate() -> None:
|
|
40
|
+
focuser = AlpacaFocuser(server="localhost", port=11111, device_type="focuser", device=0)
|
|
41
|
+
assert isinstance(focuser, IFocuser)
|
|
42
|
+
assert isinstance(focuser, IFitsHeaderBefore)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_import_dome_and_instantiate() -> None:
|
|
46
|
+
dome = AlpacaDome(server="localhost", port=11111, device_type="dome", device=0)
|
|
47
|
+
assert isinstance(dome, IDome)
|