span-panel-api 2.3.0__tar.gz → 2.3.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.
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/PKG-INFO +1 -1
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/pyproject.toml +1 -1
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/__init__.py +13 -2
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/auth.py +117 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/mqtt/connection.py +6 -1
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/LICENSE +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/README.md +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/const.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/detection.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/exceptions.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/factory.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/models.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/mqtt/__init__.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/mqtt/async_client.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/mqtt/client.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/mqtt/const.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/mqtt/field_metadata.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/mqtt/homie.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/mqtt/models.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/phase_validation.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/protocol.py +0 -0
- {span_panel_api-2.3.0 → span_panel_api-2.3.2}/src/span_panel_api/py.typed +0 -0
|
@@ -4,7 +4,15 @@ A modern, type-safe Python client library for the SPAN Panel API,
|
|
|
4
4
|
supporting MQTT/Homie (v2) transport.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from .auth import
|
|
7
|
+
from .auth import (
|
|
8
|
+
delete_fqdn,
|
|
9
|
+
download_ca_cert,
|
|
10
|
+
get_fqdn,
|
|
11
|
+
get_homie_schema,
|
|
12
|
+
regenerate_passphrase,
|
|
13
|
+
register_fqdn,
|
|
14
|
+
register_v2,
|
|
15
|
+
)
|
|
8
16
|
from .detection import DetectionResult, detect_api_version
|
|
9
17
|
from .exceptions import (
|
|
10
18
|
SpanPanelAPIError,
|
|
@@ -44,7 +52,7 @@ from .protocol import (
|
|
|
44
52
|
StreamingCapableProtocol,
|
|
45
53
|
)
|
|
46
54
|
|
|
47
|
-
__version__ = "2.3.
|
|
55
|
+
__version__ = "2.3.2"
|
|
48
56
|
# fmt: off
|
|
49
57
|
__all__ = [ # noqa: RUF022
|
|
50
58
|
# Protocols
|
|
@@ -70,8 +78,11 @@ __all__ = [ # noqa: RUF022
|
|
|
70
78
|
"V2AuthResponse",
|
|
71
79
|
"V2HomieSchema",
|
|
72
80
|
"V2StatusInfo",
|
|
81
|
+
"delete_fqdn",
|
|
73
82
|
"download_ca_cert",
|
|
83
|
+
"get_fqdn",
|
|
74
84
|
"get_homie_schema",
|
|
85
|
+
"register_fqdn",
|
|
75
86
|
"regenerate_passphrase",
|
|
76
87
|
"register_v2",
|
|
77
88
|
# Transport
|
|
@@ -241,6 +241,123 @@ async def regenerate_passphrase(host: str, token: str, timeout: float = 10.0, po
|
|
|
241
241
|
return _str(data["ebusBrokerPassword"])
|
|
242
242
|
|
|
243
243
|
|
|
244
|
+
async def register_fqdn(host: str, token: str, fqdn: str, timeout: float = 10.0, port: int = 80) -> None:
|
|
245
|
+
"""Register an FQDN with the SPAN Panel for TLS certificate SAN inclusion.
|
|
246
|
+
|
|
247
|
+
The panel regenerates its TLS server certificate to include the
|
|
248
|
+
provided FQDN in the Subject Alternative Names, allowing MQTTS
|
|
249
|
+
clients connecting via the FQDN to pass hostname verification.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
host: IP address or hostname of the SPAN Panel
|
|
253
|
+
token: Valid JWT access token from register_v2
|
|
254
|
+
fqdn: Fully qualified domain name to register
|
|
255
|
+
timeout: Request timeout in seconds
|
|
256
|
+
port: HTTP port of the panel bootstrap API
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
SpanPanelAuthError: Token invalid or expired
|
|
260
|
+
SpanPanelConnectionError: Cannot reach panel
|
|
261
|
+
SpanPanelTimeoutError: Request timed out
|
|
262
|
+
SpanPanelAPIError: Unexpected response (including 404 if unsupported)
|
|
263
|
+
"""
|
|
264
|
+
url = _build_url(host, port, "/api/v2/dns/fqdn")
|
|
265
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
266
|
+
payload = {"ebusTlsFqdn": fqdn}
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
async with httpx.AsyncClient(timeout=timeout, verify=False) as client: # nosec B501
|
|
270
|
+
response = await client.post(url, json=payload, headers=headers)
|
|
271
|
+
except httpx.ConnectError as exc:
|
|
272
|
+
raise SpanPanelConnectionError(f"Cannot reach panel at {host}") from exc
|
|
273
|
+
except httpx.TimeoutException as exc:
|
|
274
|
+
raise SpanPanelTimeoutError(f"Timed out connecting to {host}") from exc
|
|
275
|
+
|
|
276
|
+
if response.status_code in (401, 403):
|
|
277
|
+
raise SpanPanelAuthError(f"Authentication failed (HTTP {response.status_code})")
|
|
278
|
+
|
|
279
|
+
if response.status_code not in (200, 201, 204):
|
|
280
|
+
raise SpanPanelAPIError(f"Failed to register FQDN: HTTP {response.status_code}")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
async def get_fqdn(host: str, token: str, timeout: float = 10.0, port: int = 80) -> str:
|
|
284
|
+
"""Retrieve the currently registered FQDN from the SPAN Panel.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
host: IP address or hostname of the SPAN Panel
|
|
288
|
+
token: Valid JWT access token from register_v2
|
|
289
|
+
timeout: Request timeout in seconds
|
|
290
|
+
port: HTTP port of the panel bootstrap API
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
The registered FQDN, or empty string if none is configured
|
|
294
|
+
|
|
295
|
+
Raises:
|
|
296
|
+
SpanPanelAuthError: Token invalid or expired
|
|
297
|
+
SpanPanelConnectionError: Cannot reach panel
|
|
298
|
+
SpanPanelTimeoutError: Request timed out
|
|
299
|
+
SpanPanelAPIError: Unexpected response
|
|
300
|
+
"""
|
|
301
|
+
url = _build_url(host, port, "/api/v2/dns/fqdn")
|
|
302
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
async with httpx.AsyncClient(timeout=timeout, verify=False) as client: # nosec B501
|
|
306
|
+
response = await client.get(url, headers=headers)
|
|
307
|
+
except httpx.ConnectError as exc:
|
|
308
|
+
raise SpanPanelConnectionError(f"Cannot reach panel at {host}") from exc
|
|
309
|
+
except httpx.TimeoutException as exc:
|
|
310
|
+
raise SpanPanelTimeoutError(f"Timed out connecting to {host}") from exc
|
|
311
|
+
|
|
312
|
+
if response.status_code in (401, 403):
|
|
313
|
+
raise SpanPanelAuthError(f"Authentication failed (HTTP {response.status_code})")
|
|
314
|
+
|
|
315
|
+
if response.status_code == 404:
|
|
316
|
+
return ""
|
|
317
|
+
|
|
318
|
+
if response.status_code != 200:
|
|
319
|
+
raise SpanPanelAPIError(f"Failed to get FQDN: HTTP {response.status_code}")
|
|
320
|
+
|
|
321
|
+
data: dict[str, object] = response.json()
|
|
322
|
+
return _str(data.get("ebusTlsFqdn"))
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
async def delete_fqdn(host: str, token: str, timeout: float = 10.0, port: int = 80) -> None:
|
|
326
|
+
"""Remove the registered FQDN from the SPAN Panel.
|
|
327
|
+
|
|
328
|
+
The panel regenerates its TLS certificate without the FQDN in
|
|
329
|
+
the SAN list.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
host: IP address or hostname of the SPAN Panel
|
|
333
|
+
token: Valid JWT access token from register_v2
|
|
334
|
+
timeout: Request timeout in seconds
|
|
335
|
+
port: HTTP port of the panel bootstrap API
|
|
336
|
+
|
|
337
|
+
Raises:
|
|
338
|
+
SpanPanelAuthError: Token invalid or expired
|
|
339
|
+
SpanPanelConnectionError: Cannot reach panel
|
|
340
|
+
SpanPanelTimeoutError: Request timed out
|
|
341
|
+
SpanPanelAPIError: Unexpected response
|
|
342
|
+
"""
|
|
343
|
+
url = _build_url(host, port, "/api/v2/dns/fqdn")
|
|
344
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
async with httpx.AsyncClient(timeout=timeout, verify=False) as client: # nosec B501
|
|
348
|
+
response = await client.delete(url, headers=headers)
|
|
349
|
+
except httpx.ConnectError as exc:
|
|
350
|
+
raise SpanPanelConnectionError(f"Cannot reach panel at {host}") from exc
|
|
351
|
+
except httpx.TimeoutException as exc:
|
|
352
|
+
raise SpanPanelTimeoutError(f"Timed out connecting to {host}") from exc
|
|
353
|
+
|
|
354
|
+
if response.status_code in (401, 403):
|
|
355
|
+
raise SpanPanelAuthError(f"Authentication failed (HTTP {response.status_code})")
|
|
356
|
+
|
|
357
|
+
if response.status_code not in (200, 204):
|
|
358
|
+
raise SpanPanelAPIError(f"Failed to delete FQDN: HTTP {response.status_code}")
|
|
359
|
+
|
|
360
|
+
|
|
244
361
|
async def get_v2_status(host: str, timeout: float = 5.0, port: int = 80) -> V2StatusInfo:
|
|
245
362
|
"""Lightweight v2 status probe (unauthenticated).
|
|
246
363
|
|
|
@@ -175,7 +175,12 @@ class AsyncMqttBridge:
|
|
|
175
175
|
self._client.on_socket_open = self._on_socket_open_sync
|
|
176
176
|
self._client.on_socket_register_write = self._on_socket_register_write_sync
|
|
177
177
|
_LOGGER.debug("BRIDGE: Running TLS+connect in executor to %s:%s", self._host, self._port)
|
|
178
|
-
|
|
178
|
+
try:
|
|
179
|
+
await self._loop.run_in_executor(None, _blocking_tls_and_connect)
|
|
180
|
+
except OSError as exc:
|
|
181
|
+
raise SpanPanelConnectionError(
|
|
182
|
+
f"Cannot connect to MQTT broker at {self._host}:{self._port}: {exc}"
|
|
183
|
+
) from exc
|
|
179
184
|
_LOGGER.debug("BRIDGE: Executor connect returned, waiting for CONNACK...")
|
|
180
185
|
finally:
|
|
181
186
|
# Switch to async-only socket callbacks now that we are
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|