From 8e1dad9f79b17f64bb87d0f2e295257c110d1416 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Tue, 29 Jul 2025 12:17:55 -0400 Subject: [PATCH] Minor typos and fixes (#1357) * added forgotten persistent storage setting on email * improved handling of default image * function wording fix * webexteams to support token= parameter arg --- apprise/plugins/email/base.py | 6 +- apprise/plugins/slack.py | 25 +++++--- apprise/plugins/spike.py | 4 +- apprise/plugins/webexteams.py | 19 +++++- bin/README.md | 3 +- bin/checkdone.sh | 105 +++++++++++++++++++++++++++++++ tests/test_plugin_simplepush.py | 17 ++--- tests/test_plugin_webex_teams.py | 9 +++ 8 files changed, 166 insertions(+), 22 deletions(-) create mode 100755 bin/checkdone.sh diff --git a/apprise/plugins/email/base.py b/apprise/plugins/email/base.py index cd62e4ee..187f9b72 100644 --- a/apprise/plugins/email/base.py +++ b/apprise/plugins/email/base.py @@ -36,7 +36,7 @@ import re import smtplib from typing import Optional -from ...common import NotifyFormat, NotifyType +from ...common import NotifyFormat, NotifyType, PersistentStoreMode from ...conversion import convert_between from ...locale import gettext_lazy as _ from ...logger import logger @@ -81,6 +81,10 @@ class NotifyEmail(NotifyBase): # Support attachments attachment_support = True + # Our default is to no not use persistent storage beyond in-memory + # reference; this allows us to auto-generate our config if needed + storage_mode = PersistentStoreMode.AUTO + # Default Notify Format notify_format = NotifyFormat.HTML diff --git a/apprise/plugins/slack.py b/apprise/plugins/slack.py index 79c7ee4a..be6ae23e 100644 --- a/apprise/plugins/slack.py +++ b/apprise/plugins/slack.py @@ -325,8 +325,8 @@ class NotifySlack(NotifyBase): token_b=None, token_c=None, targets=None, - include_image=True, - include_footer=True, + include_image=None, + include_footer=None, use_blocks=None, **kwargs, ): @@ -414,10 +414,15 @@ class NotifySlack(NotifyBase): re.IGNORECASE, ) # Place a thumbnail image inline with the message body - self.include_image = include_image + self.include_image = \ + self.template_args["image"]["default"] \ + if include_image is None else include_image # Place a footer with each post - self.include_footer = include_footer + self.include_footer = \ + self.template_args["footer"]["default"] \ + if include_footer is None else include_footer + return def send( @@ -1224,18 +1229,18 @@ class NotifySlack(NotifyBase): ) # Get Image Flag - results["include_image"] = parse_bool( - results["qsd"].get("image", True) - ) + results["include_image"] = \ + parse_bool(results["qsd"].get( + "image", NotifySlack.template_args["image"]["default"])) # Get Payload structure (use blocks?) if "blocks" in results["qsd"] and len(results["qsd"]["blocks"]): results["use_blocks"] = parse_bool(results["qsd"]["blocks"]) # Get Footer Flag - results["include_footer"] = parse_bool( - results["qsd"].get("footer", True) - ) + results["include_footer"] = \ + parse_bool(results["qsd"].get( + "footer", NotifySlack.template_args["footer"]["default"])) return results diff --git a/apprise/plugins/spike.py b/apprise/plugins/spike.py index 4dfedd58..65d681cf 100644 --- a/apprise/plugins/spike.py +++ b/apprise/plugins/spike.py @@ -108,7 +108,6 @@ class NotifySpike(NotifyBase): def send(self, body, title="", notify_type=NotifyType.INFO, **kwargs): """Send Spike.sh Notification.""" - self.throttle() payload = { "message": title if title else body, @@ -120,6 +119,9 @@ class NotifySpike(NotifyBase): "Content-Type": "application/json", } + # Always call throttle before any remote server i/o is made + self.throttle() + try: response = requests.post( self.webhook_url, diff --git a/apprise/plugins/webexteams.py b/apprise/plugins/webexteams.py index 6a757dc7..35c460ee 100644 --- a/apprise/plugins/webexteams.py +++ b/apprise/plugins/webexteams.py @@ -122,6 +122,15 @@ class NotifyWebexTeams(NotifyBase): }, ) + template_args = dict( + NotifyBase.template_args, + **{ + "token": { + "alias_of": "token", + }, + }, + ) + def __init__(self, token, **kwargs): """Initialize Webex Teams Object.""" super().__init__(**kwargs) @@ -234,8 +243,14 @@ class NotifyWebexTeams(NotifyBase): # We're done early as we couldn't load the results return results - # The first token is stored in the hostname - results["token"] = NotifyWebexTeams.unquote(results["host"]) + # Set our token if found as an argument + if "token" in results["qsd"] and len(results["qsd"]["token"]): + results["token"] = \ + NotifyWebexTeams.unquote(results["qsd"]["token"]) + + else: + # The first token is stored in the hostname + results["token"] = NotifyWebexTeams.unquote(results["host"]) return results diff --git a/bin/README.md b/bin/README.md index c839b817..e64d6e9b 100644 --- a/bin/README.md +++ b/bin/README.md @@ -139,7 +139,8 @@ 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/test.sh`: runs the full test suite (same as `tox -e qa`) but without coveage +1. `bin/checkdone.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`) diff --git a/bin/checkdone.sh b/bin/checkdone.sh new file mode 100755 index 00000000..c30c12dc --- /dev/null +++ b/bin/checkdone.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# -*- coding: utf-8 -*- +# BSD 2-Clause License +# +# Apprise - Push Notification Library. +# Copyright (c) 2025, Chris Caron +# +# 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)/pyproject.toml" ]; then + pushd "$(dirname $SCRIPTPATH)" &>/dev/null + FOUNDROOT=$? + PYTHONPATH="$(dirname $SCRIPTPATH)" + +elif [ -f "$SCRIPTPATH/pyproject.toml" ]; then + pushd "$SCRIPTPATH" &>/dev/null + FOUNDROOT=$? + PYTHONPATH="$SCRIPTPATH" +fi + +if [ $FOUNDROOT -ne 0 ]; then + echo "Error: Could not locate Apprise pyproject.toml 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 ruff &>/dev/null +[ $? -ne 0 ] && \ + echo "Missing ruff; make sure it is installed:" && \ + echo " > pip install ruff" && \ + 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 ruff check +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 diff --git a/tests/test_plugin_simplepush.py b/tests/test_plugin_simplepush.py index 56330425..4d55333d 100644 --- a/tests/test_plugin_simplepush.py +++ b/tests/test_plugin_simplepush.py @@ -137,10 +137,11 @@ def test_plugin_simplepush_urls(): @pytest.mark.skipif( "cryptography" in sys.modules, - reason="Requires that cryptography NOT be installed", -) -def test_plugin_fcm_cryptography_import_error(): - """NotifySimplePush() Cryptography loading failure.""" + reason="Requires that cryptography NOT be installed") +def test_plugin_simpepush_cryptography_import_error(): + """ + NotifySimplePush() Cryptography loading failure + """ # Attempt to instantiate our object obj = Apprise.instantiate("spush://{}".format("Y" * 14)) @@ -150,10 +151,12 @@ def test_plugin_fcm_cryptography_import_error(): @pytest.mark.skipif( - "cryptography" not in sys.modules, reason="Requires cryptography" -) + "cryptography" not in sys.modules, reason="Requires cryptography") def test_plugin_simplepush_edge_cases(): - """NotifySimplePush() Edge Cases.""" + """ + NotifySimplePush() Edge Cases + + """ # No token with pytest.raises(TypeError): diff --git a/tests/test_plugin_webex_teams.py b/tests/test_plugin_webex_teams.py index 52281276..fc00789e 100644 --- a/tests/test_plugin_webex_teams.py +++ b/tests/test_plugin_webex_teams.py @@ -62,6 +62,15 @@ apprise_url_tests = ( "privacy_url": "wxteams://a...a/", }, ), + ( + "wxteams://?token={}".format("a" * 80), + { + # token provided - we're good + "instance": NotifyWebexTeams, + # Our expected url(privacy=True) startswith() response: + "privacy_url": "wxteams://a...a/", + }, + ), ( "webex://{}".format("a" * 140), {