arcsecond 3.20.0__tar.gz → 3.20.2__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 (117) hide show
  1. {arcsecond-3.20.0 → arcsecond-3.20.2}/PKG-INFO +1 -1
  2. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/alpaca/commands.py +74 -31
  3. arcsecond-3.20.2/arcsecond/alpaca/dome_probe.py +89 -0
  4. arcsecond-3.20.0/arcsecond/alpaca/dome_probe.py → arcsecond-3.20.2/arcsecond/alpaca/probe.py +194 -106
  5. arcsecond-3.20.2/arcsecond/alpaca/telescope_probe.py +180 -0
  6. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/api/endpoint.py +30 -1
  7. {arcsecond-3.20.0 → arcsecond-3.20.2}/pyproject.toml +1 -1
  8. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/api/test_api_endpoint.py +64 -6
  9. arcsecond-3.20.2/tests/test_alpaca_probe.py +883 -0
  10. {arcsecond-3.20.0 → arcsecond-3.20.2}/uv.lock +1 -1
  11. arcsecond-3.20.0/tests/test_alpaca_probe.py +0 -406
  12. {arcsecond-3.20.0 → arcsecond-3.20.2}/.docker/Dockerfile_postgres +0 -0
  13. {arcsecond-3.20.0 → arcsecond-3.20.2}/.docker/Dockerfile_redis +0 -0
  14. {arcsecond-3.20.0 → arcsecond-3.20.2}/.flake8 +0 -0
  15. {arcsecond-3.20.0 → arcsecond-3.20.2}/.github/dependabot.yml +0 -0
  16. {arcsecond-3.20.0 → arcsecond-3.20.2}/.github/workflows/pythonpublish.yml +0 -0
  17. {arcsecond-3.20.0 → arcsecond-3.20.2}/.github/workflows/tests.yml +0 -0
  18. {arcsecond-3.20.0 → arcsecond-3.20.2}/.gitignore +0 -0
  19. {arcsecond-3.20.0 → arcsecond-3.20.2}/LICENSE +0 -0
  20. {arcsecond-3.20.0 → arcsecond-3.20.2}/Makefile +0 -0
  21. {arcsecond-3.20.0 → arcsecond-3.20.2}/README.md +0 -0
  22. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/__init__.py +0 -0
  23. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/__version__.py +0 -0
  24. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/alpaca/__init__.py +0 -0
  25. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/api/__init__.py +0 -0
  26. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/api/config.py +0 -0
  27. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/api/constants.py +0 -0
  28. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/api/main.py +0 -0
  29. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/api/resources.py +0 -0
  30. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cli.py +0 -0
  31. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/__init__.py +0 -0
  32. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/auth.py +0 -0
  33. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/resources.py +0 -0
  34. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/__init__.py +0 -0
  35. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/constants.py +0 -0
  36. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/context.py +0 -0
  37. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/datafiles/__init__.py +0 -0
  38. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/datafiles/context.py +0 -0
  39. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/datafiles/errors.py +0 -0
  40. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/datafiles/uploader.py +0 -0
  41. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/datafiles/utils.py +0 -0
  42. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/errors.py +0 -0
  43. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/logger.py +0 -0
  44. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/uploader.py +0 -0
  45. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/utils.py +0 -0
  46. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploader/walker.py +0 -0
  47. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/cloud/uploads.py +0 -0
  48. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/errors.py +0 -0
  49. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/__init__.py +0 -0
  50. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/backups.py +0 -0
  51. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/checks.py +0 -0
  52. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/constants.py +0 -0
  53. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/database.py +0 -0
  54. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/docker/__init__.py +0 -0
  55. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/docker/constants.py +0 -0
  56. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/docker/containers.py +0 -0
  57. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/docker/docker-compose.yml +0 -0
  58. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/docker/images.py +0 -0
  59. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/docker/utils.py +0 -0
  60. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/keygen/__init__.py +0 -0
  61. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/keygen/client.py +0 -0
  62. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/keygen/utils.py +0 -0
  63. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/local.py +0 -0
  64. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/main.py +0 -0
  65. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/postgres/init-db.sh +0 -0
  66. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/setup.py +0 -0
  67. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/utils.py +0 -0
  68. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/hosting/validation.py +0 -0
  69. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/__init__.py +0 -0
  70. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/commands.py +0 -0
  71. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/detection.py +0 -0
  72. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/proxy.py +0 -0
  73. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/registry.py +0 -0
  74. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/runtime.py +0 -0
  75. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/sources/__init__.py +0 -0
  76. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/sources/base.py +0 -0
  77. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/sources/filewatch.py +0 -0
  78. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/sources/mdns.py +0 -0
  79. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/sources/network.py +0 -0
  80. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/sources/opencv.py +0 -0
  81. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/imagesources/store.py +0 -0
  82. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/options.py +0 -0
  83. {arcsecond-3.20.0 → arcsecond-3.20.2}/arcsecond/targets.py +0 -0
  84. {arcsecond-3.20.0 → arcsecond-3.20.2}/examples/example_upload_files.py +0 -0
  85. {arcsecond-3.20.0 → arcsecond-3.20.2}/requirements.txt +0 -0
  86. {arcsecond-3.20.0 → arcsecond-3.20.2}/setup.cfg +0 -0
  87. {arcsecond-3.20.0 → arcsecond-3.20.2}/setup.py +0 -0
  88. {arcsecond-3.20.0 → arcsecond-3.20.2}/sonar-project.properties +0 -0
  89. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/__init__.py +0 -0
  90. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/api/__init__.py +0 -0
  91. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/api/test_api.py +0 -0
  92. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/api/test_config.py +0 -0
  93. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/api/test_targets.py +0 -0
  94. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/cloud/__init__.py +0 -0
  95. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/cloud/uploader/__init__.py +0 -0
  96. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/cloud/uploader/datafiles/__init__.py +0 -0
  97. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/cloud/uploader/datafiles/test_uploader_errors.py +0 -0
  98. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/cloud/uploader/datafiles/test_uploader_full_process.py +0 -0
  99. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/cloud/uploader/datafiles/test_uploader_init.py +0 -0
  100. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/cloud/uploader/datafiles/test_uploader_prepare.py +0 -0
  101. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/cloud/uploader/datafiles/test_uploader_upload.py +0 -0
  102. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/conftest.py +0 -0
  103. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/fixtures/file1.fits +0 -0
  104. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_cli.py +0 -0
  105. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_hosting_backups_restore.py +0 -0
  106. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_hosting_database.py +0 -0
  107. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_hosting_local.py +0 -0
  108. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_hosting_utils_secret_key.py +0 -0
  109. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_imagesources_commands.py +0 -0
  110. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_imagesources_detection.py +0 -0
  111. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_imagesources_mdns.py +0 -0
  112. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_imagesources_network.py +0 -0
  113. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_imagesources_proxy.py +0 -0
  114. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_imagesources_store.py +0 -0
  115. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_imagesources_webcam.py +0 -0
  116. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/test_targets_planning.py +0 -0
  117. {arcsecond-3.20.0 → arcsecond-3.20.2}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arcsecond
3
- Version: 3.20.0
3
+ Version: 3.20.2
4
4
  Summary: CLI for arcsecond.io
5
5
  Project-URL: Homepage, https://github.com/arcsecond-io/cli
6
6
  Project-URL: Issues, https://github.com/arcsecond-io/cli/issues
@@ -1,14 +1,14 @@
1
1
  """
2
2
  Click command group: ``arcsecond alpaca``.
3
3
 
4
- First citizen: ``arcsecond alpaca probe dome`` — a read-only diagnostic that
5
- captures what surface a given Alpaca dome server exposes (device metadata,
6
- ``SupportedActions``, ``CommandString`` / ``CommandBool`` / ``CommandBlind``
7
- passthrough behaviour, and optional local host hints).
8
-
9
- Structure leaves room for ``arcsecond alpaca probe telescope`` /
10
- ``probe camera`` / ``probe focuser`` and ``arcsecond alpaca discover`` later
11
- under the same group.
4
+ ``arcsecond alpaca probe dome`` and ``arcsecond alpaca probe telescope`` are
5
+ read-only diagnostics that capture what surface a given Alpaca device server
6
+ exposes (device metadata, ``SupportedActions``, ``CommandString`` /
7
+ ``CommandBool`` / ``CommandBlind`` passthrough behaviour, and local host hints
8
+ when the target is this machine), one JSON report per run.
9
+
10
+ Structure leaves room for ``probe camera`` / ``probe focuser`` and
11
+ ``arcsecond alpaca discover`` later under the same group.
12
12
  """
13
13
 
14
14
  from __future__ import annotations
@@ -17,11 +17,14 @@ import json
17
17
  import os
18
18
  from datetime import datetime, timezone
19
19
  from pathlib import Path
20
+ from typing import Callable
20
21
 
21
22
  import click
22
23
 
23
24
  from ..errors import ArcsecondError
24
- from .dome_probe import ProbeProgress, probe_dome
25
+ from .dome_probe import probe_dome
26
+ from .probe import HOST_HINTS_REASON_LOCAL, ProbeProgress, ProbeResult
27
+ from .telescope_probe import probe_telescope
25
28
 
26
29
 
27
30
  @click.group(name="alpaca", help="Diagnostics for local ASCOM Alpaca devices.")
@@ -72,12 +75,15 @@ _COMMON_OPTIONS = [
72
75
  ),
73
76
  ),
74
77
  click.option(
75
- "--collect-host-info",
76
- is_flag=True,
77
- default=False,
78
+ "--collect-host-info/--no-host-info",
79
+ "collect_host_info",
80
+ default=None,
78
81
  help=(
79
- "Additionally collect best-effort local OS hints (open ports, "
80
- "COM ProgIDs matching TCS/Galil). Read-only."
82
+ "Best-effort local OS hints (open ports, COM ProgIDs matching "
83
+ "TCS/Galil), read-only. Collected automatically when HOST is this "
84
+ "machine, the only case in which they describe the Alpaca server's "
85
+ "host. --collect-host-info forces them for a remote HOST; "
86
+ "--no-host-info skips them."
81
87
  ),
82
88
  ),
83
89
  click.option(
@@ -104,37 +110,38 @@ def _default_output_path(kind: str) -> str:
104
110
  return str(Path.cwd() / f"alpaca_{kind}_probe_{stamp}.json")
105
111
 
106
112
 
113
+ def _describe_host_hints(report: dict) -> str:
114
+ hints = report.get("host_hints")
115
+ if hints is None:
116
+ return f"skipped ({report.get('host_hints_skipped_reason', 'not collected')})"
117
+ if hints.get("collected_because") == HOST_HINTS_REASON_LOCAL:
118
+ return "collected (HOST is this machine)"
119
+ return "collected (--collect-host-info)"
120
+
121
+
107
122
  def _format_progress(label: str, ok: bool, detail: str | None) -> str:
108
123
  tag = click.style("[OK ]", fg="green") if ok else click.style("[ERR]", fg="red")
109
124
  suffix = f" — {detail}" if detail else ""
110
125
  return f" {tag} {label}{suffix}"
111
126
 
112
127
 
113
- @probe_group.command(
114
- name="dome",
115
- help=(
116
- "Probe a local Alpaca dome device read-only and write a JSON report.\n\n"
117
- "Captures device metadata, SupportedActions, and the behaviour of the "
118
- "legacy CommandString / CommandBool / CommandBlind passthroughs. "
119
- "Useful for figuring out whether a proprietary driver (e.g. TCSGalil) "
120
- "exposes any vendor-specific extension surface beyond the standard "
121
- "ASCOM IDome interface."
122
- ),
123
- )
124
- @_add_options(_COMMON_OPTIONS)
125
- def probe_dome_cmd(
128
+ def _run_probe(
129
+ kind: str,
130
+ runner: Callable[..., ProbeResult],
131
+ *,
126
132
  host: str,
127
133
  port: int,
128
134
  device_number: int,
129
135
  protocol: str,
130
136
  allow_active: bool,
131
- collect_host_info: bool,
137
+ collect_host_info: bool | None,
132
138
  output_path: str | None,
133
139
  ) -> None:
134
- output_path = output_path or _default_output_path("dome")
140
+ """Drive one probe from the terminal: banner, progress lines, report, summary."""
141
+ output_path = output_path or _default_output_path(kind)
135
142
 
136
143
  click.echo(
137
- click.style("Alpaca dome probe", bold=True)
144
+ click.style(f"Alpaca {kind} probe", bold=True)
138
145
  + f" → {protocol}://{host}:{port} (device {device_number})"
139
146
  )
140
147
  if allow_active:
@@ -150,7 +157,7 @@ def probe_dome_cmd(
150
157
  )
151
158
 
152
159
  try:
153
- result = probe_dome(
160
+ result = runner(
154
161
  host=host,
155
162
  port=port,
156
163
  device_number=device_number,
@@ -177,4 +184,40 @@ def probe_dome_cmd(
177
184
  click.echo(click.style("Summary", bold=True))
178
185
  click.echo(f" Probes: {counts.ok}/{counts.total} OK")
179
186
  click.echo(f" SupportedActions: {supported_n} entries")
187
+ click.echo(f" Host hints: {_describe_host_hints(result.report)}")
180
188
  click.echo(f" Report written to {click.style(os.fspath(output_path), fg='cyan')}")
189
+
190
+
191
+ @probe_group.command(
192
+ name="dome",
193
+ help=(
194
+ "Probe a local Alpaca dome device read-only and write a JSON report.\n\n"
195
+ "Captures device metadata, SupportedActions, and the behaviour of the "
196
+ "legacy CommandString / CommandBool / CommandBlind passthroughs. "
197
+ "Useful for figuring out whether a proprietary driver (e.g. TCSGalil) "
198
+ "exposes any vendor-specific extension surface beyond the standard "
199
+ "ASCOM IDome interface. Local host hints (open ports, COM ProgIDs) "
200
+ "are added when HOST is this machine."
201
+ ),
202
+ )
203
+ @_add_options(_COMMON_OPTIONS)
204
+ def probe_dome_cmd(**options) -> None:
205
+ _run_probe("dome", probe_dome, **options)
206
+
207
+
208
+ @probe_group.command(
209
+ name="telescope",
210
+ help=(
211
+ "Probe a local Alpaca telescope (mount) device read-only and write a "
212
+ "JSON report.\n\n"
213
+ "Captures device metadata, optics, site, pointing and tracking state, "
214
+ "capabilities, CanMoveAxis and AxisRates per movable axis, "
215
+ "SupportedActions, and the behaviour of the legacy CommandString / "
216
+ "CommandBool / CommandBlind passthroughs. Never moves the mount. "
217
+ "Local host hints (open ports, COM ProgIDs) are added when HOST is "
218
+ "this machine."
219
+ ),
220
+ )
221
+ @_add_options(_COMMON_OPTIONS)
222
+ def probe_telescope_cmd(**options) -> None:
223
+ _run_probe("telescope", probe_telescope, **options)
@@ -0,0 +1,89 @@
1
+ """
2
+ Read-only diagnostic for an ASCOM Alpaca *dome* device.
3
+
4
+ The standard ASCOM Alpaca ``IDome`` interface only exposes a single
5
+ ``OpenShutter`` / ``CloseShutter`` pair. Multi-shutter domes (e.g. DFM domes
6
+ driven by TCSGalil) coordinate their shutters internally; if the driver does
7
+ not advertise per-shutter custom actions via ``SupportedActions``, there is
8
+ no canonical client-side way to drive the upper and lower shutters
9
+ independently. This probe writes down exactly what surface a dome server
10
+ exposes, so that decision can be made on evidence.
11
+
12
+ The engine, the report layout and the safety rules live in :mod:`.probe`;
13
+ this module only says which dome properties to read.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import Any, Callable, Iterable
19
+
20
+ from .probe import ProbeProgress, ProbeResult, probe_device
21
+ from .probe import iter_probe_labels as _iter_probe_labels
22
+
23
+ # Read-only device descriptors and dome state, in the order we want them
24
+ # presented in the report. Names are PascalCase to match the alpyca surface.
25
+ _READ_ONLY_PROPERTIES: tuple[str, ...] = (
26
+ "Name",
27
+ "Description",
28
+ "DriverInfo",
29
+ "DriverVersion",
30
+ "InterfaceVersion",
31
+ "ShutterStatus",
32
+ "Altitude",
33
+ "Azimuth",
34
+ "AtHome",
35
+ "AtPark",
36
+ "Slewing",
37
+ "Slaved",
38
+ "CanFindHome",
39
+ "CanPark",
40
+ "CanSetAltitude",
41
+ "CanSetPark",
42
+ "CanSetShutter",
43
+ "CanSlave",
44
+ "CanSyncAzimuth",
45
+ )
46
+
47
+
48
+ def probe_dome(
49
+ host: str,
50
+ port: int,
51
+ device_number: int = 0,
52
+ *,
53
+ protocol: str = "http",
54
+ allow_active: bool = False,
55
+ collect_host_info: bool | None = None,
56
+ dome_factory: Callable[[str, int, str], Any] | None = None,
57
+ progress: ProbeProgress | None = None,
58
+ ) -> ProbeResult:
59
+ """
60
+ Build a JSON-serialisable diagnostic report for an Alpaca dome at
61
+ ``protocol://host:port`` (device_number). See :func:`.probe.probe_device`
62
+ for the ``collect_host_info`` semantics.
63
+
64
+ ``dome_factory(address, device_number, protocol)`` is only there for
65
+ tests — production callers leave it ``None`` and the real
66
+ ``alpaca.dome.Dome`` is used.
67
+ """
68
+ if dome_factory is None:
69
+ from alpaca.dome import Dome # local import: keep cost off cold paths
70
+
71
+ dome_factory = Dome
72
+
73
+ return probe_device(
74
+ "dome",
75
+ host,
76
+ port,
77
+ device_number,
78
+ properties=_READ_ONLY_PROPERTIES,
79
+ device_factory=dome_factory,
80
+ protocol=protocol,
81
+ allow_active=allow_active,
82
+ collect_host_info=collect_host_info,
83
+ progress=progress,
84
+ )
85
+
86
+
87
+ def iter_probe_labels() -> Iterable[str]:
88
+ """Convenience for tests / docs: enumerate the labels emitted by a run."""
89
+ return _iter_probe_labels(_READ_ONLY_PROPERTIES)