diff --git a/homeassistant_api/rawasyncclient.py b/homeassistant_api/rawasyncclient.py index 9fa3ee2f..39aeb3e2 100644 --- a/homeassistant_api/rawasyncclient.py +++ b/homeassistant_api/rawasyncclient.py @@ -118,11 +118,17 @@ async def async_response_logic(response: AsyncResponseType) -> Any: # API information methods async def async_get_error_log(self) -> str: - """Returns the server error log as a string""" + """ + Returns the server error log as a string. + :code:`GET /api/error_log` + """ return cast(str, await self.async_request("error_log")) async def async_get_config(self) -> Dict[str, Any]: - """Returns the yaml configuration of homeassistant""" + """ + Returns the yaml configuration of homeassistant. + :code:`GET /api/config` + """ return cast(Dict[str, Any], await self.async_request("config")) async def async_get_logbook_entries( @@ -130,7 +136,10 @@ async def async_get_logbook_entries( *args, **kwargs, ) -> AsyncGenerator[LogbookEntry, None]: - """Returns a list of logbook entries from homeassistant.""" + """ + Returns a list of logbook entries from homeassistant. + :code:`GET /api/logbook/` + """ params, url = self.prepare_get_logbook_entry_params(*args, **kwargs) data = await self.async_request(url, params=params) for entry in data: @@ -146,6 +155,7 @@ async def async_get_entity_histories( ) -> AsyncGenerator[History, None]: """ Returns a generator of entity state histories from homeassistant. + :code:`GET /api/history/period/` """ params, url = self.prepare_get_entity_histories_params( entities=entities, @@ -161,7 +171,10 @@ async def async_get_entity_histories( yield History.parse_obj({"states": states}) async def async_get_rendered_template(self, template: str) -> str: - """Renders a given Jinja2 template string with Home Assistant context data.""" + """ + Renders a given Jinja2 template string with Home Assistant context data. + :code:`POST /api/template` + """ try: return cast(str, await self.async_request( "template", @@ -183,7 +196,10 @@ async def async_get_discovery_info() -> Dict[str, Any]: # API check methods async def async_check_api_config(self) -> bool: - """Asks Home Assistant to validate its configuration file and returns true/false.""" + """ + Asks Home Assistant to validate its configuration file and returns true/false. + :code:`POST /api/config/core/check_config` + """ res = await self.async_request("config/core/check_config", method="POST") res = cast(Dict[Any, Any], res) valid = {"valid": True, "invalid": False}.get( @@ -196,20 +212,26 @@ async def async_check_api_config(self) -> bool: return valid async def async_check_api_running(self) -> bool: - """Asks Home Assistant if its running""" + """ + Asks Home Assistant if its running. + :code:`GET /api/` + """ res = cast(Dict[Any, Any], await self.async_request("")) return res.get("message") == "API running." # Entity methods - async def async_get_entities(self) -> Tuple[Group, ...]: - """Fetches all entities from the api""" + async def async_get_entities(self) -> Dict[str, Group]: + """ + Fetches all entities from the api. + :code:`GET /api/states` + """ entities: Dict[str, Group] = {} for state in await self.async_get_states(): group_id, entity_slug = state.entity_id.split(".") if group_id not in entities: entities[group_id] = Group(group_id=group_id, _client=self) # type: ignore[arg-type] entities[group_id]._add_entity(entity_slug, state) - return tuple(entities.values()) + return entities async def async_get_entity( self, @@ -217,7 +239,10 @@ async def async_get_entity( slug: str | None = None, entity_id: str | None = None, ) -> Optional[Entity]: - """Returns a Entity model for an :code:`entity_id`""" + """ + Returns a Entity model for an :code:`entity_id`. + :code:`GET /api/states/` + """ if group_id is not None and slug is not None: state = await self.async_get_state(group_id=group_id, slug=slug) elif entity_id is not None: @@ -237,7 +262,10 @@ async def async_get_entity( # Services and domain methods async def async_get_domains(self) -> Dict[str, Domain]: - """Fetches all services from the api""" + """ + Fetches all :py:class:`Service` 's from the API. + :code:`GET /api/services` + """ data = await self.async_request("services") domains = map( lambda json: Domain.from_json(json, client=cast(Client, self)), @@ -246,7 +274,10 @@ async def async_get_domains(self) -> Dict[str, Domain]: return {domain.domain_id: domain for domain in domains} async def async_get_domain(self, domain_id: str) -> Optional[Domain]: - """Fetches all services under a particular domain.""" + """ + Fetches all :py:class:`Service`'s under a particular service :py:class:`Domain`. + Uses cached data from :py:meth:`get_domains` if available. + """ domains = await self.async_get_domains() return domains.get(domain_id) @@ -256,7 +287,10 @@ async def async_trigger_service( service: str, **service_data: Union[Dict[str, Any], List[Any], str], ) -> Tuple[State, ...]: - """Tells Home Assistant to trigger a service, returns stats changed while being called""" + """ + Tells Home Assistant to trigger a service, returns all states changed while in the process of being called. + :code:`POST /api/services//` + """ data = await self.async_request( f"services/{domain}/{service}", method="POST", @@ -272,7 +306,10 @@ async def async_get_state( # pylint: disable=duplicate-code group_id: Optional[str] = None, slug: Optional[str] = None, ) -> State: - """Fetches the state of the entity specified.""" + """ + Fetches the state of the entity specified. + :code:`GET /api/states/` + """ target_entity_id = self.prepare_entity_id( group_id=group_id, slug=slug, @@ -285,7 +322,11 @@ async def async_set_state( # pylint: disable=duplicate-code self, state: State, ) -> State: - """Sets the state of the entity given (does not have to be a real entity) and returns the updated state""" + """ + This method sets the representation of a device within Home Assistant and will not communicate with the actual device. + To communicate with the device, use :py:meth:`Service.trigger` or :py:meth:`Service.async_trigger`. + :code:`POST /api/states/` + """ data = await self.async_request( join("states", state.entity_id), method="POST", @@ -294,13 +335,19 @@ async def async_set_state( # pylint: disable=duplicate-code return State.from_json(cast(Dict[Any, Any], data)) async def async_get_states(self) -> Tuple[State, ...]: - """Gets the states of all entities within homeassistant""" + """ + Gets the states of all entities within homeassistant. + :code:`GET /api/states` + """ data = await self.async_request("states") return tuple(map(State.from_json, cast(List[Dict[Any, Any]], data))) # Event methods async def async_get_events(self) -> Tuple[Event, ...]: - """Gets the internal events that happen within homeassistant.""" + """ + Gets the Events that happen within homeassistant + :code:`GET /api/events` + """ data = await self.async_request("events") return tuple( map( @@ -310,14 +357,20 @@ async def async_get_events(self) -> Tuple[Event, ...]: ) async def async_get_event(self, name: str) -> Optional[Event]: - """Gets the :py:class:`Event` with the specified name""" + """ + Gets the :py:class:`Event` with the specified name if it has at least one listener. + Uses cached data from :py:meth:`get_events` if available. + """ for event in await self.async_get_events(): if event.event == name.strip().lower(): return event return None async def async_fire_event(self, event_type: str, **event_data: Any) -> str: - """Fires a given event_type within homeassistant. Must be an existing event_type.""" + """ + Fires a given event_type within homeassistant. Must be an existing event_type. + :code:`POST /api/events/` + """ data = await self.async_request( join("events", event_type), method="POST", @@ -326,6 +379,9 @@ async def async_fire_event(self, event_type: str, **event_data: Any) -> str: return cast(str, data.get("message", "No message provided")) async def async_get_components(self) -> Tuple[str, ...]: - """Returns a tuple of all registered components.""" + """ + Returns a tuple of all registered components. + :code:`GET /api/components` + """ data = await self.async_request("components") return tuple(cast(List[str], data)) diff --git a/homeassistant_api/rawclient.py b/homeassistant_api/rawclient.py index 09b8c89c..79ce5812 100644 --- a/homeassistant_api/rawclient.py +++ b/homeassistant_api/rawclient.py @@ -114,11 +114,17 @@ def response_logic(cls, response: ResponseType, decode_bytes: bool = True) -> An # API information methods def get_error_log(self) -> str: - """Returns the server error log as a string.""" + """ + Returns the server error log as a string. + :code:`GET /api/error_log` + """ return cast(str, self.request("error_log")) def get_config(self) -> Dict[str, Any]: - """Returns the yaml configuration of homeassistant.""" + """ + Returns the yaml configuration of homeassistant. + :code:`GET /api/config` + """ return cast(Dict[str, Any], self.request("config")) def get_logbook_entries( @@ -126,7 +132,10 @@ def get_logbook_entries( *args, **kwargs, ) -> Generator[LogbookEntry, None, None]: - """Returns a list of logbook entries from homeassistant.""" + """ + Returns a list of logbook entries from homeassistant. + :code:`GET /api/logbook/` + """ params, url = self.prepare_get_logbook_entry_params(*args, **kwargs) data = self.request(url, params=params) for entry in data: @@ -141,7 +150,8 @@ def get_entity_histories( significant_changes_only: bool = False, ) -> Generator[History, None, None]: """ - Yields entity state histories. See docs on the `History` model. + Yields entity state histories. See docs on the :py:class:`History` model. + :code:`GET /api/history/period/` """ params, url = self.prepare_get_entity_histories_params( entities=entities, @@ -160,6 +170,7 @@ def get_rendered_template(self, template: str) -> str: """ Renders a Jinja2 template with Home Assistant context data. See https://www.home-assistant.io/docs/configuration/templating. + :code:`POST /api/template` """ try: return cast( @@ -185,7 +196,10 @@ def get_discovery_info() -> Dict[str, Any]: # API check methods def check_api_config(self) -> bool: - """Asks Home Assistant to validate its configuration file.""" + """ + Asks Home Assistant to validate its configuration file. + :code:`POST /api/config/core/check_config` + """ res = cast( Dict[str, Any], self.request("config/core/check_config", method="POST") ) @@ -193,13 +207,19 @@ def check_api_config(self) -> bool: return valid def check_api_running(self) -> bool: - """Asks Home Assistant if it is running.""" + """ + Asks Home Assistant if it is running. + :code:`GET /api/` + """ res = self.request("") return cast(Dict[str, Any], res).get("message") == "API running." # Entity methods def get_entities(self) -> Dict[str, Group]: - """Fetches all entities from the api""" + """ + Fetches all entities from the api and returns them as a dictionary of :py:class:`Group`'s. + :code:`GET /api/states` + """ entities: Dict[str, Group] = {} for state in self.get_states(): group_id, entity_slug = state.entity_id.split(".") @@ -217,7 +237,10 @@ def get_entity( slug: str | None = None, entity_id: str | None = None, ) -> Optional[Entity]: - """Returns an :py:class:`Entity` model for an :code:`entity_id`""" + """ + Returns an :py:class:`Entity` model for an :code:`entity_id`. + :code:`GET /api/states/` + """ if group_id is not None and slug is not None: state = self.get_state(group_id=group_id, slug=slug) elif entity_id is not None: @@ -240,7 +263,10 @@ def get_entity( # Services and domain methods def get_domains(self) -> Dict[str, Domain]: - """Fetches all :py:class:`Service` 's from the API.""" + """ + Fetches all :py:class:`Service` 's from the API. + :code:`GET /api/services` + """ data = self.request("services") domains = map( lambda json: Domain.from_json(json, client=cast(Client, self)), @@ -249,7 +275,10 @@ def get_domains(self) -> Dict[str, Domain]: return {domain.domain_id: domain for domain in domains} def get_domain(self, domain_id: str) -> Optional[Domain]: - """Fetches all :py:class:`Service`'s under a particular service :py:class:`Domain`.""" + """ + Fetches all :py:class:`Service`'s under a particular service :py:class:`Domain`. + Uses cached data from :py:meth:`get_domains` if available. + """ return self.get_domains().get(domain_id) def trigger_service( @@ -258,7 +287,10 @@ def trigger_service( service: str, **service_data, ) -> Tuple[State, ...]: - """Tells Home Assistant to trigger a service, returns all states changed while in the process of being called.""" + """ + Tells Home Assistant to trigger a service, returns all states changed while in the process of being called. + :code:`POST /api/services//` + """ data = self.request( join("services", domain, service), method="POST", @@ -274,7 +306,10 @@ def get_state( # pylint: disable=duplicate-code group_id: Optional[str] = None, slug: Optional[str] = None, ) -> State: - """Fetches the state of the entity specified""" + """ + Fetches the state of the entity specified. + :code:`GET /api/states/` + """ entity_id = self.prepare_entity_id( group_id=group_id, slug=slug, @@ -289,7 +324,8 @@ def set_state( # pylint: disable=duplicate-code ) -> State: """ This method sets the representation of a device within Home Assistant and will not communicate with the actual device. - To communicate with the device, use :py:meth:`Service.trigger` or :py:meth:`Service.async_trigger` + To communicate with the device, use :py:meth:`Service.trigger` or :py:meth:`Service.async_trigger`. + :code:`POST /api/states/` """ data = self.request( join("states", state.entity_id), @@ -299,14 +335,20 @@ def set_state( # pylint: disable=duplicate-code return State.from_json(cast(Dict[str, Any], data)) def get_states(self) -> Tuple[State, ...]: - """Gets the states of all entities within homeassistant""" + """ + Gets the states of all entities within homeassistant. + :code:`GET /api/states` + """ data = self.request("states") states = map(State.from_json, cast(List[Dict[str, Any]], data)) return tuple(states) # Event methods def get_events(self) -> Tuple[Event, ...]: - """Gets the Events that happen within homeassistant""" + """ + Gets the Events that happen within homeassistant + :code:`GET /api/events` + """ data = self.request("events") return tuple( map( @@ -316,14 +358,20 @@ def get_events(self) -> Tuple[Event, ...]: ) def get_event(self, name: str) -> Optional[Event]: - """Gets the :py:class:`Event` with the specified name if it has at least one listener.""" + """ + Gets the :py:class:`Event` with the specified name if it has at least one listener. + Uses cached data from :py:meth:`get_events` if available. + """ for event in self.get_events(): if event.event == name.strip().lower(): return event return None def fire_event(self, event_type: str, **event_data) -> Optional[str]: - """Fires a given event_type within homeassistant. Must be an existing event_type.""" + """ + Fires a given event_type within homeassistant. Must be an existing event_type. + `POST /api/events/` + """ data = self.request( join("events", event_type), method="POST", @@ -332,5 +380,8 @@ def fire_event(self, event_type: str, **event_data) -> Optional[str]: return cast(dict[str, Any], data).get("message") def get_components(self) -> Tuple[str, ...]: - """Returns a tuple of all registered components.""" + """ + Returns a tuple of all registered components. + :code:`GET /api/components` + """ return tuple(self.request("components")) diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 3d86ff15..6175961b 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -120,7 +120,7 @@ def test_get_entities(cached_client: Client) -> None: async def test_async_get_entities(async_cached_client: Client) -> None: """Tests the `GET /api/states` endpoint.""" entities = await async_cached_client.async_get_entities() - assert any(group.group_id == "sun" for group in entities) + assert "sun" in entities def test_get_domains(cached_client: Client) -> None: