Skip to content

Commit dc914c8

Browse files
authored
Improve code quality, consistency, and developer experience (#2025)
## Summary - Make `event_listener_id` a read-only property - Make `SUPPORTED_SERVERS` immutable via `MappingProxyType` - Make `obfuscate_sensitive_data` return a new dict instead of mutating - Type `Execution.state` as `ExecutionState` instead of `str` - Return `Definition` model from `get_device_definition` instead of raw dict - Rename `deviceurl` parameter to `device_url` for consistency - Use `None` instead of empty string defaults in `Location` model - Raise `OverkizError` instead of `ValueError` for API data issues - Add `server` parameter to `create_local_server_config` ## Test plan - [ ] All existing tests pass (`pytest`) - [ ] Type checking passes (`mypy`, `ty`) - [ ] Linting passes (`ruff`)
1 parent f6427e5 commit dc914c8

7 files changed

Lines changed: 212 additions & 184 deletions

File tree

pyoverkiz/client.py

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from pyoverkiz.models import (
4040
Action,
4141
Command,
42+
Definition,
4243
Device,
4344
Event,
4445
Execution,
@@ -178,13 +179,18 @@ class OverkizClient:
178179
setup: Setup | None
179180
devices: list[Device]
180181
gateways: list[Gateway]
181-
event_listener_id: str | None
182182
session: ClientSession
183183
_ssl: ssl.SSLContext | bool = True
184184
_auth: AuthStrategy
185185
_action_queue: ActionQueue | None = None
186+
_event_listener_id: str | None
186187
settings: OverkizClientSettings
187188

189+
@property
190+
def event_listener_id(self) -> str | None:
191+
"""Return the current event listener ID (read-only)."""
192+
return self._event_listener_id
193+
188194
def __init__(
189195
self,
190196
*,
@@ -207,7 +213,7 @@ def __init__(
207213
self.setup: Setup | None = None
208214
self.devices: list[Device] = []
209215
self.gateways: list[Gateway] = []
210-
self.event_listener_id: str | None = None
216+
self._event_listener_id: str | None = None
211217

212218
self.session = session or ClientSession(headers={"User-Agent": USER_AGENT})
213219
self._ssl = verify_ssl
@@ -407,19 +413,23 @@ async def get_execution_history(self) -> list[HistoryExecution]:
407413
return structure_response(response, list[HistoryExecution])
408414

409415
@retry_on_auth_error
410-
async def get_device_definition(self, deviceurl: str) -> dict[str, Any] | None:
416+
async def get_device_definition(self, device_url: str) -> Definition | None:
411417
"""Retrieve a particular setup device definition."""
412418
response: dict = await self._get(
413-
f"setup/devices/{urllib.parse.quote_plus(deviceurl)}"
419+
f"setup/devices/{urllib.parse.quote_plus(device_url)}"
414420
)
415421

416-
return response.get("definition")
422+
raw = response.get("definition")
423+
if raw is None:
424+
return None
425+
426+
return structure_response(raw, Definition)
417427

418428
@retry_on_auth_error
419-
async def get_state(self, deviceurl: str) -> list[State]:
429+
async def get_state(self, device_url: str) -> list[State]:
420430
"""Retrieve states of requested device."""
421431
response = await self._get(
422-
f"setup/devices/{urllib.parse.quote_plus(deviceurl)}/states"
432+
f"setup/devices/{urllib.parse.quote_plus(device_url)}/states"
423433
)
424434
return structure_response(response, list[State])
425435

@@ -429,10 +439,10 @@ async def refresh_states(self) -> None:
429439
await self._post("setup/devices/states/refresh")
430440

431441
@retry_on_auth_error
432-
async def refresh_device_states(self, deviceurl: str) -> None:
442+
async def refresh_device_states(self, device_url: str) -> None:
433443
"""Ask the box to refresh all states of the given device for protocols supporting that operation."""
434444
await self._post(
435-
f"setup/devices/{urllib.parse.quote_plus(deviceurl)}/states/refresh"
445+
f"setup/devices/{urllib.parse.quote_plus(device_url)}/states/refresh"
436446
)
437447

438448
@retry_on_concurrent_requests
@@ -448,7 +458,7 @@ async def register_event_listener(self) -> str:
448458
"""
449459
response = await self._post("events/register")
450460
listener_id = cast(str, response.get("id"))
451-
self.event_listener_id = listener_id
461+
self._event_listener_id = listener_id
452462

453463
return listener_id
454464

@@ -471,8 +481,8 @@ async def unregister_event_listener(self) -> None:
471481
472482
API response status is always 200, even on unknown listener ids.
473483
"""
474-
await self._post(f"events/{self.event_listener_id}/unregister")
475-
self.event_listener_id = None
484+
await self._post(f"events/{self._event_listener_id}/unregister")
485+
self._event_listener_id = None
476486

477487
@retry_on_auth_error
478488
async def get_current_execution(self, exec_id: str) -> Execution | None:
@@ -780,38 +790,40 @@ async def get_devices_not_up_to_date(self) -> list[Device]:
780790
return structure_response(response, list[Device])
781791

782792
@retry_on_auth_error
783-
async def get_device_firmware_status(self, deviceurl: str) -> FirmwareStatus | None:
793+
async def get_device_firmware_status(
794+
self, device_url: str
795+
) -> FirmwareStatus | None:
784796
"""Check if a device's firmware is up to date.
785797
786798
Returns None if the device does not support firmware status checks.
787799
"""
788800
try:
789801
response = await self._get(
790-
f"setup/devices/{urllib.parse.quote_plus(deviceurl)}/firmwareUpToDate"
802+
f"setup/devices/{urllib.parse.quote_plus(device_url)}/firmwareUpToDate"
791803
)
792804
except UnsupportedOperationError:
793805
return None
794806
return structure_response(response, FirmwareStatus)
795807

796808
@retry_on_auth_error
797-
async def get_device_firmware_update_capability(self, deviceurl: str) -> bool:
809+
async def get_device_firmware_update_capability(self, device_url: str) -> bool:
798810
"""Check if a device supports firmware updates.
799811
800812
Returns False if the device does not support this query.
801813
"""
802814
try:
803815
response = await self._get(
804-
f"setup/devices/{urllib.parse.quote_plus(deviceurl)}/firmwareUpdateCapability"
816+
f"setup/devices/{urllib.parse.quote_plus(device_url)}/firmwareUpdateCapability"
805817
)
806818
except UnsupportedOperationError:
807819
return False
808820
return cast(bool, response["supportsFirmwareUpdate"])
809821

810822
@retry_on_auth_error
811-
async def update_device_firmware(self, deviceurl: str) -> None:
823+
async def update_device_firmware(self, device_url: str) -> None:
812824
"""Update a device's firmware to the next available version."""
813825
await self._put(
814-
f"setup/devices/{urllib.parse.quote_plus(deviceurl)}/updateFirmware"
826+
f"setup/devices/{urllib.parse.quote_plus(device_url)}/updateFirmware"
815827
)
816828

817829
@retry_on_auth_error

pyoverkiz/const.py

Lines changed: 117 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
from importlib.metadata import version
6+
from types import MappingProxyType
67

78
from pyoverkiz.enums import Server
89
from pyoverkiz.enums.server import APIType
@@ -45,117 +46,119 @@
4546
Server.SOMFY_AMERICA,
4647
]
4748

48-
SUPPORTED_SERVERS: dict[str, ServerConfig] = {
49-
Server.ATLANTIC_COZYTOUCH: ServerConfig(
50-
server=Server.ATLANTIC_COZYTOUCH,
51-
name="Atlantic Cozytouch",
52-
endpoint="https://ha110-1.overkiz.com/enduser-mobile-web/enduserAPI/",
53-
manufacturer="Atlantic",
54-
api_type=APIType.CLOUD,
55-
),
56-
Server.BRANDT: ServerConfig(
57-
server=Server.BRANDT,
58-
name="Brandt Smart Control",
59-
endpoint="https://ha3-1.overkiz.com/enduser-mobile-web/enduserAPI/",
60-
manufacturer="Brandt",
61-
api_type=APIType.CLOUD,
62-
),
63-
Server.FLEXOM: ServerConfig(
64-
server=Server.FLEXOM,
65-
name="Flexom",
66-
endpoint="https://ha108-1.overkiz.com/enduser-mobile-web/enduserAPI/",
67-
manufacturer="Bouygues",
68-
api_type=APIType.CLOUD,
69-
),
70-
Server.HEXAOM_HEXACONNECT: ServerConfig(
71-
server=Server.HEXAOM_HEXACONNECT,
72-
name="Hexaom HexaConnect",
73-
endpoint="https://ha5-1.overkiz.com/enduser-mobile-web/enduserAPI/",
74-
manufacturer="Hexaom",
75-
api_type=APIType.CLOUD,
76-
),
77-
Server.HI_KUMO_ASIA: ServerConfig(
78-
server=Server.HI_KUMO_ASIA,
79-
name="Hitachi Hi Kumo (Asia)",
80-
endpoint="https://ha203-1.overkiz.com/enduser-mobile-web/enduserAPI/",
81-
manufacturer="Hitachi",
82-
api_type=APIType.CLOUD,
83-
),
84-
Server.HI_KUMO_EUROPE: ServerConfig(
85-
server=Server.HI_KUMO_EUROPE,
86-
name="Hitachi Hi Kumo (Europe)",
87-
endpoint="https://ha117-1.overkiz.com/enduser-mobile-web/enduserAPI/",
88-
manufacturer="Hitachi",
89-
api_type=APIType.CLOUD,
90-
),
91-
Server.HI_KUMO_OCEANIA: ServerConfig(
92-
server=Server.HI_KUMO_OCEANIA,
93-
name="Hitachi Hi Kumo (Oceania)",
94-
endpoint="https://ha203-1.overkiz.com/enduser-mobile-web/enduserAPI/",
95-
manufacturer="Hitachi",
96-
api_type=APIType.CLOUD,
97-
),
98-
Server.NEXITY: ServerConfig(
99-
server=Server.NEXITY,
100-
name="Nexity Eugénie",
101-
endpoint="https://ha106-1.overkiz.com/enduser-mobile-web/enduserAPI/",
102-
manufacturer="Nexity",
103-
api_type=APIType.CLOUD,
104-
),
105-
Server.REXEL: ServerConfig(
106-
server=Server.REXEL,
107-
name="Rexel Energeasy Connect",
108-
endpoint=REXEL_BACKEND_API,
109-
manufacturer="Rexel",
110-
api_type=APIType.CLOUD,
111-
),
112-
Server.SAUTER_COZYTOUCH: ServerConfig( # duplicate of Atlantic Cozytouch
113-
server=Server.SAUTER_COZYTOUCH,
114-
name="Sauter Cozytouch",
115-
endpoint="https://ha110-1.overkiz.com/enduser-mobile-web/enduserAPI/",
116-
manufacturer="Sauter",
117-
api_type=APIType.CLOUD,
118-
),
119-
Server.SIMU_LIVEIN2: ServerConfig( # alias of https://tahomalink.com
120-
server=Server.SIMU_LIVEIN2,
121-
name="SIMU (LiveIn2)",
122-
endpoint="https://ha101-1.overkiz.com/enduser-mobile-web/enduserAPI/",
123-
manufacturer="Somfy",
124-
api_type=APIType.CLOUD,
125-
),
126-
Server.SOMFY_EUROPE: ServerConfig( # alias of https://tahomalink.com
127-
server=Server.SOMFY_EUROPE,
128-
name="Somfy (Europe)",
129-
endpoint="https://ha101-1.overkiz.com/enduser-mobile-web/enduserAPI/",
130-
manufacturer="Somfy",
131-
api_type=APIType.CLOUD,
132-
),
133-
Server.SOMFY_AMERICA: ServerConfig(
134-
server=Server.SOMFY_AMERICA,
135-
name="Somfy (North America)",
136-
endpoint="https://ha401-1.overkiz.com/enduser-mobile-web/enduserAPI/",
137-
manufacturer="Somfy",
138-
api_type=APIType.CLOUD,
139-
),
140-
Server.SOMFY_OCEANIA: ServerConfig(
141-
server=Server.SOMFY_OCEANIA,
142-
name="Somfy (Oceania)",
143-
endpoint="https://ha201-1.overkiz.com/enduser-mobile-web/enduserAPI/",
144-
manufacturer="Somfy",
145-
api_type=APIType.CLOUD,
146-
),
147-
Server.THERMOR_COZYTOUCH: ServerConfig( # duplicate of Atlantic Cozytouch
148-
server=Server.THERMOR_COZYTOUCH,
149-
name="Thermor Cozytouch",
150-
endpoint="https://ha110-1.overkiz.com/enduser-mobile-web/enduserAPI/",
151-
manufacturer="Thermor",
152-
api_type=APIType.CLOUD,
153-
),
154-
Server.UBIWIZZ: ServerConfig(
155-
server=Server.UBIWIZZ,
156-
name="Ubiwizz",
157-
endpoint="https://ha129-1.overkiz.com/enduser-mobile-web/enduserAPI/",
158-
manufacturer="Decelect",
159-
api_type=APIType.CLOUD,
160-
),
161-
}
49+
SUPPORTED_SERVERS: MappingProxyType[str, ServerConfig] = MappingProxyType(
50+
{
51+
Server.ATLANTIC_COZYTOUCH: ServerConfig(
52+
server=Server.ATLANTIC_COZYTOUCH,
53+
name="Atlantic Cozytouch",
54+
endpoint="https://ha110-1.overkiz.com/enduser-mobile-web/enduserAPI/",
55+
manufacturer="Atlantic",
56+
api_type=APIType.CLOUD,
57+
),
58+
Server.BRANDT: ServerConfig(
59+
server=Server.BRANDT,
60+
name="Brandt Smart Control",
61+
endpoint="https://ha3-1.overkiz.com/enduser-mobile-web/enduserAPI/",
62+
manufacturer="Brandt",
63+
api_type=APIType.CLOUD,
64+
),
65+
Server.FLEXOM: ServerConfig(
66+
server=Server.FLEXOM,
67+
name="Flexom",
68+
endpoint="https://ha108-1.overkiz.com/enduser-mobile-web/enduserAPI/",
69+
manufacturer="Bouygues",
70+
api_type=APIType.CLOUD,
71+
),
72+
Server.HEXAOM_HEXACONNECT: ServerConfig(
73+
server=Server.HEXAOM_HEXACONNECT,
74+
name="Hexaom HexaConnect",
75+
endpoint="https://ha5-1.overkiz.com/enduser-mobile-web/enduserAPI/",
76+
manufacturer="Hexaom",
77+
api_type=APIType.CLOUD,
78+
),
79+
Server.HI_KUMO_ASIA: ServerConfig(
80+
server=Server.HI_KUMO_ASIA,
81+
name="Hitachi Hi Kumo (Asia)",
82+
endpoint="https://ha203-1.overkiz.com/enduser-mobile-web/enduserAPI/",
83+
manufacturer="Hitachi",
84+
api_type=APIType.CLOUD,
85+
),
86+
Server.HI_KUMO_EUROPE: ServerConfig(
87+
server=Server.HI_KUMO_EUROPE,
88+
name="Hitachi Hi Kumo (Europe)",
89+
endpoint="https://ha117-1.overkiz.com/enduser-mobile-web/enduserAPI/",
90+
manufacturer="Hitachi",
91+
api_type=APIType.CLOUD,
92+
),
93+
Server.HI_KUMO_OCEANIA: ServerConfig(
94+
server=Server.HI_KUMO_OCEANIA,
95+
name="Hitachi Hi Kumo (Oceania)",
96+
endpoint="https://ha203-1.overkiz.com/enduser-mobile-web/enduserAPI/",
97+
manufacturer="Hitachi",
98+
api_type=APIType.CLOUD,
99+
),
100+
Server.NEXITY: ServerConfig(
101+
server=Server.NEXITY,
102+
name="Nexity Eugénie",
103+
endpoint="https://ha106-1.overkiz.com/enduser-mobile-web/enduserAPI/",
104+
manufacturer="Nexity",
105+
api_type=APIType.CLOUD,
106+
),
107+
Server.REXEL: ServerConfig(
108+
server=Server.REXEL,
109+
name="Rexel Energeasy Connect",
110+
endpoint=REXEL_BACKEND_API,
111+
manufacturer="Rexel",
112+
api_type=APIType.CLOUD,
113+
),
114+
Server.SAUTER_COZYTOUCH: ServerConfig( # duplicate of Atlantic Cozytouch
115+
server=Server.SAUTER_COZYTOUCH,
116+
name="Sauter Cozytouch",
117+
endpoint="https://ha110-1.overkiz.com/enduser-mobile-web/enduserAPI/",
118+
manufacturer="Sauter",
119+
api_type=APIType.CLOUD,
120+
),
121+
Server.SIMU_LIVEIN2: ServerConfig( # alias of https://tahomalink.com
122+
server=Server.SIMU_LIVEIN2,
123+
name="SIMU (LiveIn2)",
124+
endpoint="https://ha101-1.overkiz.com/enduser-mobile-web/enduserAPI/",
125+
manufacturer="Somfy",
126+
api_type=APIType.CLOUD,
127+
),
128+
Server.SOMFY_EUROPE: ServerConfig( # alias of https://tahomalink.com
129+
server=Server.SOMFY_EUROPE,
130+
name="Somfy (Europe)",
131+
endpoint="https://ha101-1.overkiz.com/enduser-mobile-web/enduserAPI/",
132+
manufacturer="Somfy",
133+
api_type=APIType.CLOUD,
134+
),
135+
Server.SOMFY_AMERICA: ServerConfig(
136+
server=Server.SOMFY_AMERICA,
137+
name="Somfy (North America)",
138+
endpoint="https://ha401-1.overkiz.com/enduser-mobile-web/enduserAPI/",
139+
manufacturer="Somfy",
140+
api_type=APIType.CLOUD,
141+
),
142+
Server.SOMFY_OCEANIA: ServerConfig(
143+
server=Server.SOMFY_OCEANIA,
144+
name="Somfy (Oceania)",
145+
endpoint="https://ha201-1.overkiz.com/enduser-mobile-web/enduserAPI/",
146+
manufacturer="Somfy",
147+
api_type=APIType.CLOUD,
148+
),
149+
Server.THERMOR_COZYTOUCH: ServerConfig( # duplicate of Atlantic Cozytouch
150+
server=Server.THERMOR_COZYTOUCH,
151+
name="Thermor Cozytouch",
152+
endpoint="https://ha110-1.overkiz.com/enduser-mobile-web/enduserAPI/",
153+
manufacturer="Thermor",
154+
api_type=APIType.CLOUD,
155+
),
156+
Server.UBIWIZZ: ServerConfig(
157+
server=Server.UBIWIZZ,
158+
name="Ubiwizz",
159+
endpoint="https://ha129-1.overkiz.com/enduser-mobile-web/enduserAPI/",
160+
manufacturer="Decelect",
161+
api_type=APIType.CLOUD,
162+
),
163+
}
164+
)

0 commit comments

Comments
 (0)