Skip to content

Commit

Permalink
Merge branch 'pydantic-hass' of https://github.com/AppDaemon/appdaemon
Browse files Browse the repository at this point in the history
…into pydantic-hass
  • Loading branch information
acockburn committed Feb 4, 2025
2 parents 43d1eef + e892502 commit e1b805b
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 213 deletions.
12 changes: 8 additions & 4 deletions appdaemon/adapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,9 @@ async def get_app(self, name: str) -> 'ADAPI':

def _check_entity(self, namespace: str, entity_id: str):
"""Ensures that the entity exists in the given namespace"""
if "." in entity_id and not self.AD.state.entity_exists(namespace, entity_id):
if entity_id is not None and \
"." in entity_id and \
not self.AD.state.entity_exists(namespace, entity_id):
self.logger.warning("%s: Entity %s not found in namespace %s", self.name, entity_id, namespace)

@staticmethod
Expand Down Expand Up @@ -1360,7 +1362,7 @@ async def listen_state(
async def listen_state(
self,
callback: Callable,
entity_id: str | Iterable[str],
entity_id: str | Iterable[str] | None = None,
namespace: str | None = None,
**kwargs
) -> str | list[str]:
Expand Down Expand Up @@ -1500,7 +1502,7 @@ async def listen_state(
namespace = namespace or self.namespace

match entity_id:
case str():
case str() | None:
self._check_entity(namespace, entity_id)
return await self.get_entity_api(namespace, entity_id).listen_state(callback, **kwargs)
case Iterable():
Expand Down Expand Up @@ -2068,13 +2070,15 @@ async def listen_event(
namespace = namespace or self.namespace

match event:
case str():
case str() | None:
return await self.AD.events.add_event_callback(self.name, namespace, callback, event, **kwargs)
case Iterable():
return [
await self.AD.events.add_event_callback(self.name, namespace, callback, e, **kwargs)
for e in event
]
case _:
self.logger.warning(f'Invalid event: {event}')

@utils.sync_decorator
async def cancel_listen_event(self, handle: str) -> bool:
Expand Down
193 changes: 113 additions & 80 deletions appdaemon/app_management.py

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions appdaemon/appdaemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from appdaemon.services import Services
from appdaemon.state import State
from appdaemon.thread_async import ThreadAsync
from appdaemon.threading import Threading
from appdaemon.threads import Threading
from appdaemon.utility_loop import Utility

if TYPE_CHECKING:
Expand Down Expand Up @@ -141,8 +141,7 @@ def __init__(self, logging: "Logging", loop: BaseEventLoop, ad_config_model: App

if self.apps is True:
assert self.config_dir is not None, "Config_dir not set. This is a development problem"
assert self.config_dir.exists(), f"{
self.config_dir} does not exist"
assert self.config_dir.exists(), f"{self.config_dir} does not exist"
assert os.access(
self.config_dir, os.R_OK | os.X_OK
), f"{self.config_dir} does not have the right permissions"
Expand Down
2 changes: 1 addition & 1 deletion appdaemon/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def gen_modules():
try:
mod: ast.Module = ast.parse(file_content, filename=file_path)
except Exception as e:
logger.error(f"{e}")
logger.warning(f"{e}")
else:
for node in get_imports(mod):
match node:
Expand Down
8 changes: 4 additions & 4 deletions appdaemon/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ class AppClassNotFound(Exception):
pass


class AppConfigNotFound(Exception):
pass


class PinOutofRange(Exception):
pass

Expand All @@ -56,6 +52,10 @@ class AppDependencyError(Exception):
pass


class AppModuleNotFound(Exception):
pass


class AppInstantiationError(Exception):
pass

Expand Down
38 changes: 21 additions & 17 deletions appdaemon/models/config/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,23 +115,27 @@ def from_config_file(cls, path: Path):

@classmethod
def from_config_files(cls, paths: Iterable[Path]):
paths = iter(paths)
cfg = read_config_file(next(paths))
for p in paths:
for new, new_cfg in read_config_file(p).items():
if new in cfg:
match new:
case "global_modules":
cfg[new].extend(new_cfg)
case "sequence":
cfg[new].update(new_cfg)
case _:
# This is the case for an app being defined more than once
# TODO: Log some kind of warning here
cfg[new].update(new_cfg)
else:
cfg[new] = new_cfg
return cls.model_validate(cfg)
try:
paths = iter(paths)
cfg = read_config_file(next(paths))
except StopIteration:
return cls()
else:
for p in paths:
for new, new_cfg in read_config_file(p).items():
if new in cfg:
match new:
case "global_modules":
cfg[new].extend(new_cfg)
case "sequence":
cfg[new].update(new_cfg)
case _:
# This is the case for an app being defined more than once
# TODO: Log some kind of warning here
cfg[new].update(new_cfg)
else:
cfg[new] = new_cfg
return cls.model_validate(cfg)

def depedency_graph(self) -> dict[str, set[str]]:
"""Maps the app names to the other apps that they depend on"""
Expand Down
4 changes: 4 additions & 0 deletions appdaemon/models/internal/app_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class LoadingActions:
term: set[str] = field(default_factory=set)
failed: set[str] = field(default_factory=set)

@property
def changes(self) -> bool:
return any(map(bool, (self.init, self.reload, self.term)))

@property
def init_set(self) -> set[str]:
return (self.init | self.reload) - self.failed
Expand Down
Loading

0 comments on commit e1b805b

Please sign in to comment.