HomeAssistant-API 5.0.0__tar.gz → 5.0.1__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 (25) hide show
  1. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/PKG-INFO +1 -1
  2. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/errors.py +19 -0
  3. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/models/base.py +7 -0
  4. homeassistant_api-5.0.1/homeassistant_api/models/domains.py +654 -0
  5. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/models/events.py +4 -2
  6. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/models/states.py +19 -8
  7. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/models/websocket.py +10 -5
  8. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/processing.py +14 -10
  9. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/rawasyncclient.py +20 -15
  10. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/rawbaseclient.py +46 -17
  11. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/rawclient.py +40 -29
  12. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/rawwebsocket.py +20 -11
  13. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/utils.py +11 -1
  14. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/websocket.py +30 -28
  15. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/pyproject.toml +1 -1
  16. homeassistant_api-5.0.0/homeassistant_api/models/domains.py +0 -177
  17. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/LICENSE +0 -0
  18. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/README.md +0 -0
  19. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/__init__.py +0 -0
  20. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/client.py +0 -0
  21. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/models/__init__.py +0 -0
  22. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/models/entity.py +0 -0
  23. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/models/history.py +0 -0
  24. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/models/logbook.py +0 -0
  25. {homeassistant_api-5.0.0 → homeassistant_api-5.0.1}/homeassistant_api/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: HomeAssistant-API
3
- Version: 5.0.0
3
+ Version: 5.0.1
4
4
  Summary: Python Wrapper for Homeassistant's REST API
5
5
  License: GPL-3.0-or-later
6
6
  Author: GrandMoff100
@@ -10,10 +10,29 @@ class HomeassistantAPIError(Exception):
10
10
  class RequestError(HomeassistantAPIError):
11
11
  """Error raised when an issue occurs when requesting to Homeassistant."""
12
12
 
13
+ def __init__(
14
+ self, data: Optional[str], /, url: str, message: Optional[str] = None
15
+ ) -> None:
16
+ if message is not None:
17
+ super().__init__(
18
+ message
19
+ + f" {url!r}"
20
+ + (f" with data: {data!r}" if data is not None else "")
21
+ )
22
+ elif data is None:
23
+ super().__init__(f"An error occurred while making the request to {url!r}")
24
+ else:
25
+ super().__init__(
26
+ f"An error occurred while making the request to {url!r} with data: {data!r}"
27
+ )
28
+
13
29
 
14
30
  class RequestTimeoutError(RequestError):
15
31
  """Error raised when a request times out."""
16
32
 
33
+ def __init__(self, message: str, url: str) -> None:
34
+ super().__init__(None, url, message)
35
+
17
36
 
18
37
  class ResponseError(HomeassistantAPIError):
19
38
  """Error raised when an issue occurs in a response from Homeassistant."""
@@ -6,6 +6,11 @@ from typing import Annotated
6
6
  from pydantic import BaseModel as PydanticBaseModel
7
7
  from pydantic import ConfigDict, PlainSerializer
8
8
 
9
+ __all__ = (
10
+ "BaseModel",
11
+ "DatetimeIsoField",
12
+ )
13
+
9
14
  DatetimeIsoField = Annotated[
10
15
  datetime,
11
16
  PlainSerializer(lambda x: x.isoformat(), return_type=str, when_used="json"),
@@ -18,4 +23,6 @@ class BaseModel(PydanticBaseModel):
18
23
  model_config = ConfigDict(
19
24
  arbitrary_types_allowed=True,
20
25
  validate_assignment=True,
26
+ extra="forbid",
27
+ protected_namespaces=(),
21
28
  )
@@ -0,0 +1,654 @@
1
+ """File for Service and Domain data models"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import gc
6
+ import inspect
7
+ from enum import Enum
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Any,
11
+ Coroutine,
12
+ Dict,
13
+ List,
14
+ Optional,
15
+ Tuple,
16
+ Union,
17
+ cast,
18
+ )
19
+
20
+ from pydantic import Field
21
+
22
+ from homeassistant_api.errors import RequestError
23
+ from homeassistant_api.utils import JSONType
24
+
25
+ from .base import BaseModel
26
+ from .states import State
27
+
28
+ if TYPE_CHECKING:
29
+ from homeassistant_api import Client, WebsocketClient
30
+
31
+
32
+ class Domain(BaseModel):
33
+ """Model representing the domain that services belong to."""
34
+
35
+ def __init__(
36
+ self,
37
+ *args,
38
+ _client: Optional[Union["Client", "WebsocketClient"]] = None,
39
+ **kwargs,
40
+ ) -> None:
41
+ super().__init__(*args, **kwargs)
42
+ if _client is None:
43
+ raise ValueError("No client passed.")
44
+ object.__setattr__(self, "_client", _client)
45
+
46
+ _client: Union["Client", "WebsocketClient"]
47
+ domain_id: str = Field(
48
+ ...,
49
+ description="The name of the domain that services belong to. "
50
+ "(e.g. :code:`frontend` in :code:`frontend.reload_themes`",
51
+ )
52
+ services: Dict[str, "Service"] = Field(
53
+ {},
54
+ description="A dictionary of all services belonging to the domain indexed by their names",
55
+ )
56
+
57
+ @classmethod
58
+ def from_json(
59
+ cls, json: Dict[str, JSONType], client: Union["Client", "WebsocketClient"]
60
+ ) -> "Domain":
61
+ """Constructs Domain and Service models from json data."""
62
+ if "domain" not in json or "services" not in json:
63
+ raise ValueError("Missing services or domain attribute in json argument.")
64
+ domain = cls(domain_id=cast(str, json.get("domain")), _client=client)
65
+ services = cast(dict[str, dict[str, JSONType]], json.get("services"))
66
+ assert isinstance(services, dict)
67
+ for service_id, data in services.items():
68
+ domain._add_service(service_id, **data)
69
+ return domain
70
+
71
+ def _add_service(self, service_id: str, **data) -> None:
72
+ """Registers services into a domain to be used or accessed. Used internally."""
73
+ # raise ValueError(data)
74
+ self.services.update(
75
+ {
76
+ service_id: Service(
77
+ service_id=service_id,
78
+ domain=self,
79
+ **data,
80
+ )
81
+ }
82
+ )
83
+
84
+ def get_service(self, service_id: str) -> Optional["Service"]:
85
+ """Return a Service with the given service_id, returns None if no such service exists"""
86
+ return self.services.get(service_id)
87
+
88
+ def __getattr__(self, attr: str):
89
+ """Allows services accessible as attributes"""
90
+ if attr in self.services:
91
+ return self.get_service(attr)
92
+ try:
93
+ return super().__getattribute__(attr)
94
+ except AttributeError as err:
95
+ try:
96
+ return object.__getattribute__(self, attr)
97
+ except AttributeError as e:
98
+ raise e from err
99
+
100
+
101
+ # Sources:
102
+ # https://developers.home-assistant.io/docs/dev_101_services/
103
+ # https://www.home-assistant.io/docs/blueprint/selectors/#date-selector
104
+ # https://github.com/home-assistant/frontend/blob/dev/src/data/selector.ts
105
+ # https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/types.ts
106
+
107
+
108
+ # Helpers
109
+ class ServiceFieldSelectorEntityFilter(BaseModel):
110
+ integration: Optional[str] = None
111
+ domain: Optional[Union[List[str], str]] = None
112
+ device_class: Optional[Union[List[str], str]] = None
113
+ supported_features: Optional[Union[List[int], int]] = None
114
+
115
+
116
+ class ServiceFieldSelectorDeviceFilter(BaseModel):
117
+ integration: Optional[str] = None
118
+ manufacturer: Optional[str] = None
119
+ model: Optional[str] = None
120
+ model_id: Optional[str] = None
121
+
122
+
123
+ class CropOptions(BaseModel):
124
+ round: bool
125
+ type: Optional[str] = None # "image/jpeg" / "image/png"
126
+ quality: Optional[Union[int, float]] = None
127
+ aspectRatio: Optional[Union[int, float]] = None
128
+
129
+
130
+ class SelectBoxOptionImage(BaseModel):
131
+ src: str
132
+ src_dark: Optional[str] = None
133
+ flip_rtl: Optional[bool] = None
134
+
135
+
136
+ class ServiceFieldSelectorNumberMode(str, Enum):
137
+ BOX = "box"
138
+ SLIDER = "slider"
139
+
140
+
141
+ class ServiceFieldSelectorSelectMode(str, Enum):
142
+ LIST = "list"
143
+ DROPDOWN = "dropdown"
144
+ BOX = "box"
145
+
146
+
147
+ class ServiceFieldSelectorQRCodeErrorCorrectionLevel(str, Enum):
148
+ LOW = "low"
149
+ MEDIUM = "medium"
150
+ QUARTILE = "quartile"
151
+ HIGH = "high"
152
+
153
+
154
+ class ServiceFieldSelectorTextType(str, Enum):
155
+ NUMBER = "number"
156
+ TEXT = "text"
157
+ SEARCH = "search"
158
+ TEL = "tel"
159
+ URL = "url"
160
+ EMAIL = "email"
161
+ PASSWORD = "password"
162
+ DATE = "date"
163
+ MONTH = "month"
164
+ WEEK = "week"
165
+ TIME = "time"
166
+ DATETIME_LOCAL = "datetime-local"
167
+ COLOR = "color"
168
+
169
+
170
+ # Selectors
171
+ class ServiceFieldSelectorAction(BaseModel):
172
+ optionsInSidebar: Optional[bool] = None
173
+
174
+
175
+ class ServiceFieldSelectorAddon(BaseModel):
176
+ name: Optional[str] = None
177
+ slug: Optional[str] = None
178
+
179
+
180
+ class ServiceFieldSelectorArea(BaseModel):
181
+ entity: Optional[
182
+ Union[List[ServiceFieldSelectorEntityFilter], ServiceFieldSelectorEntityFilter]
183
+ ] = None
184
+ device: Optional[
185
+ Union[List[ServiceFieldSelectorDeviceFilter], ServiceFieldSelectorDeviceFilter]
186
+ ] = None
187
+ multiple: Optional[bool] = None
188
+
189
+
190
+ class ServiceFieldSelectorAreasDisplay(BaseModel):
191
+ pass
192
+
193
+
194
+ class ServiceFieldSelectorAttribute(BaseModel):
195
+ entity_id: Optional[Union[List[str], str]] = None
196
+ hide_attributes: Optional[List[str]] = None
197
+
198
+
199
+ class ServiceFieldSelectorAssistPipeline(BaseModel):
200
+ include_last_used: Optional[bool] = None
201
+
202
+
203
+ class ServiceFieldSelectorBackground(BaseModel):
204
+ original: Optional[bool] = None
205
+ crop: Optional[CropOptions] = None
206
+
207
+
208
+ class ServiceFieldSelectorBackupLocation(BaseModel):
209
+ pass
210
+
211
+
212
+ class ServiceFieldSelectorBoolean(BaseModel):
213
+ pass
214
+
215
+
216
+ class ServiceFieldSelectorButtonToggle(BaseModel):
217
+ options: List[Union[str, ServiceFieldSelectorSelectOption]]
218
+ translation_key: Optional[str] = None
219
+ sort: Optional[bool] = None
220
+
221
+
222
+ class ServiceFieldSelectorColorRGB(BaseModel):
223
+ pass
224
+
225
+
226
+ class ServiceFieldSelectorColorTemp(BaseModel):
227
+ unit: Optional[str] = None
228
+ min: Optional[Union[int, float]] = None
229
+ max: Optional[Union[int, float]] = None
230
+ min_mireds: Optional[Union[int, float]] = None
231
+ max_mireds: Optional[Union[int, float]] = None
232
+
233
+
234
+ class ServiceFieldSelectorCondition(BaseModel):
235
+ optionsInSidebar: Optional[bool] = None
236
+
237
+
238
+ class ServiceFieldSelectorConfigEntry(BaseModel):
239
+ integration: Optional[str] = None
240
+
241
+
242
+ class ServiceFieldSelectorConstant(BaseModel):
243
+ label: Optional[str] = None
244
+ value: Union[str, int, float, bool]
245
+ translation_key: Optional[str] = None
246
+
247
+
248
+ class ServiceFieldSelectorConversationAgent(BaseModel):
249
+ language: Optional[str] = None # filtering by language not supported
250
+
251
+
252
+ class ServiceFieldSelectorCountry(BaseModel):
253
+ countries: List[str]
254
+ no_sort: Optional[bool] = None
255
+
256
+
257
+ class ServiceFieldSelectorDate(BaseModel):
258
+ pass
259
+
260
+
261
+ class ServiceFieldSelectorDateTime(BaseModel):
262
+ pass
263
+
264
+
265
+ class ServiceFieldSelectorDevice(BaseModel):
266
+ entity: Optional[
267
+ Union[List[ServiceFieldSelectorEntityFilter], ServiceFieldSelectorEntityFilter]
268
+ ] = None
269
+ filter: Optional[
270
+ Union[List[ServiceFieldSelectorDeviceFilter], ServiceFieldSelectorDeviceFilter]
271
+ ] = None
272
+ multiple: Optional[bool] = None
273
+
274
+
275
+ class ServiceFieldSelectorDeviceLegacy(ServiceFieldSelectorDevice):
276
+ integration: Optional[str] = None
277
+ manufacturer: Optional[str] = None
278
+ model: Optional[str] = None
279
+
280
+
281
+ class ServiceFieldSelectorDuration(BaseModel):
282
+ enable_day: Optional[bool] = None
283
+ enable_millisecond: Optional[bool] = None
284
+
285
+
286
+ class ServiceFieldSelectorEntity(BaseModel):
287
+ multiple: Optional[bool] = None
288
+ include_entities: Optional[List[str]] = None
289
+ exclude_entities: Optional[List[str]] = None
290
+ filter: Optional[
291
+ Union[List[ServiceFieldSelectorEntityFilter], ServiceFieldSelectorEntityFilter]
292
+ ] = None
293
+ reorder: Optional[bool] = None
294
+
295
+
296
+ class ServiceFieldSelectorEntityLegacy(ServiceFieldSelectorEntity):
297
+ integration: Optional[str] = None
298
+ domain: Optional[Union[List[str], str]] = None
299
+ device_class: Optional[Union[List[str], str]] = None
300
+
301
+
302
+ class ServiceFieldSelectorFloor(BaseModel):
303
+ entity: Optional[
304
+ Union[List[ServiceFieldSelectorEntityFilter], ServiceFieldSelectorEntityFilter]
305
+ ] = None
306
+ device: Optional[
307
+ Union[List[ServiceFieldSelectorDeviceFilter], ServiceFieldSelectorDeviceFilter]
308
+ ] = None
309
+ multiple: Optional[bool] = None
310
+
311
+
312
+ class ServiceFieldSelectorFile(BaseModel):
313
+ accept: str
314
+
315
+
316
+ class ServiceFieldSelectorIcon(BaseModel):
317
+ placeholder: Optional[str] = None
318
+ fallbackPath: Optional[str] = None
319
+
320
+
321
+ class ServiceFieldSelectorImage(BaseModel):
322
+ original: Optional[bool] = None
323
+ crop: Optional[CropOptions] = None
324
+
325
+
326
+ class ServiceFieldSelectorLabel(BaseModel):
327
+ multiple: Optional[bool] = None
328
+
329
+
330
+ class ServiceFieldSelectorLanguage(BaseModel):
331
+ languages: Optional[List[str]] = None
332
+ native_name: Optional[bool] = None
333
+ no_sort: Optional[bool] = None
334
+
335
+
336
+ class ServiceFieldSelectorLocation(BaseModel):
337
+ radius: Optional[bool] = None
338
+ radius_readonly: Optional[bool] = None
339
+ icon: Optional[str] = None
340
+
341
+
342
+ class ServiceFieldSelectorMedia(BaseModel):
343
+ accept: Optional[List[str]] = None
344
+
345
+
346
+ class ServiceFieldSelectorNavigation(BaseModel):
347
+ pass
348
+
349
+
350
+ class ServiceFieldSelectorNumber(BaseModel):
351
+ min: Optional[Union[int, float]] = None
352
+ max: Optional[Union[int, float]] = None
353
+ step: Optional[Union[Union[int, float], str]] = None
354
+ unit_of_measurement: Optional[str] = None
355
+ mode: Optional[ServiceFieldSelectorNumberMode] = None
356
+ slider_ticks: Optional[bool] = None
357
+ translation_key: Optional[str] = None
358
+
359
+
360
+ class ServiceFieldSelectorObjectField(BaseModel):
361
+ selector: ServiceFieldSelector
362
+ label: Optional[str] = None
363
+ required: Optional[bool] = None
364
+
365
+
366
+ class ServiceFieldSelectorObject(BaseModel):
367
+ label_field: Optional[str] = None
368
+ description_field: Optional[str] = None
369
+ translation_key: Optional[str] = None
370
+ fields: Optional[Dict[str, ServiceFieldSelectorObjectField]] = None
371
+ multiple: Optional[bool] = None
372
+
373
+
374
+ class ServiceFieldSelectorQRCode(BaseModel):
375
+ data: str
376
+ scale: Optional[Union[int, float]] = None
377
+ error_correction_level: Optional[ServiceFieldSelectorQRCodeErrorCorrectionLevel] = (
378
+ None
379
+ )
380
+ center_image: Optional[str] = None
381
+
382
+
383
+ class ServiceFieldSelectorSelectOption(BaseModel):
384
+ label: str
385
+ value: Any
386
+ description: Optional[str] = None
387
+ image: Optional[Union[str, SelectBoxOptionImage]] = None
388
+ disable: Optional[bool] = None
389
+
390
+
391
+ class ServiceFieldSelectorSelect(BaseModel):
392
+ multiple: Optional[bool] = None
393
+ custom_value: Optional[bool] = None
394
+ mode: Optional[ServiceFieldSelectorSelectMode] = None
395
+ options: List[Union[str, ServiceFieldSelectorSelectOption]]
396
+ translation_key: Optional[str] = None
397
+ sort: Optional[bool] = None
398
+ reorder: Optional[bool] = None
399
+ box_max_columns: Optional[int] = None
400
+
401
+
402
+ class ServiceFieldSelectorSelector(BaseModel):
403
+ pass
404
+
405
+
406
+ class ServiceFieldSelectorStateOption(BaseModel):
407
+ label: str
408
+ value: Any
409
+
410
+
411
+ class ServiceFieldSelectorState(BaseModel):
412
+ extra_options: Optional[List[ServiceFieldSelectorStateOption]] = None
413
+ entity_id: Optional[Union[str, List[str]]] = None
414
+ attribute: Optional[str] = None
415
+ hide_states: Optional[List[str]] = None
416
+ multiple: Optional[bool] = None
417
+
418
+
419
+ class ServiceFieldSelectorStatistic(BaseModel):
420
+ device_class: Optional[str] = None
421
+ multiple: Optional[bool] = None
422
+
423
+
424
+ class ServiceFieldSelectorTarget(BaseModel):
425
+ entity: Optional[
426
+ Union[List[ServiceFieldSelectorEntityFilter], ServiceFieldSelectorEntityFilter]
427
+ ] = None
428
+ device: Optional[
429
+ Union[List[ServiceFieldSelectorDeviceFilter], ServiceFieldSelectorDeviceFilter]
430
+ ] = None
431
+
432
+
433
+ class ServiceFieldSelectorTemplate(BaseModel):
434
+ pass
435
+
436
+
437
+ class ServiceFieldSelectorSTT(BaseModel):
438
+ language: Optional[str] = None
439
+
440
+
441
+ class ServiceFieldSelectorText(BaseModel):
442
+ multiline: Optional[bool] = None
443
+ type: Optional[ServiceFieldSelectorTextType] = None
444
+ prefix: Optional[str] = None
445
+ suffix: Optional[str] = None
446
+ autocomplete: Optional[str] = None
447
+ multiple: Optional[bool] = None
448
+
449
+
450
+ class ServiceFieldSelectorTheme(BaseModel):
451
+ include_default: Optional[bool] = None
452
+
453
+
454
+ class ServiceFieldSelectorTime(BaseModel):
455
+ no_second: Optional[bool] = None
456
+
457
+
458
+ class ServiceFieldSelectorTrigger(BaseModel):
459
+ pass
460
+
461
+
462
+ class ServiceFieldSelectorTTS(BaseModel):
463
+ language: Optional[str] = None
464
+
465
+
466
+ class ServiceFieldSelectorTTSVoice(BaseModel):
467
+ engineId: Optional[str] = None
468
+ language: Optional[str] = None
469
+
470
+
471
+ class ServiceFieldSelectorUIAction(BaseModel):
472
+ pass
473
+
474
+
475
+ class ServiceFieldSelectorUIColor(BaseModel):
476
+ default_color: Optional[str] = None
477
+ include_none: Optional[bool] = None
478
+ include_state: Optional[bool] = None
479
+
480
+
481
+ class ServiceFieldSelectorUIStateContext(BaseModel):
482
+ entity_id: Optional[str] = None
483
+ allow_name: Optional[bool] = None
484
+
485
+
486
+ class ServiceFieldSelector(BaseModel):
487
+ action: Optional[ServiceFieldSelectorAction] = None
488
+ addon: Optional[ServiceFieldSelectorAddon] = None
489
+ area: Optional[ServiceFieldSelectorArea] = None
490
+ areas_display: Optional[ServiceFieldSelectorAreasDisplay] = None
491
+ attribute: Optional[ServiceFieldSelectorAttribute] = None
492
+ assist_pipeline: Optional[ServiceFieldSelectorAssistPipeline] = None
493
+ backup_location: Optional[ServiceFieldSelectorBackupLocation] = None
494
+ background: Optional[ServiceFieldSelectorBackground] = None
495
+ boolean: Optional[ServiceFieldSelectorBoolean] = None
496
+ button_toggle: Optional[ServiceFieldSelectorButtonToggle] = None
497
+ color_rgb: Optional[ServiceFieldSelectorColorRGB] = None
498
+ color_temp: Optional[ServiceFieldSelectorColorTemp] = None
499
+ condition: Optional[ServiceFieldSelectorCondition] = None
500
+ config_entry: Optional[ServiceFieldSelectorConfigEntry] = None
501
+ constant: Optional[ServiceFieldSelectorConstant] = None
502
+ conversation_agent: Optional[ServiceFieldSelectorConversationAgent] = None
503
+ country: Optional[ServiceFieldSelectorCountry] = None
504
+ date: Optional[ServiceFieldSelectorDate] = None
505
+ datetime: Optional[ServiceFieldSelectorDateTime] = None
506
+ device: Optional[
507
+ Union[ServiceFieldSelectorDevice, ServiceFieldSelectorDeviceLegacy]
508
+ ] = None
509
+ duration: Optional[ServiceFieldSelectorDuration] = None
510
+ entity: Optional[
511
+ Union[ServiceFieldSelectorEntity, ServiceFieldSelectorEntityLegacy]
512
+ ] = None
513
+ floor: Optional[ServiceFieldSelectorFloor] = None
514
+ file: Optional[ServiceFieldSelectorFile] = None
515
+ icon: Optional[ServiceFieldSelectorIcon] = None
516
+ image: Optional[ServiceFieldSelectorImage] = None
517
+ label: Optional[ServiceFieldSelectorLabel] = None
518
+ language: Optional[ServiceFieldSelectorLanguage] = None
519
+ location: Optional[ServiceFieldSelectorLocation] = None
520
+ media: Optional[ServiceFieldSelectorMedia] = None
521
+ navigation: Optional[ServiceFieldSelectorNavigation] = None
522
+ number: Optional[ServiceFieldSelectorNumber] = None
523
+ object: Optional[ServiceFieldSelectorObject] = None
524
+ qr_code: Optional[ServiceFieldSelectorQRCode] = None
525
+ select: Optional[ServiceFieldSelectorSelect] = None
526
+ selector: Optional[ServiceFieldSelectorSelector] = None
527
+ state: Optional[ServiceFieldSelectorState] = None
528
+ statistic: Optional[ServiceFieldSelectorStatistic] = None
529
+ target: Optional[ServiceFieldSelectorTarget] = None
530
+ template: Optional[ServiceFieldSelectorTemplate] = None
531
+ stt: Optional[ServiceFieldSelectorSTT] = None
532
+ text: Optional[ServiceFieldSelectorText] = None
533
+ theme: Optional[ServiceFieldSelectorTheme] = None
534
+ time: Optional[ServiceFieldSelectorTime] = None
535
+ trigger: Optional[ServiceFieldSelectorTrigger] = None
536
+ tts: Optional[ServiceFieldSelectorTTS] = None
537
+ tts_voice: Optional[ServiceFieldSelectorTTSVoice] = None
538
+ ui_action: Optional[ServiceFieldSelectorUIAction] = None
539
+ ui_color: Optional[ServiceFieldSelectorUIColor] = None
540
+ ui_state_content: Optional[ServiceFieldSelectorUIStateContext] = None
541
+
542
+
543
+ # Service bases
544
+
545
+
546
+ class ServiceFieldFilter(BaseModel):
547
+ supported_features: Optional[Union[List[int], int]] = (
548
+ None # Bitset (any needs to be supported [or all within specified list])
549
+ )
550
+ attribute: Optional[Dict[str, Union[List[str], str]]] = None
551
+
552
+
553
+ class ServiceField(BaseModel):
554
+ """Model for service parameters/fields."""
555
+
556
+ description: Optional[str] = None
557
+ example: Optional[JSONType] = None
558
+ default: Optional[JSONType] = None
559
+ name: Optional[str] = None
560
+ required: Optional[bool] = None
561
+ advanced: Optional[bool] = None
562
+ selector: Optional[ServiceFieldSelector] = None
563
+ filter: Optional[ServiceFieldFilter] = None
564
+
565
+
566
+ class ServiceFieldCollection(BaseModel):
567
+ collapsed: Optional[bool] = None
568
+ fields: Dict[str, ServiceField]
569
+
570
+
571
+ class ServiceResponse(BaseModel):
572
+ optional: Optional[bool] = None
573
+
574
+
575
+ class Service(BaseModel):
576
+ """Model representing services from homeassistant"""
577
+
578
+ service_id: str
579
+ domain: Domain = Field(exclude=True, repr=False)
580
+ name: str
581
+ description: Optional[str] = None
582
+ fields: Optional[Dict[str, Union[ServiceField, ServiceFieldCollection]]] = None
583
+ target: Optional[ServiceFieldSelectorTarget] = None
584
+ response: Optional[ServiceResponse] = None
585
+
586
+ def trigger(self, **service_data) -> Union[
587
+ Tuple[State, ...],
588
+ Tuple[Tuple[State, ...], dict[str, JSONType]],
589
+ dict[str, JSONType],
590
+ None,
591
+ ]:
592
+ """Triggers the service associated with this object."""
593
+ try:
594
+ return self.domain._client.trigger_service_with_response(
595
+ self.domain.domain_id,
596
+ self.service_id,
597
+ **service_data,
598
+ )
599
+ except RequestError:
600
+ return self.domain._client.trigger_service(
601
+ self.domain.domain_id,
602
+ self.service_id,
603
+ **service_data,
604
+ )
605
+
606
+ async def async_trigger(
607
+ self, **service_data
608
+ ) -> Union[Tuple[State, ...], Tuple[Tuple[State, ...], dict[str, JSONType]]]:
609
+ """Triggers the service associated with this object."""
610
+ from homeassistant_api import WebsocketClient # prevent circular import
611
+
612
+ if isinstance(self.domain._client, WebsocketClient):
613
+ raise NotImplementedError(
614
+ "WebsocketClient does not support async/await syntax."
615
+ )
616
+ try:
617
+ return await self.domain._client.async_trigger_service_with_response(
618
+ self.domain.domain_id,
619
+ self.service_id,
620
+ **service_data,
621
+ )
622
+ except RequestError:
623
+ return await self.domain._client.async_trigger_service(
624
+ self.domain.domain_id,
625
+ self.service_id,
626
+ **service_data,
627
+ )
628
+
629
+ def __call__(self, **service_data) -> Union[
630
+ Union[
631
+ Tuple[State, ...],
632
+ Tuple[Tuple[State, ...], dict[str, JSONType]],
633
+ dict[str, JSONType],
634
+ None,
635
+ ],
636
+ Coroutine[
637
+ Any,
638
+ Any,
639
+ Union[Tuple[State, ...], Tuple[Tuple[State, ...], dict[str, JSONType]]],
640
+ ],
641
+ ]:
642
+ """
643
+ Triggers the service associated with this object.
644
+ """
645
+ assert (frame := inspect.currentframe()) is not None
646
+ assert (parent_frame := frame.f_back) is not None
647
+ try:
648
+ if inspect.iscoroutinefunction(
649
+ caller := gc.get_referrers(parent_frame.f_code)[0]
650
+ ) or inspect.iscoroutine(caller):
651
+ return self.async_trigger(**service_data)
652
+ except IndexError: # pragma: no cover
653
+ pass
654
+ return self.trigger(**service_data)