How to add utility methods to all Apps

I’m migrating my automations from HA and Node-Red to AppDaemon and one of the reasons I wanted to do that is because of code reuse. I’m running AD as it’s own separate docker container and I’m also running HA Core using Docker.

I’m now trying to figure out a way to create a utility class that all my apps inherit from which it itself inherits from hass.Hass pretty much like this:

# apps/base.py
 
class MyOwnBase(hass.Hass):
    # bunch of utility functions and helpers
# apps/some_folder/some_other_folder/some_app.py
from base import MyOwnBase 

class SomeApp(MyOwnBase):
    #  code

While trying to do this I had a few questions:

  1. Is there a way to get all apps reloaded if base.py changes without making it a global component and adding it to every app yaml definition?
  2. How can I modify the PYTHON_PATH on the docker container so I can just do from base import MyOwnBase without having to deal with relative imports?
  3. Is there another way (different from inheritance) to create a util class that can get the Hass object from AD?

There are a number of ways to achieve code reuse in AD that you might want to consider first instead of inheriting from your own base class. At least until later on when you’ve written some apps and become comfortable with more advanced stuff in AD.

One way is to use the get_app() method. This allows you to pull in the objects of an existing app, it is relatively easy to use however you wont get intellisense / code completion in your IDE and the code will run in the thread of the app you’re pulling in and not within the app that is calling get_app().

Another way is using services. This is probably one of the most flexible options and a lot easier to deal with dependency issues. For example, if you frequently send notifications in many apps with some kind of custom behavior, you could break that into its own app, register a service and use that from all of your other apps. The downside to this is you’ll want to use a “user defined namespace” to store your custom services. You could even use AD’s web API to call these services from HA or anywhere else.

You can also use global modules which is closest to what you’re looking for. I have the least experience with this but a few things. AFAIK, the global module has to be in the root of the apps directory so a little less organized. You will have to pass the main app’s class instance (dependency injection) or you can use import adbase, create a class that inherits from adbase.ADBase, use self.get_ad_api() within that class and pass that instance of the adapi to your custom global module.

We also have a discord chat, you’re welcome to join and discuss specific implementations if you’d like. Also, there are some folks here that have built some pretty complex apps in AD (ControllerX) you may want to take a look at.

Thanks for your answer proggie. I’ve just joined the AppDaemon discord; looking forward to meeting the community

In the end it turned out to be pretty simple to do what I wanted to do (the flexibility from AD is impressive)

I’ll leave it here in case other crazy people want to follow in my footsteps:

  1. The app directory is already included in the PYTHON_PATH, it was failing for me before because I was writing invalid python :stuck_out_tongue:
  2. There is a method called depends_on_module() that allows you to dynamically add a dependency outside of YAML, so I used that. In the end, the implementation of my base class was pretty simple:
import hassapi as hass


class BaseApp(hass.Hass):
    # We use this to make sure all our downstream classes depend on this module
    def __init__(self, ad, name, logging, args, config, app_config, global_vars):
        super().__init__(ad, name, logging, args, config, app_config, global_vars)
        self.depends_on_module("base")

By putting the depends_on_module in the constructor (instead of the initialize()) the dependency is registered even if AD decides for whatever reason not to initialize the component (for example if HA is down ATM).

Later I also realized that I could have used multiple inheritance in python to achieve pretty much the same goal, but alas here we are.

After all of this, an actual app becomes:

import base

class SomeApp(base.BaseApp):
    pass

Everything works perfectly in this scenario. If I change base.py on the root, it gets discovered cleanly and everything else is reloaded.