Skip to content

Commit 4476a02

Browse files
committed
reworked AppDir Structure
1 parent 038292a commit 4476a02

File tree

1 file changed

+93
-30
lines changed

1 file changed

+93
-30
lines changed

docs/APPGUIDE.rst

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -569,50 +569,113 @@ By declaring the above, each time the function ``self.log()`` is used within the
569569
Global Module Dependencies
570570
--------------------------
571571

572-
The previously described dependencies and load order have all been at the App level.
573-
It is however, sometimes convenient to have global modules that have no apps in them that nonetheless
574-
require dependency tracking. For instance, a global module might have a number of useful
575-
variables or functions in it. When they change, a number of apps may need to be restarted.
576-
as of AppDaemon 4.5 these dependencies are tracked autmatically and should just work.
577-
It is neccesarry to take some care about how apps are structured, especially if multiple subdirectories are used.
572+
.. admonition:: Deprecation warning
573+
:class: warning
574+
575+
Global modules are deprecated and will be removed in a future release. AppDaemon now automatically tracks and
576+
resolves dependencies using the :py:mod:`ast <ast>` package from the standard library.
578577

579578
AppDir Structure
580579
----------------
581580

582-
So far, we have assumed that all apps and their configuration files are placed in a single directory. This works fine for simple setups
583-
but as the number of apps grows, it can be useful to organize them into subdirectories. AppDaemon will automatically search all subdirectories of the apps directory for apps and configuration files. This means that you can have a directory structure like this:
581+
So far, we have assumed that all apps and their configuration files are placed in a single directory. This works fine
582+
for simple setups but as the number of apps grows, it can be useful to organize them into subdirectories. AppDaemon will
583+
automatically search all subdirectories of the `apps` directory for apps and configuration files. This means that you
584+
can have a directory structure like this:
584585

585586
.. code:: text
586587
587-
apps/
588-
app1/
589-
app1.py
590-
app1.yaml
591-
app2/
592-
app2.py
593-
app2.yaml
594-
app3/
595-
app3.py
596-
app3.yaml
597-
common/
598-
common.py
599-
common.yaml
588+
conf/apps
589+
├── app1
590+
│ ├── app1.py
591+
│ └── app1.yaml
592+
├── app2
593+
│ ├── app2.py
594+
│ └── app2.yaml
595+
├── common
596+
│ ├── my_globals.py
597+
│ └── utils.py
598+
└── some
599+
└── deep
600+
└── path
601+
├── app3.py
602+
└── app3.yaml
603+
604+
In this example, AppDaemon will find all the apps defined in `app1.yaml`, `app2.yaml`, and even `app3.yaml`, despite it
605+
being deep in a subdirectory. Each of those files would define apps using ``module: app1`` or ``module: app2`` etc. to
606+
refer to their respective python modules.
607+
608+
Addtionally, apps in `app1.py`, `app2.py`, and `app3.py` can import things directly from `my_globals.py` and `utils.py`
609+
like this:
600610

611+
.. code:: python
601612
602-
In this example, AppDaemon will find all the apps in the app1, app2, and app3 directories, as well as the common.py and common.yaml files in the common directory.
603-
The apps can be configured in their respective YAML files, and they can also import functions or classes from the common module if needed, as long as some simple rules are adhered to.
613+
# app1/app1.py
614+
from appdaemon.adapi import ADAPI
604615
605-
- If app1 wants to import a function called `common_funtion` from common.py, it can do so using the following import statement:
616+
from my_globals import MY_GLOBAL_VAR
617+
from utils import my_util_function
606618
607-
.. code:: python
619+
class MyApp(ADAPI):
620+
def initialize(self):
621+
... # app code would go here
622+
623+
.. admonition:: Note text
624+
:class: note
625+
626+
Note that there are no relative paths here. AppDaemon handles adding all the relevant subdirectories to the import path,
627+
which allows them to be directly imported, as if the files were next to each other. Furthermore, AppDaemon understands
628+
that `app1.py` depends on both `my_globals.py` and `utils.py`, so if either of those files change, AppDaemon will reload
629+
`app1.py` automatically.
630+
631+
App Packages
632+
~~~~~~~~~~~~
633+
634+
As app complexity increases, it's often useful to break the logic apart into multiple files, and sometimes these modules
635+
have the same name as modules in other directories. For example, what if an app needed its own set of utils? The module
636+
names can be managed by using ``__init__.py`` files.
637+
638+
.. code:: text
639+
640+
conf/apps
641+
├── my_app
642+
│ ├── __init__.py
643+
│ ├── foo.py
644+
│ ├── apps.yaml
645+
│ └── utils.py
646+
├── common
647+
│ ├── ... # other common modules
648+
│ └── utils.py
649+
... # more apps down here
650+
651+
In this example `foo.py` can import from both `utils.py` modules like this, which uses
652+
:py:ref:`package relative imports <relativeimports>` to reference the `utils.py` next to it as distinct from the one in
653+
the `common` directory
654+
655+
.. code-block:: python
656+
:emphasize-lines: 4,6
657+
658+
# my_app/foo.py
659+
from appdaemon.adapi import ADAPI
608660
609-
from common import common_function
661+
from utils import global_util_function
610662
611-
Note that there are no relative paths here - the AppDaemon system in combination with standard python rules will reslove this correctly,
612-
and importantly, will understand that app1 now relies on common.py, and any changes to common.py will result it common.py being reloaded,
613-
but this will also result in a reload of app1.py to pick up the changes
663+
from .utils import specific_util_function
614664
615-
- if app2 is a package in it's own right (e.g. it has an __init__.py at the top level) #### John, what happens here???
665+
class MyApp(ADAPI):
666+
def initialize(self):
667+
... # app code would go here
668+
669+
Using the ``__init__.py`` file indicates to Python/AppDaemon that the directory containing it is a package, and as such
670+
the its import name changes slightly. The `apps.yaml` file needs to be updated to reflect this.
671+
672+
.. code-block:: yaml
673+
:emphasize-lines: 3
674+
675+
# my_app/apps.yaml
676+
my_app:
677+
module: my_app.foo # not just `foo`
678+
class: MyApp
616679
617680
618681
Plugin Reloads

0 commit comments

Comments
 (0)