Skip to content

Commit

Permalink
Rework dictionary matching to accept patterns too
Browse files Browse the repository at this point in the history
  • Loading branch information
scravy committed Jan 16, 2021
1 parent 2bb8b22 commit 2ae02b3
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 29 deletions.
42 changes: 29 additions & 13 deletions apm/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,25 +330,41 @@ def match(self, value, *, ctx: MatchContext, strict: bool) -> MatchResult:
return ctx.matches()


def _match_dict(value, pattern, *, ctx: MatchContext, strict: bool) -> MatchResult:
def _match_dict(value, pattern: dict, *, ctx: MatchContext, strict: bool) -> MatchResult:
to_be_matched = {}
try:
items = value.items()
except (AttributeError, TypeError):
return ctx.no_match()
matched_keys = set()
for key, value in items:
if key in pattern:
result = ctx.match(value, pattern[key])
if not result:
return result
matched_keys.add(key)
else:
if strict:
return ctx.no_match()
for key in pattern:
if key not in matched_keys:
to_be_matched[key] = value
patterns = []
for key, pattern in pattern.items():
if isinstance(key, Pattern):
patterns.append((key, pattern))
continue
try:
value = to_be_matched[key]
except KeyError:
return ctx.no_match()
return ctx.matches()
result = ctx.match(value, pattern)
if not result:
return result
del to_be_matched[key]
possibly_mismatching_keys = set()
for key_pattern, pattern in patterns:
keys_to_remove = []
for key, value in to_be_matched.items():
if ctx.match(key, key_pattern):
if ctx.match(value, pattern):
keys_to_remove.append(key)
if key in possibly_mismatching_keys:
possibly_mismatching_keys.remove(key)
else:
possibly_mismatching_keys.add(key)
for key in keys_to_remove:
del to_be_matched[key]
return ctx.match_if(not possibly_mismatching_keys and (not strict or not to_be_matched))


def _match_tuple(value, pattern, *, ctx: MatchContext, strict: bool) -> MatchResult:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_basic_usecases.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def test_string_argresult(self):
"bar=",
Capture(Regex("[^=/]+"), name="bar"),
Regex("/*"),
), argresult=True)
))
self.assertTrue(result)
self.assertEqual("https", result.protocol)
self.assertEqual("somehost", result.host)
Expand Down
8 changes: 8 additions & 0 deletions tests/test_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ def test_syntactic_sugar_underscore(self):
self.assertEqual('metrics-server', result['name'])
self.assertEqual(4443, result['port'])

def test_regex_bind_groups(self):
result = match("abcdef", Regex(r"(?P<one>[a-c]+)(?P<two>[a-z]+)", bind_groups=True))
self.assertTrue(result)
self.assertEqual(result['one'], 'abc')
self.assertEqual(result['two'], 'def')
self.assertEqual(result.one, 'abc')
self.assertEqual(result.two, 'def')


sample_k8s_response = {
"containers": [
Expand Down
85 changes: 70 additions & 15 deletions tests/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,73 @@
from apm import *


class ComplexMatchTest(unittest.TestCase):

def test_fibonacci(self):
def fibonacci(n):
return match(n,
1, 1,
2, 1,
_, lambda x: fibonacci(x-1) + fibonacci(x-2)
)
self.assertEqual(1, fibonacci(1))
self.assertEqual(1, fibonacci(2))
self.assertEqual(2, fibonacci(3))
self.assertEqual(3, fibonacci(4))
self.assertEqual(5, fibonacci(5))
self.assertEqual(8, fibonacci(6))
class DictionaryMatchingTests(unittest.TestCase):

def test_empty_dict(self):
self.assertTrue(match({}, {}))

def test_empty_subdict(self):
self.assertTrue(match({"a": 3}, {}))

def test_empty_dict_strict(self):
self.assertTrue(match({}, Strict({})))

def test_empty_subdict_strict(self):
self.assertFalse(match({"a": 3}, Strict({})))

def test_mismatching_types(self):
self.assertFalse(match(3, {}))

def test_pattern_dict_wildcard(self):
self.assertTrue(match({"user": "id"}, {_: "id"}))

def test_pattern_dict(self):
self.assertTrue(match({
"i9": 17,
"f17": 3.0,
}, {
Regex(r"i[0-9]+"): InstanceOf(int),
Regex(r"f[0-9]+"): InstanceOf(float),
}))

def test_pattern_dict_unknown_key(self):
value = {
"i9": 17,
"f17": 3.0,
"unknown": True,
}
pattern = {
Regex(r"i[0-9]+"): InstanceOf(int),
Regex(r"f[0-9]+"): InstanceOf(float),
}
self.assertTrue(match(value, pattern))
self.assertFalse(match(value, Strict(pattern)))

def test_pattern_dict_mismatching_value_for_pattern_key(self):
self.assertFalse(match({
"i9": 17,
"i81": 9.8,
}, {
Regex(r"i[0-9]+"): InstanceOf(int),
Regex(r"f[0-9]+"): InstanceOf(float),
}))

def test_pattern_dict_mismatching_value_for_pattern_key_with_more_general_match(self):
self.assertTrue(match({
"i9": 17,
"i81": "9.8",
}, {
Regex(r"i[0-9]+"): InstanceOf(int),
Regex(r"f[0-9]+"): InstanceOf(float),
_: InstanceOf(str),
}))

def test_pattern_dict_mismatching_value_for_pattern_key_with_mismatching_more_general_match(self):
self.assertFalse(match({
"i9": 17,
"i81": "9.8",
}, {
Regex(r"i[0-9]+"): InstanceOf(int),
Regex(r"f[0-9]+"): InstanceOf(float),
_: InstanceOf(bool),
}))

0 comments on commit 2ae02b3

Please sign in to comment.