def test_hash_and_range_key_success(model, index, range_condition_lambda): """AND(hash, range) + AND(range, hash) for valid hash and range key conditions""" hash_condition = (index or model.Meta).hash_key == "value" range_condition = range_condition_lambda( column=(index or model.Meta).range_key) validate_key_condition(model, index, AndCondition(hash_condition, range_condition)) validate_key_condition(model, index, AndCondition(range_condition, hash_condition))
def test_and_bad_range_key(model, index, range_condition_lambda): """AND with valid hash range condition but bad hash key condition""" hash_condition = (index or model.Meta).hash_key == "value" range_condition = range_condition_lambda(column=(index or model.Meta).range_key) with pytest.raises(InvalidKeyCondition): validate_key_condition(model, index, AndCondition(hash_condition, range_condition)) with pytest.raises(InvalidKeyCondition): validate_key_condition(model, index, AndCondition(range_condition, hash_condition))
def test_and_bad_hash_key(model, index, range_condition_lambda): """AND with valid range key condition but bad hash key condition""" hash_condition = (index or model.Meta).hash_key.between("bad", "condition") range_condition = range_condition_lambda(column=(index or model.Meta).range_key) with pytest.raises(InvalidKeyCondition): validate_key_condition(model, index, AndCondition(hash_condition, range_condition)) with pytest.raises(InvalidKeyCondition): validate_key_condition(model, index, AndCondition(range_condition, hash_condition))
def test_and_both_and(): """(a & b) & (c & d) -> (a & b & c & d)""" a, b, c, d = [condition_for(">") for _ in range(4)] left = AndCondition(a, b) right = AndCondition(c, d) assert (left & right).operation == "and" assert (left & right).values == [a, b, c, d] assert (right & left).values == [c, d, a, b]
def test_iand_both_and(): """other's conditions are appended to self's conditions""" a, b, c, d = [condition_for(">") for _ in range(4)] left = AndCondition(a, b) right = AndCondition(c, d) original_left = left left &= right assert left is original_left assert left.values == [a, b, c, d] assert right.values == [c, d]
def test_and_both_same_key(model, index, key_name): """AND with 2 conditions, but both conditions are on the same key""" key_column = getattr(index or model.Meta, key_name) condition = key_column == "value" key = AndCondition(condition, condition) with pytest.raises(InvalidSearch): validate_key_condition(model, index, key)
def conditions_for(classes, include=None, exclude=None): """Returns lambdas that take column""" value = "value" values = ["0", "1", "2"] condition_lambdas = [] if BeginsWithCondition in classes: condition_lambdas.append( lambda column: BeginsWithCondition(column, value)) if BetweenCondition in classes: condition_lambdas.append( lambda column: BetweenCondition(column, values[0], values[1])) if ComparisonCondition in classes: condition_lambdas.extend( comparisons_for(include=include, exclude=exclude)) if Condition in classes: condition_lambdas.append(lambda column: Condition()) if ContainsCondition in classes: condition_lambdas.append( lambda column: ContainsCondition(column, value)) if InCondition in classes: condition_lambdas.append(lambda column: InCondition(column, values)) # Meta Conditions if AndCondition in classes: condition_lambdas.append( lambda column: AndCondition(column == value, column != value)) if OrCondition in classes: condition_lambdas.append( lambda column: OrCondition(column == value, column != value)) if NotCondition in classes: condition_lambdas.append(lambda column: NotCondition(column == value)) return condition_lambdas
def conditions_for(*operations, column=None): column = column or MockColumn("c") value = 0 values = [1, 2] conditions = [] if None in operations: conditions.append(Condition()) if "and" in operations: left = ComparisonCondition("==", column, value) right = ComparisonCondition("!=", column, value) conditions.append(AndCondition(left, right)) if "or" in operations: left = ComparisonCondition("==", column, value) right = ComparisonCondition("!=", column, value) conditions.append(OrCondition(left, right)) if "not" in operations: inner = ComparisonCondition("==", column, value) conditions.append(NotCondition(inner)) if "begins_with" in operations: conditions.append(BeginsWithCondition(column, value)) if "between" in operations: conditions.append(BetweenCondition(column, *values)) if "contains" in operations: conditions.append(ContainsCondition(column, value)) if "in" in operations: conditions.append(InCondition(column, values)) for operation in ("<", "<=", ">", ">=", "!=", "=="): if operation in operations: conditions.append(ComparisonCondition(operation, column, value)) return conditions
def empty_conditions(): return [ Condition(), AndCondition(), OrCondition(), NotCondition(Condition()) ]
def test_len_unpack_not(): """Even though not(not(x)) -> x shouldn't exist, its length should be the inner length""" lt, gt = conditions_for("<", ">") outer = NotCondition(lt) condition = NotCondition(outer) assert len(condition) == len(outer) == 1 # Swap inner for an AND with length 2 and_ = AndCondition(lt, gt) outer.values[0] = and_ assert len(condition) == len(outer) == len(and_) == 2
def test_and_simplifies(other): """When only one condition is an and, the other is put in a new and, in the correct place (a & b) & (c > 2) -> (a & b & (c > 2)) (a > 2) & (b & c) -> ((a > 2) & b & c) """ a, b, = [condition_for(">"), condition_for("<")] and_condition = AndCondition(a, b) assert (and_condition & other).operation == "and" assert (and_condition & other).values == [a, b, other] assert (other & and_condition).values == [other, a, b]
def test_iand_simplifies(other): """Similar to and, other value is pushed into the and (on LHS) or front of a new and (on RHS)""" a, b, = [condition_for(">"), condition_for("<")] and_condition = AndCondition(a, b) original_other = other other &= and_condition assert other is not original_other assert other.values == [original_other, a, b] original_and_condition = and_condition and_condition &= original_other assert and_condition is original_and_condition assert and_condition.values == [a, b, original_other]
def test_len_cyclic(): """Cyclic conditions count the cyclic reference""" # Here's the structure to create: # root # / \ # a b # / \ # c root root = AndCondition() a = ComparisonCondition("<", MockColumn("a"), 3) b = OrCondition() c = ComparisonCondition(">", MockColumn("c"), 3) root.values.extend([a, b]) b.values.extend([c, root]) assert len(root) == 4
def test_iter_conditions_cyclic(): """Cyclic conditions can be iterated safely""" # Here's the structure to create: # root # / \ # a b # / \ # c root root = AndCondition() a = ComparisonCondition("<", MockColumn("a"), 3) b = OrCondition() c = ComparisonCondition(">", MockColumn("c"), 3) root.values.extend([a, b]) b.values.extend([c, root]) expected = {root, a, b, c} actual = set(iter_conditions(root)) assert actual == expected
def test_and_not_two(model, index, count): """AND on hash+range fails if there aren't exactly 2 key conditions""" hash_key_column = (index or model.Meta).hash_key key = AndCondition(*[hash_key_column == "value"] * count) with pytest.raises(InvalidSearch): validate_key_condition(model, index, key)
assert a.values == [original_a, original_b] b |= original_a assert b is not original_b assert b.operation == "or" assert b.values == [original_b, original_a] # CONDITIONS REPR ==================================================================================== CONDITIONS REPR @pytest.mark.parametrize( "condition, expected", [ # and (AndCondition(), "( & )"), (AndCondition("foo"), "('foo' &)"), (AndCondition("a", "b", "c"), "('a' & 'b' & 'c')"), # or (OrCondition(), "( | )"), (OrCondition("foo"), "('foo' |)"), (OrCondition("a", "b", "c"), "('a' | 'b' | 'c')"), # not (NotCondition("a"), "(~'a')"), # comparisons (ComparisonCondition("<", column=c, value=3), "(M.c < 3)"), (ComparisonCondition(">", column=c, value=3), "(M.c > 3)"), (ComparisonCondition("<=", column=c, value=3), "(M.c <= 3)"),