Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/pydantic-hass' into pydantic-hass
Browse files Browse the repository at this point in the history
  • Loading branch information
jsl12 committed Feb 4, 2025
2 parents 4b98de4 + 0d88ef5 commit a3c0cf1
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 36 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
// "remoteUser": "devcontainer"

"mounts": [
"source=${localEnv:HOME}${localEnv:USERPROFILE}/production/,target=/conf,type=bind,consistency=cached"
//"source=${localEnv:HOME}${localEnv:USERPROFILE}/production/,target=/conf,type=bind,consistency=cached"
"source=${localEnv:HOME}${localEnv:USERPROFILE}/appdir_test/,target=/conf,type=bind,consistency=cached"
],

// Configure tool-specific properties.
Expand Down
8 changes: 8 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
"justMyCode": true,
"args": "-c /conf/ad_config/dev_test"
},
{
"name": "AppDaemon Appdir Test",
"type": "debugpy",
"request": "launch",
"module": "appdaemon",
"justMyCode": true,
"args": "-c /conf"
},
{
"name": "AppDaemon Production",
"type": "debugpy",
Expand Down
64 changes: 39 additions & 25 deletions appdaemon/app_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from functools import partial, reduce, wraps
from logging import Logger
from pathlib import Path
from typing import TYPE_CHECKING, Iterable, Literal, Set, Union
from typing import TYPE_CHECKING, Iterable, Literal, Set, Union, Generator

from pydantic import ValidationError

Expand Down Expand Up @@ -282,13 +282,15 @@ async def initialize_app(self, app_name: str):
app_cfg = self.app_config[app_name]
module_path = Path(sys.modules[app_cfg.module_name].__file__)
rel_path = module_path.relative_to(self.AD.app_dir.parent)
raise ade.NoInitializeMethod(f"Class {app_cfg.class_name} in {
rel_path} does not have an initialize method")
raise ade.NoInitializeMethod(
f"Class {app_cfg.class_name} in {rel_path} does not have an initialize method"
)

if utils.count_positional_arguments(init_func) != 0:
class_name = self.app_config[app_name].class_name
raise ade.BadInitializeMethod(
f"Wrong number of arguments for initialize method of {class_name}")
f"Wrong number of arguments for initialize method of {class_name}"
)

# Call its initialize function
await self.set_state(app_name, state="initializing")
Expand All @@ -301,8 +303,10 @@ async def initialize_app(self, app_name: str):
await self.increase_active_apps(app_name)
await self.set_state(app_name, state="idle")

event_data = {"event_type": "app_initialized",
"data": {"app": app_name}}
event_data = {
"event_type": "app_initialized",
"data": {"app": app_name}
}
await self.AD.events.process_event("admin", event_data)

async def terminate_app(self, app_name: str, delete: bool = True) -> bool:
Expand Down Expand Up @@ -405,10 +409,11 @@ async def start_app(self, app_name: str):
else:
try:
await self.initialize_app(app_name)
except Exception:
except Exception as e:
await self.set_state(app_name, state="initialize_error")
self.objects[app_name].running = False
raise
self.logger.warning(f"App '{app_name}' failed to start")
raise ade.AppInitializeError(f"Error during initialize method for '{app_name}'") from e
else:
self.objects[app_name].running = True

Expand Down Expand Up @@ -439,7 +444,10 @@ async def stop_app(self, app_name: str, delete: bool = False) -> bool:

async def restart_app(self, app):
await self.stop_app(app, delete=False)
await self.start_app(app)
try:
await self.start_app(app)
except (ade.AppDependencyError, ade.AppInitializeError) as e:
self.logger.warning(e)

def get_app_debug_level(self, name: str):
if obj := self.objects.get(name):
Expand Down Expand Up @@ -482,29 +490,34 @@ async def create_app_object(self, app_name: str) -> None:
pin = -1

# This module should already be loaded and stored in sys.modules
mod_obj = await utils.run_in_executor(self, importlib.import_module, module_name)
try:
mod_obj = await utils.run_in_executor(self, importlib.import_module, module_name)
except ModuleNotFoundError as exc:
rel_path = self.app_rel_path(app_name)
raise ade.AppModuleNotFound(
f"Unable to import '{module_name}' as defined for app '{app_name}' in {rel_path}"
) from exc

try:
app_class = getattr(mod_obj, class_name)
except AttributeError as exc:
raise ade.AppClassNotFound(
f"Unable to find '{class_name}' in module '{
mod_obj.__file__}' as defined in app '{app_name}'"
f"Unable to find '{class_name}' in module '{mod_obj.__file__}' as defined in app '{app_name}'"
) from exc

if utils.count_positional_arguments(app_class.__init__) != 3:
raise ade.AppClassSignatureError(
f"Class '{
class_name}' takes the wrong number of arguments. Check the inheritance"
f"Class '{class_name}' takes the wrong number of arguments. Check the inheritance"
)

try:
new_obj = app_class(self.AD, cfg)
except Exception as exc:
await self.set_state(app_name, state="compile_error")
await self.increase_inactive_apps(app_name)
raise ade.AppInstantiationError(f"Error when creating class '{
class_name}' for app named '{app_name}'") from exc
raise ade.AppInstantiationError(
f"Error when creating class '{class_name}' for app named '{app_name}'"
) from exc
else:
self.objects[app_name] = ManagedObject(
type="app",
Expand Down Expand Up @@ -1025,17 +1038,12 @@ async def _start_apps(self, update_actions: UpdateActions):
self.AD.app_dir.parent)

@utils.warning_decorator(
error_text=f"Error creating the app object for '{
app_name}' from {rel_path}"
error_text=f"Error creating the app object for '{app_name}' from {rel_path}"
)
async def safe_create(self: "AppManagement"):
try:
await self.create_app_object(app_name)
except ModuleNotFoundError as e:
update_actions.apps.failed.add(app_name)
self.logger.warning(
f"Failed to import module for '{app_name}': {e}")
except Exception:
except Exception as e:
update_actions.apps.failed.add(app_name)
raise # any exceptions will be handled by the warning_decorator

Expand Down Expand Up @@ -1086,7 +1094,7 @@ async def _import_modules(self, update_actions: UpdateActions) -> Set[str]:
async def safe_import(self: "AppManagement"):
try:
await self.import_module(module_name)
except Exception:
except Exception as e:
dm: DependencyManager = self.dependency_manager
update_actions.modules.failed |= dm.dependent_modules(
module_name)
Expand All @@ -1095,7 +1103,13 @@ async def safe_import(self: "AppManagement"):
for app_name in update_actions.apps.failed:
await self.set_state(app_name, state="compile_error")
await self.increase_inactive_apps(app_name)
raise

# Handle this down here to avoid having to repeat all the above logic for
# other exceptions.
if isinstance(e, ModuleNotFoundError):
raise ade.AppModuleNotFound(f"Unable to import '{module_name}'") from e
else:
raise

await safe_import(self)

Expand Down
4 changes: 2 additions & 2 deletions appdaemon/plugins/hass/hassplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ async def http_method(
return await resp.text()
else:
return await resp.json()
case 400 | 401 | 403| 404 | 405:
case 400 | 401 | 403 | 404 | 405:
try:
msg = (await resp.json())["message"]
except Exception:
Expand Down Expand Up @@ -788,7 +788,7 @@ async def get_logbook(

result = [
{
k: v if k!= "when" else (
k: v if k != "when" else (
datetime
.datetime
.fromisoformat(v)
Expand Down
19 changes: 11 additions & 8 deletions appdaemon/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,23 +368,26 @@ async def wrapper(self, *args, **kwargs):
else:
result = func(self, *args, **kwargs)
except SyntaxError as e:
logger.warning(f'Syntax error: {e}')
log_warning_block(
error_logger,
header=error_text,
exception_text=''.join(traceback.format_exception(e, limit=-1))
)
except ModuleNotFoundError as e:
logger.warning(f'Module not found: {e}')
logger.warning(error_text)
log_warning_block(
error_logger,
header=error_text,
exception_text=''.join(traceback.format_exception(e, limit=-1))
)
...
except ade.AppDependencyError as e:
logger.warning(f'Dependency error: {e}')
if self.AD.logging.separate_error_log():
error_logger.warning(f'Dependency error: {e}')
...
except (ade.AppInstantiationError, ade.AppInitializeError, ade.AppModuleNotFound) as e:
logger.warning(e)
log_warning_block(
error_logger,
header=error_text,
exception_text=''.join(traceback.format_exception(e, limit=-2))
)
...
except ValidationError as e:
log_warning_block(
error_logger,
Expand Down

0 comments on commit a3c0cf1

Please sign in to comment.