Import custom code

I would like to import some shared code across multiple apps. For instance:

conf/apps/
   app1.py
   app2.py
   shared/
      __init__.py
      utils.py

appdaemon.yaml

appdaemon:
   exclude_dirs:
      - shared

app1.py

from shared.utils import shared_function

class App(hass.Hass):
  def initialize(self):
     self.val = shared_function()

I saw an old thread on this subject where you create an app that you can then access. But it is an old thread and Appdaemon is pretty slick, so I’m wondering if there is a more simple, classically pythonic way of just importing it.

(For instance, I’d like to share validation support functions, custom exceptions, etc.)

Hi @rr326,

I am not sure if there is another way of doing so, but this is how I got around it.

You can create a folder (e.g. my_apps) inside /conf/apps, so imagine the following structure:

conf/apps/my_apps/
   app1.py
   app2.py
   my_app.py
   shared/
      __init__.py
      utils.py

my_app.py:

from app1 import App1
from app2 import App2

app1.py (Same for app2.py but with App2):

import hassapi as hass
from shared.utils import shared_function

class App1(hass.Hass):
  def initialize(self):
     self.val = shared_function()

With this, there is no need to change your appdaemon.yaml, and when you create the YAML config, it would look like this:

example_app1:
  module: my_app # from my_app.py
  class: App1 # This is the class found in my_app.py which is the one in app1.py

Again, there might be another way to do so, but this is how I have it working in my AppDaemon app with many files that can be imported from each other.

Rergards,
Xavi M.

2 Likes

Thanks @xaviml. It looks to me like the key is probalby your setup.py. That, with a pip install controllerx allows it to reference its sub modules.

Right now I am not installing anything - just including apps under the conf/app folder. I was wondering if I’d have to do something like a setup.py plus a requirements.txt file. I was hoping I wouldn’t.

Hey @rr326,

No, that is not correct, the setup.py is not needed. You just need the files I said in my previous post. If you install ControllerX through HACS, you will see that just the apps/controllerx folder gets copied, the rest is just for me to have a testing environment, it is not needed to run in AppDaemon.

Regards,
Xavi M.

Ah, geez. I don’t know what I was doing wrong in the first place. This works exactly like I would want it to.

@xaviml - thanks so much for responding. Got it.

1 Like

I think the only change I did respect to your original post was to add all the content inside a new folder.

Glad you got it working! :slight_smile:

@xaviml - sorry to harass you again, but running into a new problem.

When I updated shared_function, appdaemon sees the module changed, doesn’t see an app with it, and ignores the update. Then my functions calling shared_function are using the old code.

If I touch the parent app, it doesn’t pull in the new code.

Did you have this problem when developing your app?

Hey @rr326,

sorry to harass you again, but running into a new problem.

Not a problem! Happy to help :slight_smile:

Did you have this problem when developing your app?

Yes, I did (and do) have this problem. I need to restart AppDaemon every time I want to test the code since saving the file will not reload the app as expected with AppDaemon. My workaround to this was to have unit and integration tests in my project, so I am confident that changes will certainly work when copying the files and restarting AppDaemon. However, this took me time to do (and still improving the system), so I understand is not very convenient for your automations. Something that I did try when I realised of this problem was to reload all imported modules, but this implied adding unnecessary code to just get around this problem and I didn’t like it, but maybe it’s something you would find useful. This is a piece of code that refreshes the shared library for example:

import hassapi as hass
import importlib
import shared

importlib.reload(shared)

class App1(hass.Hass):
  def initialize(self):
     self.val = shared.utils.shared_function()

You will probably need to do this to any file where you import something internal. A final thing to say about this solution is that saving a file with no automation (e.g. shared modules) will not trigger AppDaemon to reload your code, so you will need CTRL+S a file like app1.py or my_app.py to do so. This was what I figured out, but this was with AppDaemon 3, and maybe this has changed.

Regards,
Xavi M.

2 Likes

That’s fantastic! I love it and will try.

I’ve also been wondering how to do integration tests - will look at controllerx to see how you do it.

I’m using appdaemon in docker. To develop controllerx, did you just do pip install -e and run it there? I’m imaging that is cleaner for a development workflow.

1 Like

I’ve also been wondering how to do integration tests - will look at controllerx to see how you do it.

Well, I would say that my integration tests are quite tight to the domain of ControllerX. My project takes a stream of actions (list of strings or integers) as input and calls HA services as output. I created then, a script test that given the input, a configuration, and the expected calls, it checks if expected calls are called with the right parameters.

Maybe for your use case, this would be more useful. I haven’t used it before, but seems a nice project to test your AppDaemon apps.

I’m using appdaemon in docker. To develop controllerx, did you just do pip install -e and run it there? I’m imaging that is cleaner for a development workflow.

As I said, I test everything offline, so I don’t need AppDaemon to know the project works as expected. My workflow normally is:

  • Create test for new functionalities or bugs
  • Implement functionalities for the tests to pass (Automatic testing)
  • Copy the project to my raspi (via Samba)
  • Reload AppDaemon
  • Check my setup keeps working (Manual testing)
  • Create a new release with the new functionalities or fixes for HACS.

Since all development is offline, I have a virtual environment with my project. I use pipenv to manage my dev dependencies. To avoid installation problems, I avoided using any dependency than AppDaemon which is already installed, so when I copy the code in the raspi, the code will work in the same environment than AppDaemon, so no need of installing dependencies (not even the project itself with pip install -e). Just copying the files, restart AppDaemon and you are good to go.

You can read the CONTRIBUTING.md to learn about the way I work if you are interested.

Regards,
Xavi M.

1 Like

Thanks again @xaviml!