Apprise Build System Modernization (PEP 621 / RPM CI) (#1368)

This commit is contained in:
Chris Caron
2025-07-28 19:49:53 -04:00
committed by GitHub
parent 91faed0c6d
commit 70667a5c28
470 changed files with 83634 additions and 71061 deletions

View File

@@ -1,60 +1,165 @@
# Apprise Development Tools
# 🛠️ Apprise Development Guide
# Common Testing
This directory just contains some tools that are useful when developing with Apprise. It is presumed that you've set yourself up with a working development environment before using the tools identified here:
Welcome! This guide helps you contribute to Apprise with confidence. It outlines
how to set up your local environment, run tests, lint your code, and build
packages — all using modern tools like [Tox](https://tox.readthedocs.io/) and
[Ruff](https://docs.astral.sh/ruff/).
---
## 🚀 Getting Started
Set up your local development environment using Tox:
```bash
# Using pip, setup a working development environment:
pip install -r dev-requirements.txt
# Install Tox
python -m pip install tox
```
The tools are as follows:
Tox manages dependencies, linting, testing, and builds — no need to manually
install `requirements-dev.txt`.
- :gear: `apprise`: This effectively acts as the `apprise` tool would once Apprise has been installed into your environment. However `apprise` uses the branch you're working in. So if you added a new Notification service, you can test with it as you would easily. `apprise` takes all the same parameters as the `apprise` tool does.
---
```bash
# simply make your code changes to apprise and test it out:
./bin/apprise -t title -b body \
mailto://user:pass@example.com
```
## 🧪 Running Tests
- :gear: `test.sh`: This allows you to just run the unit tests associated with this project. You can optionally specify a _keyword_ as a parameter and the unit tests will specifically focus on a single test. This is useful when you need to debug something and don't want to run the entire fleet of tests each time. e.g:
Use the `qa` environment for full testing and plugin coverage:
```bash
# Run all tests:
./bin/tests.sh
# Run just the tests associated with the rest framework:
./bin/tests.sh rest
# Run just the Apprise config related unit tests
./bin/tests.sh config
```
- :gear: `checkdone.sh`: This script just runs a lint check against the code to make sure there are no PEP8 issues and additionally runs a full test coverage report. This is what will happen once you check in your code. Nothing can be merged unless these tests pass with 100% coverage. So it's useful to have this handy to run now and then.
```bash
# Perform PEP8 and test coverage check on all code and reports
# back. It's called 'checkdone' because it checks to see if you're
# actually done with your code commit or not. :)
./bin/checkdone.sh
```
You can optionally just update your path to include this `./bin` directory and call the scripts that way as well. Hence:
```bash
# Update the path to include the bin directory:
export PATH="$(pwd)/bin:$PATH"
# Now you can call the scripts identified above from anywhere...
tox -e qa
```
## RPM Testing
To focus on specific tests (e.g., email-related):
Apprise is also packaged for Redhat/Fedora as an RPM. To verify this processs works correctly an additional tool called `build-rpm.sh` is provided. It's best tested using the Docker environments:
```bash
# To test with el9; do the following:
docker-compose run --rm rpmbuild.el9 build-rpm.sh
```bash
tox -e qa -- -k email
```
# To test with f39; do the following:
docker-compose run --rm rpmbuild.f39 build-rpm.sh
```
To run a minimal dependency test set:
```bash
tox -e minimal
```
---
## 🧹 Linting and Formatting
Apprise uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting.
This is configured via `pyproject.toml`.
Run linting:
```bash
tox -e lint
```
Fix formatting automatically (where possible):
```bash
tox -e format
```
> Linting runs automatically on all PRs that touch Python files via GitHub
> Actions and will fail builds on violation.
---
## ✅ Pre-Commit Check (Recommended)
Before pushing or creating a PR, validate your work with:
```bash
tox -e lint,qa
```
Or use a combined check shortcut (if defined):
```bash
tox -e checkdone
```
This ensures your changes are linted, tested, and PR-ready.
---
## 📨 CLI Testing
You can run the `apprise` CLI using your local code without installation or run within docker containers:
```bash
# From the root of the repo
./bin/apprise -t "Title" -b "Body" mailto://user:pass@example.com
```
Alternatively you can continue to use the `tox` environment:
```bash
# syntax tox -e apprise -- [options], e.g.:
tox -e apprise -- -vv -b "test body" -t "test title" mailto://credentials
```
Optionally, add the `bin/apprise` to tests your changes
```bash
bin/apprise -vv -b "test body" -t "test title" <schema>
```
---
## 📦 RPM Build & Verification
Apprise supports RPM packaging for Fedora and RHEL-based systems. Use Docker
to safely test builds:
```bash
# Build RPM for EL9
docker-compose run --rm rpmbuild.el9 build-rpm.sh
# Build RPM for Fedora 42
docker-compose run --rm rpmbuild.f42 build-rpm.sh
```
## 📦 Specific Environment Emulation
You can also emulate your own docker environment and just test/build inside that
```bash
# Python v3.9 Testing
docker-compose run --rm test.py39 bash
# Python v3.10 Testing
docker-compose run --rm test.py310 bash
# Python v3.11 Testing
docker-compose run --rm test.py311 bash
# Python v3.12 Testing
docker-compose run --rm test.py312 bash
```
Once you've entered one of these environments, you can leverage the following command to work with:
1. `bin/test.sh`: runs the full test suite (same as `tox -e qa`)
1. `bin/apprise`: launches the Apprise CLI using the local build (same as `tox -e apprise`)
1. `ruff check . --fix`: auto-formats the codebase (same as `tox -e format`)
1. `ruff check .`: performs lint-only validation (same as `tox -e lint`)
1. `coverage run --source=apprise -m pytest tests`: manual test execution with coverage
The only advantage of this route is the overhead associated with each `tox` call is gone (faster responses). Otherwise just utilizing the `tox` commands can sometimes be easier.
## 🧪 GitHub Actions
GitHub Actions runs:
- ✅ Full test suite with coverage
- ✅ Linting (using Ruff)
- ✅ Packaging and validation
Linting **must pass** before PRs can be merged.
---
## 🧠 Developer Tips
- Add new plugins by following [`demo.py`](https://github.com/caronc/apprise/blob/master/apprise/plugins/demo.py) as a template.
- Write unit tests under `tests/` using the `AppriseURLTester` pattern.
- All new plugins must include test coverage and pass linting.

View File

@@ -55,8 +55,8 @@ sys.path.insert(
sys.path.insert(0, join(getcwd()))
# Apprise tool now importable
from apprise.cli import main # noqa E402
import logging # noqa E402
from apprise.cli import main
import logging
if __name__ == "__main__":

View File

@@ -26,90 +26,57 @@
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#!/usr/bin/env bash
set -e
# Directory where Apprise Source Code can be found
APPRISE_DIR="/apprise"
# Set Apprise root directory
APPRISE_DIR="${APPRISE_DIR:-/apprise}"
PYTHON=python3
PIP=pip3
VENV_CMD="$PYTHON -m venv"
TOX="tox -c $APPRISE_DIR/tox.ini"
DIST_DIR="${DIST_DIR:-$PWD/dist}"
mkdir -p "$DIST_DIR"
mkenv(){
# Prepares RPM Environment
cat << _EOF > $HOME/.rpmmacros
# macros
%_topdir $APPRISE_DIR
%_sourcedir %{_topdir}/dist
%_specdir %{_topdir}/dist
%_rpmdir %{_topdir}/dist/rpm
%_srcrpmdir %{_topdir}/dist/rpm
%_builddir %{_topdir}/build/rpm
_EOF
# Prepare our working directories if not already present
mkdir -p $APPRISE_DIR/{dist/rpm,build/rpm}
return 0
}
echo "==> Cleaning previous builds"
$TOX -e clean --notest
clean(){
# Tidy .pyc files
find $APPRISE_DIR -name '*.pyc' -delete &>/dev/null
find $APPRISE_DIR -type d -name '__pycache__' -exec rm -rf {} \ &>/dev/null;
# Remove previously build details
[ -d "$APPRISE_DIR/apprise.egg-info" ] && rm -rf $APPRISE_DIR/apprise.egg-info
[ -d "$APPRISE_DIR/build" ] && rm -rf $APPRISE_DIR/build
[ -d "$APPRISE_DIR/BUILDROOT" ] && rm -rf $APPRISE_DIR/BUILDROOT
}
echo "==> Linting RPM spec"
rpmlint "$APPRISE_DIR/packaging/redhat/python-apprise.spec"
build(){
# Test spec file for any issues
rpmlint "$APPRISE_DIR/packaging/redhat/python-apprise.spec"
[ $? -ne 0 ] && echo "RPMLint Failed!" && return 1
echo "==> Running tests"
$TOX -e py312
# Prepare RPM Package
# Detect our version
local VER=$(rpmspec -q --qf "%{version}\n" \
"$APPRISE_DIR/packaging/redhat/python-apprise.spec" 2>/dev/null | head -n1)
[ -z "$VER" ] && echo "Could not detect Apprise RPM Version" && return 1
echo "==> Generating man pages"
ronn --roff --organization="Chris Caron <lead2gold@gmail.com>" \
"$APPRISE_DIR/packaging/man/apprise.md"
if [ ! -f "$APPRISE_DIR/dist/apprise-$VER.tar.gz" ]; then
# Build Apprise
if [ ! -x $HOME/dev/bin/activate ]; then
$VENV_CMD $HOME/dev
[ $? -ne 0 ] && echo "Could not create Virtual Python Environment" && return 1
fi
. $HOME/dev/bin/activate
$PIP install coverage babel wheel markdown
echo "==> Extracting translations"
$TOX -e i18n || { echo "Translation extraction failed!" ; exit 1; }
pushd $APPRISE_DIR
# Build Man Page
ronn --roff $APPRISE_DIR/packaging/man/apprise.md
$PYTHON setup.py extract_messages
$PYTHON setup.py sdist
echo "==> Compiling translations"
$TOX -e compile || { echo "Translation compilation failed!" ; exit 1; }
# exit from our virtual environment
deactivate
fi
echo "==> Building source distribution"
$TOX -e build-sdist || { echo "sdist build failed!" ; exit 1; }
# Prepare our RPM Source and SPEC dependencies
find "$APPRISE_DIR/packaging/man/" -type f -name '*.1' \
-exec cp --verbose {} "$APPRISE_DIR/dist" \;
find "$APPRISE_DIR/packaging/redhat" -type f -name '*.patch' \
-exec cp --verbose {} "$APPRISE_DIR/dist" \;
find "$APPRISE_DIR/packaging/redhat" -type f -name '*.spec' \
-exec cp --verbose {} "$APPRISE_DIR/dist" \;
VERSION=$(rpmspec -q --qf "%{version}\n" "$APPRISE_DIR/packaging/redhat/python-apprise.spec" | head -n1)
TARBALL="$APPRISE_DIR/dist/apprise-${VERSION}.tar.gz"
# Build and Test our RPM Package
rpmbuild -ba "$APPRISE_DIR/dist/python-apprise.spec"
return $?
}
if [[ ! -f "$TARBALL" ]]; then
echo "Tarball not found: $TARBALL"
exit 1
fi
# Prepare our environment
mkenv
echo "==> Copying tarball to SOURCES directory"
mkdir -p "$APPRISE_DIR/SOURCES"
cp "$TARBALL" "$APPRISE_DIR/SOURCES/"
# Clean
clean
echo "==> Building RPM"
rpmbuild --define "_topdir $APPRISE_DIR" \
--define "_sourcedir $APPRISE_DIR/SOURCES" \
--define "_specdir $APPRISE_DIR/packaging/redhat" \
--define "_srcrpmdir $APPRISE_DIR/SRPMS" \
--define "_rpmdir $DIST_DIR" \
-ba "$APPRISE_DIR/packaging/redhat/python-apprise.spec"
# Build
build
echo "✅ RPM build completed successfully"
# Return our build status
exit $?

View File

@@ -1,105 +0,0 @@
#!/bin/bash
# -*- coding: utf-8 -*-
# BSD 2-Clause License
#
# Apprise - Push Notification Library.
# Copyright (c) 2025, Chris Caron <lead2gold@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")
# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
PYTHONPATH=""
FOUNDROOT=1
if [ -f "$(dirname $SCRIPTPATH)/setup.cfg" ]; then
pushd "$(dirname $SCRIPTPATH)" &>/dev/null
FOUNDROOT=$?
PYTHONPATH="$(dirname $SCRIPTPATH)"
elif [ -f "$SCRIPTPATH/setup.cfg" ]; then
pushd "$SCRIPTPATH" &>/dev/null
FOUNDROOT=$?
PYTHONPATH="$SCRIPTPATH"
fi
if [ $FOUNDROOT -ne 0 ]; then
echo "Error: Could not locate apprise setup.cfg file."
exit 1
fi
# Tidy previous reports (if present)
[ -d .coverage-reports ] && rm -rf .coverage-reports
# This is a useful tool for checking for any lint errors and additionally
# checking the overall coverage.
which flake8 &>/dev/null
[ $? -ne 0 ] && \
echo "Missing flake8; make sure it is installed:" && \
echo " > pip install flake8" && \
exit 1
which coverage &>/dev/null
[ $? -ne 0 ] && \
echo "Missing coverage; make sure it is installed:" &&
echo " > pip install pytest-cov coverage" && \
exit 1
echo "Performing PEP8 check..."
LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH flake8 . --show-source --statistics
if [ $? -ne 0 ]; then
echo "PEP8 check failed"
exit 1
fi
echo "PEP8 check succeeded; no errors found! :)"
echo
# Run our unit test coverage check
echo "Running test coverage check..."
pushd $PYTHONPATH &>/dev/null
if [ ! -z "$@" ]; then
LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH coverage run -m pytest -vv -k "$@"
RET=$?
else
LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH coverage run -m pytest -vv
RET=$?
fi
if [ $RET -ne 0 ]; then
echo "Tests failed."
exit 1
fi
# Build our report
LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH coverage combine
# Prepare XML Reference
LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH coverage xml
# Print our report
LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH coverage report --show-missing

View File

@@ -45,14 +45,14 @@ SCRIPTPATH=$(dirname "$SCRIPT")
PYTHONPATH=""
if [ -f "$(dirname $SCRIPTPATH)/setup.cfg" ]; then
if [ -f "$(dirname $SCRIPTPATH)/pyproject.toml" ]; then
PYTHONPATH="$(dirname $SCRIPTPATH)"
elif [ -f "$SCRIPTPATH/setup.cfg" ]; then
elif [ -f "$SCRIPTPATH/pyproject.toml" ]; then
PYTHONPATH="$SCRIPTPATH"
else
echo "Error: Could not locate apprise setup.cfg file."
echo "Error: Could not locate apprise pyproject.toml file."
exit 1
fi