Skip to content

Commit 0b30a80

Browse files
committed
Add JSONType
1 parent 11d4c3c commit 0b30a80

11 files changed

Lines changed: 120 additions & 97 deletions

File tree

homeassistant_api/models/domains.py

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from pydantic import Field
2121

2222
from homeassistant_api.errors import RequestError
23+
from homeassistant_api.utils import JSONType
2324

2425
from .base import BaseModel
2526
from .states import State
@@ -55,13 +56,13 @@ def __init__(
5556

5657
@classmethod
5758
def from_json(
58-
cls, json: Dict[str, Any], client: Union["Client", "WebsocketClient"]
59+
cls, json: Dict[str, JSONType], client: Union["Client", "WebsocketClient"]
5960
) -> "Domain":
6061
"""Constructs Domain and Service models from json data."""
6162
if "domain" not in json or "services" not in json:
6263
raise ValueError("Missing services or domain attribute in json argument.")
6364
domain = cls(domain_id=cast(str, json.get("domain")), _client=client)
64-
services = json.get("services")
65+
services = cast(dict[str, dict[str, JSONType]], json.get("services"))
6566
assert isinstance(services, dict)
6667
for service_id, data in services.items():
6768
domain._add_service(service_id, **data)
@@ -103,8 +104,6 @@ def __getattr__(self, attr: str):
103104
# https://github.com/home-assistant/frontend/blob/dev/src/data/selector.ts
104105
# https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/types.ts
105106

106-
number = Union[int, float]
107-
108107

109108
# Helpers
110109
class ServiceFieldSelectorEntityFilter(BaseModel):
@@ -124,8 +123,8 @@ class ServiceFieldSelectorDeviceFilter(BaseModel):
124123
class CropOptions(BaseModel):
125124
round: bool
126125
type: Optional[str] # "image/jpeg" / "image/png"
127-
quality: Optional[number] = None
128-
aspectRatio: Optional[number] = None
126+
quality: Optional[int | float] = None
127+
aspectRatio: Optional[int | float] = None
129128

130129

131130
class SelectBoxOptionImage(BaseModel):
@@ -226,10 +225,10 @@ class ServiceFieldSelectorColorRGB(BaseModel):
226225

227226
class ServiceFieldSelectorColorTemp(BaseModel):
228227
unit: Optional[str] = None
229-
min: Optional[number] = None
230-
max: Optional[number] = None
231-
min_mireds: Optional[number] = None
232-
max_mireds: Optional[number] = None
228+
min: Optional[int | float] = None
229+
max: Optional[int | float] = None
230+
min_mireds: Optional[int | float] = None
231+
max_mireds: Optional[int | float] = None
233232

234233

235234
class ServiceFieldSelectorCondition(BaseModel):
@@ -242,7 +241,7 @@ class ServiceFieldSelectorConfigEntry(BaseModel):
242241

243242
class ServiceFieldSelectorConstant(BaseModel):
244243
label: Optional[str] = None
245-
value: Union[str, number, bool]
244+
value: Union[str, int, float, bool]
246245
translation_key: Optional[str] = None
247246

248247

@@ -349,9 +348,9 @@ class ServiceFieldSelectorNavigation(BaseModel):
349348

350349

351350
class ServiceFieldSelectorNumber(BaseModel):
352-
min: Optional[number] = None
353-
max: Optional[number] = None
354-
step: Optional[Union[number, str]] = None
351+
min: Optional[int | float] = None
352+
max: Optional[int | float] = None
353+
step: Optional[Union[int | float, str]] = None
355354
unit_of_measurement: Optional[str] = None
356355
mode: Optional[ServiceFieldSelectorNumberMode] = None
357356
slider_ticks: Optional[bool] = None
@@ -374,7 +373,7 @@ class ServiceFieldSelectorObject(BaseModel):
374373

375374
class ServiceFieldSelectorQRCode(BaseModel):
376375
data: str
377-
scale: Optional[number] = None
376+
scale: Optional[int | float] = None
378377
error_correction_level: Optional[ServiceFieldSelectorQRCodeErrorCorrectionLevel] = (
379378
None
380379
)
@@ -555,14 +554,12 @@ class ServiceField(BaseModel):
555554
"""Model for service parameters/fields."""
556555

557556
description: Optional[str] = None
558-
example: Optional[Union[str, number, bool, List[str], Dict]] = None
559-
default: Optional[Union[str, number, bool, List[str], Dict]] = None
557+
example: Optional[JSONType] = None
558+
default: Optional[JSONType] = None
560559
name: Optional[str] = None
561560
required: Optional[bool] = None
562561
advanced: Optional[bool] = None
563-
selector: Optional[Dict[str, Any]] = (
564-
None # TODO: I believe it would be beneficial to parse it the way I do
565-
)
562+
selector: Optional[ServiceFieldSelector] = None
566563
filter: Optional[ServiceFieldFilter] = None
567564

568565

@@ -588,8 +585,8 @@ class Service(BaseModel):
588585

589586
def trigger(self, entity_id: Optional[str] = None, **service_data) -> Union[
590587
Tuple[State, ...],
591-
Tuple[Tuple[State, ...], Dict[str, Any]],
592-
dict[str, Any],
588+
Tuple[Tuple[State, ...], dict[str, JSONType]],
589+
dict[str, JSONType],
593590
None,
594591
]:
595592
"""Triggers the service associated with this object."""
@@ -612,7 +609,7 @@ def trigger(self, entity_id: Optional[str] = None, **service_data) -> Union[
612609

613610
async def async_trigger(
614611
self, entity_id: Optional[str] = None, **service_data
615-
) -> Union[Tuple[State, ...], Tuple[Tuple[State, ...], Dict[str, Any]]]:
612+
) -> Union[Tuple[State, ...], Tuple[Tuple[State, ...], dict[str, JSONType]]]:
616613
"""Triggers the service associated with this object."""
617614
if entity_id is not None:
618615
service_data["entity_id"] = entity_id
@@ -639,12 +636,14 @@ async def async_trigger(
639636
def __call__(self, entity_id: Optional[str] = None, **service_data) -> Union[
640637
Union[
641638
Tuple[State, ...],
642-
Tuple[Tuple[State, ...], Dict[str, Any]],
643-
dict[str, Any],
639+
Tuple[Tuple[State, ...], dict[str, JSONType]],
640+
dict[str, JSONType],
644641
None,
645642
],
646643
Coroutine[
647-
Any, Any, Union[Tuple[State, ...], Tuple[Tuple[State, ...], Dict[str, Any]]]
644+
Any,
645+
Any,
646+
Union[Tuple[State, ...], Tuple[Tuple[State, ...], dict[str, JSONType]]],
648647
],
649648
]:
650649
"""

homeassistant_api/models/events.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Event Model File"""
22

3-
from typing import TYPE_CHECKING, Any, Dict, Optional
3+
from typing import TYPE_CHECKING, Optional
44

55
from pydantic import Field
66

7+
from homeassistant_api.utils import JSONType
8+
79
from .base import BaseModel
810

911
if TYPE_CHECKING:
@@ -38,6 +40,6 @@ async def async_fire(self, **event_data) -> str:
3840
return await self._client.async_fire_event(self.event, **event_data)
3941

4042
@classmethod
41-
def from_json(cls, json: Dict[str, Any], client: "Client") -> "Event":
43+
def from_json(cls, json: dict[str, JSONType], client: "Client") -> "Event":
4244
"""Constructs Event model from json data"""
4345
return cls(**json, _client=client)

homeassistant_api/models/states.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"""Module for the Entity State model."""
22

33
from datetime import datetime, timezone
4-
from typing import Any, Dict, Optional
4+
from typing import Optional
55

66
from pydantic import Field
77

8-
from .base import BaseModel, DatetimeIsoField
8+
from homeassistant_api.utils import JSONType
9+
10+
from homeassistant_api.base import BaseModel, DatetimeIsoField
911

1012

1113
class Context(BaseModel):
@@ -25,7 +27,7 @@ class Context(BaseModel):
2527
)
2628

2729
@classmethod
28-
def from_json(cls, json: Dict[str, Any]) -> "Context":
30+
def from_json(cls, json: dict[str, JSONType]) -> "Context":
2931
"""Constructs Context model from json data"""
3032
return cls.model_validate(json)
3133

@@ -37,7 +39,7 @@ class State(BaseModel):
3739
state: str = Field(
3840
..., description="The string representation of the state of the entity."
3941
)
40-
attributes: Dict[str, Any] = Field(
42+
attributes: dict[str, JSONType] = Field(
4143
{}, description="A dictionary of extra attributes of the state."
4244
)
4345
last_changed: DatetimeIsoField = Field(
@@ -57,6 +59,6 @@ class State(BaseModel):
5759
)
5860

5961
@classmethod
60-
def from_json(cls, json: Dict[str, Any]) -> "State":
62+
def from_json(cls, json: dict[str, JSONType]) -> "State":
6163
"""Constructs State model from json data"""
6264
return cls.model_validate(json)

homeassistant_api/models/websocket.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from typing import Any, Literal, Optional, Union
44

5+
from homeassistant_api.utils import JSONType
6+
57
from .base import BaseModel
68
from .states import Context, DatetimeIsoField
79

@@ -67,7 +69,7 @@ class FiredEvent(BaseModel):
6769
"""A model to parse the `event` key of fired event websocket responses."""
6870

6971
event_type: str
70-
data: dict[str, Any]
72+
data: dict[str, JSONType]
7173

7274
origin: Literal["LOCAL", "REMOTE"]
7375
# REMOTE if another API client or webhook fired the event
@@ -79,14 +81,14 @@ class FiredEvent(BaseModel):
7981

8082
class TemplateEvent(BaseModel):
8183
result: str
82-
listeners: dict[str, Any]
84+
listeners: dict[str, JSONType]
8385

8486

8587
class FiredTrigger(BaseModel):
8688
"""A model to parse the `trigger` key of fired event websocket responses."""
8789

8890
context: Optional[Context]
89-
variables: dict[str, Any]
91+
variables: dict[str, JSONType]
9092

9193

9294
class EventResponse(BaseModel):

homeassistant_api/processing.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
from requests import Response
1212
from requests_cache.models.response import CachedResponse
1313

14-
from .errors import (
14+
from homeassistant_api.utils import JSONType
15+
16+
from homeassistant_api.errors import (
1517
EndpointNotFoundError,
1618
InternalServerError,
1719
MalformedDataError,
@@ -109,10 +111,10 @@ def process(self) -> Any:
109111

110112
# List of default processors
111113
@Processing.processor("application/json") # type: ignore[arg-type]
112-
def process_json(response: ResponseType) -> dict[str, Any]:
114+
def process_json(response: ResponseType) -> dict[str, JSONType]:
113115
"""Returns the json dict content of the response."""
114116
try:
115-
return cast(dict[str, Any], response.json())
117+
return cast(dict[str, JSONType], response.json())
116118
except (json.JSONDecodeError, simplejson.JSONDecodeError) as err:
117119
raise MalformedDataError(
118120
f"Home Assistant responded with non-json response: {repr(response.text)}"
@@ -127,10 +129,10 @@ def process_text(response: ResponseType) -> str:
127129

128130

129131
@Processing.processor("application/json") # type: ignore[arg-type]
130-
async def async_process_json(response: AsyncResponseType) -> dict[str, Any]:
132+
async def async_process_json(response: AsyncResponseType) -> dict[str, JSONType]:
131133
"""Returns the json dict content of the response."""
132134
try:
133-
return cast(dict[str, Any], await response.json())
135+
return cast(dict[str, JSONType], await response.json())
134136
except (json.JSONDecodeError, simplejson.JSONDecodeError) as err:
135137
raise MalformedDataError(
136138
f"Home Assistant responded with non-json response: {repr(await response.text())}"

homeassistant_api/rawasyncclient.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
)
2222

2323
import aiohttp
24-
import aiohttp_client_cache
24+
import aiohttp_client_cache.session
2525

2626
from .errors import BadTemplateError, RequestError, RequestTimeoutError
2727
from .models import Domain, Entity, Event, Group, History, LogbookEntry, State
2828
from .processing import AsyncResponseType, Processing
2929
from .rawbaseclient import RawBaseClient
30-
from .utils import prepare_entity_id
30+
from .utils import JSONType, prepare_entity_id
3131

3232
if TYPE_CHECKING:
3333
from homeassistant_api import Client
@@ -47,14 +47,14 @@ class RawAsyncClient(RawBaseClient):
4747
""" # pylint: disable=line-too-long
4848

4949
async_cache_session: Union[
50-
aiohttp_client_cache.CachedSession, aiohttp.ClientSession
50+
aiohttp_client_cache.session.CachedSession, aiohttp.ClientSession
5151
]
5252

5353
def __init__(
5454
self,
5555
*args,
5656
async_cache_session: Union[
57-
aiohttp_client_cache.CachedSession,
57+
aiohttp_client_cache.session.CachedSession,
5858
Literal[False],
5959
Literal[None],
6060
] = None, # Explicitly disable cache with async_cache_session=False
@@ -129,12 +129,12 @@ async def async_get_error_log(self) -> str:
129129
"""
130130
return cast(str, await self.async_request("error_log"))
131131

132-
async def async_get_config(self) -> Dict[str, Any]:
132+
async def async_get_config(self) -> dict[str, JSONType]:
133133
"""
134134
Returns the yaml configuration of homeassistant.
135135
:code:`GET /api/config`
136136
"""
137-
return cast(Dict[str, Any], await self.async_request("config"))
137+
return cast(dict[str, JSONType], await self.async_request("config"))
138138

139139
async def async_get_logbook_entries(
140140
self,
@@ -272,7 +272,7 @@ async def async_get_domains(self) -> Dict[str, Domain]:
272272
data = await self.async_request("services")
273273
domains = map(
274274
lambda json: Domain.from_json(json, client=cast(Client, self)),
275-
cast(Tuple[Dict[str, Any], ...], data),
275+
cast(Tuple[dict[str, JSONType], ...], data),
276276
)
277277
return {domain.domain_id: domain for domain in domains}
278278

@@ -288,7 +288,7 @@ async def async_trigger_service(
288288
self,
289289
domain: str,
290290
service: str,
291-
**service_data: Union[Dict[str, Any], List[Any], str],
291+
**service_data: Union[dict[str, JSONType], List[Any], str],
292292
) -> Tuple[State, ...]:
293293
"""
294294
Tells Home Assistant to trigger a service, returns all states changed while in the process of being called.
@@ -305,16 +305,16 @@ async def async_trigger_service_with_response(
305305
self,
306306
domain: str,
307307
service: str,
308-
**service_data: Union[Dict[str, Any], List[Any], str],
309-
) -> tuple[tuple[State, ...], dict[str, Any]]:
308+
**service_data: Union[dict[str, JSONType], List[Any], str],
309+
) -> tuple[tuple[State, ...], dict[str, JSONType]]:
310310
"""
311311
Tells Home Assistant to trigger a service, returns the response from the service call.
312312
:code:`POST /api/services/<domain>/<service>`
313313
314314
Returns a list of the states changed and the response from the service call.
315315
"""
316316
data = cast(
317-
dict[str, Any],
317+
dict[str, dict[str, JSONType]],
318318
await self.async_request(
319319
join("services", domain, service) + "?return_response",
320320
method="POST",
@@ -383,7 +383,7 @@ async def async_get_events(self) -> Tuple[Event, ...]:
383383
return tuple(
384384
map(
385385
lambda json: Event.from_json(json, client=cast(Client, self)),
386-
cast(List[Dict[str, Any]], data),
386+
cast(List[dict[str, JSONType]], data),
387387
)
388388
)
389389

0 commit comments

Comments
 (0)