Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow HomeAssistant Connector to work with Debug=False (and logging improvements) #3302

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions cookbook/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,11 @@ def ready(self):
import cookbook.signals # noqa

if not settings.DISABLE_EXTERNAL_CONNECTORS:
try:
from cookbook.connectors.connector_manager import ConnectorManager # Needs to be here to prevent loading race condition of oauth2 modules in models.py
handler = ConnectorManager()
post_save.connect(handler, dispatch_uid="connector_manager")
post_delete.connect(handler, dispatch_uid="connector_manager")
except Exception as e:
traceback.print_exc()
print('Failed to initialize connectors')
pass
from cookbook.connectors.connector_manager import ConnectorManager # Needs to be here to prevent loading race condition of oauth2 modules in models.py
handler = ConnectorManager()
post_save.connect(handler, dispatch_uid="post_save-connector_manager")
post_delete.connect(handler, dispatch_uid="post_delete-connector_manager")

# if not settings.DISABLE_TREE_FIX_STARTUP:
# # when starting up run fix_tree to:
# # a) make sure that nodes are sorted when switching between sort modes
Expand All @@ -45,4 +41,4 @@ def ready(self):
# except Exception:
# if DEBUG:
# traceback.print_exc()
# pass # dont break startup just because fix could not run, need to investigate cases when this happens
# pass # dont break startup just because fix could not run, need to investigate cases when this happens
12 changes: 11 additions & 1 deletion cookbook/connectors/connector_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ class Work:
actionType: ActionType


class Singleton(type):
_instances = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]


# The way ConnectionManager works is as follows:
# 1. On init, it starts a worker & creates a queue for 'Work'
# 2. Then any time its called, it verifies the type of action (create/update/delete) and if the item is of interest, pushes the Work (non-blocking) to the queue.
Expand All @@ -39,7 +48,8 @@ class Work:
# 3.2 If work is of type REGISTERED_CLASSES, it asynchronously fires of all connectors and wait for them to finish (runtime should depend on the 'slowest' connector)
# 4. Work is marked as consumed, and next entry of the queue is consumed.
# Each 'Work' is processed in sequential by the worker, so the throughput is about [workers * the slowest connector]
class ConnectorManager:
# The Singleton class is used for ConnectorManager to have a self-reference and so Python does not garbage collect it
class ConnectorManager(metaclass=Singleton):
_logger: Logger
_queue: queue.Queue
_listening_to_classes = REGISTERED_CLASSES | ConnectorConfig
Expand Down
18 changes: 10 additions & 8 deletions cookbook/connectors/homeassistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Dict, Tuple
from urllib.parse import urljoin

from aiohttp import ClientError, request
from aiohttp import request, ClientResponseError

from cookbook.connectors.connector import Connector
from cookbook.models import ShoppingListEntry, ConnectorConfig, Space
Expand All @@ -13,6 +13,8 @@ class HomeAssistant(Connector):
_config: ConnectorConfig
_logger: Logger

_required_foreign_keys = ("food", "unit", "created_by")

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")
Expand All @@ -38,7 +40,7 @@ async def on_shopping_list_entry_created(self, space: Space, shopping_list_entry

item, description = _format_shopping_list_entry(shopping_list_entry)

self._logger.debug(f"adding {item=}")
self._logger.debug(f"adding {item=} with {description=} to {self._config.todo_entity}")

data = {
"entity_id": self._config.todo_entity,
Expand All @@ -50,8 +52,8 @@ async def on_shopping_list_entry_created(self, space: Space, shopping_list_entry

try:
await self.homeassistant_api_call("POST", "services/todo/add_item", data)
except ClientError as err:
self._logger.warning(f"received an exception from the api: {err=}, {type(err)=} {data=}")
except ClientResponseError as err:
self._logger.warning(f"received an exception from the api: {err.request_info.url=}, {err.request_info.method=}, {err.status=}, {err.message=}, {type(err)=}")

async def on_shopping_list_entry_updated(self, space: Space, shopping_list_entry: ShoppingListEntry) -> None:
if not self._config.on_shopping_list_entry_updated_enabled:
Expand All @@ -62,14 +64,14 @@ async def on_shopping_list_entry_deleted(self, space: Space, shopping_list_entry
if not self._config.on_shopping_list_entry_deleted_enabled:
return

if not hasattr(shopping_list_entry._state.fields_cache, "food"):
if not all(k in shopping_list_entry._state.fields_cache for k in self._required_foreign_keys):
# 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

item, _ = _format_shopping_list_entry(shopping_list_entry)

self._logger.debug(f"removing {item=}")
self._logger.debug(f"removing {item=} from {self._config.todo_entity}")

data = {
"entity_id": self._config.todo_entity,
Expand All @@ -78,9 +80,9 @@ async def on_shopping_list_entry_deleted(self, space: Space, shopping_list_entry

try:
await self.homeassistant_api_call("POST", "services/todo/remove_item", data)
except ClientError as err:
except ClientResponseError as err:
# This error will always trigger if the item is not present/found
self._logger.debug(f"received an exception from the api: {err=}, {type(err)=}")
self._logger.debug(f"received an exception from the api: {err.request_info.url=}, {err.request_info.method=}, {err.status=}, {err.message=}, {type(err)=}")

async def close(self) -> None:
pass
Expand Down
Loading