python-swidget 1.4.0__tar.gz → 1.4.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.
@@ -1,7 +1,8 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: python-swidget
3
- Version: 1.4.0
3
+ Version: 1.4.2
4
4
  Summary: Python API for Swidget smart devices
5
+ Home-page: https://github.com/swidget/python-swidget
5
6
  License: GPL-3.0-or-later
6
7
  Author: Swidget
7
8
  Requires-Python: >=3.8,<4.0
@@ -12,8 +13,6 @@ Classifier: Programming Language :: Python :: 3.9
12
13
  Classifier: Programming Language :: Python :: 3.10
13
14
  Classifier: Programming Language :: Python :: 3.11
14
15
  Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Programming Language :: Python :: 3.14
17
16
  Provides-Extra: docs
18
17
  Requires-Dist: aiohttp (>=3.8.1)
19
18
  Requires-Dist: anyio
@@ -22,13 +21,13 @@ Requires-Dist: importlib-metadata
22
21
  Requires-Dist: m2r (>=0,<1) ; extra == "docs"
23
22
  Requires-Dist: mistune (<2.0.0) ; extra == "docs"
24
23
  Requires-Dist: pydantic (>=2,<3)
25
- Requires-Dist: requests (==2.32.5)
24
+ Requires-Dist: requests (>=2.32,<3.0)
26
25
  Requires-Dist: sphinx (>=4,<5) ; extra == "docs"
27
26
  Requires-Dist: sphinx_rtd_theme (>=0,<1) ; extra == "docs"
28
27
  Requires-Dist: sphinxcontrib-programoutput (>=0,<1) ; extra == "docs"
29
28
  Requires-Dist: ssdp (==1.1.1)
30
29
  Requires-Dist: types-requests (>=2.32.0,<3.0.0)
31
- Requires-Dist: urllib3 (==2.5.0)
30
+ Requires-Dist: urllib3 (>=2.0,<3.0)
32
31
  Project-URL: Repository, https://github.com/swidget/python-swidget
33
32
  Description-Content-Type: text/markdown
34
33
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-swidget"
3
- version = "1.4.0"
3
+ version = "1.4.2"
4
4
  description = "Python API for Swidget smart devices"
5
5
  license = "GPL-3.0-or-later"
6
6
  authors = ["Swidget"]
@@ -22,9 +22,9 @@ importlib-metadata = "*"
22
22
  asyncclick = ">=8"
23
23
  pydantic = "^2"
24
24
  ssdp = "1.1.1"
25
- requests = "2.32.5"
25
+ requests = "^2.32"
26
26
  types-requests = "^2.32.0"
27
- urllib3 = '2.5.0'
27
+ urllib3 = "^2.0"
28
28
 
29
29
  # required only for docs
30
30
  sphinx = { version = "^4", optional = true }
@@ -14,7 +14,12 @@ to be handled by the user of the library.
14
14
 
15
15
  from importlib_metadata import version
16
16
 
17
- from swidget.discovery import SwidgetDiscoveredDevice, discover_devices, discover_single
17
+ from swidget.discovery import (
18
+ SwidgetDiscoveredDevice,
19
+ detect_secure,
20
+ discover_devices,
21
+ discover_single,
22
+ )
18
23
  from swidget.exceptions import SwidgetException
19
24
  from swidget.provision import provision_wifi
20
25
  from swidget.swidgetdevice import (
@@ -6,6 +6,7 @@ from typing import Any, Type
6
6
  from urllib.parse import urlparse
7
7
 
8
8
  import ssdp
9
+ from aiohttp import ClientError, ClientSession, ClientTimeout, TCPConnector
9
10
 
10
11
  from swidget.swidgetdevice import DeviceType, SwidgetDevice
11
12
 
@@ -17,10 +18,50 @@ from .swidgettimerswitch import SwidgetTimerSwitch
17
18
 
18
19
  RESPONSE_SEC = 5
19
20
  SWIDGET_ST = "urn:swidget:pico:1"
21
+ # Generous timeout: TLS handshakes on the device's MCU can take several
22
+ # seconds on first connection.
23
+ DETECT_TIMEOUT_SEC = 10
20
24
  _LOGGER = logging.getLogger(__name__)
21
25
  devices = dict()
22
26
 
23
27
 
28
+ async def detect_secure(host: str, timeout: float = DETECT_TIMEOUT_SEC) -> bool:
29
+ """Determine whether a Swidget device requires HTTPS+auth.
30
+
31
+ Probes ``/api/v1/summary`` over HTTPS first (no credentials). A 403
32
+ response means the firmware enforces auth — the device is in secure
33
+ mode. If HTTPS is unreachable, falls back to HTTP; a 200 there means
34
+ the device is in plaintext mode.
35
+
36
+ :param host: Hostname or IP of the device.
37
+ :param timeout: Per-request timeout in seconds. Defaults are generous
38
+ because the device performs TLS termination on a low-power MCU
39
+ and the first handshake can be several seconds.
40
+ :return: True if the device requires HTTPS+auth, False if plaintext.
41
+ :raises SwidgetException: If neither protocol returns a usable response.
42
+ """
43
+ timeout_obj = ClientTimeout(total=timeout)
44
+ connector = TCPConnector(ssl=False, force_close=True)
45
+ async with ClientSession(connector=connector, timeout=timeout_obj) as session:
46
+ try:
47
+ async with session.get(f"https://{host}/api/v1/summary") as resp:
48
+ # 403 = "auth required" → secure firmware.
49
+ # 200 without creds is unusual but still indicates HTTPS works.
50
+ if resp.status in (200, 403):
51
+ return True
52
+ except (ClientError, asyncio.TimeoutError):
53
+ pass
54
+
55
+ try:
56
+ async with session.get(f"http://{host}/api/v1/summary") as resp:
57
+ if resp.status == 200:
58
+ return False
59
+ except (ClientError, asyncio.TimeoutError):
60
+ pass
61
+
62
+ raise SwidgetException(f"Could not reach Swidget device at {host}")
63
+
64
+
24
65
  class SwidgetDiscoveredDevice:
25
66
  """Stub class to capture details about discovered devices."""
26
67
 
File without changes