diff --git a/appdaemon.yaml b/appdaemon.yaml index 993720c..89246e4 100644 --- a/appdaemon.yaml +++ b/appdaemon.yaml @@ -1,11 +1,14 @@ appdaemon: - import_method: expert + uvloop: True + use_dictionary_unpacking: True + # import_method: expert # import_paths: # - /conf/apps/my_repo latitude: 0 longitude: 0 elevation: 30 time_zone: America/Chicago + check_app_updates_profile: True plugins: HASS: type: hass diff --git a/apps/app1/database.py b/apps/app1/database.py index f9a3648..9288e0e 100644 --- a/apps/app1/database.py +++ b/apps/app1/database.py @@ -4,4 +4,5 @@ from appdaemon.adapi import ADAPI class Database: def __init__(self, ad: ADAPI) -> None: self.ad = ad - # self.ad.logger.info(' NEW LOG LINE '.center(50, '=')) + # self.ad.logger.info(' NEW LOG LINE Database '.center(50, '=')) + # self.ad.call_service() diff --git a/apps/app1/notification.py b/apps/app1/notification.py index 143fdc3..cafb9e5 100644 --- a/apps/app1/notification.py +++ b/apps/app1/notification.py @@ -4,7 +4,7 @@ from appdaemon.adapi import ADAPI class Notification: def __init__(self, ad: ADAPI) -> None: self.ad = ad - self.ad.logger.info(' NEW LOG LINE '.center(50, '=')) + # self.ad.logger.info(' NEW LOG LINE '.center(50, '=')) def send(self, message: str = "message not specified") -> None: self.ad.log(message, level="DEBUG") diff --git a/apps/app2/__init__.py b/apps/app2/__init__.py index e69de29..7c1aaa7 100644 --- a/apps/app2/__init__.py +++ b/apps/app2/__init__.py @@ -0,0 +1,8 @@ +import appdaemon + +# import app2.database + +from . import database + +# from .database import OtherDatabaseApp +# from .database import OtherDatabaseApp as NormalApp \ No newline at end of file diff --git a/apps/app2/database.py b/apps/app2/database.py index fe59707..7f57f5f 100644 --- a/apps/app2/database.py +++ b/apps/app2/database.py @@ -1,6 +1,9 @@ from appdaemon.adapi import ADAPI -class OtherDatabaseApp(ADAPI): +from app1.subdir.foo import Foo + +class OtherDatabaseApp(Foo): def initialize(self): self.log(f'Initialized from {__file__}') + self.log(' CHANGE '.center(50, '=')) \ No newline at end of file diff --git a/apps/apps.yaml b/apps/apps.yaml new file mode 100644 index 0000000..4c84034 --- /dev/null +++ b/apps/apps.yaml @@ -0,0 +1,3 @@ +hello_world: + module: hello + class: HelloWorld diff --git a/apps/databases.yaml b/apps/databases.yaml index bb458eb..efb2b5f 100644 --- a/apps/databases.yaml +++ b/apps/databases.yaml @@ -1,11 +1,19 @@ -# App1: -# module: app1.database -# class: DatabaseApp +App1: + module: aadfasdf + class: Database + other_kwargs: value + dependencies: Sound App2: module: app2.database class: OtherDatabaseApp + dependencies: + - App1 + - App1Foo + - globals App1Foo: module: app1.subdir.foo class: Foo + globals: + - hello diff --git a/apps/global_apps.yaml b/apps/global_apps.yaml index 2dcccf6..33de708 100644 --- a/apps/global_apps.yaml +++ b/apps/global_apps.yaml @@ -1,6 +1,10 @@ globals: module: globals global: true + companion_app: module: app1.notification - global: true \ No newline at end of file + global: true + +global_modules: + - hello diff --git a/apps/hello.py b/apps/hello.py index 499a7e1..1dcac73 100755 --- a/apps/hello.py +++ b/apps/hello.py @@ -1,25 +1,8 @@ -import logging -import logging.config +from pathlib import Path from appdaemon.adapi import ADAPI class HelloWorld(ADAPI): def initialize(self): - self.log(f'Initialized app from {__file__}') - - # logging.config.dictConfig( - # { - # 'version': 1, - # 'disable_existing_loggers': False, - # 'formatters': {'basic': {'style': '{', 'format': '{message}'}}, - # 'handlers': {'rich': {'()': 'rich.logging.RichHandler'}}, - # 'loggers': { - # 'AppDaemon._app_management': { - # 'level': 'DEBUG', - # 'propagate': False, - # 'handlers': ['rich'], - # } - # }, - # } - # ) + self.log(f'Initialized app from {Path(__file__).relative_to(self.AD.app_dir.parent)}') diff --git a/apps/my_repo/src/Dependencies.ipynb b/apps/my_repo/src/Dependencies.ipynb new file mode 100644 index 0000000..efc6390 --- /dev/null +++ b/apps/my_repo/src/Dependencies.ipynb @@ -0,0 +1,667 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import ast\n", + "import importlib\n", + "import sys\n", + "from pathlib import Path\n", + "from types import ModuleType\n", + "from typing import Dict, Iterable, Iterator, Mapping, Set, Tuple\n", + "\n", + "from rich import print" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from appdaemon.dependency import (\n", + " Graph,\n", + " find_all_dependents,\n", + " get_all_nodes,\n", + " get_dependency_graph,\n", + " get_file_deps,\n", + " get_full_module_name,\n", + " resolve_relative_import,\n", + " reverse_graph,\n", + " topo_sort,\n", + ")\n", + "from appdaemon.models.internal.file_check import FileCheck" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Logging" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import logging.config\n", + "\n", + "from rich.console import Console\n", + "from rich.highlighter import NullHighlighter\n", + "\n", + "console = Console()\n", + "\n", + "logging.config.dictConfig(\n", + " {\n", + " 'version': 1,\n", + " 'disable_existing_loggers': False,\n", + " 'formatters': {'basic': {'style': '{', 'format': '{message}'}},\n", + " 'handlers': {\n", + " 'rich': {\n", + " '()': 'rich.logging.RichHandler',\n", + " 'formatter': 'basic',\n", + " 'console': console,\n", + " 'highlighter': NullHighlighter(),\n", + " 'markup': True\n", + " }\n", + " },\n", + " 'root': {'level': 'DEBUG', 'handlers': ['rich']},\n", + " }\n", + ")\n", + "\n", + "logger = logging.getLogger()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## App Load Example\n", + "\n", + "Example of using importlib to (re)load an app class from a module." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "change\n" + ] + }, + { + "data": { + "text/html": [ + "
<class 'my_pkg.my_sub_pkg.deep_pkg.deep_mod.Deep'>\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m<\u001b[0m\u001b[1;95mclass\u001b[0m\u001b[39m \u001b[0m\u001b[32m'my_pkg.my_sub_pkg.deep_pkg.deep_mod.Deep'\u001b[0m\u001b[1m>\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# apps_path = '/home/john/conf/apps'\n", + "# module_name = 'app2'\n", + "# class_name = 'NormalApp'\n", + "\n", + "apps_path = '/home/john/conf/apps/my_repo/src'\n", + "module_name = 'my_pkg.my_sub_pkg.deep_pkg'\n", + "class_name = 'Deep'\n", + "\n", + "if apps_path not in sys.path:\n", + " sys.path.insert(0, apps_path)\n", + "\n", + "if mod := sys.modules.get(module_name):\n", + " importlib.reload(mod)\n", + "else:\n", + " mod = importlib.import_module(module_name)\n", + "\n", + "class_def = getattr(mod, class_name)\n", + "print(class_def)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['my_pkg',\n", + " 'my_pkg.foo',\n", + " 'my_pkg.my_sub_pkg',\n", + " 'my_pkg.my_sub_pkg.bar',\n", + " 'my_pkg.my_sub_pkg.deep_pkg',\n", + " 'my_pkg.my_sub_pkg.deep_pkg.deep_mod']" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pkgs = sorted(name for name in sys.modules.keys() if name.startswith('my'))\n", + "pkgs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## File Mapping\n", + "\n", + "Mapping each file to the package string it will be imported from, so it can be retrieved from `sys.modules`\n", + "\n", + "Flow\n", + "\n", + "- List of changed files\n", + "- Package associated with each file\n", + "- Packages that each file depends on" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "app_dir = Path('/home/john/conf/apps')\n", + "# files_to_modules = paths_to_modules(app_dir)\n", + "# print(files_to_modules)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dependencies\n", + "\n", + "Find which modules each module depends on.\n", + "\n", + "```python\n", + "deps = {\n", + " 'A': {'B', 'C'},\n", + " 'B': {'C'},\n", + " 'C': {'D'},\n", + " 'D': set()\n", + "}\n", + "```\n", + "\n", + "`A` depends on `B` and `C`.\n", + "\n", + "If `D` needs to get reloaded, what else does too?" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
True\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[3;92mTrue\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "{'C': {'A', 'B'}, 'D': {'C'}, 'B': {'A'}, 'A': set()}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "deps = {'A': {'B', 'C'}, 'B': {'C'}, 'C': {'D'}, 'D': set()}\n", + "\n", + "reversable = deps == reverse_graph(reverse_graph(deps))\n", + "print(reversable)\n", + "\n", + "reverse_graph(deps)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
{\n",
+       "    'hello': {'pathlib', 'logging.config', 'appdaemon.adapi', 'logging'},\n",
+       "    'globals': set(),\n",
+       "    'my_pkg': {'my_pkg.foo', 'my_pkg.my_sub_pkg.bar'},\n",
+       "    'my_pkg.foo': {'pathlib', 'appdaemon.adbase'},\n",
+       "    'my_pkg.my_sub_pkg.bar': {'pathlib', 'appdaemon.adbase'},\n",
+       "    'my_pkg.my_sub_pkg': {'my_pkg.my_sub_pkg.bar'},\n",
+       "    'my_pkg.my_sub_pkg.baz': {'pathlib', 'appdaemon.adbase'},\n",
+       "    'my_pkg.my_sub_pkg.deep_pkg.deep_mod': {'pathlib', 'appdaemon.adbase'},\n",
+       "    'my_pkg.my_sub_pkg.deep_pkg': {'my_pkg.foo', 'my_pkg.my_sub_pkg.deep_pkg.deep_mod', 'my_pkg.my_sub_pkg.bar'},\n",
+       "    'app1.notification': {'appdaemon.adapi'},\n",
+       "    'app1': set(),\n",
+       "    'app1.database': {'appdaemon.adapi'},\n",
+       "    'foo': {'appdaemon.adapi'},\n",
+       "    'test': {'app1.notification', 'appdaemon.adbase'},\n",
+       "    'app2': {'app2.database', 'appdaemon'},\n",
+       "    'app2.database': {'appdaemon.adapi', 'app1.subdir.foo'}\n",
+       "}\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m{\u001b[0m\n", + " \u001b[32m'hello'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'pathlib'\u001b[0m, \u001b[32m'logging.config'\u001b[0m, \u001b[32m'appdaemon.adapi'\u001b[0m, \u001b[32m'logging'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'globals'\u001b[0m: \u001b[1;35mset\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m,\n", + " \u001b[32m'my_pkg'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'my_pkg.foo'\u001b[0m, \u001b[32m'my_pkg.my_sub_pkg.bar'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'my_pkg.foo'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'pathlib'\u001b[0m, \u001b[32m'appdaemon.adbase'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'my_pkg.my_sub_pkg.bar'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'pathlib'\u001b[0m, \u001b[32m'appdaemon.adbase'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'my_pkg.my_sub_pkg'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'my_pkg.my_sub_pkg.bar'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'my_pkg.my_sub_pkg.baz'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'pathlib'\u001b[0m, \u001b[32m'appdaemon.adbase'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'my_pkg.my_sub_pkg.deep_pkg.deep_mod'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'pathlib'\u001b[0m, \u001b[32m'appdaemon.adbase'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'my_pkg.my_sub_pkg.deep_pkg'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'my_pkg.foo'\u001b[0m, \u001b[32m'my_pkg.my_sub_pkg.deep_pkg.deep_mod'\u001b[0m, \u001b[32m'my_pkg.my_sub_pkg.bar'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'app1.notification'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'appdaemon.adapi'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'app1'\u001b[0m: \u001b[1;35mset\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m,\n", + " \u001b[32m'app1.database'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'appdaemon.adapi'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'foo'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'appdaemon.adapi'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'test'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'app1.notification'\u001b[0m, \u001b[32m'appdaemon.adbase'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'app2'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'app2.database'\u001b[0m, \u001b[32m'appdaemon'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[32m'app2.database'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'appdaemon.adapi'\u001b[0m, \u001b[32m'app1.subdir.foo'\u001b[0m\u001b[1m}\u001b[0m\n", + "\u001b[1m}\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "module_dependencies = get_dependency_graph(app_dir.rglob('*.py'))\n", + "print(module_dependencies)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
{'my_pkg.my_sub_pkg.deep_pkg.deep_mod', 'my_pkg.my_sub_pkg.deep_pkg'}\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m{\u001b[0m\u001b[32m'my_pkg.my_sub_pkg.deep_pkg.deep_mod'\u001b[0m, \u001b[32m'my_pkg.my_sub_pkg.deep_pkg'\u001b[0m\u001b[1m}\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "files = [\n", + " Path('/home/john/conf/apps/my_repo/src/my_pkg/my_sub_pkg/deep_pkg/__init__.py'),\n", + " Path('/home/john/conf/apps/my_repo/src/my_pkg/my_sub_pkg/deep_pkg/deep_mod.py'),\n", + "]\n", + "\n", + "changed_pkg = set(get_full_module_name(f) for f in files)\n", + "reversed_deps = reverse_graph(module_dependencies)\n", + "dependents = find_all_dependents(changed_pkg, reversed_deps)\n", + "to_load = changed_pkg | dependents\n", + "print(to_load)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
--------------------------------------------------\n",
+       "
\n" + ], + "text/plain": [ + "--------------------------------------------------\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
from ...foo import Foo\n",
+       "
\n" + ], + "text/plain": [ + "from \u001b[33m...\u001b[0mfoo import Foo\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
my_pkg.foo\n",
+       "
\n" + ], + "text/plain": [ + "my_pkg.foo\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
--------------------------------------------------\n",
+       "
\n" + ], + "text/plain": [ + "--------------------------------------------------\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
from ..bar import Bar\n",
+       "
\n" + ], + "text/plain": [ + "from ..bar import Bar\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
my_pkg.my_sub_pkg.bar\n",
+       "
\n" + ], + "text/plain": [ + "my_pkg.my_sub_pkg.bar\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
--------------------------------------------------\n",
+       "
\n" + ], + "text/plain": [ + "--------------------------------------------------\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
from . import deep_mod\n",
+       "
\n" + ], + "text/plain": [ + "from . import deep_mod\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
my_pkg.my_sub_pkg.deep_pkg\n",
+       "
\n" + ], + "text/plain": [ + "my_pkg.my_sub_pkg.deep_pkg\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
--------------------------------------------------\n",
+       "
\n" + ], + "text/plain": [ + "--------------------------------------------------\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
from .. import bar\n",
+       "
\n" + ], + "text/plain": [ + "from .. import bar\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
my_pkg.my_sub_pkg\n",
+       "
\n" + ], + "text/plain": [ + "my_pkg.my_sub_pkg\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
--------------------------------------------------\n",
+       "
\n" + ], + "text/plain": [ + "--------------------------------------------------\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
from .deep_mod import Deep\n",
+       "
\n" + ], + "text/plain": [ + "from .deep_mod import Deep\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
my_pkg.my_sub_pkg.deep_pkg.deep_mod\n",
+       "
\n" + ], + "text/plain": [ + "my_pkg.my_sub_pkg.deep_pkg.deep_mod\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def test_rel_imports(file: Path):\n", + " with file.open('r') as f:\n", + " file_content = f.read()\n", + " \n", + " lines = file_content.splitlines()\n", + "\n", + " tree = ast.parse(file_content, filename=file)\n", + " for node in ast.walk(tree):\n", + " if isinstance(node, ast.ImportFrom):\n", + " abs_module = resolve_relative_import(node, file)\n", + " print('-' * 50)\n", + " print(lines[node.lineno-1])\n", + " print(abs_module)\n", + "\n", + "test_rel_imports(files[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Partial Dependency Graph\n", + "\n", + "This is an example of generating the dependency graph using an iterable of file Paths." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "p = Path('/home/john/conf/apps/my_repo/src/my_pkg/my_sub_pkg')\n", + "files = p.rglob('*.py')\n", + "\n", + "dep_graph = {get_full_module_name(f): get_file_deps(f) for f in files} \n", + "dep_graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "reload_deps = {m: module_dependencies[m] for m in to_load}\n", + "print(reload_deps)\n", + "topo_sort(reload_deps)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "files = [\n", + " Path('/home/john/conf/apps/my_repo/src/my_pkg/my_sub_pkg/deep_pkg/__init__.py'),\n", + " Path('/home/john/conf/apps/my_repo/src/my_pkg/my_sub_pkg/deep_pkg/deep_mod.py'),\n", + "]\n", + "\n", + "for f in files:\n", + " mod_name = get_file_deps(f)\n", + " print(mod_name)\n", + " break\n", + "\n", + "with f.open(\"r\") as file:\n", + " file_content = file.read()\n", + "tree = ast.parse(file_content, filename=f)\n", + "for node in ast.walk(tree):\n", + " if isinstance(node, ast.ImportFrom):\n", + " print(node.level, node.module)\n", + " if node.level:\n", + " abs_module = resolve_relative_import(node, f)\n", + " if not node.module:\n", + " abs_module += f'.{node.names[0].name}'\n", + " print(abs_module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## File Checking" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "files = app_dir.rglob('*.py')\n", + "\n", + "fc = FileCheck.from_paths(files)\n", + "print('\\n'.join(f'{f.relative_to(app_dir.parent)}' for f in fc.new))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "appdaemon", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/apps/my_repo/src/my_pkg/my_sub_pkg/bar.py b/apps/my_repo/src/my_pkg/my_sub_pkg/bar.py index f68ee32..a33d399 100644 --- a/apps/my_repo/src/my_pkg/my_sub_pkg/bar.py +++ b/apps/my_repo/src/my_pkg/my_sub_pkg/bar.py @@ -8,5 +8,5 @@ filename = Path(__file__).name class Bar(ADBase): def initialize(self): self.adapi = self.get_ad_api() - self.adapi.log(f'Initialized app from {filename}') - # self.adapi.log(f'CHANGED') + self.log = self.adapi.log + self.log(f'Initialized app from {filename}') diff --git a/apps/my_repo/src/my_pkg/my_sub_pkg/baz.py b/apps/my_repo/src/my_pkg/my_sub_pkg/baz.py index 52847f9..f41bb1c 100644 --- a/apps/my_repo/src/my_pkg/my_sub_pkg/baz.py +++ b/apps/my_repo/src/my_pkg/my_sub_pkg/baz.py @@ -2,9 +2,12 @@ from pathlib import Path from appdaemon.adbase import ADBase +# from . import deep_pkg + filename = Path(__file__).name # print(f' Importing {filename} '.center(50, '-')) + class Baz(ADBase): def initialize(self): self.adapi = self.get_ad_api() diff --git a/apps/my_repo/src/my_pkg/my_sub_pkg/deep_pkg/__init__.py b/apps/my_repo/src/my_pkg/my_sub_pkg/deep_pkg/__init__.py new file mode 100644 index 0000000..fb909bf --- /dev/null +++ b/apps/my_repo/src/my_pkg/my_sub_pkg/deep_pkg/__init__.py @@ -0,0 +1,7 @@ +from ...foo import Foo +from ..bar import Bar +from . import deep_mod +from .. import bar +from .deep_mod import Deep + +# print(Deep.version) \ No newline at end of file diff --git a/apps/my_repo/src/my_pkg/my_sub_pkg/deep_pkg/deep_mod.py b/apps/my_repo/src/my_pkg/my_sub_pkg/deep_pkg/deep_mod.py new file mode 100644 index 0000000..8f6c4df --- /dev/null +++ b/apps/my_repo/src/my_pkg/my_sub_pkg/deep_pkg/deep_mod.py @@ -0,0 +1,13 @@ +from pathlib import Path + +from appdaemon.adbase import ADBase + +filename = Path(__file__).name + +class Deep(ADBase): + version = 'change' + + def initialize(self): + self.adapi = self.get_ad_api() + self.adapi.log(f'Initialized app from {filename}') + # self.adapi.log(f'CHANGED') diff --git a/apps/test/test.py b/apps/test/test.py index 7fd2af2..fa3941d 100644 --- a/apps/test/test.py +++ b/apps/test/test.py @@ -6,4 +6,4 @@ class Test(ADBase): def initialize(self): self.adapi = self.get_ad_api() self.notify = Notification(self.adapi) - self.notify.send("Test message from Test2 app.") + self.notify.send("Test notification from Test2 app.")