Development
If you would like to improve AppDaemon, we are pleased to receive Pull Requests in the official AppDaemon repository. Here are a few things to help you get started.
Please note, if some documentation is required to make sense of the PR, the PR will not be accepted without it.
Tools
Although it’s certainly possible to use other tools for any of this, these are what’s used by the contributors.
Python
- uv
An extremely fast Python package and project manager, written in Rust.
- ruff
An extremely fast Python linter and code formatter, written in Rust.
- pytest
The pytest framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries.
- pre-commit
A framework for managing and maintaining multi-language pre-commit hooks. Once enabled, these run things like the linter on every commit.
- sphinx for readthedocs
Sphinx is a powerful documentation generator that has many features for writing technical documentation. Sphinx is written in Python, and supports documentation written in reStructuredText and Markdown.
IDEs
VSCode
The AppDaemon repo itself contains some configuration specifically for VSCode, which makes some routine tasks easier.
- Python
The Python extension makes Visual Studio Code an excellent Python editor, works on any operating system, and is usable with a variety of Python interpreters.
- Python testing
The Python extension builds on the built-in testing features in VS Code and provides test discovery, test coverage, and running and debugging tests for Python’s built-in unittest framework and pytest.
- Ruff Extension
A Visual Studio Code extension for Ruff, an extremely fast Python linter and code formatter, written in Rust. Available on the Visual Studio Marketplace.
PyCharm
PyCharm is another popular Python IDE. To run and debug AppDaemon from PyCharm, create a Python run configuration with the following important settings:
- Run the module (not the
__main__.py): Create a new Run/Debug Configuration: Run → Edit Configurations… → + → Python. Select the
Moduleoption and enter the package/module (e.g.appdaemon). Do not point the configuration at__main__.py— run the package/module instead.
- Run the module (not the
- Set parameters as usual:
If you need to pass arguments (for example to point to a config directory), set them in “Parameters” (e.g.
-c ./conf).
- Set the working directory to the project root:
Do not set the
appdaemondirectory as the working directory; set the project root instead (the directory that containsappdaemon).
- Disable
Add source roots to PYTHONPATH: In the same configuration, uncheck the “Add source roots to PYTHONPATH” option (and “Add content roots to PYTHONPATH” if present in your PyCharm version). This prevents PyCharm from changing import paths in a way that differs from typical runtime environments.
- Disable
Dev Setup
Pre-requisites
For the easiest setup, install uv first.
$ curl -LsSf https://astral.sh/uv/install.sh | sh
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Clone the repository
Download a copy of the official AppDaemon repository by cloning it somewhere locally. The dev branch is generally used for this because it’s what PRs are submitted against.
$ git clone -b dev https://github.com/AppDaemon/appdaemon.git
You can clone specific versions by changing dev to something else. Including a path like ./ad-442 will clone it there instead of into ./appdaemon.
$ git clone -b 4.4.2 https://github.com/AppDaemon/appdaemon.git ./ad-442
All subsequent commands need to be run from inside the newy created directory.
Dependencies
Use the uv sync command to create the Python virtual environment and install the dependencies.
$ uv sync
The extra doc is optional, but needed to work on the documentation.
$ uv sync --group doc
Pre-Commit Hooks
Install the pre-commit hooks that will run some checks/linting prior to each commit. These are the same as what’s run as part of the CI pipeline, and any PRs will have to pass these checks before being accepted.
$ uv run pre-commit install
Open VSCode
$ code .
Dev Workflow
Updating
When there are updates on the dev branch and you want to pull over the latest changes, run the following command from the repo directory:
$ git pull
You can also change to a new branch by using this command. This will check out a local testing branch that will track the origin/testing branch on GitHub, so it can be updated in the future with pull commands.
$ git checkout --track origin/testing
Config
Copy the default configuration file (edit it if you need to tweak some settings):
$ cp conf/appdaemon.yaml.example conf/appdaemon.yaml
Building
To build a Python distribution package (wheel), run the following command:
$ uv build --wheel --refresh
It will output the result of the build inside a ./dist folder. This must be done before building the Docker image because the build process installs AppDaemon from this wheel.
$ docker build -t acockburn/appdaemon:local-dev .
For convenience there’s an included script that handles both of these steps.
$ ./scripts/docker-build.sh
$ ./scripts/multiplatform-docker-build.sh
Running
Using uv to run AppDaemon ensures that the dependencies are all met.
$ uv run appdaemon -c ./conf
In most cases, it is possible to share configuration directories with other AppDaemon instances. However, you must be aware of AppDaemon apps that use new features as they will likely cause errors for the other pre-existing version. It is recommended to use an entirely separate configuration directory for your development environment.
One-off tests of different versions can also be easily run using uv. This creates and uses the necessary python environment in a cache directory.
$ uvx -p 3.11 --from git+https://github.com/AppDaemon/appdaemon@testing appdaemon -c /conf
Documentation
Assistance with the docs is always welcome, whether its fixing typos and incorrect information or reorganizing and adding to the docs to make them more helpful. To work on the docs, submit a pull request with the changes, and I will review and merge them in the usual way. I use Read the Docs to build and host the documentation pages. You can easily preview your edits locally, by running the following command:
$ uv run \
--group doc \
sphinx-autobuild \
--show-traceback --fresh-env \
--host 0.0.0.0 --port 9999 \
--watch ./appdaemon \
--watch ./tests \
docs/ build/docs
This will start a local web server (http://localhost:9999) that will host the documentation. If any of the files change, the server will automatically regenerate the documentation its hosting, which takes a moment, but this feature is still very useful. When you finish your edits, you can stop the server via Ctrl-C.
Dependencies
The pyproject.toml file defines the dependencies according to the PEP 631 convention, and the dependencies are tracked using a lockfile, which is managed by uv. In general, only the minimum versions are specified in pyproject.toml, but uv resolves everything to the latest compatible versions and stores the exact versions in the lockfile. This pins all the exact versions of all the dependencies, both direct and indirect.
dependencies = [
"aiohttp >= 3.9.0",
"aiohttp_jinja2>=1.5.1",
"astral>=3.2",
"bcrypt>=4.0.1",
"deepdiff>=8.2.0",
"feedparser>=6.0.10",
"paho-mqtt>=1.6.1,<3",
"pid>=3.0.4",
"python-dateutil>=2.8.2",
"python-socketio>=5.5",
"pytz>=2022.7.1",
"pyyaml>=6.0.1",
"requests>=2.28.2",
"sockjs>=0.11",
"uvloop>=0.21.0; sys_platform != 'win32'",
"tomli>=2.2.1",
"tomli_w>=1.0",
"pydantic>=2.10.6,<3"
]
Testing
See the Testing AppDaemon page for more details.
VSCode Tasks
VSCode has a feature called tasks, which allow it to integrate with external tools. Essentially it allows developers to run pre-defined commands and scripts using the integrated terminal. AppDaemon has several custom tasks defined to help streamline development.
With the default keybindings, you can reach the command palette with Ctrl-Shift-P or F1. Once it’s open, the list can be quickly filtered by starting to type run task. After selecting Tasks: Run Task, it will show a list of all the available tasks to run.
- Auto Build Docs
Runs the Sphinx documentation server on localhost:9999 with live reloading.
- Build Docker Image
Builds the Docker image for the application
- Build Multi-Platform Docker Images
Builds Docker images for multiple platforms and analyzes their sizes
- Install Dependencies
Installs all the dependencies, including dev and doc extras
- Ruff Statistics
Displays the statistics of all the linting violations
Docker
The general idea is that the wheel for AppDaemon should be built before the Docker image is, because AppDaemon gets installed from that wheel during the process to build the Docker image.
Building the container locally requires Docker Engine 23.0+ (released in 2023), since it enables the Docker BuildKit by default.
Layers and Caching
Efficient caching is very important for the arm/v6 and arm/v7 architectures because those versions take a while to build from scratch. Dependencies are installed in a separate operation before installing AppDaemon itself to make efficient use of Docker’s layers. Cache mounts are also used on relevant operations to significantly speed up the build process and prevent unnecessarily redownloading or rebuilding packages.
Stages
The Docker image is split up using a multi-stage build in order to keep the final image size as small as possible (~45 MB on all platforms).
- builder stage
The builder stage fully constructs the python environment needed to run AppDaemon, but sometimes installing python packages requires some extra tools to to build them beforehand. For example, the orjson and uvloop packages don’t provide pre-built wheels for the arm/v6 and arm/v7 architectures, so they need a few extra tools (git, rust, etc.) installed in order to be able to build wheels for them when the AppDaemon dependencies are installed.
- runtime stage
The runtime stage copies the now-ready python environment into a fresh image without any of the baggage from the previous stage.
Multi-Platform
The general idea is that the Docker buildkit creates and sets up a Docker container that has all the necessary stuff to build on different platforms. There’s a way to specify multiple platforms and use this container to build images for all of the simultaneously. A VSCode task is provided that runs a script that handles all of this automatically. This allows developers to easily test builds across the supported platforms.
The supported platforms are:
linux/amd64Covers most consumer systems as well as many cloud systems
linux/arm64/v8Covers systems that use Apple Silicon or more modern Raspberry Pis (v3+)
linux/arm/v7Covers older Raspberry Pis (v2)
linux/arm/v6Covers the original Raspberry Pi and Pi Zero
Helper Script
There’s a script that will set up buildx for the different platforms before building the Docker image for all of them.
This can be used for testing the full build process without having to push to GitHub.
GitHub Actions
AppDaemon makes use of several GitHub Actions for its CI pipeline, which make use of astral-sh/setup-uv
- UV_LOCKED
Forces uv to use the lockfile (uv.lock) as-is for dependency resolution. This ensures reproducible installs and prevents changes to dependencies unless the lockfile is updated.
- UV_COMPILE_BYTECODE
Tells uv to compile Python files to bytecode (.pyc) after installation. This improves startup performance when using the cache.
- UV_NO_EDITABLE
Disables editable installs, so all packages are installed as regular, non-editable distributions.
- UV_NO_PROGRESS
Suppresses progress bars and other interactive output, making logs cleaner and more suitable for CI environments.
Lint and Test
The linting stage sets up the pre-commit hooks and runs them, which includes running the linter. If that succeeds, then the testing stage runs pytest with the tests marked as ci for each version of python.
Build and Deploy
The CI pipeline automates building and publishing the Python package, Docker image, and documentation. When changes are pushed to the dev branch or a git tag, the workflow:
Builds the documentation using Sphinx and uploads it as a GitHub artifact in
docs/_build/Builds the Python wheel and uploads it as an artifact in
dist/. On tagged releases, the package is published to PyPI.Builds a multi-platform Docker image using Buildx. On tagged releases or pushes to dev, the image is published to Docker Hub.
Uses caching to speed up builds.
Tags Docker images according to release versions, pre-releases, branches, or PRs.
Docker Build and Push
The Docker image is built for each platform (linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6) and published to Docker Hub on dev branch pushes and git tags.
See the Docker section for more information about the image.
Dependencies
Dependencies are broadly specified in the pyproject.toml, usually by a minimum version number. The exact versions
used in development and the CI pipeline are pinned in the uv.lock file, which is managed by uv.
AppDaemon uses a pre-commit hook that checks whether the
lockfile is up-to-date with whatever is specified in pyproject.toml. If it’s not, the commit will be rejected and
the lockfile will be updated. The commit will succeed after adding the updated lockfile to the commit.
Dependency Groups
There’s 2 additional dependency groups that can be installed: dev and doc.
- dev
The
devgroup is automatically installed when using uv sync during development, and includes the tools for linting and testing.- doc
The
docgroup includes the tools needed to build the documentation.
Dependabot
Dependabot is a GitHub tool that checks for outdated dependencies and opens PRs to individually bump the versions.
- pip/uv
This combination will open PRs to bump the versions of all the python packages in the
uv.lockfile.- GitHub Actions
This will open PRs to bump the versions of any GitHub actions used in the workflows.
- Docker
This will open PRs to bump the versions of any base Docker images used in the Dockerfile.