mirror of
https://github.com/caronc/apprise.git
synced 2024-12-12 09:51:05 +01:00
CLI environment variable over-ride support (#1231)
This commit is contained in:
parent
1065c021d3
commit
01c1082ad8
12
README.md
12
README.md
@ -39,6 +39,7 @@ System Administrators and DevOps who wish to send a notification now no longer n
|
|||||||
* [Configuration Files](#cli-configuration-files)
|
* [Configuration Files](#cli-configuration-files)
|
||||||
* [File Attachments](#cli-file-attachments)
|
* [File Attachments](#cli-file-attachments)
|
||||||
* [Loading Custom Notifications/Hooks](#cli-loading-custom-notificationshooks)
|
* [Loading Custom Notifications/Hooks](#cli-loading-custom-notificationshooks)
|
||||||
|
* [Environment Variables](#cli-environment-variables)
|
||||||
* [Developer API Usage](#developer-api-usage)
|
* [Developer API Usage](#developer-api-usage)
|
||||||
* [Configuration Files](#api-configuration-files)
|
* [Configuration Files](#api-configuration-files)
|
||||||
* [File Attachments](#api-file-attachments)
|
* [File Attachments](#api-file-attachments)
|
||||||
@ -352,6 +353,17 @@ apprise -vv --title 'custom override' \
|
|||||||
|
|
||||||
You can read more about creating your own custom notifications and/or hooks [here](https://github.com/caronc/apprise/wiki/decorator_notify).
|
You can read more about creating your own custom notifications and/or hooks [here](https://github.com/caronc/apprise/wiki/decorator_notify).
|
||||||
|
|
||||||
|
## CLI Environment Variables
|
||||||
|
|
||||||
|
Those using the Command Line Interface (CLI) can also leverage environment variables to pre-set the default settings:
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|------------------------ | ----------------- |
|
||||||
|
| `APPRISE_URLS` | Specify the default URLs to notify IF none are otherwise specified on the command line explicitly. If the `--config` (`-c`) is specified, then this will over-rides any reference to this variable. Use white space and/or a comma (`,`) to delimit multiple entries.
|
||||||
|
| `APPRISE_CONFIG_PATH` | Explicitly specify the config search path to use (over-riding the default). The path(s) defined here must point to the absolute filename to open/reference. Use a semi-colon (`;`), line-feed (`\n`), and/or carriage return (`\r`) to delimit multiple entries.
|
||||||
|
| `APPRISE_PLUGIN_PATH` | Explicitly specify the custom plugin search path to use (over-riding the default). Use a semi-colon (`;`), line-feed (`\n`), and/or carriage return (`\r`) to delimit multiple entries.
|
||||||
|
| `APPRISE_STORAGE_PATH` | Explicitly specify the persistent storage path to use (over-riding the default).
|
||||||
|
|
||||||
# Developer API Usage
|
# Developer API Usage
|
||||||
|
|
||||||
To send a notification from within your python application, just do the following:
|
To send a notification from within your python application, just do the following:
|
||||||
|
@ -68,6 +68,21 @@ DEFAULT_STORAGE_PRUNE_DAYS = \
|
|||||||
DEFAULT_STORAGE_UID_LENGTH = \
|
DEFAULT_STORAGE_UID_LENGTH = \
|
||||||
int(os.environ.get('APPRISE_STORAGE_UID_LENGTH', 8))
|
int(os.environ.get('APPRISE_STORAGE_UID_LENGTH', 8))
|
||||||
|
|
||||||
|
# Defines the envrionment variable to parse if defined. This is ONLY
|
||||||
|
# Referenced if:
|
||||||
|
# - No Configuration Files were found/loaded/specified
|
||||||
|
# - No URLs were provided directly into the CLI Call
|
||||||
|
DEFAULT_ENV_APPRISE_URLS = 'APPRISE_URLS'
|
||||||
|
|
||||||
|
# Defines the over-ride path for the configuration files read
|
||||||
|
DEFAULT_ENV_APPRISE_CONFIG_PATH = 'APPRISE_CONFIG_PATH'
|
||||||
|
|
||||||
|
# Defines the over-ride path for the plugins to load
|
||||||
|
DEFAULT_ENV_APPRISE_PLUGIN_PATH = 'APPRISE_PLUGIN_PATH'
|
||||||
|
|
||||||
|
# Defines the over-ride path for the persistent storage
|
||||||
|
DEFAULT_ENV_APPRISE_STORAGE_PATH = 'APPRISE_STORAGE_PATH'
|
||||||
|
|
||||||
# Defines our click context settings adding -h to the additional options that
|
# Defines our click context settings adding -h to the additional options that
|
||||||
# can be specified to get the help menu to come up
|
# can be specified to get the help menu to come up
|
||||||
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
||||||
@ -496,11 +511,53 @@ def main(ctx, body, title, config, attach, urls, notification_type, theme, tag,
|
|||||||
# issue. For consistency, we also return a 2
|
# issue. For consistency, we also return a 2
|
||||||
ctx.exit(2)
|
ctx.exit(2)
|
||||||
|
|
||||||
if not plugin_path:
|
#
|
||||||
# Prepare a default set of plugin path
|
# Apply Environment Over-rides if defined
|
||||||
plugin_path = \
|
#
|
||||||
[path for path in DEFAULT_PLUGIN_PATHS
|
_config_paths = DEFAULT_CONFIG_PATHS
|
||||||
if exists(path_decode(path))]
|
if 'APPRISE_CONFIG' in os.environ:
|
||||||
|
# Deprecate (this was from previous versions of Apprise <= 1.9.1)
|
||||||
|
logger.deprecate(
|
||||||
|
'APPRISE_CONFIG environment variable has been changed to '
|
||||||
|
f'{DEFAULT_ENV_APPRISE_CONFIG_PATH}')
|
||||||
|
logger.debug(
|
||||||
|
'Loading provided APPRISE_CONFIG (deprecated) environment '
|
||||||
|
'variable')
|
||||||
|
_config_paths = (os.environ.get('APPRISE_CONFIG', '').strip(), )
|
||||||
|
|
||||||
|
elif DEFAULT_ENV_APPRISE_CONFIG_PATH in os.environ:
|
||||||
|
logger.debug(
|
||||||
|
f'Loading provided {DEFAULT_ENV_APPRISE_CONFIG_PATH} '
|
||||||
|
'environment variable')
|
||||||
|
_config_paths = re.split(
|
||||||
|
r'[\r\n;]+', os.environ.get(
|
||||||
|
DEFAULT_ENV_APPRISE_CONFIG_PATH).strip())
|
||||||
|
|
||||||
|
_plugin_paths = DEFAULT_PLUGIN_PATHS
|
||||||
|
if DEFAULT_ENV_APPRISE_PLUGIN_PATH in os.environ:
|
||||||
|
logger.debug(
|
||||||
|
f'Loading provided {DEFAULT_ENV_APPRISE_PLUGIN_PATH} environment '
|
||||||
|
'variable')
|
||||||
|
_plugin_paths = re.split(
|
||||||
|
r'[\r\n;]+', os.environ.get(
|
||||||
|
DEFAULT_ENV_APPRISE_PLUGIN_PATH).strip())
|
||||||
|
|
||||||
|
if DEFAULT_ENV_APPRISE_STORAGE_PATH in os.environ:
|
||||||
|
logger.debug(
|
||||||
|
f'Loading provided {DEFAULT_ENV_APPRISE_STORAGE_PATH} environment '
|
||||||
|
'variable')
|
||||||
|
storage_path = \
|
||||||
|
os.environ.get(DEFAULT_ENV_APPRISE_STORAGE_PATH).strip()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Continue with initialization process
|
||||||
|
#
|
||||||
|
|
||||||
|
# Prepare a default set of plugin paths to scan; anything specified
|
||||||
|
# on the CLI always trumps
|
||||||
|
plugin_paths = \
|
||||||
|
[path for path in _plugin_paths if exists(path_decode(path))] \
|
||||||
|
if not plugin_path else plugin_path
|
||||||
|
|
||||||
if storage_uid_length < 2:
|
if storage_uid_length < 2:
|
||||||
click.echo(
|
click.echo(
|
||||||
@ -533,7 +590,7 @@ def main(ctx, body, title, config, attach, urls, notification_type, theme, tag,
|
|||||||
async_mode=disable_async is not True,
|
async_mode=disable_async is not True,
|
||||||
|
|
||||||
# Load our plugins
|
# Load our plugins
|
||||||
plugin_paths=plugin_path,
|
plugin_paths=plugin_paths,
|
||||||
|
|
||||||
# Load our persistent storage path
|
# Load our persistent storage path
|
||||||
storage_path=path_decode(storage_path),
|
storage_path=path_decode(storage_path),
|
||||||
@ -636,8 +693,7 @@ def main(ctx, body, title, config, attach, urls, notification_type, theme, tag,
|
|||||||
# 1. URLs by command line
|
# 1. URLs by command line
|
||||||
# 2. Configuration by command line
|
# 2. Configuration by command line
|
||||||
# 3. URLs by environment variable: APPRISE_URLS
|
# 3. URLs by environment variable: APPRISE_URLS
|
||||||
# 4. Configuration by environment variable: APPRISE_CONFIG
|
# 4. Default Configuration File(s)
|
||||||
# 5. Default Configuration File(s) (if found)
|
|
||||||
#
|
#
|
||||||
elif urls and not storage_action:
|
elif urls and not storage_action:
|
||||||
if tag:
|
if tag:
|
||||||
@ -662,8 +718,10 @@ def main(ctx, body, title, config, attach, urls, notification_type, theme, tag,
|
|||||||
a.add(AppriseConfig(
|
a.add(AppriseConfig(
|
||||||
paths=config, asset=asset, recursion=recursion_depth))
|
paths=config, asset=asset, recursion=recursion_depth))
|
||||||
|
|
||||||
elif os.environ.get('APPRISE_URLS', '').strip():
|
elif os.environ.get(DEFAULT_ENV_APPRISE_URLS, '').strip():
|
||||||
logger.debug('Loading provided APPRISE_URLS environment variable')
|
logger.debug(
|
||||||
|
f'Loading provided {DEFAULT_ENV_APPRISE_URLS} environment '
|
||||||
|
'variable')
|
||||||
if tag:
|
if tag:
|
||||||
# Ignore any tags specified
|
# Ignore any tags specified
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@ -671,19 +729,12 @@ def main(ctx, body, title, config, attach, urls, notification_type, theme, tag,
|
|||||||
tag = None
|
tag = None
|
||||||
|
|
||||||
# Attempt to use our APPRISE_URLS environment variable (if populated)
|
# Attempt to use our APPRISE_URLS environment variable (if populated)
|
||||||
a.add(os.environ['APPRISE_URLS'].strip())
|
a.add(os.environ[DEFAULT_ENV_APPRISE_URLS].strip())
|
||||||
|
|
||||||
elif os.environ.get('APPRISE_CONFIG', '').strip():
|
|
||||||
logger.debug('Loading provided APPRISE_CONFIG environment variable')
|
|
||||||
# Fall back to config environment variable (if populated)
|
|
||||||
a.add(AppriseConfig(
|
|
||||||
paths=os.environ['APPRISE_CONFIG'].strip(),
|
|
||||||
asset=asset, recursion=recursion_depth))
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Load default configuration
|
# Load default configuration
|
||||||
a.add(AppriseConfig(
|
a.add(AppriseConfig(
|
||||||
paths=[f for f in DEFAULT_CONFIG_PATHS if isfile(path_decode(f))],
|
paths=[f for f in _config_paths if isfile(path_decode(f))],
|
||||||
asset=asset, recursion=recursion_depth))
|
asset=asset, recursion=recursion_depth))
|
||||||
|
|
||||||
if not dry_run and not (a or storage_action):
|
if not dry_run and not (a or storage_action):
|
||||||
|
@ -29,10 +29,8 @@ import copy
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import contextlib
|
|
||||||
import os
|
import os
|
||||||
import binascii
|
import binascii
|
||||||
import locale
|
|
||||||
import platform
|
import platform
|
||||||
import typing
|
import typing
|
||||||
import base64
|
import base64
|
||||||
@ -1518,39 +1516,6 @@ def cwe312_url(url):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def environ(*remove, **update):
|
|
||||||
"""
|
|
||||||
Temporarily updates the ``os.environ`` dictionary in-place.
|
|
||||||
|
|
||||||
The ``os.environ`` dictionary is updated in-place so that the modification
|
|
||||||
is sure to work in all situations.
|
|
||||||
|
|
||||||
:param remove: Environment variable(s) to remove.
|
|
||||||
:param update: Dictionary of environment variables and values to
|
|
||||||
add/update.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Create a backup of our environment for restoration purposes
|
|
||||||
env_orig = os.environ.copy()
|
|
||||||
loc_orig = locale.getlocale()
|
|
||||||
try:
|
|
||||||
os.environ.update(update)
|
|
||||||
[os.environ.pop(k, None) for k in remove]
|
|
||||||
yield
|
|
||||||
|
|
||||||
finally:
|
|
||||||
# Restore our snapshot
|
|
||||||
os.environ = env_orig.copy()
|
|
||||||
try:
|
|
||||||
# Restore locale
|
|
||||||
locale.setlocale(locale.LC_ALL, loc_orig)
|
|
||||||
|
|
||||||
except locale.Error:
|
|
||||||
# Handle this case
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def apply_template(template, app_mode=TemplateType.RAW, **kwargs):
|
def apply_template(template, app_mode=TemplateType.RAW, **kwargs):
|
||||||
"""
|
"""
|
||||||
Takes a template in a str format and applies all of the keywords
|
Takes a template in a str format and applies all of the keywords
|
||||||
|
@ -159,6 +159,9 @@ visit the [Apprise GitHub page][serviceurls] and see what's available.
|
|||||||
|
|
||||||
[serviceurls]: https://github.com/caronc/apprise/wiki#notification-services
|
[serviceurls]: https://github.com/caronc/apprise/wiki#notification-services
|
||||||
|
|
||||||
|
The **environment variable** of `APPRISE_URLS` (comma/space delimited) can be specified to
|
||||||
|
provide the default set of URLs you wish to notify if none are otherwise specified.
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|
||||||
Send a notification to as many servers as you want to specify as you can
|
Send a notification to as many servers as you want to specify as you can
|
||||||
@ -215,8 +218,13 @@ files and loads them:
|
|||||||
~/.config/apprise/plugins
|
~/.config/apprise/plugins
|
||||||
/var/lib/apprise/plugins
|
/var/lib/apprise/plugins
|
||||||
|
|
||||||
|
The **environment variable** of `APPRISE_PLUGIN_PATH` can be specified to override
|
||||||
|
the list identified above with one of your own. use a semi-colon (`;`), line-feed (`\n`),
|
||||||
|
and/or carriage return (`\r`) to delimit multiple entries.
|
||||||
|
|
||||||
Simply create your own python file with the following bare minimum content in
|
Simply create your own python file with the following bare minimum content in
|
||||||
it:
|
it:
|
||||||
|
|
||||||
from apprise.decorators import notify
|
from apprise.decorators import notify
|
||||||
|
|
||||||
# This example assumes you want your function to trigger on foobar://
|
# This example assumes you want your function to trigger on foobar://
|
||||||
@ -263,6 +271,10 @@ in the following local locations for configuration files and loads them:
|
|||||||
The **configuration files** specified above can also be identified with a `.yml`
|
The **configuration files** specified above can also be identified with a `.yml`
|
||||||
extension or even just entirely removing the `.conf` extension altogether.
|
extension or even just entirely removing the `.conf` extension altogether.
|
||||||
|
|
||||||
|
The **environment variable** of `APPRISE_CONFIG_PATH` can be specified to override
|
||||||
|
the list identified above with one of your own. use a semi-colon (`;`), line-feed (`\n`),
|
||||||
|
and/or carriage return (`\r`) to delimit multiple entries.
|
||||||
|
|
||||||
If a default configuration file is referenced in any way by the **apprise**
|
If a default configuration file is referenced in any way by the **apprise**
|
||||||
tool, you no longer need to provide it a Service URL. Usage of the **apprise**
|
tool, you no longer need to provide it a Service URL. Usage of the **apprise**
|
||||||
tool simplifies to:
|
tool simplifies to:
|
||||||
@ -281,6 +293,23 @@ configuration that you want and only specifically notify a subset of them:
|
|||||||
[tagging]: https://github.com/caronc/apprise/wiki/CLI_Usage#label-leverage-tagging
|
[tagging]: https://github.com/caronc/apprise/wiki/CLI_Usage#label-leverage-tagging
|
||||||
[pstorage]: https://github.com/caronc/apprise/wiki/persistent_storage
|
[pstorage]: https://github.com/caronc/apprise/wiki/persistent_storage
|
||||||
|
|
||||||
|
## ENVIRONMENT VARIABLES
|
||||||
|
`APPRISE_URLS`:
|
||||||
|
Specify the default URLs to notify IF none are otherwise specified on the command line
|
||||||
|
explicitly. If the `--config` (`-c`) is specified, then this will over-rides any
|
||||||
|
reference to this variable. Use white space and/or a comma (`,`) to delimit multiple entries.
|
||||||
|
|
||||||
|
`APPRISE_CONFIG_PATH`:
|
||||||
|
Explicitly specify the config search path to use (over-riding the default).
|
||||||
|
Use a semi-colon (`;`), line-feed (`\n`), and/or carriage return (`\r`) to delimit multiple entries.
|
||||||
|
|
||||||
|
`APPRISE_PLUGIN_PATH`:
|
||||||
|
Explicitly specify the custom plugin search path to use (over-riding the default).
|
||||||
|
Use a semi-colon (`;`), line-feed (`\n`), and/or carriage return (`\r`) to delimit multiple entries.
|
||||||
|
|
||||||
|
`APPRISE_STORAGE_PATH`:
|
||||||
|
Explicitly specify the persistent storage path to use (over-riding the default).
|
||||||
|
|
||||||
## BUGS
|
## BUGS
|
||||||
|
|
||||||
If you find any bugs, please make them known at:
|
If you find any bugs, please make them known at:
|
||||||
|
@ -29,9 +29,11 @@
|
|||||||
from .rest import AppriseURLTester
|
from .rest import AppriseURLTester
|
||||||
from .asyncio import OuterEventLoop
|
from .asyncio import OuterEventLoop
|
||||||
from .module import reload_plugin
|
from .module import reload_plugin
|
||||||
|
from .environment import environ
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AppriseURLTester',
|
'AppriseURLTester',
|
||||||
'OuterEventLoop',
|
'OuterEventLoop',
|
||||||
'reload_plugin',
|
'reload_plugin',
|
||||||
|
'environ',
|
||||||
]
|
]
|
||||||
|
68
test/helpers/environment.py
Normal file
68
test/helpers/environment.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 2-Clause License
|
||||||
|
#
|
||||||
|
# Apprise - Push Notification Library.
|
||||||
|
# Copyright (c) 2024, 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import contextlib
|
||||||
|
import locale
|
||||||
|
|
||||||
|
# Disable logging for a cleaner testing output
|
||||||
|
import logging
|
||||||
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def environ(*remove, **update):
|
||||||
|
"""
|
||||||
|
Temporarily updates the ``os.environ`` dictionary in-place.
|
||||||
|
|
||||||
|
The ``os.environ`` dictionary is updated in-place so that the modification
|
||||||
|
is sure to work in all situations.
|
||||||
|
|
||||||
|
:param remove: Environment variable(s) to remove.
|
||||||
|
:param update: Dictionary of environment variables and values to
|
||||||
|
add/update.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create a backup of our environment for restoration purposes
|
||||||
|
env_orig = os.environ.copy()
|
||||||
|
loc_orig = locale.getlocale()
|
||||||
|
try:
|
||||||
|
os.environ.update(update)
|
||||||
|
[os.environ.pop(k, None) for k in remove]
|
||||||
|
yield
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Restore our snapshot
|
||||||
|
os.environ = env_orig.copy()
|
||||||
|
try:
|
||||||
|
# Restore locale
|
||||||
|
locale.setlocale(locale.LC_ALL, loc_orig)
|
||||||
|
|
||||||
|
except locale.Error:
|
||||||
|
# Handle this case
|
||||||
|
pass
|
@ -40,7 +40,7 @@ from apprise import cli
|
|||||||
from apprise import NotifyBase
|
from apprise import NotifyBase
|
||||||
from apprise import NotificationManager
|
from apprise import NotificationManager
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
from apprise.utils import environ
|
from helpers import environ
|
||||||
from apprise.locale import gettext_lazy as _
|
from apprise.locale import gettext_lazy as _
|
||||||
|
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
@ -515,6 +515,22 @@ def test_apprise_cli_nux_env(tmpdir):
|
|||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
|
|
||||||
with environ(APPRISE_CONFIG=str(t2)):
|
with environ(APPRISE_CONFIG=str(t2)):
|
||||||
|
# Deprecated test case
|
||||||
|
result = runner.invoke(cli.main, [
|
||||||
|
'-b', 'has myTag',
|
||||||
|
'--tag', 'myTag',
|
||||||
|
])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
with environ(APPRISE_CONFIG_PATH=str(t2)):
|
||||||
|
# Our configuration file will load from our environmment variable
|
||||||
|
result = runner.invoke(cli.main, [
|
||||||
|
'-b', 'has myTag',
|
||||||
|
'--tag', 'myTag',
|
||||||
|
])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
with environ(APPRISE_CONFIG_PATH=str(t2) + ';/another/path'):
|
||||||
# Our configuration file will load from our environmment variable
|
# Our configuration file will load from our environmment variable
|
||||||
result = runner.invoke(cli.main, [
|
result = runner.invoke(cli.main, [
|
||||||
'-b', 'has myTag',
|
'-b', 'has myTag',
|
||||||
@ -677,6 +693,17 @@ def test_apprise_cli_modules(tmpdir):
|
|||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
with environ(
|
||||||
|
APPRISE_PLUGIN_PATH=str(notify_cmod) + ';' + str(notify_cmod2)):
|
||||||
|
# Leverage our environment variables to specify the plugin path
|
||||||
|
result = runner.invoke(cli.main, [
|
||||||
|
'-b', 'body',
|
||||||
|
'climod://',
|
||||||
|
'climod2://',
|
||||||
|
])
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
sys.platform == "win32", reason="Unreliable results to be determined")
|
sys.platform == "win32", reason="Unreliable results to be determined")
|
||||||
|
127
test/test_apprise_helpers.py
Normal file
127
test/test_apprise_helpers.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 2-Clause License
|
||||||
|
#
|
||||||
|
# Apprise - Push Notification Library.
|
||||||
|
# Copyright (c) 2024, 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import helpers
|
||||||
|
|
||||||
|
# Disable logging for a cleaner testing output
|
||||||
|
import logging
|
||||||
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
|
# Ensure we don't create .pyc files for these tests
|
||||||
|
sys.dont_write_bytecode = True
|
||||||
|
|
||||||
|
|
||||||
|
def test_environ_temporary_change():
|
||||||
|
"""helpers: environ() testing
|
||||||
|
"""
|
||||||
|
# This is a helper function; but it does enough that we want to verify
|
||||||
|
# our usage of it works correctly; yes... we're testing a test
|
||||||
|
|
||||||
|
e_key1 = 'APPRISE_TEMP1'
|
||||||
|
e_key2 = 'APPRISE_TEMP2'
|
||||||
|
e_key3 = 'APPRISE_TEMP3'
|
||||||
|
|
||||||
|
e_val1 = 'ABCD'
|
||||||
|
e_val2 = 'DEFG'
|
||||||
|
e_val3 = 'HIJK'
|
||||||
|
|
||||||
|
os.environ[e_key1] = e_val1
|
||||||
|
os.environ[e_key2] = e_val2
|
||||||
|
os.environ[e_key3] = e_val3
|
||||||
|
|
||||||
|
# Ensure our environment variable stuck
|
||||||
|
assert e_key1 in os.environ
|
||||||
|
assert e_val1 in os.environ[e_key1]
|
||||||
|
assert e_key2 in os.environ
|
||||||
|
assert e_val2 in os.environ[e_key2]
|
||||||
|
assert e_key3 in os.environ
|
||||||
|
assert e_val3 in os.environ[e_key3]
|
||||||
|
|
||||||
|
with helpers.environ(e_key1, e_key3):
|
||||||
|
# Eliminates Environment Variable 1 and 3
|
||||||
|
assert e_key1 not in os.environ
|
||||||
|
assert e_key2 in os.environ
|
||||||
|
assert e_val2 in os.environ[e_key2]
|
||||||
|
assert e_key3 not in os.environ
|
||||||
|
|
||||||
|
# after with is over, environment is restored to normal
|
||||||
|
assert e_key1 in os.environ
|
||||||
|
assert e_val1 in os.environ[e_key1]
|
||||||
|
assert e_key2 in os.environ
|
||||||
|
assert e_val2 in os.environ[e_key2]
|
||||||
|
assert e_key3 in os.environ
|
||||||
|
assert e_val3 in os.environ[e_key3]
|
||||||
|
|
||||||
|
d_key = 'APPRISE_NOT_SET'
|
||||||
|
n_key = 'APPRISE_NEW_KEY'
|
||||||
|
n_val = 'NEW_VAL'
|
||||||
|
|
||||||
|
# Verify that our temporary variables (defined above) are not pre-existing
|
||||||
|
# environemnt variables as we'll be setting them below
|
||||||
|
assert n_key not in os.environ
|
||||||
|
assert d_key not in os.environ
|
||||||
|
|
||||||
|
# makes it easier to pass in the arguments
|
||||||
|
updates = {
|
||||||
|
e_key1: e_val3,
|
||||||
|
e_key2: e_val1,
|
||||||
|
n_key: n_val,
|
||||||
|
}
|
||||||
|
with helpers.environ(d_key, e_key3, **updates):
|
||||||
|
# Attempt to eliminate an undefined key (silently ignored)
|
||||||
|
# Eliminates Environment Variable 3
|
||||||
|
# Environment Variable 1 takes on the value of Env 3
|
||||||
|
# Environment Variable 2 takes on the value of Env 1
|
||||||
|
# Set a brand new variable that previously didn't exist
|
||||||
|
assert e_key1 in os.environ
|
||||||
|
assert e_val3 in os.environ[e_key1]
|
||||||
|
assert e_key2 in os.environ
|
||||||
|
assert e_val1 in os.environ[e_key2]
|
||||||
|
assert e_key3 not in os.environ
|
||||||
|
|
||||||
|
# Can't delete a variable that doesn't exist; so we're in the same
|
||||||
|
# state here.
|
||||||
|
assert d_key not in os.environ
|
||||||
|
|
||||||
|
# Our temporary variables will be found now
|
||||||
|
assert n_key in os.environ
|
||||||
|
assert n_val in os.environ[n_key]
|
||||||
|
|
||||||
|
# after with is over, environment is restored to normal
|
||||||
|
assert e_key1 in os.environ
|
||||||
|
assert e_val1 in os.environ[e_key1]
|
||||||
|
assert e_key2 in os.environ
|
||||||
|
assert e_val2 in os.environ[e_key2]
|
||||||
|
assert e_key3 in os.environ
|
||||||
|
assert e_val3 in os.environ[e_key3]
|
||||||
|
|
||||||
|
# Even our temporary variables are now missing
|
||||||
|
assert n_key not in os.environ
|
||||||
|
assert d_key not in os.environ
|
@ -34,7 +34,7 @@ import ctypes
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from apprise import locale
|
from apprise import locale
|
||||||
from apprise.utils import environ
|
from helpers import environ
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
|
|
||||||
# Disable logging for a cleaner testing output
|
# Disable logging for a cleaner testing output
|
||||||
|
@ -2545,93 +2545,6 @@ def test_apprise_validate_regex():
|
|||||||
"- abcd -", r'-(?P<value>[ABCD]+)-', None, fmt="{value}") is None
|
"- abcd -", r'-(?P<value>[ABCD]+)-', None, fmt="{value}") is None
|
||||||
|
|
||||||
|
|
||||||
def test_environ_temporary_change():
|
|
||||||
"""utils: environ() testing
|
|
||||||
"""
|
|
||||||
|
|
||||||
e_key1 = 'APPRISE_TEMP1'
|
|
||||||
e_key2 = 'APPRISE_TEMP2'
|
|
||||||
e_key3 = 'APPRISE_TEMP3'
|
|
||||||
|
|
||||||
e_val1 = 'ABCD'
|
|
||||||
e_val2 = 'DEFG'
|
|
||||||
e_val3 = 'HIJK'
|
|
||||||
|
|
||||||
os.environ[e_key1] = e_val1
|
|
||||||
os.environ[e_key2] = e_val2
|
|
||||||
os.environ[e_key3] = e_val3
|
|
||||||
|
|
||||||
# Ensure our environment variable stuck
|
|
||||||
assert e_key1 in os.environ
|
|
||||||
assert e_val1 in os.environ[e_key1]
|
|
||||||
assert e_key2 in os.environ
|
|
||||||
assert e_val2 in os.environ[e_key2]
|
|
||||||
assert e_key3 in os.environ
|
|
||||||
assert e_val3 in os.environ[e_key3]
|
|
||||||
|
|
||||||
with utils.environ(e_key1, e_key3):
|
|
||||||
# Eliminates Environment Variable 1 and 3
|
|
||||||
assert e_key1 not in os.environ
|
|
||||||
assert e_key2 in os.environ
|
|
||||||
assert e_val2 in os.environ[e_key2]
|
|
||||||
assert e_key3 not in os.environ
|
|
||||||
|
|
||||||
# after with is over, environment is restored to normal
|
|
||||||
assert e_key1 in os.environ
|
|
||||||
assert e_val1 in os.environ[e_key1]
|
|
||||||
assert e_key2 in os.environ
|
|
||||||
assert e_val2 in os.environ[e_key2]
|
|
||||||
assert e_key3 in os.environ
|
|
||||||
assert e_val3 in os.environ[e_key3]
|
|
||||||
|
|
||||||
d_key = 'APPRISE_NOT_SET'
|
|
||||||
n_key = 'APPRISE_NEW_KEY'
|
|
||||||
n_val = 'NEW_VAL'
|
|
||||||
|
|
||||||
# Verify that our temporary variables (defined above) are not pre-existing
|
|
||||||
# environemnt variables as we'll be setting them below
|
|
||||||
assert n_key not in os.environ
|
|
||||||
assert d_key not in os.environ
|
|
||||||
|
|
||||||
# makes it easier to pass in the arguments
|
|
||||||
updates = {
|
|
||||||
e_key1: e_val3,
|
|
||||||
e_key2: e_val1,
|
|
||||||
n_key: n_val,
|
|
||||||
}
|
|
||||||
with utils.environ(d_key, e_key3, **updates):
|
|
||||||
# Attempt to eliminate an undefined key (silently ignored)
|
|
||||||
# Eliminates Environment Variable 3
|
|
||||||
# Environment Variable 1 takes on the value of Env 3
|
|
||||||
# Environment Variable 2 takes on the value of Env 1
|
|
||||||
# Set a brand new variable that previously didn't exist
|
|
||||||
assert e_key1 in os.environ
|
|
||||||
assert e_val3 in os.environ[e_key1]
|
|
||||||
assert e_key2 in os.environ
|
|
||||||
assert e_val1 in os.environ[e_key2]
|
|
||||||
assert e_key3 not in os.environ
|
|
||||||
|
|
||||||
# Can't delete a variable that doesn't exist; so we're in the same
|
|
||||||
# state here.
|
|
||||||
assert d_key not in os.environ
|
|
||||||
|
|
||||||
# Our temporary variables will be found now
|
|
||||||
assert n_key in os.environ
|
|
||||||
assert n_val in os.environ[n_key]
|
|
||||||
|
|
||||||
# after with is over, environment is restored to normal
|
|
||||||
assert e_key1 in os.environ
|
|
||||||
assert e_val1 in os.environ[e_key1]
|
|
||||||
assert e_key2 in os.environ
|
|
||||||
assert e_val2 in os.environ[e_key2]
|
|
||||||
assert e_key3 in os.environ
|
|
||||||
assert e_val3 in os.environ[e_key3]
|
|
||||||
|
|
||||||
# Even our temporary variables are now missing
|
|
||||||
assert n_key not in os.environ
|
|
||||||
assert d_key not in os.environ
|
|
||||||
|
|
||||||
|
|
||||||
def test_apply_templating():
|
def test_apply_templating():
|
||||||
"""utils: apply_template() testing
|
"""utils: apply_template() testing
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user