Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-kmatch for openSUSE:Factory checked in at 2022-03-24 22:57:34 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-kmatch (Old) and /work/SRC/openSUSE:Factory/.python-kmatch.new.1900 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-kmatch" Thu Mar 24 22:57:34 2022 rev:6 rq:964380 version:0.4.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-kmatch/python-kmatch.changes 2020-08-19 18:58:37.639904688 +0200 +++ /work/SRC/openSUSE:Factory/.python-kmatch.new.1900/python-kmatch.changes 2022-03-24 22:57:57.264247867 +0100 @@ -1,0 +2,9 @@ +Wed Mar 23 11:54:58 UTC 2022 - [email protected] + +- version update to 0.4.0 + * no upstream changelog +- added patches + fix https://github.com/ambitioninc/kmatch/issues/42 + + python-kmatch-no-mock.patch + +------------------------------------------------------------------- Old: ---- kmatch-0.3.0.tar.gz New: ---- kmatch-0.4.0.tar.gz python-kmatch-no-mock.patch ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-kmatch.spec ++++++ --- /var/tmp/diff_new_pack.gbOvmr/_old 2022-03-24 22:57:57.748248334 +0100 +++ /var/tmp/diff_new_pack.gbOvmr/_new 2022-03-24 22:57:57.752248338 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-kmatch # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,13 +18,15 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-kmatch -Version: 0.3.0 +Version: 0.4.0 Release: 0 Summary: A language for matching/validating/filtering Python dictionaries License: MIT Group: Development/Languages/Python URL: https://github.com/ambitioninc/kmatch Source: https://files.pythonhosted.org/packages/source/k/kmatch/kmatch-%{version}.tar.gz +# https://github.com/ambitioninc/kmatch/issues/42 +Patch0: python-kmatch-no-mock.patch BuildRequires: %{python_module setuptools} BuildRequires: dos2unix BuildRequires: fdupes @@ -43,13 +45,14 @@ %prep %setup -q -n kmatch-%{version} +%patch0 -p1 sed -i '/nose/d' setup.py dos2unix README.rst LICENSE chmod a-x README.rst LICENSE rm -r *.egg-info -mv kmatch/tests/ .tests %build +mv kmatch/tests/ .tests %python_build %install ++++++ kmatch-0.3.0.tar.gz -> kmatch-0.4.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kmatch-0.3.0/PKG-INFO new/kmatch-0.4.0/PKG-INFO --- old/kmatch-0.3.0/PKG-INFO 2019-07-11 19:26:05.000000000 +0200 +++ new/kmatch-0.4.0/PKG-INFO 2020-12-29 02:58:44.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: kmatch -Version: 0.3.0 +Version: 0.4.0 Summary: A language for matching/validating/filtering Python dictionaries Home-page: https://github.com/ambitioninc/kmatch Author: Wes Kendall @@ -47,10 +47,10 @@ Keywords: matching,dictionaries,filtering,validation Platform: UNKNOWN -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kmatch-0.3.0/kmatch/kmatch.py new/kmatch-0.4.0/kmatch/kmatch.py --- old/kmatch-0.3.0/kmatch/kmatch.py 2019-07-10 23:28:52.000000000 +0200 +++ new/kmatch-0.4.0/kmatch/kmatch.py 2020-12-22 22:27:55.000000000 +0100 @@ -1,202 +1,202 @@ -from copy import deepcopy -from operator import not_, lt, le, eq, ge, ne, gt, xor -import re - - -class K(object): - """ - Implements the kmatch language. Takes a dictionary specifying the pattern, compiles it, validates - it, and provides the user with the match function. - """ - _OPERATOR_MAP = { - '&': all, - '|': any, - '!': not_, - '^': xor, - } - _VALUE_FILTER_MAP = { - '==': eq, - '!=': ne, - '<': lt, - '>': gt, - '<=': le, - '>=': ge, - '=~': lambda match_str, regex: regex.match(match_str) if match_str is not None else False, - } - _KEY_FILTER_MAP = { - '?': lambda key, value: key in value, - '!?': lambda key, value: key not in value, - } - - def __init__(self, p, suppress_key_errors=False, suppress_exceptions=False): - """ - Sets the pattern, performs validation on the pattern, and compiles its regexs if it has any. - - :param p: The kmatch pattern - :type p: list - :param suppress_key_errors: Suppress KeyError exceptions on filters and return False instead - :type suppress_key_errors: bool - :param suppress_exceptions: Suppress all exceptions on filters and return False instead - :type suppress_exceptions: bool - :raises: :class:`ValueError <exceptions.ValueError>` on an invalid pattern or regex - """ - self._raw_pattern = deepcopy(p) - self._compiled_pattern = deepcopy(p) - self._suppress_key_errors = suppress_key_errors - self._suppress_exceptions = suppress_exceptions - - # Validate the pattern is in the appropriate format - self._validate(self._compiled_pattern) - - # Compile any regexs in the pattern - self._compile(self._compiled_pattern) - - @property - def pattern(self): - """ - Gets the kmatch pattern. - - :returns: The kmatch pattern dictionary originally provided to the K object - :rtype: list - """ - return self._raw_pattern - - def _is_operator(self, p): - return len(p) == 2 and p[0] in self._OPERATOR_MAP and isinstance(p[1], (list, tuple)) - - def _is_value_filter(self, p): - return len(p) == 3 and p[0] in self._VALUE_FILTER_MAP - - def _is_key_filter(self, p): - return len(p) == 2 and p[0] in self._KEY_FILTER_MAP - - def _compile(self, p): - """ - Recursively compiles the regexs in the pattern (p). - """ - if self._is_value_filter(p) and p[0] == '=~': - try: - p[2] = re.compile(p[2], re.DOTALL) - except: # Python doesn't document exactly what exceptions re.compile throws - raise ValueError('Bad regex - {0}'.format(p[2])) - elif self._is_operator(p): - for operator_or_filter in (p[1] if p[0] != '!' else [p[1]]): - self._compile(operator_or_filter) - - def _validate(self, p): - """ - Recursively validates the pattern (p), ensuring it adheres to the proper key names and structure. - """ - if self._is_operator(p): - for operator_or_filter in (p[1] if p[0] != '!' else [p[1]]): - if p[0] == '^': - self._validate_xor_args(p) - self._validate(operator_or_filter) - elif not self._is_value_filter(p) and not self._is_key_filter(p): - raise ValueError('Not a valid operator or filter - {0}'.format(p)) - - def _validate_xor_args(self, p): - """ - Raises ValueError if 2 arguments are not passed to an XOR - """ - if len(p[1]) != 2: - raise ValueError('Invalid syntax: XOR only accepts 2 arguments, got {0}: {1}'.format(len(p[1]), p)) - - def _match(self, p, value): - """ - Calls either _match_operator or _match_operand depending on the pattern (p) provided. - """ - if self._is_operator(p): - return self._match_operator(p, value) - else: - try: - if self._is_value_filter(p): - return self._match_value_filter(p, value) - else: - return self._match_key_filter(p, value) - except KeyError: - if self._suppress_key_errors or self._suppress_exceptions: - return False - else: - raise - except TypeError: - if self._suppress_exceptions: - return False - else: - raise - - def _match_operator(self, p, value): - """ - Returns True or False if the operator (&, |, or ! with filters, or ^ with filters) matches the value dictionary - """ - if p[0] == '!': - return self._OPERATOR_MAP[p[0]](self._match(p[1], value)) - elif p[0] == '^': - return self._OPERATOR_MAP[p[0]](self._match(p[1][0], value), self._match(p[1][1], value)) - else: - return self._OPERATOR_MAP[p[0]]([self._match(operator_or_filter, value) for operator_or_filter in p[1]]) - - def _match_value_filter(self, p, value): - """ - Returns True of False if value in the pattern p matches the filter. - """ - return self._VALUE_FILTER_MAP[p[0]](value[p[1]], p[2]) - - def _match_key_filter(self, p, value): - """ - Returns True of False if key in the pattern p and the value matches the filter. - """ - return self._KEY_FILTER_MAP[p[0]](p[1], value) - - def match(self, value): - """ - Matches the value to the pattern. - - :param value: The value to be matched - :type value: dict - :rtype: bool - :returns: True if the value matches the pattern, False otherwise - :raises: :class:`KeyError <exceptions.KeyError>` if key from pattern does not exist in input value and the - suppress_key_errors class variable is False - """ - return self._match(self._compiled_pattern, value) - - def get_field_keys(self, pattern=None): - """ - Builds a set of all field keys used in the pattern including nested fields. - - :param pattern: The kmatch pattern to get field keys from or None to use self.pattern - :type pattern: list or None - :returns: A set object of all field keys used in the pattern - :rtype: set - """ - # Use own pattern or passed in argument for recursion - pattern = pattern or self.pattern - - # Validate the pattern so we can make assumptions about the data - self._validate(pattern) - - keys = set() - - # Valid pattern length can only be 2 or 3 - # With key filters, field key is second item just like 3 item patterns - if len(pattern) == 2 and pattern[0] not in self._KEY_FILTER_MAP: - if pattern[0] in ('&', '|', '^'): - # Pass each nested pattern to get_field_keys - for filter_item in pattern[1]: - keys = keys.union(self.get_field_keys(filter_item)) - else: - # pattern[0] == '!' - keys = keys.union(self.get_field_keys(pattern[1])) - else: - # Pattern length is 3 - keys.add(pattern[1]) - return keys - - @property - def suppress_exceptions(self): - return self._suppress_exceptions - - @suppress_exceptions.setter - def suppress_exceptions(self, suppress_exceptions): - self._suppress_exceptions = suppress_exceptions +from copy import deepcopy +from operator import not_, lt, le, eq, ge, ne, gt, xor +import re + + +class K(object): + """ + Implements the kmatch language. Takes a dictionary specifying the pattern, compiles it, validates + it, and provides the user with the match function. + """ + _OPERATOR_MAP = { + '&': all, + '|': any, + '!': not_, + '^': xor, + } + _VALUE_FILTER_MAP = { + '==': eq, + '!=': ne, + '<': lt, + '>': gt, + '<=': le, + '>=': ge, + '=~': lambda match_str, regex: regex.match(match_str) if match_str is not None else False, + } + _KEY_FILTER_MAP = { + '?': lambda key, value: key in value, + '!?': lambda key, value: key not in value, + } + + def __init__(self, p, suppress_key_errors=False, suppress_exceptions=False): + """ + Sets the pattern, performs validation on the pattern, and compiles its regexs if it has any. + + :param p: The kmatch pattern + :type p: list + :param suppress_key_errors: Suppress KeyError exceptions on filters and return False instead + :type suppress_key_errors: bool + :param suppress_exceptions: Suppress all exceptions on filters and return False instead + :type suppress_exceptions: bool + :raises: :class:`ValueError <exceptions.ValueError>` on an invalid pattern or regex + """ + self._raw_pattern = deepcopy(p) + self._compiled_pattern = deepcopy(p) + self._suppress_key_errors = suppress_key_errors + self._suppress_exceptions = suppress_exceptions + + # Validate the pattern is in the appropriate format + self._validate(self._compiled_pattern) + + # Compile any regexs in the pattern + self._compile(self._compiled_pattern) + + @property + def pattern(self): + """ + Gets the kmatch pattern. + + :returns: The kmatch pattern dictionary originally provided to the K object + :rtype: list + """ + return self._raw_pattern + + def _is_operator(self, p): + return len(p) == 2 and p[0] in self._OPERATOR_MAP and isinstance(p[1], (list, tuple)) + + def _is_value_filter(self, p): + return len(p) == 3 and p[0] in self._VALUE_FILTER_MAP + + def _is_key_filter(self, p): + return len(p) == 2 and p[0] in self._KEY_FILTER_MAP + + def _compile(self, p): + """ + Recursively compiles the regexs in the pattern (p). + """ + if self._is_value_filter(p) and p[0] == '=~': + try: + p[2] = re.compile(p[2], re.DOTALL) + except: # Python doesn't document exactly what exceptions re.compile throws + raise ValueError('Bad regex - {0}'.format(p[2])) + elif self._is_operator(p): + for operator_or_filter in (p[1] if p[0] != '!' else [p[1]]): + self._compile(operator_or_filter) + + def _validate(self, p): + """ + Recursively validates the pattern (p), ensuring it adheres to the proper key names and structure. + """ + if self._is_operator(p): + for operator_or_filter in (p[1] if p[0] != '!' else [p[1]]): + if p[0] == '^': + self._validate_xor_args(p) + self._validate(operator_or_filter) + elif not self._is_value_filter(p) and not self._is_key_filter(p): + raise ValueError('Not a valid operator or filter - {0}'.format(p)) + + def _validate_xor_args(self, p): + """ + Raises ValueError if 2 arguments are not passed to an XOR + """ + if len(p[1]) != 2: + raise ValueError('Invalid syntax: XOR only accepts 2 arguments, got {0}: {1}'.format(len(p[1]), p)) + + def _match(self, p, value): + """ + Calls either _match_operator or _match_operand depending on the pattern (p) provided. + """ + if self._is_operator(p): + return self._match_operator(p, value) + else: + try: + if self._is_value_filter(p): + return self._match_value_filter(p, value) + else: + return self._match_key_filter(p, value) + except KeyError: + if self._suppress_key_errors or self._suppress_exceptions: + return False + else: + raise + except TypeError: + if self._suppress_exceptions: + return False + else: + raise + + def _match_operator(self, p, value): + """ + Returns True or False if the operator (&, |, or ! with filters, or ^ with filters) matches the value dictionary + """ + if p[0] == '!': + return self._OPERATOR_MAP[p[0]](self._match(p[1], value)) + elif p[0] == '^': + return self._OPERATOR_MAP[p[0]](self._match(p[1][0], value), self._match(p[1][1], value)) + else: + return self._OPERATOR_MAP[p[0]]([self._match(operator_or_filter, value) for operator_or_filter in p[1]]) + + def _match_value_filter(self, p, value): + """ + Returns True of False if value in the pattern p matches the filter. + """ + return self._VALUE_FILTER_MAP[p[0]](value[p[1]], p[2]) + + def _match_key_filter(self, p, value): + """ + Returns True of False if key in the pattern p and the value matches the filter. + """ + return self._KEY_FILTER_MAP[p[0]](p[1], value) + + def match(self, value): + """ + Matches the value to the pattern. + + :param value: The value to be matched + :type value: dict + :rtype: bool + :returns: True if the value matches the pattern, False otherwise + :raises: :class:`KeyError <exceptions.KeyError>` if key from pattern does not exist in input value and the + suppress_key_errors class variable is False + """ + return self._match(self._compiled_pattern, value) + + def get_field_keys(self, pattern=None): + """ + Builds a set of all field keys used in the pattern including nested fields. + + :param pattern: The kmatch pattern to get field keys from or None to use self.pattern + :type pattern: list or None + :returns: A set object of all field keys used in the pattern + :rtype: set + """ + # Use own pattern or passed in argument for recursion + pattern = pattern or self.pattern + + # Validate the pattern so we can make assumptions about the data + self._validate(pattern) + + keys = set() + + # Valid pattern length can only be 2 or 3 + # With key filters, field key is second item just like 3 item patterns + if len(pattern) == 2 and pattern[0] not in self._KEY_FILTER_MAP: + if pattern[0] in ('&', '|', '^'): + # Pass each nested pattern to get_field_keys + for filter_item in pattern[1]: + keys = keys.union(self.get_field_keys(filter_item)) + else: + # pattern[0] == '!' + keys = keys.union(self.get_field_keys(pattern[1])) + else: + # Pattern length is 3 + keys.add(pattern[1]) + return keys + + @property + def suppress_exceptions(self): + return self._suppress_exceptions + + @suppress_exceptions.setter + def suppress_exceptions(self, suppress_exceptions): + self._suppress_exceptions = suppress_exceptions diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kmatch-0.3.0/kmatch/tests/kmatch_tests.py new/kmatch-0.4.0/kmatch/tests/kmatch_tests.py --- old/kmatch-0.3.0/kmatch/tests/kmatch_tests.py 2019-07-10 23:28:52.000000000 +0200 +++ new/kmatch-0.4.0/kmatch/tests/kmatch_tests.py 2020-12-22 22:27:55.000000000 +0100 @@ -1,407 +1,407 @@ -from sys import version -from unittest import TestCase -from mock import patch - -from kmatch import K - - -class KPatternTest(TestCase): - """ - Tests the pattern function in K. - """ - def test_pattern(self): - k = K(['=~', 'hi', 'hi']) - self.assertEquals(k.pattern, ['=~', 'hi', 'hi']) - - -class KMatchTest(TestCase): - """ - Tests the match function in K. - """ - def test_basic_lte_true(self): - self.assertTrue(K(['<=', 'f', 0]).match({'f': -1})) - - def test_basic_lte_false(self): - self.assertFalse(K(['<=', 'f', 0]).match({'f': 1})) - - def test_basic_lte_non_extant(self): - with self.assertRaises(KeyError): - self.assertFalse(K(['<=', 'f', 0]).match({})) - - def test_basic_lt_non_extant(self): - with self.assertRaises(KeyError): - self.assertFalse(K(['<', 'f', 0]).match({})) - - def test_basic_eq_true(self): - self.assertTrue(K(['==', 'f', 0]).match({'f': 0})) - - def test_basic_eq_false(self): - self.assertFalse(K(['==', 'f', 0]).match({'f': 1})) - - def test_basic_eq_non_extant(self): - with self.assertRaises(KeyError): - self.assertFalse(K(['==', 'f', 0]).match({})) - - def test_basic_gte_true(self): - self.assertTrue(K(['>=', 'f', 0]).match({'f': 0})) - - def test_basic_gte_false(self): - self.assertFalse(K(['>=', 'f', 0]).match({'f': -1})) - - def test_basic_gte_non_extant(self): - with self.assertRaises(KeyError): - self.assertFalse(K(['>=', 'f', 0]).match({})) - - def test_basic_gt_non_extant(self): - with self.assertRaises(KeyError): - self.assertFalse(K(['>', 'f', 0]).match({})) - - def test_null_regex_match_false(self): - self.assertFalse(K(['=~', 'f', '^hi$']).match({'f': None})) - - def test_basic_regex_true(self): - self.assertTrue(K(['=~', 'f', '^hi$']).match({'f': 'hi'})) - - def test_multiline_regex_true(self): - self.assertTrue(K(['=~', 'f', '.*hi.*']).match({'f': 'foo\nhi'})) - - def test_basic_regex_false(self): - self.assertFalse(K(['=~', 'f', '^hi$']).match({'f': ' hi'})) - - def test_basic_regex_non_extant(self): - with self.assertRaises(KeyError): - self.assertFalse(K(['=~', 'f', '^hi$']).match({})) - - def test_basic_equals_non_extant(self): - with self.assertRaises(KeyError): - self.assertTrue(K(['==', 'f', None]).match({})) - - def test_basic_not_equals_non_extant(self): - with self.assertRaises(KeyError): - self.assertFalse(K(['!=', 'f', None]).match({})) - - def test_basic_existence_true(self): - self.assertTrue(K(['?', 'k']).match({'k': 'val'})) - - def test_basic_existence_false(self): - self.assertFalse(K(['?', 'k']).match({'k1': 'val'})) - - def test_basic_nonexistence_true(self): - self.assertTrue(K(['!?', 'k']).match({'k1': 'val'})) - - def test_basic_nonexistence_false(self): - self.assertFalse(K(['!?', 'k']).match({'k': 'val'})) - - def test_basic_suppress_key_errors(self): - self.assertFalse(K(['==', 'k', 3], suppress_key_errors=True).match({})) - - def test_basic_suppress_exceptions(self): - self.assertFalse(K(['==', 'k', 3], suppress_exceptions=True).match({})) - - def test_not_field_true(self): - self.assertTrue(K([ - '!', ['>=', 'f', 3], - ]).match({'f': 1})) - - def test_compound_suppress_key_errors_gte_true(self): - self.assertTrue(K([ - '|', [ - ['==', 'f1', 5], - ['>', 'f', 5], - ] - ], suppress_key_errors=True).match({'f': 6})) - - def test_compound_suppress_exceptions_gte_true(self): - self.assertTrue(K([ - '|', [ - ['==', 'f1', 5], - ['>', 'f', 5], - ] - ], suppress_exceptions=True).match({'f': 6})) - - def test_type_exception(self): - """ - Handles different data type comparisons in py3 - """ - if version[0] == '2': # pragma: no cover - with patch('kmatch.K._match_value_filter') as mock_match_value_filter: - mock_match_value_filter.side_effect = TypeError - - with self.assertRaises(TypeError): - K(['>=', 'k', 3]).match({'k': None}) - with self.assertRaises(TypeError): - K(['>=', 'k', 3]).match({'k': ''}) - self.assertFalse(K(['>=', 'k', 3], suppress_exceptions=True).match({'k': None})) - self.assertFalse(K(['>=', 'k', 3], suppress_exceptions=True).match({'k': ''})) - - if version[0] == '3': # pragma: no cover - with self.assertRaises(TypeError): - K(['>=', 'k', 3]).match({'k': None}) - with self.assertRaises(TypeError): - K(['>=', 'k', 3]).match({'k': ''}) - self.assertFalse(K(['>=', 'k', 3], suppress_exceptions=True).match({'k': None})) - self.assertFalse(K(['>=', 'k', 3], suppress_exceptions=True).match({'k': ''})) - - def test_compound_existence_gte_true(self): - self.assertTrue(K([ - '&', [ - ['?', 'f'], - ['>', 'f', 5], - ] - ]).match({'f': 6})) - - def test_compound_and_lte_gte_single_field_true(self): - self.assertTrue(K([ - '&', [ - ['>=', 'f', 3], - ['<=', 'f', 7], - ] - ]).match({'f': 5})) - - def test_compound_and_lte_gte_double_field_true(self): - self.assertTrue(K([ - '&', [ - ['>=', 'f1', 3], - ['<=', 'f2', 7], - ] - ]).match({'f1': 5, 'f2': 0})) - - def test_compound_or_regex_double_field_true(self): - self.assertTrue(K([ - '|', [ - ['=~', 'f1', '^Email$'], - ['=~', 'f2', '^Call$'], - ] - ]).match({'f1': 'Email', 'f2': 'Reminder'})) - - def test_compound_or_regex_double_field_false(self): - self.assertFalse(K([ - '|', [ - ['=~', 'f1', '^Email$'], - ['=~', 'f2', '^Call$'], - ] - ]).match({'f1': 'Emails', 'f2': 'Reminder'})) - - def test_nested_compound_or_and_regex_double_field_true(self): - self.assertTrue(K([ - '&', [ - ['>=', 'f2', 10], [ - '|', [ - ['=~', 'f1', '^Email$'], - ['=~', 'f1', '^Call$'], - ] - ] - ] - ]).match({'f1': 'Email', 'f2': 20})) - - def test_nested_compound_or_and_regex_double_field_false(self): - self.assertFalse(K([ - '&', [ - ['>=', 'f2', 10], [ - '|', [ - ['=~', 'f1', '^Email$'], - ['=~', 'f1', '^Call$'], - ] - ] - ] - ]).match({'f1': 'Email', 'f2': 2})) - - def test_two_nested_ors_true(self): - self.assertTrue(K([ - '&', [ - ['|', [ - ['=~', 'f1', '^Email$'], - ['=~', 'f1', '^Call$'], - ]], - ['|', [ - ['>=', 'f2', 3], - ['>=', 'f3', 1], - ]] - ] - ]).match({'f1': 'Call', 'f2': 5, 'f3': 2})) - - def test_two_nested_ors_false(self): - self.assertFalse(K([ - '&', [ - ['|', [ - ['=~', 'f1', '^Email$'], - ['=~', 'f1', '^Call$'], - ]], - ['!', ['>=', 'f2', 3]], - ] - ]).match({'f1': 'Call', 'f2': 4})) - - def test_string_choice_or_true(self): - self.assertTrue(K([ - '|', [ - ['==', 'f1', 'Email'], - ['==', 'f1', 'Call'], - ['==', 'f1', 'Task'], - ] - ]).match({'f1': 'Task', 'f2': 2})) - - def test_xor_true(self): - self.assertTrue(K([ - '^', [ - ['?', 'email'], - ['?', 'e-mail'] - ] - ]).match({'email': '[email protected]'})) - self.assertTrue(K([ - '^', [ - ['?', 'email'], - ['?', 'e-mail'] - ] - ]).match({'e-mail': '[email protected]'})) - - def test_xor_false(self): - self.assertFalse(K([ - '^', [ - ['?', 'email'], - ['?', 'e-mail'] - ] - ]).match({'email': '[email protected]', - 'e-mail': '[email protected]'})) - - def test_get_field_keys(self): - """ - Verifies that all field keys are returned - """ - pattern = ['&', [ - ['?', 'foo'], - ['=~', 'one', 'one value'], - ['=~', 'two', 'two value'], - ['|', [ - ['=~', 'three', 'three value'], - ['!', ['=~', 'one', 'other one value']], - ['^', [ - ['==', 'five', 'five value'], - ['==', 'five', 'five value'] - ]], - ['&', [ - ['=~', 'four', 'four value'], - ]] - ]], - ]] - self.assertEqual(K(pattern).get_field_keys(), set(['one', 'two', 'three', 'four', 'five', 'foo'])) - - def test_get_field_keys_invalid_pattern(self): - """ - Verifies that an error is raised for invalid patterns - """ - pattern = ['&', [ - ['invalid', 'one', 'one value'] - ]] - with self.assertRaises(ValueError): - K(pattern).get_field_keys() - - def test_properties(self): - k = K(['<=', 'f', 0]) - self.assertFalse(k.suppress_exceptions) - k.suppress_exceptions = True - self.assertTrue(k.suppress_exceptions) - - -class KInitTest(TestCase): - """ - Tests the init function in K, which validates and compiles the pattern. - """ - def test_empty(self): - with self.assertRaises(ValueError): - K([]) - - def test_null_regex(self): - with self.assertRaises(ValueError): - K(['=~', 'f', None]) - - def test_invalid_regex(self): - with self.assertRaises(ValueError): - K(['=~', 'f', []]) - - def test_non_list_operand(self): - with self.assertRaises(ValueError): - K(['&', {}]) - - def test_invalid_operator_name(self): - with self.assertRaises(ValueError): - K(['INVALID', ['=~', 'f', 'r']]) - - def test_no_field_key_present(self): - with self.assertRaises(ValueError): - K(['>=', 'r']) - - def test_invalid_filter_key(self): - with self.assertRaises(ValueError): - K(['r', 'invalid_filter', 'r']) - - def test_too_many_filters(self): - with self.assertRaises(ValueError): - K(['r', '=~', 'r', '>=', 'r']) - - def test_non_dict_list(self): - with self.assertRaises(ValueError): - K('aaa') - - def test_invalid_xor(self): - with self.assertRaises(ValueError): - K([ - '^', - [ - ['?', 'a'], - ['?', 'b'], - ['?', 'c'] - ] - ]) - with self.assertRaises(ValueError): - K([ - '^', - [ - ['?', 'a'], - ] - ]) - - @patch('kmatch.kmatch.re.compile', spec_set=True, side_effect=lambda x, flags: '{0}_compiled'.format(x)) - def test_unnested(self, mock_compile): - k = K(['=~', 'field', 'hi']) - self.assertEquals(mock_compile.call_count, 1) - self.assertEquals(k._compiled_pattern, ['=~', 'field', 'hi_compiled']) - - @patch('kmatch.kmatch.re.compile', spec_set=True, side_effect=lambda x, flags: '{0}_compiled'.format(x)) - def test_nested_list_of_single_dict(self, mock_compile): - k = K(['!', ['=~', 'field', 'hi']]) - self.assertEquals(mock_compile.call_count, 1) - self.assertEquals(k._compiled_pattern, ['!', ['=~', 'field', 'hi_compiled']]) - - @patch('kmatch.kmatch.re.compile', spec_set=True, side_effect=lambda x, flags: '{0}_compiled'.format(x)) - def test_nested_list_of_lists(self, mock_compile): - k = K(['&', [['=~', 'f', 'hi'], ['=~', 'f', 'hello']]]) - self.assertEquals(mock_compile.call_count, 2) - self.assertEquals( - k._compiled_pattern, - ['&', [['=~', 'f', 'hi_compiled'], ['=~', 'f', 'hello_compiled']]]) - - @patch('kmatch.kmatch.re.compile', spec_set=True, side_effect=lambda x, flags: '{0}_compiled'.format(x)) - def test_triply_nested_list_of_dicts(self, mock_compile): - k = K(['&', [ - ['=~', 'f', 'hi'], - ['=~', 'f', 'hello'], - ['|', [ - ['=~', 'f', 'or_hi'], - ['=~', 'f', 'or_hello'], - ['&', [ - ['=~', 'f', 'and_hi'], - ]] - ]] - ]]) - self.assertEquals(mock_compile.call_count, 5) - self.assertEquals(k._compiled_pattern, ['&', [ - ['=~', 'f', 'hi_compiled'], - ['=~', 'f', 'hello_compiled'], - ['|', [ - ['=~', 'f', 'or_hi_compiled'], - ['=~', 'f', 'or_hello_compiled'], - ['&', [ - ['=~', 'f', 'and_hi_compiled'], - ]] - ]] - ]]) +from sys import version +from unittest import TestCase +from mock import patch + +from kmatch import K + + +class KPatternTest(TestCase): + """ + Tests the pattern function in K. + """ + def test_pattern(self): + k = K(['=~', 'hi', 'hi']) + self.assertEquals(k.pattern, ['=~', 'hi', 'hi']) + + +class KMatchTest(TestCase): + """ + Tests the match function in K. + """ + def test_basic_lte_true(self): + self.assertTrue(K(['<=', 'f', 0]).match({'f': -1})) + + def test_basic_lte_false(self): + self.assertFalse(K(['<=', 'f', 0]).match({'f': 1})) + + def test_basic_lte_non_extant(self): + with self.assertRaises(KeyError): + self.assertFalse(K(['<=', 'f', 0]).match({})) + + def test_basic_lt_non_extant(self): + with self.assertRaises(KeyError): + self.assertFalse(K(['<', 'f', 0]).match({})) + + def test_basic_eq_true(self): + self.assertTrue(K(['==', 'f', 0]).match({'f': 0})) + + def test_basic_eq_false(self): + self.assertFalse(K(['==', 'f', 0]).match({'f': 1})) + + def test_basic_eq_non_extant(self): + with self.assertRaises(KeyError): + self.assertFalse(K(['==', 'f', 0]).match({})) + + def test_basic_gte_true(self): + self.assertTrue(K(['>=', 'f', 0]).match({'f': 0})) + + def test_basic_gte_false(self): + self.assertFalse(K(['>=', 'f', 0]).match({'f': -1})) + + def test_basic_gte_non_extant(self): + with self.assertRaises(KeyError): + self.assertFalse(K(['>=', 'f', 0]).match({})) + + def test_basic_gt_non_extant(self): + with self.assertRaises(KeyError): + self.assertFalse(K(['>', 'f', 0]).match({})) + + def test_null_regex_match_false(self): + self.assertFalse(K(['=~', 'f', '^hi$']).match({'f': None})) + + def test_basic_regex_true(self): + self.assertTrue(K(['=~', 'f', '^hi$']).match({'f': 'hi'})) + + def test_multiline_regex_true(self): + self.assertTrue(K(['=~', 'f', '.*hi.*']).match({'f': 'foo\nhi'})) + + def test_basic_regex_false(self): + self.assertFalse(K(['=~', 'f', '^hi$']).match({'f': ' hi'})) + + def test_basic_regex_non_extant(self): + with self.assertRaises(KeyError): + self.assertFalse(K(['=~', 'f', '^hi$']).match({})) + + def test_basic_equals_non_extant(self): + with self.assertRaises(KeyError): + self.assertTrue(K(['==', 'f', None]).match({})) + + def test_basic_not_equals_non_extant(self): + with self.assertRaises(KeyError): + self.assertFalse(K(['!=', 'f', None]).match({})) + + def test_basic_existence_true(self): + self.assertTrue(K(['?', 'k']).match({'k': 'val'})) + + def test_basic_existence_false(self): + self.assertFalse(K(['?', 'k']).match({'k1': 'val'})) + + def test_basic_nonexistence_true(self): + self.assertTrue(K(['!?', 'k']).match({'k1': 'val'})) + + def test_basic_nonexistence_false(self): + self.assertFalse(K(['!?', 'k']).match({'k': 'val'})) + + def test_basic_suppress_key_errors(self): + self.assertFalse(K(['==', 'k', 3], suppress_key_errors=True).match({})) + + def test_basic_suppress_exceptions(self): + self.assertFalse(K(['==', 'k', 3], suppress_exceptions=True).match({})) + + def test_not_field_true(self): + self.assertTrue(K([ + '!', ['>=', 'f', 3], + ]).match({'f': 1})) + + def test_compound_suppress_key_errors_gte_true(self): + self.assertTrue(K([ + '|', [ + ['==', 'f1', 5], + ['>', 'f', 5], + ] + ], suppress_key_errors=True).match({'f': 6})) + + def test_compound_suppress_exceptions_gte_true(self): + self.assertTrue(K([ + '|', [ + ['==', 'f1', 5], + ['>', 'f', 5], + ] + ], suppress_exceptions=True).match({'f': 6})) + + def test_type_exception(self): + """ + Handles different data type comparisons in py3 + """ + if version[0] == '2': # pragma: no cover + with patch('kmatch.K._match_value_filter') as mock_match_value_filter: + mock_match_value_filter.side_effect = TypeError + + with self.assertRaises(TypeError): + K(['>=', 'k', 3]).match({'k': None}) + with self.assertRaises(TypeError): + K(['>=', 'k', 3]).match({'k': ''}) + self.assertFalse(K(['>=', 'k', 3], suppress_exceptions=True).match({'k': None})) + self.assertFalse(K(['>=', 'k', 3], suppress_exceptions=True).match({'k': ''})) + + if version[0] == '3': # pragma: no cover + with self.assertRaises(TypeError): + K(['>=', 'k', 3]).match({'k': None}) + with self.assertRaises(TypeError): + K(['>=', 'k', 3]).match({'k': ''}) + self.assertFalse(K(['>=', 'k', 3], suppress_exceptions=True).match({'k': None})) + self.assertFalse(K(['>=', 'k', 3], suppress_exceptions=True).match({'k': ''})) + + def test_compound_existence_gte_true(self): + self.assertTrue(K([ + '&', [ + ['?', 'f'], + ['>', 'f', 5], + ] + ]).match({'f': 6})) + + def test_compound_and_lte_gte_single_field_true(self): + self.assertTrue(K([ + '&', [ + ['>=', 'f', 3], + ['<=', 'f', 7], + ] + ]).match({'f': 5})) + + def test_compound_and_lte_gte_double_field_true(self): + self.assertTrue(K([ + '&', [ + ['>=', 'f1', 3], + ['<=', 'f2', 7], + ] + ]).match({'f1': 5, 'f2': 0})) + + def test_compound_or_regex_double_field_true(self): + self.assertTrue(K([ + '|', [ + ['=~', 'f1', '^Email$'], + ['=~', 'f2', '^Call$'], + ] + ]).match({'f1': 'Email', 'f2': 'Reminder'})) + + def test_compound_or_regex_double_field_false(self): + self.assertFalse(K([ + '|', [ + ['=~', 'f1', '^Email$'], + ['=~', 'f2', '^Call$'], + ] + ]).match({'f1': 'Emails', 'f2': 'Reminder'})) + + def test_nested_compound_or_and_regex_double_field_true(self): + self.assertTrue(K([ + '&', [ + ['>=', 'f2', 10], [ + '|', [ + ['=~', 'f1', '^Email$'], + ['=~', 'f1', '^Call$'], + ] + ] + ] + ]).match({'f1': 'Email', 'f2': 20})) + + def test_nested_compound_or_and_regex_double_field_false(self): + self.assertFalse(K([ + '&', [ + ['>=', 'f2', 10], [ + '|', [ + ['=~', 'f1', '^Email$'], + ['=~', 'f1', '^Call$'], + ] + ] + ] + ]).match({'f1': 'Email', 'f2': 2})) + + def test_two_nested_ors_true(self): + self.assertTrue(K([ + '&', [ + ['|', [ + ['=~', 'f1', '^Email$'], + ['=~', 'f1', '^Call$'], + ]], + ['|', [ + ['>=', 'f2', 3], + ['>=', 'f3', 1], + ]] + ] + ]).match({'f1': 'Call', 'f2': 5, 'f3': 2})) + + def test_two_nested_ors_false(self): + self.assertFalse(K([ + '&', [ + ['|', [ + ['=~', 'f1', '^Email$'], + ['=~', 'f1', '^Call$'], + ]], + ['!', ['>=', 'f2', 3]], + ] + ]).match({'f1': 'Call', 'f2': 4})) + + def test_string_choice_or_true(self): + self.assertTrue(K([ + '|', [ + ['==', 'f1', 'Email'], + ['==', 'f1', 'Call'], + ['==', 'f1', 'Task'], + ] + ]).match({'f1': 'Task', 'f2': 2})) + + def test_xor_true(self): + self.assertTrue(K([ + '^', [ + ['?', 'email'], + ['?', 'e-mail'] + ] + ]).match({'email': '[email protected]'})) + self.assertTrue(K([ + '^', [ + ['?', 'email'], + ['?', 'e-mail'] + ] + ]).match({'e-mail': '[email protected]'})) + + def test_xor_false(self): + self.assertFalse(K([ + '^', [ + ['?', 'email'], + ['?', 'e-mail'] + ] + ]).match({'email': '[email protected]', + 'e-mail': '[email protected]'})) + + def test_get_field_keys(self): + """ + Verifies that all field keys are returned + """ + pattern = ['&', [ + ['?', 'foo'], + ['=~', 'one', 'one value'], + ['=~', 'two', 'two value'], + ['|', [ + ['=~', 'three', 'three value'], + ['!', ['=~', 'one', 'other one value']], + ['^', [ + ['==', 'five', 'five value'], + ['==', 'five', 'five value'] + ]], + ['&', [ + ['=~', 'four', 'four value'], + ]] + ]], + ]] + self.assertEqual(K(pattern).get_field_keys(), set(['one', 'two', 'three', 'four', 'five', 'foo'])) + + def test_get_field_keys_invalid_pattern(self): + """ + Verifies that an error is raised for invalid patterns + """ + pattern = ['&', [ + ['invalid', 'one', 'one value'] + ]] + with self.assertRaises(ValueError): + K(pattern).get_field_keys() + + def test_properties(self): + k = K(['<=', 'f', 0]) + self.assertFalse(k.suppress_exceptions) + k.suppress_exceptions = True + self.assertTrue(k.suppress_exceptions) + + +class KInitTest(TestCase): + """ + Tests the init function in K, which validates and compiles the pattern. + """ + def test_empty(self): + with self.assertRaises(ValueError): + K([]) + + def test_null_regex(self): + with self.assertRaises(ValueError): + K(['=~', 'f', None]) + + def test_invalid_regex(self): + with self.assertRaises(ValueError): + K(['=~', 'f', []]) + + def test_non_list_operand(self): + with self.assertRaises(ValueError): + K(['&', {}]) + + def test_invalid_operator_name(self): + with self.assertRaises(ValueError): + K(['INVALID', ['=~', 'f', 'r']]) + + def test_no_field_key_present(self): + with self.assertRaises(ValueError): + K(['>=', 'r']) + + def test_invalid_filter_key(self): + with self.assertRaises(ValueError): + K(['r', 'invalid_filter', 'r']) + + def test_too_many_filters(self): + with self.assertRaises(ValueError): + K(['r', '=~', 'r', '>=', 'r']) + + def test_non_dict_list(self): + with self.assertRaises(ValueError): + K('aaa') + + def test_invalid_xor(self): + with self.assertRaises(ValueError): + K([ + '^', + [ + ['?', 'a'], + ['?', 'b'], + ['?', 'c'] + ] + ]) + with self.assertRaises(ValueError): + K([ + '^', + [ + ['?', 'a'], + ] + ]) + + @patch('kmatch.kmatch.re.compile', spec_set=True, side_effect=lambda x, flags: '{0}_compiled'.format(x)) + def test_unnested(self, mock_compile): + k = K(['=~', 'field', 'hi']) + self.assertEquals(mock_compile.call_count, 1) + self.assertEquals(k._compiled_pattern, ['=~', 'field', 'hi_compiled']) + + @patch('kmatch.kmatch.re.compile', spec_set=True, side_effect=lambda x, flags: '{0}_compiled'.format(x)) + def test_nested_list_of_single_dict(self, mock_compile): + k = K(['!', ['=~', 'field', 'hi']]) + self.assertEquals(mock_compile.call_count, 1) + self.assertEquals(k._compiled_pattern, ['!', ['=~', 'field', 'hi_compiled']]) + + @patch('kmatch.kmatch.re.compile', spec_set=True, side_effect=lambda x, flags: '{0}_compiled'.format(x)) + def test_nested_list_of_lists(self, mock_compile): + k = K(['&', [['=~', 'f', 'hi'], ['=~', 'f', 'hello']]]) + self.assertEquals(mock_compile.call_count, 2) + self.assertEquals( + k._compiled_pattern, + ['&', [['=~', 'f', 'hi_compiled'], ['=~', 'f', 'hello_compiled']]]) + + @patch('kmatch.kmatch.re.compile', spec_set=True, side_effect=lambda x, flags: '{0}_compiled'.format(x)) + def test_triply_nested_list_of_dicts(self, mock_compile): + k = K(['&', [ + ['=~', 'f', 'hi'], + ['=~', 'f', 'hello'], + ['|', [ + ['=~', 'f', 'or_hi'], + ['=~', 'f', 'or_hello'], + ['&', [ + ['=~', 'f', 'and_hi'], + ]] + ]] + ]]) + self.assertEquals(mock_compile.call_count, 5) + self.assertEquals(k._compiled_pattern, ['&', [ + ['=~', 'f', 'hi_compiled'], + ['=~', 'f', 'hello_compiled'], + ['|', [ + ['=~', 'f', 'or_hi_compiled'], + ['=~', 'f', 'or_hello_compiled'], + ['&', [ + ['=~', 'f', 'and_hi_compiled'], + ]] + ]] + ]]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kmatch-0.3.0/kmatch/version.py new/kmatch-0.4.0/kmatch/version.py --- old/kmatch-0.3.0/kmatch/version.py 2019-07-10 23:29:15.000000000 +0200 +++ new/kmatch-0.4.0/kmatch/version.py 2020-12-22 22:28:32.000000000 +0100 @@ -1 +1 @@ -__version__ = '0.3.0' +__version__ = '0.4.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kmatch-0.3.0/kmatch.egg-info/PKG-INFO new/kmatch-0.4.0/kmatch.egg-info/PKG-INFO --- old/kmatch-0.3.0/kmatch.egg-info/PKG-INFO 2019-07-11 19:26:04.000000000 +0200 +++ new/kmatch-0.4.0/kmatch.egg-info/PKG-INFO 2020-12-29 02:58:43.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: kmatch -Version: 0.3.0 +Version: 0.4.0 Summary: A language for matching/validating/filtering Python dictionaries Home-page: https://github.com/ambitioninc/kmatch Author: Wes Kendall @@ -47,10 +47,10 @@ Keywords: matching,dictionaries,filtering,validation Platform: UNKNOWN -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kmatch-0.3.0/setup.cfg new/kmatch-0.4.0/setup.cfg --- old/kmatch-0.3.0/setup.cfg 2019-07-11 19:26:05.000000000 +0200 +++ new/kmatch-0.4.0/setup.cfg 2020-12-29 02:58:44.000000000 +0100 @@ -4,6 +4,14 @@ cover-min-percentage = 100 cover-package = kmatch +[build_sphinx] +source-dir = docs/ +build-dir = docs/_build +all_files = 1 + +[upload_sphinx] +upload-dir = docs/_build/html + [flake8] max-line-length = 120 exclude = docs,env,*.egg diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kmatch-0.3.0/setup.py new/kmatch-0.4.0/setup.py --- old/kmatch-0.3.0/setup.py 2018-08-09 22:08:11.000000000 +0200 +++ new/kmatch-0.4.0/setup.py 2020-12-28 19:23:57.000000000 +0100 @@ -28,10 +28,10 @@ keywords='matching, dictionaries, filtering, validation', packages=find_packages(), classifiers=[ - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', ++++++ python-kmatch-no-mock.patch ++++++ Index: kmatch-0.4.0/kmatch/tests/kmatch_tests.py =================================================================== --- kmatch-0.4.0.orig/kmatch/tests/kmatch_tests.py 2020-12-22 22:27:55.000000000 +0100 +++ kmatch-0.4.0/kmatch/tests/kmatch_tests.py 2022-03-23 12:33:03.395890702 +0100 @@ -1,6 +1,6 @@ from sys import version from unittest import TestCase -from mock import patch +from unittest.mock import patch from kmatch import K
