Skip to content

Commit

Permalink
Merge pull request #3067 from Mikhail5555/move-away-from-homeassistan…
Browse files Browse the repository at this point in the history
…t-api

use normal async client for api calls
  • Loading branch information
vabene1111 authored Mar 28, 2024
2 parents be388b0 + 41a4485 commit b623abf
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 29 deletions.
74 changes: 46 additions & 28 deletions cookbook/connectors/homeassistant.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,53 @@
import logging
from logging import Logger
from typing import Dict, Tuple
from urllib.parse import urljoin

from homeassistant_api import Client, HomeassistantAPIError, Domain
from aiohttp import ClientError, request

from cookbook.connectors.connector import Connector
from cookbook.models import ShoppingListEntry, ConnectorConfig, Space


class HomeAssistant(Connector):
_domains_cache: dict[str, Domain]
_config: ConnectorConfig
_logger: Logger
_client: Client

def __init__(self, config: ConnectorConfig):
if not config.token or not config.url or not config.todo_entity:
raise ValueError("config for HomeAssistantConnector in incomplete")

self._domains_cache = dict()
if config.url[-1] != "/":
config.url += "/"
self._config = config
self._logger = logging.getLogger("connector.HomeAssistant")
self._client = Client(self._config.url, self._config.token, async_cache_session=False, use_async=True)

async def homeassistant_api_call(self, method: str, path: str, data: Dict) -> str:
headers = {
"Authorization": f"Bearer {self._config.token}",
"Content-Type": "application/json"
}
async with request(method, urljoin(self._config.url, path), headers=headers, json=data) as response:
response.raise_for_status()
return await response.json()

async def on_shopping_list_entry_created(self, space: Space, shopping_list_entry: ShoppingListEntry) -> None:
if not self._config.on_shopping_list_entry_created_enabled:
return

item, description = _format_shopping_list_entry(shopping_list_entry)

todo_domain = self._domains_cache.get('todo')
try:
if todo_domain is None:
todo_domain = await self._client.async_get_domain('todo')
self._domains_cache['todo'] = todo_domain
logging.debug(f"adding {item=} to {self._config.name}")

data = {
"entity_id": self._config.todo_entity,
"item": item,
"description": description,
}

logging.debug(f"pushing {item} to {self._config.name}")
await todo_domain.add_item(entity_id=self._config.todo_entity, item=item)
except HomeassistantAPIError as err:
try:
await self.homeassistant_api_call("POST", "services/todo/add_item", data)
except ClientError as err:
self._logger.warning(f"[HomeAssistant {self._config.name}] Received an exception from the api: {err=}, {type(err)=}")

async def on_shopping_list_entry_updated(self, space: Space, shopping_list_entry: ShoppingListEntry) -> None:
Expand All @@ -48,24 +59,31 @@ async def on_shopping_list_entry_deleted(self, space: Space, shopping_list_entry
if not self._config.on_shopping_list_entry_deleted_enabled:
return

item, description = _format_shopping_list_entry(shopping_list_entry)
if not hasattr(shopping_list_entry._state.fields_cache, "food"):
# Sometimes the food foreign key is not loaded, and we cant load it from an async process
self._logger.debug("required property was not present in ShoppingListEntry")
return

todo_domain = self._domains_cache.get('todo')
try:
if todo_domain is None:
todo_domain = await self._client.async_get_domain('todo')
self._domains_cache['todo'] = todo_domain
item, _ = _format_shopping_list_entry(shopping_list_entry)

logging.debug(f"deleting {item} from {self._config.name}")
await todo_domain.remove_item(entity_id=self._config.todo_entity, item=item)
except HomeassistantAPIError as err:
self._logger.warning(f"[HomeAssistant {self._config.name}] Received an exception from the api: {err=}, {type(err)=}")
logging.debug(f"removing {item=} from {self._config.name}")

data = {
"entity_id": self._config.todo_entity,
"item": item,
}

try:
await self.homeassistant_api_call("POST", "services/todo/remove_item", data)
except ClientError as err:
# This error will always trigger if the item is not present/found
self._logger.debug(f"[HomeAssistant {self._config.name}] Received an exception from the api: {err=}, {type(err)=}")

async def close(self) -> None:
await self._client.async_cache_session.close()
pass


def _format_shopping_list_entry(shopping_list_entry: ShoppingListEntry):
def _format_shopping_list_entry(shopping_list_entry: ShoppingListEntry) -> Tuple[str, str]:
item = shopping_list_entry.food.name
if shopping_list_entry.amount > 0:
item += f" ({shopping_list_entry.amount:.2f}".rstrip('0').rstrip('.')
Expand All @@ -76,10 +94,10 @@ def _format_shopping_list_entry(shopping_list_entry: ShoppingListEntry):
else:
item += ")"

description = "Imported by TandoorRecipes"
description = "From TandoorRecipes"
if shopping_list_entry.created_by.first_name and len(shopping_list_entry.created_by.first_name) > 0:
description += f", created by {shopping_list_entry.created_by.first_name}"
description += f", by {shopping_list_entry.created_by.first_name}"
else:
description += f", created by {shopping_list_entry.created_by.username}"
description += f", by {shopping_list_entry.created_by.username}"

return item, description
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ django-auth-ldap==4.6.0
pyppeteer==2.0.0
validators==0.20.0
pytube==15.0.0
homeassistant-api==4.1.1.post2
aiohttp==3.9.3

# Development
pytest==8.0.0
Expand Down

0 comments on commit b623abf

Please sign in to comment.