Example #1
0
def test_save_set_only(engine, session):
    user = User(id="user_id")

    # Expect a SET on email
    user.email = "*****@*****.**"

    expected = {
        "Key": {
            "id": {
                "S": user.id
            }
        },
        "ExpressionAttributeNames": {
            "#n0": "email"
        },
        "TableName": "User",
        "ReturnValues": "NONE",
        "UpdateExpression": "SET #n0=:v1",
        "ExpressionAttributeValues": {
            ":v1": {
                "S": "*****@*****.**"
            }
        }
    }
    engine.save(user)
    session.save_item.assert_called_once_with(expected)
Example #2
0
def wx(engine):
    """prepared write tx with one item"""
    user = User(id="numberoverzero")
    other = User(id="other")
    items = [
        TxItem.new("save", user, condition=User.id.is_(None)),
        TxItem.new("delete", other),
        TxItem.new("check", other, condition=User.email.begins_with("foo"))
    ]
    tx = PreparedTransaction()
    tx.prepare(engine, "w", items)
    return tx
Example #3
0
def test_load_objects(engine, session):
    user1 = User(id="user1")
    user2 = User(id="user2")
    expected = {
        "User": {
            "Keys": [{
                "id": {
                    "S": "user1"
                }
            }, {
                "id": {
                    "S": "user2"
                }
            }],
            "ConsistentRead": False
        }
    }
    response = {
        "User": [{
            "age": {
                "N": 5
            },
            "name": {
                "S": "foo"
            },
            "id": {
                "S": "user1"
            }
        }, {
            "age": {
                "N": 10
            },
            "name": {
                "S": "bar"
            },
            "id": {
                "S": "user2"
            }
        }]
    }

    def respond(RequestItems):
        assert ordered(RequestItems) == ordered(expected)
        return response

    session.load_items.side_effect = respond
    engine.load(user1, user2)

    assert user1.age == 5
    assert user1.name == "foo"
    assert user2.age == 10
    assert user2.name == "bar"
Example #4
0
def test_delete_atomic(engine, session):
    user = User(id="user_id")

    # Tell the tracking system the user's id was saved to DynamoDB
    object_saved.send(engine, engine=engine, obj=user)

    expected = {
        "ConditionExpression": "(#n0 = :v1)",
        "ExpressionAttributeValues": {
            ":v1": {
                "S": user.id
            }
        },
        "TableName": "User",
        "ReturnValues": "NONE",
        "Key": {
            "id": {
                "S": user.id
            }
        },
        "ExpressionAttributeNames": {
            "#n0": "id"
        }
    }
    engine.delete(user, atomic=True)
    session.delete_item.assert_called_once_with(expected)
Example #5
0
def rx(engine):
    """prepared read tx with one item"""
    user = User(id="numberoverzero")
    items = [TxItem.new("get", user)]
    tx = PreparedTransaction()
    tx.prepare(engine, "r", items)
    return tx
Example #6
0
def test_delete_multiple_condition(engine, session, caplog):
    users = [User(id=str(i)) for i in range(3)]
    condition = User.id == "foo"
    expected_calls = [{
        "Key": {
            "id": {
                "S": user.id
            }
        },
        "ExpressionAttributeValues": {
            ":v1": {
                "S": "foo"
            }
        },
        "ExpressionAttributeNames": {
            "#n0": "id"
        },
        "ConditionExpression": "(#n0 = :v1)",
        "TableName": "User",
        "ReturnValues": "NONE",
    } for user in users]
    engine.delete(*users, condition=condition)
    for expected in expected_calls:
        session.delete_item.assert_any_call(expected)
    assert session.delete_item.call_count == 3

    assert caplog.record_tuples[-1] == ("bloop.engine", logging.INFO,
                                        "successfully deleted 3 objects")
Example #7
0
def test_delete_atomic_condition(engine, session):
    user = User(id="user_id", email="*****@*****.**")

    # Tell the tracking system the user's id and email were saved to DynamoDB
    object_saved.send(engine, engine=engine, obj=user)

    expected = {
        "ConditionExpression": "((#n0 = :v1) AND (#n2 = :v3) AND (#n4 = :v5))",
        "ExpressionAttributeValues": {
            ":v1": {
                "S": "foo"
            },
            ":v3": {
                "S": "*****@*****.**"
            },
            ":v5": {
                "S": user.id
            }
        },
        "ExpressionAttributeNames": {
            "#n0": "name",
            "#n2": "email",
            "#n4": "id"
        },
        "Key": {
            "id": {
                "S": user.id
            }
        },
        "TableName": "User",
        "ReturnValues": "NONE",
    }
    engine.delete(user, condition=User.name.is_("foo"), atomic=True)
    session.delete_item.assert_called_once_with(expected)
Example #8
0
def test_delete_atomic_new(engine, session):
    """atomic delete on new object should expect no columns to exist"""
    user = User(id="user_id")
    expected = {
        "TableName":
        "User",
        "ReturnValues":
        "NONE",
        "ExpressionAttributeNames": {
            "#n4": "id",
            "#n0": "age",
            "#n8": "name",
            "#n6": "j",
            "#n2": "email"
        },
        "Key": {
            "id": {
                "S": user.id
            }
        },
        "ConditionExpression":
        ("((attribute_not_exists(#n0)) AND (attribute_not_exists(#n2)) "
         "AND (attribute_not_exists(#n4)) AND (attribute_not_exists(#n6))"
         " AND (attribute_not_exists(#n8)))")
    }
    engine.delete(user, atomic=True)
    session.delete_item.assert_called_once_with(expected)
Example #9
0
def test_save_twice(engine, session):
    """Save sends full local values, not just deltas from last save"""
    user = User(id="user_id", age=5)
    expected = {
        "Key": {
            "id": {
                "S": user.id
            }
        },
        "TableName": "User",
        "ReturnValues": "NONE",
        "ExpressionAttributeNames": {
            "#n0": "age"
        },
        "ExpressionAttributeValues": {
            ":v1": {
                "N": "5"
            }
        },
        "UpdateExpression": "SET #n0=:v1",
    }
    engine.save(user)
    engine.save(user)

    session.save_item.assert_called_with(expected)
    assert session.save_item.call_count == 2
Example #10
0
def test_dump_key(engine):
    user = User(id="foo")
    user_key = {"id": {"S": "foo"}}
    assert dump_key(engine, user) == user_key

    obj = HashAndRange(foo=4, bar=5)
    obj_key = {"bar": {"N": "5"}, "foo": {"N": "4"}}
    assert dump_key(engine, obj) == obj_key
Example #11
0
def test_read_item(engine):
    engine.bind(User)
    user = User(id="numberoverzero")
    tx = ReadTransaction(engine)

    tx.load(user)
    p = tx.prepare()

    expected_items = [TxItem.new("get", user, None, False)]
    assert tx._items == expected_items
    assert p.items == expected_items
    assert p.first_commit_at is None
Example #12
0
def test_load_missing_attrs(engine, session):
    """When an instance of a Model is loaded into, existing attributes should be
    overwritten with new values, or if there is no new value, should be deleted
    """
    obj = User(id="user_id", age=4, name="user")

    response = {"User": [{"age": {"N": 5}, "id": {"S": obj.id}}]}

    session.load_items.return_value = response
    engine.load(obj)
    assert obj.age == 5
    assert obj.name == ""
Example #13
0
def test_load_equivalent_objects(engine, session):
    """Two objects with the same key are both loaded"""
    user = User(id="user_id")
    same_user = User(id=user.id)

    expected = {
        "User": {
            "Keys": [{
                "id": {
                    "S": user.id
                }
            }],
            "ConsistentRead": False
        }
    }
    response = {
        "User": [{
            "age": {
                "N": 5
            },
            "name": {
                "S": "foo"
            },
            "id": {
                "S": user.id
            }
        }]
    }

    def respond(RequestItems):
        assert ordered(RequestItems) == ordered(expected)
        return response

    session.load_items.side_effect = respond
    engine.load(user, same_user)

    assert user.age == 5
    assert user.name == "foo"
    assert same_user.age == 5
    assert same_user.name == "foo"
Example #14
0
def test_missing_objects(engine, session, caplog):
    """When objects aren't loaded, MissingObjects is raised with a list of missing objects"""
    # Patch batch_get_items to return no results
    session.load_items.return_value = {}

    users = [User(id=str(i)) for i in range(3)]

    with pytest.raises(MissingObjects) as excinfo:
        engine.load(*users)
    assert set(excinfo.value.objects) == set(users)

    assert caplog.record_tuples == [("bloop.engine", logging.INFO,
                                     "loaded 0 of 3 objects")]
Example #15
0
def test_delete_new(engine, session):
    """When an object is first created, a non-atomic delete shouldn't expect anything."""
    user = User(id="user_id")
    expected = {
        "TableName": "User",
        "ReturnValues": "NONE",
        "Key": {
            "id": {
                "S": user.id
            }
        }
    }
    engine.delete(user)
    session.delete_item.assert_called_once_with(expected)
Example #16
0
def test_dump_key(engine):
    class HashAndRange(BaseModel):
        foo = Column(Integer, hash_key=True)
        bar = Column(Integer, range_key=True)

    engine.bind(HashAndRange)

    user = User(id="foo")
    user_key = {"id": {"S": "foo"}}
    assert dump_key(engine, user) == user_key

    obj = HashAndRange(foo=4, bar=5)
    obj_key = {"bar": {"N": "5"}, "foo": {"N": "4"}}
    assert dump_key(engine, obj) == obj_key
Example #17
0
def test_load_missing_key(engine):
    """Trying to load objects with missing hash and range keys raises"""
    user = User(age=2)
    with pytest.raises(MissingKey):
        engine.load(user)

    complex_models = [
        ComplexModel(),
        ComplexModel(name=uuid.uuid4()),
        ComplexModel(date="no hash")
    ]
    for model in complex_models:
        with pytest.raises(MissingKey):
            engine.load(model)
Example #18
0
def test_save_atomic_condition(engine, session):
    user = User(id="user_id")
    # Tell the tracking system the user's id was saved to DynamoDB
    object_saved.send(engine, engine=engine, obj=user)
    # Mutate a field; part of the update but not an expected condition
    user.name = "new_foo"
    # Condition on the mutated field with a different value
    condition = User.name == "expect_foo"

    expected = {
        "ConditionExpression": "((#n0 = :v1) AND (#n2 = :v3))",
        "ExpressionAttributeNames": {
            "#n0": "name",
            "#n2": "id"
        },
        "ExpressionAttributeValues": {
            ":v1": {
                "S": "expect_foo"
            },
            ":v3": {
                "S": user.id
            },
            ":v4": {
                "S": "new_foo"
            }
        },
        "Key": {
            "id": {
                "S": user.id
            }
        },
        "TableName": "User",
        "ReturnValues": "NONE",
        "UpdateExpression": "SET #n0=:v4"
    }
    engine.save(user, condition=condition, atomic=True)
    session.save_item.assert_called_once_with(expected)
Example #19
0
def test_abstract_object_operations_raise(engine, op_name, plural):
    class Abstract(BaseModel):
        class Meta:
            abstract = True

    engine.bind(Abstract)

    abstract = Abstract(id=5)
    concrete = User(age=5)

    with pytest.raises(InvalidModel):
        operation = getattr(engine, op_name)
        operation(abstract)
    if plural:
        with pytest.raises(InvalidModel):
            operation = getattr(engine, op_name)
            operation(abstract, concrete)
Example #20
0
def test_save_single_with_condition(engine, session):
    user = User(id="user_id")
    condition = User.id.is_(None)
    expected = {
        "TableName": "User",
        "ReturnValues": "NONE",
        "ExpressionAttributeNames": {
            "#n0": "id"
        },
        "ConditionExpression": "(attribute_not_exists(#n0))",
        "Key": {
            "id": {
                "S": user.id
            }
        }
    }
    engine.save(user, condition=condition)
    session.save_item.assert_called_once_with(expected)
Example #21
0
def test_delete_sync(engine, session):
    """Engine.delete(sync='old') the previous values should be loaded and the object should be marked dirty"""

    session.delete_item.return_value = {
        "id": {
            "S": "user_id"
        },
        "age": {
            "N": "3"
        }
    }
    user = User(id="user_id", age=4)
    engine.delete(user, sync="old")
    assert user.age == 3
    assert global_tracking.get_snapshot(user) == (User.age.is_(None)
                                                  & User.email.is_(None)
                                                  & User.id.is_(None)
                                                  & User.joined.is_(None)
                                                  & User.name.is_(None))
Example #22
0
def test_save_sync(engine, session, sync):
    """Engine.save(sync='old'|'new') the returned values should be loaded and the object should not be marked dirty"""

    session.save_item.return_value = {
        "id": {
            "S": "user_id"
        },
        "age": {
            "N": "3"
        }
    }
    user = User(id="user_id", age=4)
    engine.save(user, sync=sync)
    assert user.age == 3

    assert global_tracking.get_snapshot(user) == (
        User.age.is_({"N": "3"}) & User.email.is_(None)
        & User.id.is_({"S": "user_id"}) & User.joined.is_(None)
        & User.name.is_(None))
Example #23
0
def test_delete_complex_item(engine):
    engine.bind(User)
    user = User(id="numberoverzero")
    tx = WriteTransaction(engine)

    condition = User.id.begins_with("foo")
    tx.delete(user, condition=condition, atomic=True)
    p = tx.prepare()

    expected_items = [TxItem.new("delete", user, condition, True)]
    assert tx._items == expected_items
    assert p.items == expected_items
    assert p.first_commit_at is None
    assert len(p._request) == 1
    entry = p._request[0]["Delete"]
    expected_fields = {
        "Key", "TableName", "ConditionExpression", "ExpressionAttributeNames",
        "ExpressionAttributeValues"
    }
    assert set(entry.keys()) == expected_fields
Example #24
0
def test_save_del_only(engine, session):
    user = User(id="user_id", age=4)

    # Expect a REMOVE on age
    del user.age

    expected = {
        "Key": {
            "id": {
                "S": user.id
            }
        },
        "ExpressionAttributeNames": {
            "#n0": "age"
        },
        "TableName": "User",
        "ReturnValues": "NONE",
        "UpdateExpression": "REMOVE #n0"
    }
    engine.save(user)
    session.save_item.assert_called_once_with(expected)
Example #25
0
def test_save_condition_key_only(engine, session):
    """Even when the diff is empty, an UpdateItem should be issued
    (in case this is really a create - the item doesn't exist yet)
    """
    user = User(id="user_id")
    condition = User.id.is_(None)
    expected = {
        "ConditionExpression": "(attribute_not_exists(#n0))",
        "TableName": "User",
        "ReturnValues": "NONE",
        "ExpressionAttributeNames": {
            "#n0": "id"
        },
        "Key": {
            "id": {
                "S": user.id
            }
        }
    }
    engine.save(user, condition=condition)
    session.save_item.assert_called_once_with(expected)
Example #26
0
def test_save_list_with_condition(engine, session, caplog):
    users = [User(id=str(i)) for i in range(3)]
    condition = User.id.is_(None)
    expected_calls = [{
        "ConditionExpression": "(attribute_not_exists(#n0))",
        "ExpressionAttributeNames": {
            "#n0": "id"
        },
        "Key": {
            "id": {
                "S": user.id
            }
        },
        "TableName": "User",
        "ReturnValues": "NONE",
    } for user in users]
    engine.save(*users, condition=condition)
    for expected in expected_calls:
        session.save_item.assert_any_call(expected)
    assert session.save_item.call_count == 3

    assert caplog.record_tuples[-1] == ("bloop.engine", logging.INFO,
                                        "successfully saved 3 objects")
Example #27
0
def test_load_object(engine, session):
    user_id = "user_id"
    expected = {
        "User": {
            "Keys": [{
                "id": {
                    "S": "user_id"
                }
            }],
            "ConsistentRead": True
        }
    }
    response = {
        "User": [{
            "age": {
                "N": 5
            },
            "name": {
                "S": "foo"
            },
            "id": {
                "S": "user_id"
            }
        }]
    }

    def respond(RequestItems):
        assert RequestItems == expected
        return response

    session.load_items.side_effect = respond
    user = User(id=user_id)
    engine.load(user, consistent=True)

    assert user.age == 5
    assert user.name == "foo"
    assert user.id == user_id
Example #28
0
def test_load_repeated_objects(engine, session):
    """The same object is only loaded once"""
    user = User(id="user_id")
    expected = {
        "User": {
            "Keys": [{
                "id": {
                    "S": user.id
                }
            }],
            "ConsistentRead": False
        }
    }
    response = {
        "User": [{
            "age": {
                "N": 5
            },
            "name": {
                "S": "foo"
            },
            "id": {
                "S": user.id
            }
        }],
    }

    def respond(RequestItems):
        assert ordered(RequestItems) == ordered(expected)
        return response

    session.load_items.side_effect = respond
    engine.load(user, user)

    assert user.age == 5
    assert user.name == "foo"
Example #29
0
def test_delete_unknown_sync(engine, sync):
    user = User(id="user_id", age=4)
    with pytest.raises(ValueError):
        engine.delete(user, sync=sync)