diff --git a/README.md b/README.md index 0c17ca68..de109aba 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,11 @@ -## Python wrapper for Homeassistant's [REST API](https://developers.home-assistant.io/docs/api/rest/) +## Python wrapper for Homeassistant's [Websocket API](https://developers.home-assistant.io/docs/api/websocket/) and [REST API](https://developers.home-assistant.io/docs/api/rest/) + +> Note: As of [this comment](https://github.com/home-assistant/architecture/discussions/1074#discussioncomment-9196867) the REST API is not getting any new features or endpoints. +> However, it is not going to be deprecated according to [this comment](https://github.com/home-assistant/developers.home-assistant/pull/2150#pullrequestreview-2017433583) +> But it is recommended to use the Websocket API for new integrations. Here is a quick example. @@ -18,17 +22,15 @@ Here is a quick example. from homeassistant_api import Client with Client( - '', + '', # i.e. 'http://homeassistant.local:8123/api/' '' ) as client: - - light = client.get_domain("light") - - light.turn_on(entity_id="light.living_room_lamp") + light = client.trigger_service('light', 'turn_on', {'entity_id': 'light.living_room'}) ``` -All the methods also support async! -Just prefix the method with `async_` +All the methods also support async/await! +Just prefix the method with `async_` and pass the `use_async=True` argument to the `Client` constructor. +Then you can use the methods as coroutines (i.e. `await light.async_turn_on(...)`). ## Documentation diff --git a/docs/conf.py b/docs/conf.py index f9f26fd8..8f8e4f98 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,5 @@ """Configuration file for the Sphinx documentation builder.""" + # # This file only contains a selection of the most common options. For a full # list see the documentation: diff --git a/homeassistant_api/__init__.py b/homeassistant_api/__init__.py index 0c061f24..8f6ca386 100644 --- a/homeassistant_api/__init__.py +++ b/homeassistant_api/__init__.py @@ -1,6 +1,5 @@ """Interact with your Homeassistant Instance remotely.""" - __all__ = ( "Client", "State", diff --git a/homeassistant_api/client.py b/homeassistant_api/client.py index 878b437e..94c23c86 100644 --- a/homeassistant_api/client.py +++ b/homeassistant_api/client.py @@ -1,6 +1,9 @@ """Module containing the primary Client class.""" + import logging from typing import Any +import urllib.parse as urlparse +import warnings from .rawasyncclient import RawAsyncClient from .rawclient import RawClient @@ -21,12 +24,26 @@ class Client(RawClient, RawAsyncClient): def __init__( self, - *args: Any, + api_url: str, + token: str, use_async: bool = False, verify_ssl: bool = True, - **kwargs: Any + **kwargs: Any, ) -> None: - if use_async: - RawAsyncClient.__init__(self, *args, verify_ssl=verify_ssl, **kwargs) + parsed = urlparse.urlparse(api_url) + + if parsed.scheme in {"http", "https"}: + if use_async: + RawAsyncClient.__init__( + self, api_url, token, verify_ssl=verify_ssl, **kwargs + ) + else: + RawClient.__init__( + self, api_url, token, verify_ssl=verify_ssl, **kwargs + ) + warnings.warn( + "The REST API is being phased out and will be removed in a far future release. Please use the WebSocket API instead.", + DeprecationWarning, + ) else: - RawClient.__init__(self, *args, verify_ssl=verify_ssl, **kwargs) + raise ValueError(f"Unknown scheme {parsed.scheme} in {api_url}") diff --git a/homeassistant_api/errors.py b/homeassistant_api/errors.py index a6396cda..015c93ad 100644 --- a/homeassistant_api/errors.py +++ b/homeassistant_api/errors.py @@ -3,7 +3,7 @@ from typing import Union -class HomeassistantAPIError(BaseException): +class HomeassistantAPIError(Exception): """Base class for custom errors""" @@ -84,3 +84,11 @@ class UnexpectedStatusCodeError(ResponseError): def __init__(self, status_code: int) -> None: super().__init__(f"Response has unexpected status code: {status_code!r}") + + +class WebsocketError(HomeassistantAPIError): + """Error raised when an issue occurs with the websocket connection.""" + + +class ReceivingError(WebsocketError): + """Error raised when an issue occurs when receiving a message from the websocket server.""" diff --git a/homeassistant_api/models/__init__.py b/homeassistant_api/models/__init__.py index 3beeda4c..d34928b2 100644 --- a/homeassistant_api/models/__init__.py +++ b/homeassistant_api/models/__init__.py @@ -1,4 +1,5 @@ """The Model objects for the entire library.""" + from .base import BaseModel from .domains import Domain, Service, ServiceField from .entity import Entity, Group diff --git a/homeassistant_api/models/base.py b/homeassistant_api/models/base.py index 4e6a7789..92b9b418 100644 --- a/homeassistant_api/models/base.py +++ b/homeassistant_api/models/base.py @@ -7,12 +7,14 @@ DatetimeIsoField = Annotated[ - datetime, PlainSerializer(lambda x: x.isoformat(), return_type=str, when_used='json') + datetime, + PlainSerializer(lambda x: x.isoformat(), return_type=str, when_used="json"), ] class BaseModel(PydanticBaseModel): """Base model that all Library Models inherit from.""" + model_config = ConfigDict( arbitrary_types_allowed=True, validate_assignment=True, diff --git a/homeassistant_api/models/domains.py b/homeassistant_api/models/domains.py index b6a4639d..5af69f17 100644 --- a/homeassistant_api/models/domains.py +++ b/homeassistant_api/models/domains.py @@ -1,10 +1,13 @@ """File for Service and Domain data models""" + import gc import inspect from typing import TYPE_CHECKING, Any, Coroutine, Dict, Optional, Tuple, Union, cast from pydantic import Field +from homeassistant_api.errors import RequestError + from .base import BaseModel from .states import State @@ -36,9 +39,7 @@ def __init__(self, *args, _client: Optional["Client"] = None, **kwargs) -> None: def from_json(cls, json: Dict[str, Any], client: "Client") -> "Domain": """Constructs Domain and Service models from json data.""" if "domain" not in json or "services" not in json: - raise ValueError( - "Missing services or attribute attribute in json argument." - ) + raise ValueError("Missing services or domain attribute in json argument.") domain = cls(domain_id=cast(str, json.get("domain")), _client=client) services = json.get("services") assert isinstance(services, dict) @@ -67,7 +68,13 @@ def __getattr__(self, attr: str): """Allows services accessible as attributes""" if attr in self.services: return self.get_service(attr) - return super().__getattribute__(attr) + try: + return super().__getattribute__(attr) + except AttributeError as err: + try: + return object.__getattribute__(self, attr) + except AttributeError as e: + raise e from err class ServiceField(BaseModel): @@ -89,26 +96,49 @@ class Service(BaseModel): description: Optional[str] = None fields: Optional[Dict[str, ServiceField]] = None - def trigger(self, **service_data) -> Tuple[State, ...]: - """Triggers the service associated with this object.""" - return self.domain._client.trigger_service( - self.domain.domain_id, - self.service_id, - **service_data, - ) - - async def async_trigger(self, **service_data) -> Tuple[State, ...]: + def trigger( + self, **service_data + ) -> Union[Tuple[State, ...], Tuple[Tuple[State, ...], Dict[str, Any]]]: """Triggers the service associated with this object.""" - return await self.domain._client.async_trigger_service( - self.domain.domain_id, - self.service_id, - **service_data, - ) + try: + return self.domain._client.trigger_service_with_response( + self.domain.domain_id, + self.service_id, + **service_data, + ) + except RequestError: + return self.domain._client.trigger_service( + self.domain.domain_id, + self.service_id, + **service_data, + ) - def __call__( + async def async_trigger( self, **service_data - ) -> Union[Tuple[State, ...], Coroutine[Any, Any, Tuple[State, ...]]]: + ) -> Union[Tuple[State, ...], Tuple[Tuple[State, ...], Dict[str, Any]]]: """Triggers the service associated with this object.""" + try: + return await self.domain._client.async_trigger_service_with_response( + self.domain.domain_id, + self.service_id, + **service_data, + ) + except RequestError: + return await self.domain._client.async_trigger_service( + self.domain.domain_id, + self.service_id, + **service_data, + ) + + def __call__(self, **service_data) -> Union[ + Union[Tuple[State, ...], Tuple[Tuple[State, ...], Dict[str, Any]]], + Coroutine[ + Any, Any, Union[Tuple[State, ...], Tuple[Tuple[State, ...], Dict[str, Any]]] + ], + ]: + """ + Triggers the service associated with this object. + """ assert (frame := inspect.currentframe()) is not None assert (parent_frame := frame.f_back) is not None try: diff --git a/homeassistant_api/models/history.py b/homeassistant_api/models/history.py index 8b746b07..f683d471 100644 --- a/homeassistant_api/models/history.py +++ b/homeassistant_api/models/history.py @@ -1,4 +1,5 @@ """Module for the History model.""" + from typing import Tuple from pydantic import Field diff --git a/homeassistant_api/models/logbook.py b/homeassistant_api/models/logbook.py index da38772a..08e21c7d 100644 --- a/homeassistant_api/models/logbook.py +++ b/homeassistant_api/models/logbook.py @@ -1,4 +1,5 @@ """Module for the Logbook Entry model.""" + from typing import Optional from pydantic import Field diff --git a/homeassistant_api/models/states.py b/homeassistant_api/models/states.py index ebf71bd4..0ff01844 100644 --- a/homeassistant_api/models/states.py +++ b/homeassistant_api/models/states.py @@ -1,4 +1,5 @@ """Module for the Entity State model.""" + from datetime import datetime from typing import Any, Dict, Optional diff --git a/homeassistant_api/rawasyncclient.py b/homeassistant_api/rawasyncclient.py index 35f74ca6..18c6197c 100644 --- a/homeassistant_api/rawasyncclient.py +++ b/homeassistant_api/rawasyncclient.py @@ -1,4 +1,5 @@ """Module for interacting with Home Assistant asyncronously.""" + from __future__ import annotations import asyncio @@ -176,11 +177,14 @@ async def async_get_rendered_template(self, template: str) -> str: :code:`POST /api/template` """ try: - return cast(str, await self.async_request( - "template", - json=dict(template=template), - method="POST", - )) + return cast( + str, + await self.async_request( + "template", + json=dict(template=template), + method="POST", + ), + ) except RequestError as err: raise BadTemplateError( "Your template is invalid. " @@ -291,6 +295,34 @@ async def async_trigger_service( ) return tuple(map(State.from_json, cast(List[Dict[Any, Any]], data))) + async def async_trigger_service_with_response( + self, + domain: str, + service: str, + **service_data: Union[Dict[str, Any], List[Any], str], + ) -> tuple[tuple[State, ...], dict[str, Any]]: + """ + Tells Home Assistant to trigger a service, returns the response from the service call. + :code:`POST /api/services//` + + Returns a list of the states changed and the response from the service call. + """ + data = cast( + dict[str, Any], + await self.async_request( + join("services", domain, service) + "?return_response", + method="POST", + json=service_data, + ), + ) + states = tuple( + map( + State.from_json, + cast(List[Dict[Any, Any]], data.get("changed_states", [])), + ) + ) + return states, data.get("service_response", {}) + # EntityState methods async def async_get_state( # pylint: disable=duplicate-code self, diff --git a/homeassistant_api/rawbaseclient.py b/homeassistant_api/rawbaseclient.py index 6f3c2df6..b5c2a8af 100644 --- a/homeassistant_api/rawbaseclient.py +++ b/homeassistant_api/rawbaseclient.py @@ -104,15 +104,14 @@ def prepare_get_entity_histories_params( end_timestamp: Optional[datetime] = None, significant_changes_only: bool = False, ) -> Tuple[Dict[str, Optional[str]], str]: - """Pre-logic for `Client.get_entity_histories` and `Client.async_get_entity_histories`.""" params: Dict[str, Optional[str]] = {} if entities is not None: params["filter_entity_id"] = ",".join([ent.entity_id for ent in entities]) if end_timestamp is not None: - params[ - "end_time" - ] = end_timestamp.isoformat() # Params are automatically URL encoded + params["end_time"] = ( + end_timestamp.isoformat() + ) # Params are automatically URL encoded if significant_changes_only: params["significant_changes_only"] = None if start_timestamp is not None: @@ -134,9 +133,11 @@ def prepare_get_logbook_entry_params( if filter_entities is not None: params.update( { - "entity": filter_entities - if isinstance(filter_entities, str) - else ",".join(filter_entities) + "entity": ( + filter_entities + if isinstance(filter_entities, str) + else ",".join(filter_entities) + ) } ) if end_timestamp is not None: diff --git a/homeassistant_api/rawclient.py b/homeassistant_api/rawclient.py index 99c56141..2f4b92e8 100644 --- a/homeassistant_api/rawclient.py +++ b/homeassistant_api/rawclient.py @@ -1,4 +1,5 @@ """Module for all interaction with homeassistant.""" + from __future__ import annotations import json @@ -290,6 +291,34 @@ def trigger_service( ) return tuple(map(State.from_json, cast(List[Dict[str, Any]], data))) + def trigger_service_with_response( + self, + domain: str, + service: str, + **service_data, + ) -> tuple[tuple[State, ...], dict[str, Any]]: + """ + Tells Home Assistant to trigger a service, returns the response from the service call. + :code:`POST /api/services//` + + Returns a list of the states changed and the response from the service call. + """ + data = cast( + dict[str, Any], + self.request( + join("services", domain, service) + "?return_response", + method="POST", + json=service_data, + ), + ) + states = tuple( + map( + State.from_json, + cast(List[Dict[Any, Any]], data.get("changed_states", [])), + ) + ) + return states, data.get("service_response", {}) + # EntityState methods def get_state( # pylint: disable=duplicate-code self, diff --git a/poetry.lock b/poetry.lock index bf433a7f..6b5521fa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -196,9 +196,6 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} - [[package]] name = "appdirs" version = "1.4.4" @@ -262,7 +259,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">1", markers = "python_version <= \"3.8\""} pydantic = ">=2.0,<3.0.0" pydantic-settings = ">=2.0,<3.0.0" Sphinx = ">=4.0" @@ -284,9 +280,6 @@ files = [ {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] -[package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} - [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] @@ -1318,17 +1311,6 @@ files = [ [package.extras] cli = ["click (>=5.0)"] -[[package]] -name = "pytz" -version = "2024.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, -] - [[package]] name = "pyyaml" version = "6.0.2" @@ -1915,6 +1897,84 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +[[package]] +name = "websockets" +version = "14.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "websockets-14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29"}, + {file = "websockets-14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179"}, + {file = "websockets-14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250"}, + {file = "websockets-14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0"}, + {file = "websockets-14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0"}, + {file = "websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199"}, + {file = "websockets-14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58"}, + {file = "websockets-14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078"}, + {file = "websockets-14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434"}, + {file = "websockets-14.1-cp310-cp310-win32.whl", hash = "sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10"}, + {file = "websockets-14.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e"}, + {file = "websockets-14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512"}, + {file = "websockets-14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac"}, + {file = "websockets-14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280"}, + {file = "websockets-14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1"}, + {file = "websockets-14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3"}, + {file = "websockets-14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6"}, + {file = "websockets-14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0"}, + {file = "websockets-14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89"}, + {file = "websockets-14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23"}, + {file = "websockets-14.1-cp311-cp311-win32.whl", hash = "sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e"}, + {file = "websockets-14.1-cp311-cp311-win_amd64.whl", hash = "sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09"}, + {file = "websockets-14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed"}, + {file = "websockets-14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d"}, + {file = "websockets-14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707"}, + {file = "websockets-14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a"}, + {file = "websockets-14.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45"}, + {file = "websockets-14.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58"}, + {file = "websockets-14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058"}, + {file = "websockets-14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4"}, + {file = "websockets-14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05"}, + {file = "websockets-14.1-cp312-cp312-win32.whl", hash = "sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0"}, + {file = "websockets-14.1-cp312-cp312-win_amd64.whl", hash = "sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f"}, + {file = "websockets-14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3630b670d5057cd9e08b9c4dab6493670e8e762a24c2c94ef312783870736ab9"}, + {file = "websockets-14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36ebd71db3b89e1f7b1a5deaa341a654852c3518ea7a8ddfdf69cc66acc2db1b"}, + {file = "websockets-14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5b918d288958dc3fa1c5a0b9aa3256cb2b2b84c54407f4813c45d52267600cd3"}, + {file = "websockets-14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00fe5da3f037041da1ee0cf8e308374e236883f9842c7c465aa65098b1c9af59"}, + {file = "websockets-14.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8149a0f5a72ca36720981418eeffeb5c2729ea55fa179091c81a0910a114a5d2"}, + {file = "websockets-14.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77569d19a13015e840b81550922056acabc25e3f52782625bc6843cfa034e1da"}, + {file = "websockets-14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf5201a04550136ef870aa60ad3d29d2a59e452a7f96b94193bee6d73b8ad9a9"}, + {file = "websockets-14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:88cf9163ef674b5be5736a584c999e98daf3aabac6e536e43286eb74c126b9c7"}, + {file = "websockets-14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:836bef7ae338a072e9d1863502026f01b14027250a4545672673057997d5c05a"}, + {file = "websockets-14.1-cp313-cp313-win32.whl", hash = "sha256:0d4290d559d68288da9f444089fd82490c8d2744309113fc26e2da6e48b65da6"}, + {file = "websockets-14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8621a07991add373c3c5c2cf89e1d277e49dc82ed72c75e3afc74bd0acc446f0"}, + {file = "websockets-14.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01bb2d4f0a6d04538d3c5dfd27c0643269656c28045a53439cbf1c004f90897a"}, + {file = "websockets-14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:414ffe86f4d6f434a8c3b7913655a1a5383b617f9bf38720e7c0799fac3ab1c6"}, + {file = "websockets-14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fda642151d5affdee8a430bd85496f2e2517be3a2b9d2484d633d5712b15c56"}, + {file = "websockets-14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd7c11968bc3860d5c78577f0dbc535257ccec41750675d58d8dc66aa47fe52c"}, + {file = "websockets-14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a032855dc7db987dff813583d04f4950d14326665d7e714d584560b140ae6b8b"}, + {file = "websockets-14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7e7ea2f782408c32d86b87a0d2c1fd8871b0399dd762364c731d86c86069a78"}, + {file = "websockets-14.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:39450e6215f7d9f6f7bc2a6da21d79374729f5d052333da4d5825af8a97e6735"}, + {file = "websockets-14.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ceada5be22fa5a5a4cdeec74e761c2ee7db287208f54c718f2df4b7e200b8d4a"}, + {file = "websockets-14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3fc753451d471cff90b8f467a1fc0ae64031cf2d81b7b34e1811b7e2691bc4bc"}, + {file = "websockets-14.1-cp39-cp39-win32.whl", hash = "sha256:14839f54786987ccd9d03ed7f334baec0f02272e7ec4f6e9d427ff584aeea8b4"}, + {file = "websockets-14.1-cp39-cp39-win_amd64.whl", hash = "sha256:d9fd19ecc3a4d5ae82ddbfb30962cf6d874ff943e56e0c81f5169be2fda62979"}, + {file = "websockets-14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8"}, + {file = "websockets-14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e"}, + {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098"}, + {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb"}, + {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7"}, + {file = "websockets-14.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d"}, + {file = "websockets-14.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddaa4a390af911da6f680be8be4ff5aaf31c4c834c1a9147bc21cbcbca2d4370"}, + {file = "websockets-14.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a4c805c6034206143fbabd2d259ec5e757f8b29d0a2f0bf3d2fe5d1f60147a4a"}, + {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:205f672a6c2c671a86d33f6d47c9b35781a998728d2c7c2a3e1cf3333fcb62b7"}, + {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef440054124728cc49b01c33469de06755e5a7a4e83ef61934ad95fc327fbb0"}, + {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7591d6f440af7f73c4bd9404f3772bfee064e639d2b6cc8c94076e71b2471c1"}, + {file = "websockets-14.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:25225cc79cfebc95ba1d24cd3ab86aaa35bcd315d12fa4358939bd55e9bd74a5"}, + {file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e"}, + {file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8"}, +] + [[package]] name = "yarl" version = "1.11.1" @@ -2041,5 +2101,5 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" -python-versions = "^3.8,<4.0.0" -content-hash = "d34208b22c2ba46b75659320716599123140bfe47a61eb0af570447f6e9f1d8d" +python-versions = "^3.9,<4.0.0" +content-hash = "f952a0fbb8688f207621a984abf9b372012048efe1021a59d20ef11b1a4cda15" diff --git a/pyproject.toml b/pyproject.toml index cd52b2db..151ca1df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,10 +19,11 @@ packages = [{ include = "homeassistant_api" }] aiohttp = "^3.8.1" aiohttp-client-cache = ">=0.6.1" pydantic = ">=2.0,<2.9" -python = "^3.8,<4.0.0" +python = "^3.9,<4.0.0" requests = "^2.27.1" requests-cache = "^0.9.2" simplejson = "^3.17.6" +websockets = "^14.1" [tool.poetry.group.docs] optional = true diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 6175961b..587fc140 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -171,6 +171,28 @@ async def test_async_trigger_service(async_cached_client: Client) -> None: assert isinstance(resp, tuple) +def test_trigger_service_with_response(cached_client: Client) -> None: + """Tests the `POST /api/services//?return_response` endpoint.""" + weather = cached_client.get_domain("weather") + assert weather is not None + changed_states, data = weather.get_forecasts( + entity_id="weather.forecast_home", + type="hourly", + ) + assert data is not None + + +async def test_async_trigger_service_with_response(async_cached_client: Client) -> None: + """Tests the `POST /api/services//?return_response` endpoint.""" + weather = await async_cached_client.async_get_domain("weather") + assert weather is not None + changed_states, data = await weather.get_forecasts( + entity_id="weather.forecast_home", + type="hourly", + ) + assert data is not None + + def test_get_states(cached_client: Client) -> None: """Tests the `GET /api/states` endpoint.""" states = cached_client.get_states() diff --git a/tests/test_errors.py b/tests/test_errors.py index c1ae8029..ad84d02d 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -2,7 +2,7 @@ import json import os -import unittest +import unittest.mock from typing import Dict import aiohttp @@ -204,3 +204,8 @@ def test_exception_response_error() -> None: def test_exception_unexpected_status_code() -> None: with pytest.raises(UnexpectedStatusCodeError): Processing(make_response(0, "", {})).process() + + +def test_unkown_scheme(cached_client: Client) -> None: + with pytest.raises(ValueError): + Client("ftp://example.com", "token") \ No newline at end of file diff --git a/tests/test_models.py b/tests/test_models.py index be62cc04..e96c8f5e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,4 +1,5 @@ """Module that tests model methods.""" + import copy from datetime import datetime diff --git a/volumes/config/.storage/auth b/volumes/config/.storage/auth index 2233a3f4..a095fe2e 100644 --- a/volumes/config/.storage/auth +++ b/volumes/config/.storage/auth @@ -77,6 +77,7 @@ "jwt_key": "d8213eff81721687a0ea17e8d56e29f8c140015bd046c3924b2b2b4e2b8d77c095e80de14610e955d52650f503a5ec701f46626700496d2021b653f42dff8982", "last_used_at": "2023-03-11T22:29:03.495460+00:00", "last_used_ip": "127.0.0.1", + "expire_at": null, "credential_id": "711894da0c83415095b3a6314ff20d86", "version": "2023.3.3" }, @@ -93,6 +94,7 @@ "jwt_key": "66b526e5aea268d6751e986e2d486c62ef0274113c1f660d6716b33c347db39591f11ce6be6f3af63d2afa4febc5f2322fac581f05c2e4b6d24060c79631be65", "last_used_at": "2023-03-11T22:35:32.104562+00:00", "last_used_ip": "127.0.0.1", + "expire_at": null, "credential_id": "711894da0c83415095b3a6314ff20d86", "version": "2023.3.3" }, @@ -109,9 +111,27 @@ "jwt_key": "254697dbff6aafdda02191c86a90529745f774c0e122dd6bdbc22bf0790698f2bb7d4c956736aa8d7c804c8d92266c6b365cce3b191f8d6f10aa706ca3863aa7", "last_used_at": "2023-03-11T22:41:41.322766+00:00", "last_used_ip": null, + "expire_at": null, "credential_id": null, "version": "2023.3.3" }, + { + "id": "701b4cc2befd4b72ac99a06d7b38181e", + "user_id": "e85fc7b7b8924dc9b024ce90ad23799e", + "client_id": "http://localhost:8123/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2024-12-20T22:49:28.919077+00:00", + "access_token_expiration": 1800.0, + "token": "783a5dd38b8e987a6dc7263605d2ae9629487fff7a48884e4b83f39983064a49e89474b7ba0d47ec78b76af8c7ab0e5a0ecce3c0d6ec751af62355a8a3611b72", + "jwt_key": "dab8d894766cbdc856cc2475b7f3fd4cb7c1279fbb499a6c1709d5f4264d11842882a28cb3b67259cc983ff7b156781063835d81d76f0356ad0d477d1b09404d", + "last_used_at": "2024-12-20T22:49:28.919136+00:00", + "last_used_ip": "172.18.0.1", + "expire_at": 1742510968.919136, + "credential_id": "711894da0c83415095b3a6314ff20d86", + "version": "2024.12.3" + }, { "id": "cabbfc0d5e024c2f90b998ffdbea7e61", "user_id": "6ebcf011775a47c1bc279a1777a1e5a7", @@ -125,6 +145,7 @@ "jwt_key": "fdeaf63844d1b702572ce014db18ff05074e775f04460ea5674653cee999b7fc0733f9eb99c835b7d6e6317e84bf418c21fcf2ceb65060675782be333896e135", "last_used_at": null, "last_used_ip": null, + "expire_at": null, "credential_id": null, "version": "2022.12.9" } diff --git a/volumes/config/.storage/core.area_registry b/volumes/config/.storage/core.area_registry index 660ab768..2e08d5c2 100644 --- a/volumes/config/.storage/core.area_registry +++ b/volumes/config/.storage/core.area_registry @@ -1,6 +1,6 @@ { "version": 1, - "minor_version": 1, + "minor_version": 7, "key": "core.area_registry", "data": { "areas": [ @@ -8,19 +8,34 @@ "name": "Living Room", "id": "living_room", "picture": null, - "aliases": [] + "aliases": [], + "icon": null, + "floor_id": null, + "labels": [], + "created_at": "1970-01-01T00:00:00+00:00", + "modified_at": "1970-01-01T00:00:00+00:00" }, { "name": "Kitchen", "id": "kitchen", "picture": null, - "aliases": [] + "aliases": [], + "icon": null, + "floor_id": null, + "labels": [], + "created_at": "1970-01-01T00:00:00+00:00", + "modified_at": "1970-01-01T00:00:00+00:00" }, { "name": "Bedroom", "id": "bedroom", "picture": null, - "aliases": [] + "aliases": [], + "icon": null, + "floor_id": null, + "labels": [], + "created_at": "1970-01-01T00:00:00+00:00", + "modified_at": "1970-01-01T00:00:00+00:00" } ] } diff --git a/volumes/config/.storage/core.config b/volumes/config/.storage/core.config index e02abd30..a17e6aa1 100644 --- a/volumes/config/.storage/core.config +++ b/volumes/config/.storage/core.config @@ -1,6 +1,6 @@ { "version": 1, - "minor_version": 3, + "minor_version": 4, "key": "core.config", "data": { "latitude": 52.3731339, @@ -13,6 +13,7 @@ "internal_url": null, "currency": "USD", "country": "US", - "language": "en" + "language": "en", + "radius": 100 } } \ No newline at end of file diff --git a/volumes/config/.storage/core.config_entries b/volumes/config/.storage/core.config_entries index 3486ee31..22d6390e 100644 --- a/volumes/config/.storage/core.config_entries +++ b/volumes/config/.storage/core.config_entries @@ -1,35 +1,12 @@ { "version": 1, - "minor_version": 1, + "minor_version": 4, "key": "core.config_entries", "data": { "entries": [ - { - "entry_id": "5f8426fa502435857743f302651753c9", - "version": 1, - "domain": "sun", - "title": "Sun", - "data": {}, - "options": {}, - "pref_disable_new_entities": false, - "pref_disable_polling": false, - "source": "import", - "unique_id": null, - "disabled_by": null - }, - { - "entry_id": "bb18b688994e8cfa5e4e880452735628", - "version": 1, - "domain": "radio_browser", - "title": "Radio Browser", - "data": {}, - "options": {}, - "pref_disable_new_entities": false, - "pref_disable_polling": false, - "source": "onboarding", - "unique_id": null, - "disabled_by": null - } + {"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"sun","entry_id":"5f8426fa502435857743f302651753c9","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"import","title":"Sun","unique_id":null,"version":1}, + {"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"radio_browser","entry_id":"bb18b688994e8cfa5e4e880452735628","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"onboarding","title":"Radio Browser","unique_id":null,"version":1}, + {"created_at":"2024-12-20T17:06:07.193748+00:00","data":{"elevation":1000.0,"latitude":52.3731339,"longitude":4.8903147,"name":"Home"},"disabled_by":null,"discovery_keys":{},"domain":"met","entry_id":"01JFJGH76SD417XC4YJTG8QJWB","minor_version":1,"modified_at":"2024-12-20T17:06:07.193752+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","title":"Home","unique_id":null,"version":1} ] } } \ No newline at end of file diff --git a/volumes/config/.storage/core.device_registry b/volumes/config/.storage/core.device_registry new file mode 100644 index 00000000..192d9d5d --- /dev/null +++ b/volumes/config/.storage/core.device_registry @@ -0,0 +1,12 @@ +{ + "version": 1, + "minor_version": 8, + "key": "core.device_registry", + "data": { + "devices": [ + {"area_id":null,"config_entries":["5f8426fa502435857743f302651753c9"],"configuration_url":null,"connections":[],"created_at":"2024-12-17T01:58:48.779854+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"b3d3fadee11d077d99d948c173631abc","identifiers":[["sun","5f8426fa502435857743f302651753c9"]],"labels":[],"manufacturer":null,"model":null,"model_id":null,"modified_at":"2024-12-17T01:58:48.779886+00:00","name_by_user":null,"name":"Sun","primary_config_entry":"5f8426fa502435857743f302651753c9","serial_number":null,"sw_version":null,"via_device_id":null}, + {"area_id":null,"config_entries":["01JFJGH76SD417XC4YJTG8QJWB"],"configuration_url":"https://www.met.no/en","connections":[],"created_at":"2024-12-20T17:06:07.780759+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"6586dbdeb10a88ae4603b47e600c6124","identifiers":[["met","01JFJGH76SD417XC4YJTG8QJWB"]],"labels":[],"manufacturer":"Met.no","model":"Forecast","model_id":null,"modified_at":"2024-12-20T17:06:07.780798+00:00","name_by_user":null,"name":"Forecast","primary_config_entry":"01JFJGH76SD417XC4YJTG8QJWB","serial_number":null,"sw_version":null,"via_device_id":null} + ], + "deleted_devices": [] + } +} \ No newline at end of file diff --git a/volumes/config/.storage/core.entity_registry b/volumes/config/.storage/core.entity_registry index 3f870b68..2d820259 100644 --- a/volumes/config/.storage/core.entity_registry +++ b/volumes/config/.storage/core.entity_registry @@ -1,34 +1,21 @@ { "version": 1, - "minor_version": 8, + "minor_version": 15, "key": "core.entity_registry", "data": { "entities": [ - { - "area_id": null, - "capabilities": null, - "config_entry_id": null, - "device_class": null, - "device_id": null, - "disabled_by": null, - "entity_category": null, - "entity_id": "person.test_user", - "hidden_by": null, - "icon": null, - "id": "e6de57e591560fad68f1c3b52bf0b295", - "has_entity_name": false, - "name": null, - "options": {}, - "original_device_class": null, - "original_icon": null, - "original_name": "Test User", - "platform": "person", - "supported_features": 0, - "unique_id": "test_user", - "unit_of_measurement": null, - "translation_key": null, - "aliases": [] - } - ] + {"aliases":[],"area_id":null,"categories":{},"capabilities":null,"config_entry_id":null,"created_at":"1970-01-01T00:00:00+00:00","device_class":null,"device_id":null,"disabled_by":null,"entity_category":null,"entity_id":"person.test_user","hidden_by":null,"icon":null,"id":"e6de57e591560fad68f1c3b52bf0b295","has_entity_name":false,"labels":[],"modified_at":"1970-01-01T00:00:00+00:00","name":null,"options":{},"original_device_class":null,"original_icon":null,"original_name":"Test User","platform":"person","supported_features":0,"translation_key":null,"unique_id":"test_user","previous_unique_id":null,"unit_of_measurement":null}, + {"aliases":[],"area_id":null,"categories":{},"capabilities":null,"config_entry_id":"5f8426fa502435857743f302651753c9","created_at":"2024-12-17T01:58:48.780004+00:00","device_class":null,"device_id":"b3d3fadee11d077d99d948c173631abc","disabled_by":null,"entity_category":"diagnostic","entity_id":"sensor.sun_next_dawn","hidden_by":null,"icon":null,"id":"d7e14c775a20078d98703325848bd203","has_entity_name":true,"labels":[],"modified_at":"2024-12-17T01:58:48.780072+00:00","name":null,"options":{},"original_device_class":"timestamp","original_icon":null,"original_name":"Next dawn","platform":"sun","supported_features":0,"translation_key":"next_dawn","unique_id":"5f8426fa502435857743f302651753c9-next_dawn","previous_unique_id":null,"unit_of_measurement":null}, + {"aliases":[],"area_id":null,"categories":{},"capabilities":null,"config_entry_id":"5f8426fa502435857743f302651753c9","created_at":"2024-12-17T01:58:48.780360+00:00","device_class":null,"device_id":"b3d3fadee11d077d99d948c173631abc","disabled_by":null,"entity_category":"diagnostic","entity_id":"sensor.sun_next_dusk","hidden_by":null,"icon":null,"id":"e1a35874a09227109783c6d1301a10cf","has_entity_name":true,"labels":[],"modified_at":"2024-12-17T01:58:48.780404+00:00","name":null,"options":{},"original_device_class":"timestamp","original_icon":null,"original_name":"Next dusk","platform":"sun","supported_features":0,"translation_key":"next_dusk","unique_id":"5f8426fa502435857743f302651753c9-next_dusk","previous_unique_id":null,"unit_of_measurement":null}, + {"aliases":[],"area_id":null,"categories":{},"capabilities":null,"config_entry_id":"5f8426fa502435857743f302651753c9","created_at":"2024-12-17T01:58:48.780603+00:00","device_class":null,"device_id":"b3d3fadee11d077d99d948c173631abc","disabled_by":null,"entity_category":"diagnostic","entity_id":"sensor.sun_next_midnight","hidden_by":null,"icon":null,"id":"9ec059b13886380e2d5d4a9002e89879","has_entity_name":true,"labels":[],"modified_at":"2024-12-17T01:58:48.780633+00:00","name":null,"options":{},"original_device_class":"timestamp","original_icon":null,"original_name":"Next midnight","platform":"sun","supported_features":0,"translation_key":"next_midnight","unique_id":"5f8426fa502435857743f302651753c9-next_midnight","previous_unique_id":null,"unit_of_measurement":null}, + {"aliases":[],"area_id":null,"categories":{},"capabilities":null,"config_entry_id":"5f8426fa502435857743f302651753c9","created_at":"2024-12-17T01:58:48.780796+00:00","device_class":null,"device_id":"b3d3fadee11d077d99d948c173631abc","disabled_by":null,"entity_category":"diagnostic","entity_id":"sensor.sun_next_noon","hidden_by":null,"icon":null,"id":"04bc6ee0081da1191cb27e2ee5842148","has_entity_name":true,"labels":[],"modified_at":"2024-12-17T01:58:48.780832+00:00","name":null,"options":{},"original_device_class":"timestamp","original_icon":null,"original_name":"Next noon","platform":"sun","supported_features":0,"translation_key":"next_noon","unique_id":"5f8426fa502435857743f302651753c9-next_noon","previous_unique_id":null,"unit_of_measurement":null}, + {"aliases":[],"area_id":null,"categories":{},"capabilities":null,"config_entry_id":"5f8426fa502435857743f302651753c9","created_at":"2024-12-17T01:58:48.780976+00:00","device_class":null,"device_id":"b3d3fadee11d077d99d948c173631abc","disabled_by":null,"entity_category":"diagnostic","entity_id":"sensor.sun_next_rising","hidden_by":null,"icon":null,"id":"ec87eec8ebe365ec0cd1119962627570","has_entity_name":true,"labels":[],"modified_at":"2024-12-17T01:58:48.781008+00:00","name":null,"options":{},"original_device_class":"timestamp","original_icon":null,"original_name":"Next rising","platform":"sun","supported_features":0,"translation_key":"next_rising","unique_id":"5f8426fa502435857743f302651753c9-next_rising","previous_unique_id":null,"unit_of_measurement":null}, + {"aliases":[],"area_id":null,"categories":{},"capabilities":null,"config_entry_id":"5f8426fa502435857743f302651753c9","created_at":"2024-12-17T01:58:48.781147+00:00","device_class":null,"device_id":"b3d3fadee11d077d99d948c173631abc","disabled_by":null,"entity_category":"diagnostic","entity_id":"sensor.sun_next_setting","hidden_by":null,"icon":null,"id":"594acbedee290ea279f1d63cf6efbb3b","has_entity_name":true,"labels":[],"modified_at":"2024-12-17T01:58:48.781172+00:00","name":null,"options":{},"original_device_class":"timestamp","original_icon":null,"original_name":"Next setting","platform":"sun","supported_features":0,"translation_key":"next_setting","unique_id":"5f8426fa502435857743f302651753c9-next_setting","previous_unique_id":null,"unit_of_measurement":null}, + {"aliases":[],"area_id":null,"categories":{},"capabilities":{"state_class":"measurement"},"config_entry_id":"5f8426fa502435857743f302651753c9","created_at":"2024-12-17T01:58:48.781300+00:00","device_class":null,"device_id":"b3d3fadee11d077d99d948c173631abc","disabled_by":"integration","entity_category":"diagnostic","entity_id":"sensor.sun_solar_elevation","hidden_by":null,"icon":null,"id":"0df1d222a5fedd8d5e62a2ff2be79094","has_entity_name":true,"labels":[],"modified_at":"2024-12-17T01:58:48.781322+00:00","name":null,"options":{},"original_device_class":null,"original_icon":null,"original_name":"Solar elevation","platform":"sun","supported_features":0,"translation_key":"solar_elevation","unique_id":"5f8426fa502435857743f302651753c9-solar_elevation","previous_unique_id":null,"unit_of_measurement":"°"}, + {"aliases":[],"area_id":null,"categories":{},"capabilities":{"state_class":"measurement"},"config_entry_id":"5f8426fa502435857743f302651753c9","created_at":"2024-12-17T01:58:48.781397+00:00","device_class":null,"device_id":"b3d3fadee11d077d99d948c173631abc","disabled_by":"integration","entity_category":"diagnostic","entity_id":"sensor.sun_solar_azimuth","hidden_by":null,"icon":null,"id":"104de75b64e950af398ec4fe9998338c","has_entity_name":true,"labels":[],"modified_at":"2024-12-17T01:58:48.781417+00:00","name":null,"options":{},"original_device_class":null,"original_icon":null,"original_name":"Solar azimuth","platform":"sun","supported_features":0,"translation_key":"solar_azimuth","unique_id":"5f8426fa502435857743f302651753c9-solar_azimuth","previous_unique_id":null,"unit_of_measurement":"°"}, + {"aliases":[],"area_id":null,"categories":{},"capabilities":null,"config_entry_id":"5f8426fa502435857743f302651753c9","created_at":"2024-12-17T01:58:48.781483+00:00","device_class":null,"device_id":"b3d3fadee11d077d99d948c173631abc","disabled_by":"integration","entity_category":"diagnostic","entity_id":"sensor.sun_solar_rising","hidden_by":null,"icon":null,"id":"888993d87923ff452ad1e53f6b3a943f","has_entity_name":true,"labels":[],"modified_at":"2024-12-17T01:58:48.781506+00:00","name":null,"options":{},"original_device_class":null,"original_icon":null,"original_name":"Solar rising","platform":"sun","supported_features":0,"translation_key":"solar_rising","unique_id":"5f8426fa502435857743f302651753c9-solar_rising","previous_unique_id":null,"unit_of_measurement":null}, + {"aliases":[],"area_id":null,"categories":{},"capabilities":null,"config_entry_id":"01JFJGH76SD417XC4YJTG8QJWB","created_at":"2024-12-20T17:06:07.780994+00:00","device_class":null,"device_id":"6586dbdeb10a88ae4603b47e600c6124","disabled_by":null,"entity_category":null,"entity_id":"weather.forecast_home","hidden_by":null,"icon":null,"id":"6ae588672696a2601b0152f22ee235f3","has_entity_name":true,"labels":[],"modified_at":"2024-12-20T17:06:07.781055+00:00","name":null,"options":{},"original_device_class":null,"original_icon":null,"original_name":"Home","platform":"met","supported_features":3,"translation_key":null,"unique_id":"52.3731339-4.8903147","previous_unique_id":null,"unit_of_measurement":null} + ], + "deleted_entities": [] } } \ No newline at end of file diff --git a/volumes/config/.storage/core.restore_state b/volumes/config/.storage/core.restore_state index 32d5f535..803d1e2c 100644 --- a/volumes/config/.storage/core.restore_state +++ b/volumes/config/.storage/core.restore_state @@ -4,25 +4,9 @@ "key": "core.restore_state", "data": [ { - "state": { - "entity_id": "person.test_user", - "state": "unknown", - "attributes": { - "editable": true, - "id": "test_user", - "user_id": "e85fc7b7b8924dc9b024ce90ad23799e", - "friendly_name": "Test User" - }, - "last_changed": "2023-03-11T22:49:37.025955+00:00", - "last_updated": "2023-03-11T22:49:38.001160+00:00", - "context": { - "id": "01GV9DV0YHA545RWX832E5W3XW", - "parent_id": null, - "user_id": null - } - }, + "state": {"entity_id":"person.test_user","state":"unknown","attributes":{"editable":true,"id":"test_user","device_trackers":[],"user_id":"e85fc7b7b8924dc9b024ce90ad23799e","friendly_name":"Test User"},"last_changed":"2024-12-20T16:29:12.365217+00:00","last_reported":"2024-12-20T16:29:13.064958+00:00","last_updated":"2024-12-20T16:29:13.064958+00:00","context":{"id":"01JFJEDMZ89PTCR8TSCGBK4PP6","parent_id":null,"user_id":null}}, "extra_data": null, - "last_seen": "2023-03-11T22:49:38.002949+00:00" + "last_seen": "2024-12-22T02:24:58.979320+00:00" } ] } \ No newline at end of file diff --git a/volumes/config/.storage/frontend.user_data_e85fc7b7b8924dc9b024ce90ad23799e b/volumes/config/.storage/frontend.user_data_e85fc7b7b8924dc9b024ce90ad23799e index 2a759c58..97f5c02c 100644 --- a/volumes/config/.storage/frontend.user_data_e85fc7b7b8924dc9b024ce90ad23799e +++ b/volumes/config/.storage/frontend.user_data_e85fc7b7b8924dc9b024ce90ad23799e @@ -7,6 +7,8 @@ "language": "en", "number_format": "language", "time_format": "language", + "date_format": "language", + "time_zone": "local", "first_weekday": "language" } } diff --git a/volumes/config/.storage/http b/volumes/config/.storage/http index b517dacf..06543f78 100644 --- a/volumes/config/.storage/http +++ b/volumes/config/.storage/http @@ -7,12 +7,17 @@ "127.0.0.1" ], "use_x_forwarded_for": true, - "ssl_profile": "modern", - "ip_ban_enabled": true, "login_attempts_threshold": -1, - "server_port": 8123, + "server_host": [ + "0.0.0.0", + "::" + ], + "ip_ban_enabled": true, + "use_x_frame_options": true, "cors_allowed_origins": [ "https://cast.home-assistant.io" - ] + ], + "server_port": 8123, + "ssl_profile": "modern" } } \ No newline at end of file diff --git a/volumes/config/configuration.yaml b/volumes/config/configuration.yaml index 86776625..0db73a01 100644 --- a/volumes/config/configuration.yaml +++ b/volumes/config/configuration.yaml @@ -12,6 +12,7 @@ trace: counter: tag: notify: +weather: logger: default: info