Skip to content
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
85 changes: 50 additions & 35 deletions appdaemon/app_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from functools import partial, reduce, wraps
from logging import Logger
from pathlib import Path
from typing import TYPE_CHECKING, Literal, TypeVar
from typing import TYPE_CHECKING, Any, Literal, TypeVar

from pydantic import ValidationError

Expand Down Expand Up @@ -432,7 +432,7 @@ async def stop_app(self, app_name: str, delete: bool = False) -> bool:
else:
return True

async def restart_app(self, app):
async def restart_app(self, app: str) -> None:
await self.stop_app(app, delete=False)
try:
await self.start_app(app)
Expand Down Expand Up @@ -513,8 +513,11 @@ async def create_app_object(self, app_name: str) -> None:
def get_managed_app_names(self, include_globals: bool = False) -> set[str]:
apps = set(name for name, o in self.objects.items() if o.type == "app")
if include_globals:
apps |= set(name for name, cfg in self.app_config.root.items()
if isinstance(cfg, GlobalModule))
globals = set(
name for name, cfg in self.app_config.root.items()
if isinstance(cfg, GlobalModule)
)
apps |= globals
return apps

def add_plugin_object(self, name: str, object: "PluginBase", use_dictionary_unpacking: bool = False) -> None:
Expand Down Expand Up @@ -763,7 +766,7 @@ async def wrapper(*args, **kwargs):
return wrapper

# @utils.timeit
async def check_app_updates(self, plugin_ns: str = None, mode: UpdateMode = UpdateMode.NORMAL):
async def check_app_updates(self, plugin_ns: str | None = None, mode: UpdateMode = UpdateMode.NORMAL):
"""Checks the states of the Python files that define the apps, reloading when necessary.

Called as part of :meth:`.utility_loop.Utility.loop`
Expand All @@ -778,12 +781,18 @@ async def check_app_updates(self, plugin_ns: str = None, mode: UpdateMode = Upda
async with self.check_updates_lock:
await self._process_filters()

if mode == UpdateMode.INIT:
await self._process_import_paths()
await self._init_dep_manager()

update_actions = UpdateActions()

match mode:
case UpdateMode.INIT:
await self._process_import_paths()
await self._init_dep_manager()
case UpdateMode.RELOAD_APPS:
all_apps = self.get_managed_app_names(include_globals=False)
modules = self.dependency_manager.modules_from_apps(all_apps)
update_actions.apps.reload |= all_apps
update_actions.modules.reload |= modules

await self.check_app_config_files(update_actions)

await self._handle_sequence_change(update_actions, mode)
Expand Down Expand Up @@ -1285,40 +1294,39 @@ def get_app_file(self, app: str) -> str:
self.get_state(app, attribute="config_path")
)

async def manage_services(self,
namespace: str,
domain: str,
service: str,
app: str | None = None,
__name: str | None = None,
**kwargs):
async def manage_services(
self,
namespace: str,
domain: str,
service: Literal["start", "stop", "restart", "reload", "enable", "disable", "create", "edit", "remove"],
app: str | None = None,
__name: str | None = None,
**kwargs
) -> None | bool | Any:
assert namespace == 'admin' and domain == 'app'

if app is None and service != "reload":
self.logger.warning(
"App not specified when calling '%s' service from %s. Specify App", service, __name)
return

match service:
case "reload" | "create":
pass
case _:
if app not in self.app_config:
if app not in self.get_managed_app_names(include_globals=False):
self.logger.warning(
"Specified App '%s' is not a valid App from %s", app, __name)
"Specified app '%s' for service '%s' is not valid from %s",
app,
service,
__name
)
return

match service:
case "start":
asyncio.ensure_future(self.start_app(app))
case "stop":
asyncio.ensure_future(self.stop_app(app, delete=False))
case "restart":
asyncio.ensure_future(self.restart_app(app))
case "reload":
asyncio.ensure_future(
self.check_app_updates(mode=UpdateMode.INIT))
case _:
match (service, app):
case ("start", str()):
asyncio.create_task(self.start_app(app))
case ("stop", str()):
asyncio.create_task(self.stop_app(app, delete=False))
case ("restart", str()):
asyncio.create_task(self.restart_app(app))
case ("reload", _):
asyncio.create_task(self.check_app_updates(mode=UpdateMode.RELOAD_APPS))
case (_, str()):
# first the check app updates needs to be stopped if on
mode = copy.deepcopy(self.AD.production_mode)

Expand All @@ -1343,3 +1351,10 @@ async def manage_services(self,
self.AD.production_mode = mode

return result
case _:
self.logger.warning(
"Invalid app service call '%s' with app '%s' from app %s.",
service,
app,
__name
)
5 changes: 3 additions & 2 deletions appdaemon/dependency_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,9 @@ def dependent_modules(self, modules: str | Iterable[str]):
return self.python_deps.get_dependents(modules)

def modules_from_apps(self, apps: str | Iterable[str], dependents: bool = True) -> set[str]:
"""Find the importable names of all the python modules that depend on the given apps. Includes
the transitive closure by default.
"""Find the importable names of all the python modules that the given apps depend on.

This includes the transitive closure by default.

Args:
apps (str | Iterable[str]):
Expand Down
1 change: 1 addition & 0 deletions appdaemon/models/internal/app_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class UpdateMode(Enum):

INIT = auto()
NORMAL = auto()
RELOAD_APPS = auto()
PLUGIN_FAILED = auto()
PLUGIN_RESTART = auto()
TERMINATE = auto()
Expand Down
Loading