CLI environment variable over-ride support (#1231)

This commit is contained in:
Chris Caron 2024-10-27 14:02:19 -04:00 committed by GitHub
parent 1065c021d3
commit 01c1082ad8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 337 additions and 143 deletions

View File

@ -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:

View File

@ -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):

View File

@ -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

View File

@ -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:

View File

@ -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',
] ]

View 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

View File

@ -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")

View 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

View File

@ -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

View File

@ -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
""" """