From 3b750449eb1b5e60160cd8e08065b4f7438cd718 Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Sat, 10 Aug 2024 13:43:05 -0500 Subject: [PATCH] initial commit --- .gitignore | 2 + README.md | 3 + conf/apps/apps.yaml | 11 +++ conf/apps/food-repo/src/food/__init__.py | 5 ++ conf/apps/food-repo/src/food/eggs.py | 5 ++ conf/apps/food-repo/src/food/ham.py | 5 ++ conf/apps/food-repo/src/food/spam.py | 5 ++ conf/apps/hello.py | 5 ++ test_startup.py | 91 ++++++++++++++++++++++++ 9 files changed, 132 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 conf/apps/apps.yaml create mode 100644 conf/apps/food-repo/src/food/__init__.py create mode 100644 conf/apps/food-repo/src/food/eggs.py create mode 100644 conf/apps/food-repo/src/food/ham.py create mode 100644 conf/apps/food-repo/src/food/spam.py create mode 100644 conf/apps/hello.py create mode 100644 test_startup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f071240 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.pytest_cache \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fd3076 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# AppDaemon Dev Testing + +Repo to keep some code for testing during AppDaemon development diff --git a/conf/apps/apps.yaml b/conf/apps/apps.yaml new file mode 100644 index 0000000..6dba59d --- /dev/null +++ b/conf/apps/apps.yaml @@ -0,0 +1,11 @@ +hello_world: + module: hello + class: HelloWorld + +food_app: + module: food + class: Eggs + dependencies: + - hello_world + # class: Ham + # class: Spam \ No newline at end of file diff --git a/conf/apps/food-repo/src/food/__init__.py b/conf/apps/food-repo/src/food/__init__.py new file mode 100644 index 0000000..ebac54a --- /dev/null +++ b/conf/apps/food-repo/src/food/__init__.py @@ -0,0 +1,5 @@ +from .eggs import Eggs +from .ham import Ham +from .spam import Spam + +__all__ = ['Eggs', 'Ham', 'Spam'] diff --git a/conf/apps/food-repo/src/food/eggs.py b/conf/apps/food-repo/src/food/eggs.py new file mode 100644 index 0000000..49c1a9e --- /dev/null +++ b/conf/apps/food-repo/src/food/eggs.py @@ -0,0 +1,5 @@ +from appdaemon.adapi import ADAPI + +class Eggs(ADAPI): + def initialize(self): + self.log(f'{self.__class__.__name__} Initialized') diff --git a/conf/apps/food-repo/src/food/ham.py b/conf/apps/food-repo/src/food/ham.py new file mode 100644 index 0000000..7ef3624 --- /dev/null +++ b/conf/apps/food-repo/src/food/ham.py @@ -0,0 +1,5 @@ +from appdaemon.adapi import ADAPI + +class Ham(ADAPI): + def initialize(self): + self.log(f'{self.__class__.__name__} Initialized') diff --git a/conf/apps/food-repo/src/food/spam.py b/conf/apps/food-repo/src/food/spam.py new file mode 100644 index 0000000..4279cad --- /dev/null +++ b/conf/apps/food-repo/src/food/spam.py @@ -0,0 +1,5 @@ +from appdaemon.adapi import ADAPI + +class Spam(ADAPI): + def initialize(self): + self.log(f'{self.__class__.__name__} Initialized') diff --git a/conf/apps/hello.py b/conf/apps/hello.py new file mode 100644 index 0000000..e375568 --- /dev/null +++ b/conf/apps/hello.py @@ -0,0 +1,5 @@ +from appdaemon.adapi import ADAPI + +class HelloWorld(ADAPI): + def initialize(self): + self.log(f'{self.__class__.__name__} Initialized') diff --git a/test_startup.py b/test_startup.py new file mode 100644 index 0000000..4b738e2 --- /dev/null +++ b/test_startup.py @@ -0,0 +1,91 @@ +import asyncio +import logging +from pathlib import Path +from typing import List + +import pytest +from appdaemon.appdaemon import AppDaemon +from appdaemon.logging import Logging +from appdaemon.models.config import AppDaemonConfig + +CONFIG_DIR = Path(__file__).parent / 'conf' + + +@pytest.fixture +def base_config() -> AppDaemonConfig: + return AppDaemonConfig( + latitude=0.0, + longitude=0.0, + elevation=0, + time_zone='America/Chicago', + config_dir=CONFIG_DIR, + config_file='appdaemon.yaml', + stop_function=lambda: print('Stopping'), + ) + + +@pytest.fixture +def ad_system(base_config) -> AppDaemon: + loop = asyncio.new_event_loop() + ad = AppDaemon(Logging(), loop, base_config) + return ad + + +# def test_dependency_graph(): +# app_dir = Path('/home/john/conf/apps') +# files = app_dir.rglob('*.yaml') +# files = (f for f in files if f.stem != 'secrets' and f.stem != 'appdaemon') + +# cfg = AllAppConfig.from_config_files(files) +# graph = cfg.depedency_graph() +# rev = cfg.reversed_dependency_graph() + +# assert 'App1Foo' in graph['App2'], 'App2 is not dependent on App1Foo' +# assert 'App2' in rev['App1Foo'], 'App1 is not a predicate of App1Foo' + +# print(graph) + + +async def delayed_stop(ad: AppDaemon, delay: float): + await asyncio.sleep(delay) + ad.stop() + + +def get_load_order(caplog: pytest.LogCaptureFixture) -> List[str]: + for record in caplog.records: + if record.name == 'AppDaemon._app_management': + if 'Determined module load order' in record.msg: + return record.args[0] + + +def validate_module_load_order(ad: AppDaemon, module_load_order: List[str]): + dependency_graph = ad.app_management.module_dependencies + for node, deps in dependency_graph.items(): + # skip parts that + if not node.startswith('appdaemon'): + continue + + node_idx = module_load_order.index(node) + for dep in deps: + dep_idx = module_load_order.index(dep) + assert dep_idx < node_idx + + +def test_startup(ad_system: AppDaemon, caplog: pytest.LogCaptureFixture): + assert isinstance(ad_system, AppDaemon) + + # logger = logging.getLogger('AppDaemon._app_management') + logging.getLogger('AppDaemon').propagate = True + + stop_coro = delayed_stop(ad_system, 2.0) + + with caplog.at_level(logging.DEBUG, logger='AppDaemon._app_management'): + ad_system.loop.run_until_complete(stop_coro) + + assert "Started 'hello_world'" in caplog.text + assert 'App initialization complete' in caplog.text + + module_load_order = get_load_order(caplog) + validate_module_load_order(ad_system, module_load_order) + + print()