def test_nested_struct(): """adapted from use case""" import json def _json(spec): return Auto((json.loads, _str_json, Match(spec))) _str_json = Ref( 'json', Match( Or(And(dict, {Ref('json'): Ref('json')}), And(list, [Ref('json')]), And(type(u''), Auto(str)), object))) rule_spec = Match({ 'rule_id': Or('', Regex(r'\d+')), 'rule_name': str, 'effect': Or('approve', 'custom_approvers'), 'rule_approvers': _json([{ 'pk': int, 'level': int }]), 'rule_data': _json([ # list of condition-objects { Optional('value', 'null'): _json(Or(None, int, float, str, [int, float, str])), 'field': Auto(int), # id of row from FilterField 'operator': str, # corresponds to FilterOperator.display_name } ]), Optional('save_as_new', False): Or(str, bool), }) rule = dict(rule_id='1', rule_name='test rule', effect='approve', rule_approvers=json.dumps([{ 'pk': 2, 'level': 1 }]), rule_data=json.dumps([{ 'value': json.dumps([1, 2]), 'field': 2, 'operator': '>' }, { 'field': 2, 'operator': '==' }])) glom(rule, rule_spec) rule['save_as_new'] = 'true' glom(rule, rule_spec)
def test_json_ref(): assert glom( {'a': {'b': [0, 1]}}, Ref('json', Match(Or( And(dict, {Ref('json'): Ref('json')}), And(list, [Ref('json')]), And(0, Val(None)), object)))) == {'a': {'b': [None, 1]}}
def test_check_ported_tests(): """ Tests ported from Check() to make sure all the functionality has an analogue. """ target = [{'id': 0}, {'id': 1}, {'id': 2}] # check that skipping non-passing values works assert glom(target, [Coalesce(M(T['id']) == 0, default=SKIP)]) == [{'id': 0}] # TODO: should M(subspec, default='') work? I lean no. # NB: this is not a very idiomatic use of Match, just brought over for Check reasons assert glom(target, [Match({'id': And(int, M == 1)}, default=SKIP)]) == [{'id': 1}] assert glom(target, [Match({'id': And(int, M <= 1)}, default=STOP)]) == [{'id': 0}, {'id': 1}] # check that stopping chain execution on non-passing values works spec = (Or(Match(len), Val(STOP)), T[0]) assert glom('hello', spec, glom_debug=True) == 'h' assert glom('', spec) == '' # would fail with IndexError if STOP didn't work target = [1, u'a'] assert glom(target, [Match(unicode, default=SKIP)]) == ['a'] assert glom(target, Match([Or(unicode, int)])) == [1, 'a'] target = ['1'] assert glom(target, [(M(T), int)]) == [1] assert glom(target, M(T)) == ['1'] failing_checks = [({'a': {'b': 1}}, {'a': ('a', 'b', Match(str))}, '''expected type str, not int'''), # TODO: bbrepr at least, maybe include path like Check did ({'a': {'b': 1}}, {'a': ('a', Match({'b': str}))}, '''expected type str, not int'''), # TODO: include subspec path ('b') (1, Match(Or(unicode, bool))), (1, Match(unicode)), (1, Match(0)), (1, Match(Or(0, 2))), ('-3.14', Match(lambda x: int(x) > 0)), # ('-3.14', M(lambda x: int(x) > 0)), # TODO: M doesn't behave quite like Match because it's mode-free ] for fc in failing_checks: if len(fc) == 2: target, check = fc msg = None else: target, check, msg = fc with pytest.raises(MatchError) as exc_info: glom(target, check) if msg is not None: actual_msg = str(exc_info.value) assert actual_msg.find(msg) != -1 assert repr(exc_info.value) return
def test_basic(): _chk(Match(1), 1, 2) _chk(Match(int), 1, 1.0) # test unordered sequence comparisons _chk(Match([int]), [1], ["1"]) _chk(Match({int}), {1}, [1]) _chk(Match(frozenset({float})), frozenset({}), frozenset({"1"})) _chk(Match(len), [1], []) with pytest.raises(MatchError): glom(None, Match(len)) with pytest.raises(MatchError): glom([1], Match([])) # empty shouldn't match glom({"a": 1, "b": 2}, Match({str: int})) glom(2, M == 2) glom(int, M == int) glom(1.0, M > 0) glom(1.0, M >= 1) glom(1.0, M < 2) glom(1.0, M <= 1) glom(1.0, M != None) glom(1.0, (M > 0) & float) glom(1.0, (M > 100) | float) assert Match(('a', 'b')).matches(('a', 'b', 'c')) is False # test idiom for enum with pytest.raises(MatchError): glom("c", Match("a")) glom("c", Not(Match("a"))) with pytest.raises(MatchError): glom("c", Match(Or("a", "b"))) with pytest.raises(ValueError): And() with pytest.raises(TypeError): And('a', bad_kwarg=True) with pytest.raises(ValueError): Or() with pytest.raises(TypeError): Or('a', bad_kwarg=True) _chk(Match(Or("a", "b")), "a", "c") glom({None: 1}, Match({object: object})) _chk(Match((int, str)), (1, "cat"), (1, 2)) with pytest.raises(MatchError): glom({1: 2}, Match({(): int})) with pytest.raises(MatchError): glom(1, Match({})) Match(M > 0).verify(1.0) assert Match(M).matches(False) is False assert Match(M).matches(True) is True
def test_and_or_reduction(): and_spec = And(T['a'], T['b']) & T['c'] assert repr(and_spec) == "And(T['a'], T['b'], T['c'])" or_spec = Or(T['a'], T['b']) | T['c'] assert repr(or_spec) == "Or(T['a'], T['b'], T['c'])"
def test_pattern_matching(): pattern_matcher = Or(And(Match(1), Val('one')), And(Match(2), Val('two')), And(Match(float), Val('float'))) assert glom(1, pattern_matcher) == 'one' assert glom(1.1, pattern_matcher) == 'float' # obligatory fibonacci fib = (M > 2) & (lambda n: glom(n - 1, fib) + glom(n - 2, fib)) | T assert glom(5, fib) == 8 factorial = (lambda t: t + 1, Ref('fact', (lambda t: t - 1, (M == 0) & Fill(1) | (S(r=Ref('fact')), S, lambda s: s['r'] * s[T])))) assert glom(4, factorial) == 4 * 3 * 2
def test_reprs(): repr(M) repr(M == 1) repr(M | M == 1) repr(M & M == 1) repr(~M) repr(And(1, 2)) repr(Or(1, 2)) repr(Not(1)) repr(MatchError("uh oh")) repr(TypeMatchError("uh oh {0}", dict)) assert repr(And(M == 1, float)) == "(M == 1) & float" assert repr(eval(repr(And(M == 1, float)))) == "(M == 1) & float" assert repr(Regex('[ab]')) == "Regex('[ab]')" assert repr(Regex('[ab]', flags=1)) == "Regex('[ab]', flags=1)" assert 'search' in repr(Regex('[ab]', func=re.search)) assert repr(And(1)) == 'And(1)' assert repr(~And(1)) == 'Not(And(1))' assert repr(~Or(M) & Or(M)) == '~(M) & M' assert repr(Not(M < 3)) == '~(M < 3)' assert repr(~(M < 4)) == '~(M < 4)' assert repr(~M | "default") == "~M | 'default'" assert repr(Or(M, default=1)) == "Or(M, default=1)"
def test_defaults(): assert glom(1, Match(2, default=3)) == 3 assert glom(1, Or(M == 2, default=3)) == 3 assert glom(1, And(M == 2, default=3)) == 3
def default_if_none(sub_schema, default_factory): return Or( And(M == None, Auto(lambda t: default_factory())), sub_schema)
def in_range(sub_schema, _min, _max): 'check that sub_schema is between _min and _max' return Match(And(sub_schema, _min < M, M < _max))
def as_type(sub_schema, typ): 'after checking sub_schema, pass the result to typ()' return And(sub_schema, Auto(typ))