From 0c5bd8afe10352edfc5633d60ee2d5a1b4441191 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 13 Nov 2023 21:38:41 -0500 Subject: [PATCH] Improved YAML group support (#998) --- apprise/config/ConfigBase.py | 60 +++++++++++---- test/test_config_base.py | 143 ++++++++++++++++++++++++++++++++--- 2 files changed, 177 insertions(+), 26 deletions(-) diff --git a/apprise/config/ConfigBase.py b/apprise/config/ConfigBase.py index adddc4f5..77c5ce90 100644 --- a/apprise/config/ConfigBase.py +++ b/apprise/config/ConfigBase.py @@ -393,7 +393,11 @@ class ConfigBase(URLBase): # Track our groups groups.add(tag) - # Store what we know is worth keping + # Store what we know is worth keeping + if tag not in group_tags: # pragma: no cover + # handle cases where the tag doesn't exist + group_tags[tag] = set() + results |= group_tags[tag] - tag_groups # Get simple tag assignments @@ -898,19 +902,11 @@ class ConfigBase(URLBase): # groups root directive # groups = result.get('groups', None) - if not isinstance(groups, (list, tuple)): - # Not a problem; we simply have no group entry - groups = list() - - # Iterate over each group defined and store it - for no, entry in enumerate(groups): - if not isinstance(entry, dict): - ConfigBase.logger.warning( - 'No assignment for group {}, entry #{}'.format( - entry, no + 1)) - continue - - for _groups, tags in entry.items(): + if isinstance(groups, dict): + # + # Dictionary + # + for _groups, tags in groups.items(): for group in parse_list(_groups, cast=str): if isinstance(tags, (list, tuple)): _tags = set() @@ -932,7 +928,41 @@ class ConfigBase(URLBase): else: group_tags[group] |= tags - # + elif isinstance(groups, (list, tuple)): + # + # List of Dictionaries + # + + # Iterate over each group defined and store it + for no, entry in enumerate(groups): + if not isinstance(entry, dict): + ConfigBase.logger.warning( + 'No assignment for group {}, entry #{}'.format( + entry, no + 1)) + continue + + for _groups, tags in entry.items(): + for group in parse_list(_groups, cast=str): + if isinstance(tags, (list, tuple)): + _tags = set() + for e in tags: + if isinstance(e, dict): + _tags |= set(e.keys()) + else: + _tags |= set(parse_list(e, cast=str)) + + # Final assignment + tags = _tags + + else: + tags = set(parse_list(tags, cast=str)) + + if group not in group_tags: + group_tags[group] = tags + + else: + group_tags[group] |= tags + # include root directive # includes = result.get('include', None) diff --git a/test/test_config_base.py b/test/test_config_base.py index 819c16cd..3004abd0 100644 --- a/test/test_config_base.py +++ b/test/test_config_base.py @@ -191,7 +191,7 @@ urls: - tag: devops, admin """, asset=AppriseAsset()) - # We expect to parse 3 entries from the above + # We expect to parse 2 entries from the above assert isinstance(result, tuple) assert len(result) == 2 assert isinstance(result[0], list) @@ -263,7 +263,7 @@ def test_config_base_config_parse_text(): # A relative include statement (with trailing spaces) include apprise.cfg """, asset=AppriseAsset()) - # We expect to parse 3 entries from the above + # We expect to parse 4 entries from the above assert isinstance(result, list) assert isinstance(config, list) assert len(result) == 4 @@ -409,7 +409,7 @@ def test_config_base_config_tag_groups_text(): """, asset=AppriseAsset()) - # We expect to parse 3 entries from the above + # We expect to parse 4 entries from the above assert isinstance(result, list) assert isinstance(config, list) assert len(result) == 4 @@ -442,7 +442,7 @@ def test_config_base_config_tag_groups_text(): groupD=form://localhost """) - # We expect to parse 3 entries from the above + # We expect to parse 0 entries from the above assert isinstance(result, list) assert isinstance(config, list) assert len(result) == 0 @@ -827,7 +827,7 @@ urls: - dbus:// """, asset=asset) - # We expect to parse 3 entries from the above + # We expect to parse 2 entries from the above assert isinstance(result, list) assert len(result) == 2 @@ -861,7 +861,7 @@ urls: assert 'admin' in entry.tags assert 'devops' in entry.tags - # We expect to parse 3 entries from the above + # We expect to parse 2 entries from the above assert isinstance(result, list) assert len(result) == 2 @@ -886,7 +886,7 @@ urls: - entry """, asset=asset) - # We expect to parse 3 entries from the above + # We expect to parse 0 entries from the above assert isinstance(result, list) assert len(result) == 0 @@ -932,7 +932,7 @@ urls: - json://localhost: """, asset=asset) - # We expect to parse 3 entries from the above + # We expect to parse 1 entries from the above assert isinstance(result, list) assert len(result) == 1 @@ -1155,9 +1155,9 @@ def test_yaml_vs_text_tagging(): assert 'mytag' in yaml_result[0] -def test_config_base_config_tag_groups_yaml(): +def test_config_base_config_tag_groups_yaml_01(): """ - API: ConfigBase.config_tag_groups_yaml object + API: ConfigBase.config_tag_groups_yaml #1 object """ @@ -1233,7 +1233,7 @@ urls: """, asset=asset) - # We expect to parse 3 entries from the above + # We expect to parse 4 entries from the above assert isinstance(result, list) assert isinstance(config, list) assert len(result) == 4 @@ -1261,11 +1261,132 @@ urls: assert len([x for x in apobj.find('group3')]) == 2 assert len([x for x in apobj.find('group4')]) == 0 assert len([x for x in apobj.find('group5')]) == 0 + # json:// -- group6 -> 4 -> TagA + # xml:// -- group6 -> TagC assert len([x for x in apobj.find('group6')]) == 2 assert len([x for x in apobj.find('4')]) == 1 assert len([x for x in apobj.find('groupN')]) == 1 +def test_config_base_config_tag_groups_yaml_02(): + """ + API: ConfigBase.config_tag_groups_yaml #2 object + + """ + + # general reference used below + asset = AppriseAsset() + + # Valid Configuration + result, config = ConfigBase.config_parse_yaml(""" +# if no version is specified then version 1 is presumed +version: 1 + +groups: + group1: tagB, tagC, tagNotAssigned + group2: + - tagA + - tagC + group3: + - tagD: optional comment + - tagA: optional comment #2 + + # No assignment type 2 + group5: + + # Integer assignment (since it's not a list, the last element prevails + # and replaces the above); '4' does not get appended as it would in + # the event this was a list instead + group6: 3 + group6: 3, 4, 5, test + group6: 3.5, tagC + + # Recursion + groupA: groupB + groupB: groupA + # And Again... (just because) + groupA: groupB + groupB: groupA + + # Self assignment + groupX: groupX + + # Set up a larger recursive loop + groupG: groupH + groupH: groupI, groupJ + groupI: groupJ, groupG + groupJ: groupK, groupH, groupI + groupK: groupG + + # No tags assigned + groupK: ",, , ," + " , ": ",, , ," + + # Multi Assignments + groupL, groupM: tagD, tagA + 4, groupN: + - tagD + - tagE, TagA + + # Add one more tag to groupL making it different then GroupM by 1 + groupL: tagB +# +# Define your notification urls: +# +urls: + - form://localhost: + - tag: tagA + - mailto://test:password@gmail.com: + - tag: tagB + - xml://localhost: + - tag: tagC + - json://localhost: + - tag: tagD, tagA + +""", asset=asset) + + # We expect to parse 4 entries from the above + assert isinstance(result, list) + assert isinstance(config, list) + assert len(result) == 4 + + # Our first element is our group tags + assert len(result[0].tags) == 5 + assert 'group2' in result[0].tags + assert 'group3' in result[0].tags + assert 'groupL' in result[0].tags + assert 'groupM' in result[0].tags + assert 'tagA' in result[0].tags + + # No additional configuration is loaded + assert len(config) == 0 + + apobj = Apprise() + assert apobj.add(result) + # We match against 1 entry + assert len([x for x in apobj.find('tagA')]) == 2 + assert len([x for x in apobj.find('tagB')]) == 1 + assert len([x for x in apobj.find('tagC')]) == 1 + assert len([x for x in apobj.find('tagD')]) == 1 + assert len([x for x in apobj.find('group1')]) == 2 + assert len([x for x in apobj.find('group2')]) == 3 + assert len([x for x in apobj.find('group3')]) == 2 + assert len([x for x in apobj.find('group4')]) == 0 + assert len([x for x in apobj.find('group5')]) == 0 + # NOT json:// -- group6 -> 4 -> TagA (not appended because dict storage) + # ^ + # | + # See: test_config_base_config_tag_groups_yaml_01 (above) + # dict storage (as this tests for) causes last entry to + # prevail; previous assignments are lost + # + # xml:// -- group6 -> TagC + assert len([x for x in apobj.find('group6')]) == 1 + assert len([x for x in apobj.find('4')]) == 1 + assert len([x for x in apobj.find('groupN')]) == 1 + assert len([x for x in apobj.find('groupK')]) == 0 + + def test_config_base_config_parse_yaml_globals(): """ API: ConfigBase.config_parse_yaml globals