Added always special tag (will always notify) (#561)

This commit is contained in:
Chris Caron 2022-04-10 12:22:37 -04:00 committed by GitHub
parent b28cd4cdff
commit 5d14259227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 133 additions and 11 deletions

View File

@ -28,6 +28,7 @@ import six
from itertools import chain from itertools import chain
from .common import NotifyType from .common import NotifyType
from .common import MATCH_ALL_TAG from .common import MATCH_ALL_TAG
from .common import MATCH_ALWAYS_TAG
from .conversion import convert_between from .conversion import convert_between
from .utils import is_exclusive_match from .utils import is_exclusive_match
from .utils import parse_list from .utils import parse_list
@ -303,7 +304,7 @@ class Apprise(object):
""" """
self.servers[:] = [] self.servers[:] = []
def find(self, tag=MATCH_ALL_TAG): def find(self, tag=MATCH_ALL_TAG, match_always=True):
""" """
Returns an list of all servers matching against the tag specified. Returns an list of all servers matching against the tag specified.
@ -319,6 +320,10 @@ class Apprise(object):
# tag=[('tagA', 'tagC'), 'tagB'] = (tagA and tagC) or tagB # tag=[('tagA', 'tagC'), 'tagB'] = (tagA and tagC) or tagB
# tag=[('tagB', 'tagC')] = tagB and tagC # tag=[('tagB', 'tagC')] = tagB and tagC
# A match_always flag allows us to pick up on our 'any' keyword
# and notify these services under all circumstances
match_always = MATCH_ALWAYS_TAG if match_always else None
# Iterate over our loaded plugins # Iterate over our loaded plugins
for entry in self.servers: for entry in self.servers:
@ -332,13 +337,14 @@ class Apprise(object):
for server in servers: for server in servers:
# Apply our tag matching based on our defined logic # Apply our tag matching based on our defined logic
if is_exclusive_match( if is_exclusive_match(
logic=tag, data=server.tags, match_all=MATCH_ALL_TAG): logic=tag, data=server.tags, match_all=MATCH_ALL_TAG,
match_always=match_always):
yield server yield server
return return
def notify(self, body, title='', notify_type=NotifyType.INFO, def notify(self, body, title='', notify_type=NotifyType.INFO,
body_format=None, tag=MATCH_ALL_TAG, attach=None, body_format=None, tag=MATCH_ALL_TAG, match_always=True,
interpret_escapes=None): attach=None, interpret_escapes=None):
""" """
Send a notification to all of the plugins previously loaded. Send a notification to all of the plugins previously loaded.
@ -368,7 +374,7 @@ class Apprise(object):
self.async_notify( self.async_notify(
body, title, body, title,
notify_type=notify_type, body_format=body_format, notify_type=notify_type, body_format=body_format,
tag=tag, attach=attach, tag=tag, match_always=match_always, attach=attach,
interpret_escapes=interpret_escapes, interpret_escapes=interpret_escapes,
), ),
debug=self.debug debug=self.debug
@ -466,8 +472,8 @@ class Apprise(object):
return py3compat.asyncio.toasyncwrap(status) return py3compat.asyncio.toasyncwrap(status)
def _notifyall(self, handler, body, title='', notify_type=NotifyType.INFO, def _notifyall(self, handler, body, title='', notify_type=NotifyType.INFO,
body_format=None, tag=MATCH_ALL_TAG, attach=None, body_format=None, tag=MATCH_ALL_TAG, match_always=True,
interpret_escapes=None): attach=None, interpret_escapes=None):
""" """
Creates notifications for all of the plugins loaded. Creates notifications for all of the plugins loaded.
@ -509,7 +515,7 @@ class Apprise(object):
if interpret_escapes is None else interpret_escapes if interpret_escapes is None else interpret_escapes
# Iterate over our loaded plugins # Iterate over our loaded plugins
for server in self.find(tag): for server in self.find(tag, match_always=match_always):
# If our code reaches here, we either did not define a tag (it # If our code reaches here, we either did not define a tag (it
# was set to None), or we did define a tag and the logic above # was set to None), or we did define a tag and the logic above
# determined we need to notify the service it's associated with # determined we need to notify the service it's associated with

View File

@ -32,6 +32,7 @@ from . import URLBase
from .AppriseAsset import AppriseAsset from .AppriseAsset import AppriseAsset
from .common import MATCH_ALL_TAG from .common import MATCH_ALL_TAG
from .common import MATCH_ALWAYS_TAG
from .utils import GET_SCHEMA_RE from .utils import GET_SCHEMA_RE
from .utils import parse_list from .utils import parse_list
from .utils import is_exclusive_match from .utils import is_exclusive_match
@ -266,7 +267,7 @@ class AppriseConfig(object):
# Return our status # Return our status
return True return True
def servers(self, tag=MATCH_ALL_TAG, *args, **kwargs): def servers(self, tag=MATCH_ALL_TAG, match_always=True, *args, **kwargs):
""" """
Returns all of our servers dynamically build based on parsed Returns all of our servers dynamically build based on parsed
configuration. configuration.
@ -277,7 +278,15 @@ class AppriseConfig(object):
This is for filtering the configuration files polled for This is for filtering the configuration files polled for
results. results.
If the anytag is set, then any notification that is found
set with that tag are included in the response.
""" """
# A match_always flag allows us to pick up on our 'any' keyword
# and notify these services under all circumstances
match_always = MATCH_ALWAYS_TAG if match_always else None
# Build our tag setup # Build our tag setup
# - top level entries are treated as an 'or' # - top level entries are treated as an 'or'
# - second level (or more) entries are treated as 'and' # - second level (or more) entries are treated as 'and'
@ -294,7 +303,8 @@ class AppriseConfig(object):
# Apply our tag matching based on our defined logic # Apply our tag matching based on our defined logic
if is_exclusive_match( if is_exclusive_match(
logic=tag, data=entry.tags, match_all=MATCH_ALL_TAG): logic=tag, data=entry.tags, match_all=MATCH_ALL_TAG,
match_always=match_always):
# Build ourselves a list of services dynamically and return the # Build ourselves a list of services dynamically and return the
# as a list # as a list
response.extend(entry.servers()) response.extend(entry.servers())

View File

@ -187,3 +187,7 @@ CONTENT_LOCATIONS = (
# This is a reserved tag that is automatically assigned to every # This is a reserved tag that is automatically assigned to every
# Notification Plugin # Notification Plugin
MATCH_ALL_TAG = 'all' MATCH_ALL_TAG = 'all'
# Will cause notification to trigger under any circumstance even if an
# exclusive tagging was provided.
MATCH_ALWAYS_TAG = 'always'

View File

@ -28,8 +28,11 @@ import six
import json import json
import contextlib import contextlib
import os import os
from itertools import chain
from os.path import expanduser from os.path import expanduser
from functools import reduce from functools import reduce
from .common import MATCH_ALL_TAG
from .common import MATCH_ALWAYS_TAG
try: try:
# Python 2.7 # Python 2.7
@ -995,7 +998,8 @@ def parse_list(*args):
return sorted([x for x in filter(bool, list(set(result)))]) return sorted([x for x in filter(bool, list(set(result)))])
def is_exclusive_match(logic, data, match_all='all'): def is_exclusive_match(logic, data, match_all=MATCH_ALL_TAG,
match_always=MATCH_ALWAYS_TAG):
""" """
The data variable should always be a set of strings that the logic can be The data variable should always be a set of strings that the logic can be
@ -1011,6 +1015,9 @@ def is_exclusive_match(logic, data, match_all='all'):
logic=['tagA', 'tagB'] = tagA or tagB logic=['tagA', 'tagB'] = tagA or tagB
logic=[('tagA', 'tagC'), 'tagB'] = (tagA and tagC) or tagB logic=[('tagA', 'tagC'), 'tagB'] = (tagA and tagC) or tagB
logic=[('tagB', 'tagC')] = tagB and tagC logic=[('tagB', 'tagC')] = tagB and tagC
If `match_always` is not set to None, then its value is added as an 'or'
to all specified logic searches.
""" """
if isinstance(logic, six.string_types): if isinstance(logic, six.string_types):
@ -1026,6 +1033,10 @@ def is_exclusive_match(logic, data, match_all='all'):
# garbage input # garbage input
return False return False
if match_always:
# Add our match_always to our logic searching if secified
logic = chain(logic, [match_always])
# Track what we match against; but by default we do not match # Track what we match against; but by default we do not match
# against anything # against anything
matched = False matched = False

View File

@ -416,6 +416,33 @@ def test_apprise_config_tagging(tmpdir):
# all matches everything # all matches everything
assert len(ac.servers(tag='all')) == 3 assert len(ac.servers(tag='all')) == 3
# Test cases using the `always` keyword
# Create ourselves a config object
ac = AppriseConfig()
# Add an item associated with tag a
assert ac.add(configs=str(t), asset=AppriseAsset(), tag='a,always') is True
# Add an item associated with tag b
assert ac.add(configs=str(t), asset=AppriseAsset(), tag='b') is True
# Add an item associated with tag a or b
assert ac.add(configs=str(t), asset=AppriseAsset(), tag='c,d') is True
# Now filter: a:
assert len(ac.servers(tag='a')) == 1
# Now filter: a or b:
assert len(ac.servers(tag='a,b')) == 2
# Now filter: e
# we'll match the `always'
assert len(ac.servers(tag='e')) == 1
assert len(ac.servers(tag='e', match_always=False)) == 0
# all matches everything
assert len(ac.servers(tag='all')) == 3
# Now filter: d
# we'll match the `always' tag
assert len(ac.servers(tag='d')) == 2
assert len(ac.servers(tag='d', match_always=False)) == 1
def test_apprise_config_instantiate(): def test_apprise_config_instantiate():
""" """
@ -1173,6 +1200,59 @@ def test_config_base_parse_yaml_file03(tmpdir):
assert sum(1 for _ in a.find('test1, test3')) == 1 assert sum(1 for _ in a.find('test1, test3')) == 1
def test_config_base_parse_yaml_file04(tmpdir):
"""
API: ConfigBase.parse_yaml_file (#4)
Test the always keyword
"""
t = tmpdir.mkdir("always-keyword").join("apprise.yml")
t.write("""urls:
- pover://nsisxnvnqixq39t0cw54pxieyvtdd9@2jevtmstfg5a7hfxndiybasttxxfku:
- tag: test1,always
- pover://rg8ta87qngcrkc6t4qbykxktou0uug@tqs3i88xlufexwl8t4asglt4zp5wfn:
- tag: test2
- pover://jcqgnlyq2oetea4qg3iunahj8d5ijm@evalvutkhc8ipmz2lcgc70wtsm0qpb:
- tag: test3""")
# Create ourselves a config object
ac = AppriseConfig(paths=str(t))
# The number of configuration files that exist
assert len(ac) == 1
# no notifications are loaded
assert len(ac.servers()) == 3
# Test our ability to add Config objects to our apprise object
a = Apprise()
# Add our configuration object
assert a.add(servers=ac) is True
# Detect our 3 entry as they should have loaded successfully
assert len(a) == 3
# No match still matches `always` keyword
assert sum(1 for _ in a.find('no-match')) == 1
# Unless we explicitly do not look for that file
assert sum(1 for _ in a.find('no-match', match_always=False)) == 0
# Match everything
assert sum(1 for _ in a.find('all')) == 3
# Match test1 entry (also has `always` keyword
assert sum(1 for _ in a.find('test1')) == 1
assert sum(1 for _ in a.find('test1', match_always=False)) == 1
# Match test2 entry (and test1 due to always keyword)
assert sum(1 for _ in a.find('test2')) == 2
assert sum(1 for _ in a.find('test2', match_always=False)) == 1
# Match test3 entry (and test1 due to always keyword)
assert sum(1 for _ in a.find('test3')) == 2
assert sum(1 for _ in a.find('test3', match_always=False)) == 1
# Match test1 or test3 entry
assert sum(1 for _ in a.find('test1, test3')) == 2
def test_apprise_config_template_parse(tmpdir): def test_apprise_config_template_parse(tmpdir):
""" """
API: AppriseConfig parsing of templates API: AppriseConfig parsing of templates

View File

@ -1588,6 +1588,17 @@ def test_exclusive_match():
assert utils.is_exclusive_match(logic=['www'], data=data) is False assert utils.is_exclusive_match(logic=['www'], data=data) is False
assert utils.is_exclusive_match(logic='all', data=data) is True assert utils.is_exclusive_match(logic='all', data=data) is True
#
# Update our data set so we can do more advance checks
#
data = set(['always', 'entry1'])
# We'll always match on the with keyword always
assert utils.is_exclusive_match(logic='always', data=data) is True
assert utils.is_exclusive_match(logic='garbage', data=data) is True
# However we will not match if we turn this feature off
assert utils.is_exclusive_match(
logic='garbage', data=data, match_always=False) is False
# Change default value from 'all' to 'match_me'. Logic matches # Change default value from 'all' to 'match_me'. Logic matches
# so we pass # so we pass
assert utils.is_exclusive_match( assert utils.is_exclusive_match(