mirror of https://github.com/caronc/apprise
				
				
				
			Added `always` special tag (will always notify) (#561)
							parent
							
								
									b28cd4cdff
								
							
						
					
					
						commit
						5d14259227
					
				| 
						 | 
				
			
			@ -28,6 +28,7 @@ import six
 | 
			
		|||
from itertools import chain
 | 
			
		||||
from .common import NotifyType
 | 
			
		||||
from .common import MATCH_ALL_TAG
 | 
			
		||||
from .common import MATCH_ALWAYS_TAG
 | 
			
		||||
from .conversion import convert_between
 | 
			
		||||
from .utils import is_exclusive_match
 | 
			
		||||
from .utils import parse_list
 | 
			
		||||
| 
						 | 
				
			
			@ -303,7 +304,7 @@ class Apprise(object):
 | 
			
		|||
        """
 | 
			
		||||
        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.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -319,6 +320,10 @@ class Apprise(object):
 | 
			
		|||
        #     tag=[('tagA', 'tagC'), 'tagB']  = (tagA and tagC) or tagB
 | 
			
		||||
        #     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
 | 
			
		||||
        for entry in self.servers:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -332,13 +337,14 @@ class Apprise(object):
 | 
			
		|||
            for server in servers:
 | 
			
		||||
                # Apply our tag matching based on our defined logic
 | 
			
		||||
                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
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    def notify(self, body, title='', notify_type=NotifyType.INFO,
 | 
			
		||||
               body_format=None, tag=MATCH_ALL_TAG, attach=None,
 | 
			
		||||
               interpret_escapes=None):
 | 
			
		||||
               body_format=None, tag=MATCH_ALL_TAG, match_always=True,
 | 
			
		||||
               attach=None, interpret_escapes=None):
 | 
			
		||||
        """
 | 
			
		||||
        Send a notification to all of the plugins previously loaded.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -368,7 +374,7 @@ class Apprise(object):
 | 
			
		|||
                self.async_notify(
 | 
			
		||||
                    body, title,
 | 
			
		||||
                    notify_type=notify_type, body_format=body_format,
 | 
			
		||||
                    tag=tag, attach=attach,
 | 
			
		||||
                    tag=tag, match_always=match_always, attach=attach,
 | 
			
		||||
                    interpret_escapes=interpret_escapes,
 | 
			
		||||
                ),
 | 
			
		||||
                debug=self.debug
 | 
			
		||||
| 
						 | 
				
			
			@ -466,8 +472,8 @@ class Apprise(object):
 | 
			
		|||
            return py3compat.asyncio.toasyncwrap(status)
 | 
			
		||||
 | 
			
		||||
    def _notifyall(self, handler, body, title='', notify_type=NotifyType.INFO,
 | 
			
		||||
                   body_format=None, tag=MATCH_ALL_TAG, attach=None,
 | 
			
		||||
                   interpret_escapes=None):
 | 
			
		||||
                   body_format=None, tag=MATCH_ALL_TAG, match_always=True,
 | 
			
		||||
                   attach=None, interpret_escapes=None):
 | 
			
		||||
        """
 | 
			
		||||
        Creates notifications for all of the plugins loaded.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -509,7 +515,7 @@ class Apprise(object):
 | 
			
		|||
            if interpret_escapes is None else interpret_escapes
 | 
			
		||||
 | 
			
		||||
        # 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
 | 
			
		||||
            # 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,7 @@ from . import URLBase
 | 
			
		|||
from .AppriseAsset import AppriseAsset
 | 
			
		||||
 | 
			
		||||
from .common import MATCH_ALL_TAG
 | 
			
		||||
from .common import MATCH_ALWAYS_TAG
 | 
			
		||||
from .utils import GET_SCHEMA_RE
 | 
			
		||||
from .utils import parse_list
 | 
			
		||||
from .utils import is_exclusive_match
 | 
			
		||||
| 
						 | 
				
			
			@ -266,7 +267,7 @@ class AppriseConfig(object):
 | 
			
		|||
        # Return our status
 | 
			
		||||
        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
 | 
			
		||||
        configuration.
 | 
			
		||||
| 
						 | 
				
			
			@ -277,7 +278,15 @@ class AppriseConfig(object):
 | 
			
		|||
        This is for filtering the configuration files polled for
 | 
			
		||||
        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
 | 
			
		||||
        #   - top level entries are treated as an 'or'
 | 
			
		||||
        #   - 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
 | 
			
		||||
            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
 | 
			
		||||
                # as a list
 | 
			
		||||
                response.extend(entry.servers())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -187,3 +187,7 @@ CONTENT_LOCATIONS = (
 | 
			
		|||
# This is a reserved tag that is automatically assigned to every
 | 
			
		||||
# Notification Plugin
 | 
			
		||||
MATCH_ALL_TAG = 'all'
 | 
			
		||||
 | 
			
		||||
# Will cause notification to trigger under any circumstance even if an
 | 
			
		||||
# exclusive tagging was provided.
 | 
			
		||||
MATCH_ALWAYS_TAG = 'always'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,8 +28,11 @@ import six
 | 
			
		|||
import json
 | 
			
		||||
import contextlib
 | 
			
		||||
import os
 | 
			
		||||
from itertools import chain
 | 
			
		||||
from os.path import expanduser
 | 
			
		||||
from functools import reduce
 | 
			
		||||
from .common import MATCH_ALL_TAG
 | 
			
		||||
from .common import MATCH_ALWAYS_TAG
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    # Python 2.7
 | 
			
		||||
| 
						 | 
				
			
			@ -995,7 +998,8 @@ def parse_list(*args):
 | 
			
		|||
    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
 | 
			
		||||
| 
						 | 
				
			
			@ -1011,6 +1015,9 @@ def is_exclusive_match(logic, data, match_all='all'):
 | 
			
		|||
        logic=['tagA', 'tagB']            = tagA or tagB
 | 
			
		||||
        logic=[('tagA', 'tagC'), 'tagB']  = (tagA and tagC) or tagB
 | 
			
		||||
        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):
 | 
			
		||||
| 
						 | 
				
			
			@ -1026,6 +1033,10 @@ def is_exclusive_match(logic, data, match_all='all'):
 | 
			
		|||
        # garbage input
 | 
			
		||||
        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
 | 
			
		||||
    # against anything
 | 
			
		||||
    matched = False
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -416,6 +416,33 @@ def test_apprise_config_tagging(tmpdir):
 | 
			
		|||
    # all matches everything
 | 
			
		||||
    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():
 | 
			
		||||
    """
 | 
			
		||||
| 
						 | 
				
			
			@ -1173,6 +1200,59 @@ def test_config_base_parse_yaml_file03(tmpdir):
 | 
			
		|||
    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):
 | 
			
		||||
    """
 | 
			
		||||
    API: AppriseConfig parsing of templates
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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='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
 | 
			
		||||
    # so we pass
 | 
			
		||||
    assert utils.is_exclusive_match(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue