# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging, pytest from enum import Enum from common.orm.Exceptions import ConstraintException from common.orm.Database import Database from common.orm.Factory import get_database_backend from common.orm.backend.BackendEnum import BackendEnum from common.orm.backend._Backend import _Backend from common.orm.fields.BooleanField import BooleanField from common.orm.fields.EnumeratedField import EnumeratedField from common.orm.fields.FloatField import FloatField from common.orm.fields.ForeignKeyField import ForeignKeyField from common.orm.fields.IntegerField import IntegerField from common.orm.fields.PrimaryKeyField import PrimaryKeyField from common.orm.fields.StringField import StringField from common.orm.model.Model import DEFAULT_PRIMARY_KEY_NAME, Model logging.basicConfig(level=logging.INFO) LOGGER = logging.getLogger(__name__) def test_database_instantiation(): with pytest.raises(AttributeError) as e: Database(None) str_class_path = '{}.{}'.format(_Backend.__module__, _Backend.__name__) assert str(e.value) == 'backend must inherit from {}'.format(str_class_path) assert Database(get_database_backend(BackendEnum.INMEMORY)) is not None def test_model_without_attributes(): with pytest.raises(AttributeError) as e: Model(None, 'valid-uuid') str_class_path = '{}.{}'.format(Database.__module__, Database.__name__) assert str(e.value) == 'database must inherit from {}'.format(str_class_path) database = Database(get_database_backend(BackendEnum.INMEMORY)) with pytest.raises(ValueError) as e: Model(database, '') msg = '{:s}() is out of range: allow_empty(False).' assert str(e.value) == msg.format(DEFAULT_PRIMARY_KEY_NAME) with pytest.raises(TypeError) as e: Model(database, 23) msg = '{:s}(23) is of a wrong type(int). Accepted type_or_types().' assert str(e.value) == msg.format(DEFAULT_PRIMARY_KEY_NAME) with pytest.raises(TypeError) as e: Model(database, 23.5) msg = '{:s}(23.5) is of a wrong type(float). Accepted type_or_types().' assert str(e.value) == msg.format(DEFAULT_PRIMARY_KEY_NAME) with pytest.raises(TypeError) as e: Model(database, True) msg = '{:s}(True) is of a wrong type(bool). Accepted type_or_types().' assert str(e.value) == msg.format(DEFAULT_PRIMARY_KEY_NAME) with pytest.raises(TypeError) as e: Model(database, ['a']) msg = '{:s}([\'a\']) is of a wrong type(list). Accepted type_or_types().' assert str(e.value) == msg.format(DEFAULT_PRIMARY_KEY_NAME) Model(database, 'valid-primary-key') def test_model_with_primarykey(): database = Database(get_database_backend(BackendEnum.INMEMORY)) with pytest.raises(AttributeError) as e: class WrongTestModel(Model): # pylint: disable=unused-variable pk = PrimaryKeyField() name = StringField(min_length=1) age = IntegerField(min_value=0) salary = FloatField(min_value=0.0) active = BooleanField() pk2 = PrimaryKeyField() assert str(e.value) == 'PrimaryKeyField for Model(WrongTestModel) already set to attribute(pk)' class GenderEnum(Enum): FEMALE = 'female' MALE = 'male' class TestModel(Model): pk = PrimaryKeyField() name = StringField(min_length=1) age = IntegerField(min_value=0) salary = FloatField(min_value=0.0) active = BooleanField() gender = EnumeratedField(GenderEnum) backend_key_instances = TestModel.get_backend_key_instances() backend_key_instance = TestModel.get_backend_key_instance('pk') backend_key_references = TestModel.get_backend_key_references('pk') assert backend_key_instances == 'TestModel/instances' assert backend_key_instance == 'TestModel[pk]' assert backend_key_references == 'TestModel[pk]/references' assert TestModel.get_backend_key_lock(backend_key_instances ) == 'TestModel/instances/lock' assert TestModel.get_backend_key_lock(backend_key_instance ) == 'TestModel[pk]/lock' assert TestModel.get_backend_key_lock(backend_key_references) == 'TestModel[pk]/references/lock' with pytest.raises(ValueError) as e: TestModel(database, None) assert str(e.value) == 'pk(None) is required. It cannot be None.' with pytest.raises(ValueError) as e: TestModel(database, '') assert str(e.value) == 'pk() is out of range: allow_empty(False).' obj = TestModel(database, 'valid-pk') assert obj is not None with pytest.raises(ValueError) as e: obj.pk = 'another-valid-pk' assert str(e.value) == 'PrimaryKeyField cannot be modified' def test_model_with_primarykey_and_attributes(): database = Database(get_database_backend(BackendEnum.INMEMORY)) class GenderEnum(Enum): FEMALE = 'female' MALE = 'male' with pytest.raises(AttributeError) as e: class BadTestModel(Model): pk_auto = StringField() # field using default name of primary key name = StringField(min_length=5, max_length=10) age = IntegerField(min_value=0) salary = FloatField(min_value=0.0) active = BooleanField() gender = EnumeratedField(GenderEnum) msg = 'PrimaryKeyField for Model(BadTestModel) not defined and attribute "pk_auto" already used. '\ 'Leave attribute name "pk_auto" for automatic PrimaryKeyField, or set a PrimaryKeyField.' assert str(e.value) == msg class TestModel(Model): pk = PrimaryKeyField() name = StringField(min_length=5, max_length=10) age = IntegerField(min_value=0) salary = FloatField(min_value=0.0) active = BooleanField() gender = EnumeratedField(GenderEnum) obj = TestModel(database, 'valid-pk') assert obj is not None with pytest.raises(AttributeError) as e: del obj.name assert str(e.value) == 'Attribute "name" cannot be deleted' with pytest.raises(TypeError) as e: obj.name = 55 assert str(e.value) == "name(55) is of a wrong type(int). Accepted type_or_types()." with pytest.raises(TypeError) as e: obj.name = 55.5 assert str(e.value) == "name(55.5) is of a wrong type(float). Accepted type_or_types()." with pytest.raises(TypeError) as e: obj.name = True assert str(e.value) == "name(True) is of a wrong type(bool). Accepted type_or_types()." with pytest.raises(TypeError) as e: obj.age = 'too old' assert str(e.value) == "age(too old) is of a wrong type(str). Accepted type_or_types()." with pytest.raises(TypeError) as e: obj.age = 37.5 assert str(e.value) == "age(37.5) is of a wrong type(float). Accepted type_or_types()." with pytest.raises(TypeError) as e: obj.salary = 'too high' msg = "salary(too high) is of a wrong type(str). Accepted type_or_types((, ))." assert str(e.value) == msg with pytest.raises(TypeError) as e: obj.active = 'active' assert str(e.value) == "active(active) is of a wrong type(str). Accepted type_or_types()." with pytest.raises(TypeError) as e: obj.active = 27 assert str(e.value) == "active(27) is of a wrong type(int). Accepted type_or_types()." with pytest.raises(TypeError) as e: obj.active = 92.5 assert str(e.value) == "active(92.5) is of a wrong type(float). Accepted type_or_types()." with pytest.raises(ValueError) as e: obj.name = '' assert str(e.value) == 'name() is out of range: allow_empty(False).' with pytest.raises(ValueError) as e: obj.name = 'John' assert str(e.value) == 'name(John) is out of range: min_length(5).' with pytest.raises(ValueError) as e: obj.name = 'John Smith Willson' assert str(e.value) == 'name(John Smith Willson) is out of range: max_value(10).' with pytest.raises(TypeError) as e: obj.gender = 51 assert str(e.value) == "gender(51) is of a wrong type(int). Accepted type_or_types()." with pytest.raises(TypeError) as e: obj.gender = 55.5 assert str(e.value) == "gender(55.5) is of a wrong type(float). Accepted type_or_types()." with pytest.raises(TypeError) as e: obj.gender = False assert str(e.value) == "gender(False) is of a wrong type(bool). Accepted type_or_types()." with pytest.raises(TypeError) as e: obj.gender = 'male' assert str(e.value) == "gender(male) is of a wrong type(str). Accepted type_or_types()." obj.name = 'John Smith' obj.age = 37 obj.salary = 5023.52 obj.active = True obj.gender = GenderEnum.MALE assert repr(obj) == "TestModel(pk='valid-pk'(PK), name='John Smith', age=37, salary=5023.52, active=True, "\ "gender=)" def test_model_database_operations(): database = Database(get_database_backend(BackendEnum.INMEMORY)) class GenderEnum(Enum): FEMALE = 'female' MALE = 'male' class TestModel(Model): pk = PrimaryKeyField() name = StringField(min_length=5, max_length=30) age = IntegerField(min_value=0, required=True) salary = FloatField(min_value=0.0) active = BooleanField() gender = EnumeratedField(GenderEnum) obj_john = TestModel(database, 'john') assert obj_john is not None obj_john.name = 'John Smith' obj_john.salary = 5023.52 obj_john.active = True assert repr(obj_john) == "TestModel(pk='john'(PK), name='John Smith', age=None, salary=5023.52, active=True, "\ "gender=None)" with pytest.raises(ValueError) as e: obj_john.save() assert str(e.value) == 'age(None) is required. It cannot be None.' obj_john.age = 37 assert repr(obj_john) == "TestModel(pk='john'(PK), name='John Smith', age=37, salary=5023.52, active=True, "\ "gender=None)" with pytest.raises(ValueError) as e: obj_john.save() assert str(e.value) == 'gender(None) is required. It cannot be None.' obj_john.gender = GenderEnum.MALE obj_john.save() db_entries = database.dump() assert len(db_entries) == 2 assert db_entries[0] == ( 'set', 'TestModel/instances', "{'TestModel[john]'}") assert db_entries[1] == ( 'dict', 'TestModel[john]', "{'active': 'True', 'age': '37', 'gender': 'MALE', 'name': 'John Smith', 'pk': 'john', "\ "'salary': '5023.52'}") obj_john2 = TestModel(database, 'john', auto_load=False) assert obj_john2 is not None assert repr(obj_john2) == "TestModel(pk='john'(PK), name=None, age=None, salary=None, active=None, gender=None)" obj_john2.load() assert repr(obj_john2) == "TestModel(pk='john'(PK), name='John Smith', age=37, salary=5023.52, active=True, "\ "gender=)" obj_john2 = TestModel(database, 'john', auto_load=True) assert obj_john2 is not None assert repr(obj_john2) == "TestModel(pk='john'(PK), name='John Smith', age=37, salary=5023.52, active=True, "\ "gender=)" obj_john2.delete() assert len(database.dump()) == 0 obj_john2.save() db_entries = database.dump() LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) for db_entry in db_entries: LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 2 assert db_entries[0] == ( 'set', 'TestModel/instances', "{'TestModel[john]'}") assert db_entries[1] == ( 'dict', 'TestModel[john]', "{'active': 'True', 'age': '37', 'gender': 'MALE', 'name': 'John Smith', 'pk': 'john', "\ "'salary': '5023.52'}") obj_jane = TestModel(database, 'jane', auto_load=True) obj_jane.name = 'Jane Willson' obj_jane.age = 26 obj_jane.salary = 6071.72 obj_jane.active = True obj_jane.gender = GenderEnum.FEMALE assert repr(obj_jane) == "TestModel(pk='jane'(PK), name='Jane Willson', age=26, salary=6071.72, active=True, "\ "gender=)" obj_jane.save() obj_julia = TestModel(database, 'julia', auto_load=True) obj_julia.name = 'Julia Simons' obj_julia.age = 42 obj_julia.salary = 5451.13 obj_julia.active = True obj_julia.gender = GenderEnum.FEMALE assert repr(obj_julia) == "TestModel(pk='julia'(PK), name='Julia Simons', age=42, salary=5451.13, active=True, "\ "gender=)" obj_julia.save() db_entries = database.dump() LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) for db_entry in db_entries: LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) LOGGER.info('-----------------------------------------------------------') test_model_pks = sorted(TestModel.get_primary_keys(database)) assert len(test_model_pks) == 3 assert test_model_pks[0] == 'TestModel[jane]' assert test_model_pks[1] == 'TestModel[john]' assert test_model_pks[2] == 'TestModel[julia]' database.clear_all() assert len(database.dump()) == 0 def test_model_foreignkeys(): database = Database(get_database_backend(BackendEnum.INMEMORY)) class GenderEnum(Enum): FEMALE = 'female' MALE = 'male' class Team(Model): pk = PrimaryKeyField() name = StringField(max_length=10, required=True) class Workplace(Model): pk = PrimaryKeyField() name = StringField(max_length=10, required=True) class Member(Model): pk = PrimaryKeyField() team = ForeignKeyField(Team) place = ForeignKeyField(Workplace, required=False) name = StringField(max_length=10, required=True) gender = EnumeratedField(GenderEnum) team_dev_ops = Team(database, 'dev-ops') team_dev_ops.name = 'Dev Ops' assert team_dev_ops is not None assert repr(team_dev_ops) == "Team(pk='dev-ops'(PK), name='Dev Ops')" workplace_bcn = Workplace(database, 'bcn') workplace_bcn.name = 'Barcelona' assert workplace_bcn is not None assert repr(workplace_bcn) == "Workplace(pk='bcn'(PK), name='Barcelona')" member_john = Member(database, 'john') member_john.name = 'John' member_john.team = team_dev_ops member_john.place = workplace_bcn assert member_john is not None assert repr(member_john) == "Member(pk='john'(PK), team='Team[dev-ops]', place='Workplace[bcn]', name='John', "\ "gender=None)" with pytest.raises(ValueError) as e: member_john.save() assert str(e.value) == 'gender(None) is required. It cannot be None.' member_john.gender = GenderEnum.MALE with pytest.raises(ConstraintException) as e: member_john.save() assert str(e.value) == 'Required Keys (Team[dev-ops], Workplace[bcn]) does not exist' workplace_bcn.save() assert repr(Workplace(database, workplace_bcn.pk)) == "Workplace(pk='bcn'(PK), name='Barcelona')" with pytest.raises(ConstraintException) as e: member_john.save() assert str(e.value) == 'Required Keys (Team[dev-ops]) does not exist' team_dev_ops.save() assert repr(Team(database, team_dev_ops.pk)) == "Team(pk='dev-ops'(PK), name='Dev Ops')" member_john.save() assert repr(Member(database, member_john.pk)) == \ "Member(pk='john'(PK), team='Team[dev-ops]', place='Workplace[bcn]', name='John', "\ "gender=)" with pytest.raises(ConstraintException) as e: workplace_bcn.delete() assert str(e.value) == 'Instance is used by Keys (Member[john]:place)' with pytest.raises(ConstraintException) as e: team_dev_ops.delete() assert str(e.value) == 'Instance is used by Keys (Member[john]:team)' workplace_mad = Workplace(database, 'mad') workplace_mad.name = 'Madrid' assert workplace_mad is not None assert repr(workplace_mad) == "Workplace(pk='mad'(PK), name='Madrid')" member_john = Member(database, 'john') member_john.name = 'John' member_john.place = workplace_mad assert member_john is not None assert repr(member_john) == \ "Member(pk='john'(PK), team='Team[dev-ops]', place='Workplace[mad]', name='John', "\ "gender=)" with pytest.raises(ConstraintException) as e: member_john.save() assert str(e.value) == 'Required Keys (Workplace[mad]) does not exist' workplace_mad.save() assert repr(Workplace(database, workplace_mad.pk)) == "Workplace(pk='mad'(PK), name='Madrid')" member_john.save() member_john = Member(database, 'john') with pytest.raises(ValueError) as e: del member_john.place del member_john.team assert str(e.value) == 'team(None) is required. It cannot be None.' member_jane = Member(database, 'jane') member_jane.name = 'Jane' member_jane.place = workplace_mad assert member_jane is not None assert repr(member_jane) == "Member(pk='jane'(PK), team=None, place='Workplace[mad]', name='Jane', gender=None)" with pytest.raises(ValueError) as e: member_jane.save() assert str(e.value) == 'team(None) is required. It cannot be None.' member_jane.team = team_dev_ops with pytest.raises(ValueError) as e: member_jane.save() assert str(e.value) == 'gender(None) is required. It cannot be None.' member_jane.gender = GenderEnum.FEMALE member_jane.save() assert repr(Member(database, member_jane.pk)) == \ "Member(pk='jane'(PK), team='Team[dev-ops]', place='Workplace[mad]', name='Jane', "\ "gender=)" member_brad = Member(database, 'brad') assert member_brad is not None assert repr(member_brad) == "Member(pk='brad'(PK), team=None, place=None, name=None, gender=None)" with pytest.raises(ValueError) as e: member_brad.save() assert str(e.value) == 'team(None) is required. It cannot be None.' member_brad.team = team_dev_ops with pytest.raises(ValueError) as e: member_brad.save() assert str(e.value) == 'name(None) is required. It cannot be None.' member_brad.name = 'Brad' assert repr(member_brad) == "Member(pk='brad'(PK), team=\'Team[dev-ops]\', place=None, name='Brad', gender=None)" with pytest.raises(ValueError) as e: member_brad.save() assert str(e.value) == 'gender(None) is required. It cannot be None.' member_brad.gender = GenderEnum.MALE member_brad.save() assert repr(Member(database, member_brad.pk)) == \ "Member(pk='brad'(PK), team='Team[dev-ops]', place=None, name='Brad', gender=)" team_admin = Team(database, 'admin') team_admin.name = 'Admin' team_admin.save() assert repr(Team(database, team_admin.pk)) == "Team(pk='admin'(PK), name='Admin')" member_brad = Member(database, member_brad.pk) assert repr(member_brad) == \ "Member(pk='brad'(PK), team='Team[dev-ops]', place=None, name='Brad', gender=)" member_brad.team = team_admin assert repr(member_brad) == \ "Member(pk='brad'(PK), team='Team[admin]', place=None, name='Brad', gender=)" member_brad.save() assert repr(Member(database, member_brad.pk)) == \ "Member(pk='brad'(PK), team='Team[admin]', place=None, name='Brad', gender=)" references = sorted(team_dev_ops.references()) assert len(references) == 2 assert references[0] == ('Member[jane]', 'team') assert references[1] == ('Member[john]', 'team') references = sorted(workplace_bcn.references()) assert len(references) == 0 references = sorted(workplace_mad.references()) assert len(references) == 2 assert references[0] == ('Member[jane]', 'place') assert references[1] == ('Member[john]', 'place') references = sorted(workplace_mad.references('Member')) assert len(references) == 2 assert references[0] == ('Member[jane]', 'place') assert references[1] == ('Member[john]', 'place') references = sorted(workplace_mad.references({'Member'})) assert len(references) == 2 assert references[0] == ('Member[jane]', 'place') assert references[1] == ('Member[john]', 'place') references = sorted(workplace_mad.references(['Member'])) assert len(references) == 2 assert references[0] == ('Member[jane]', 'place') assert references[1] == ('Member[john]', 'place') references = sorted(workplace_mad.references(('Member',))) assert len(references) == 2 assert references[0] == ('Member[jane]', 'place') assert references[1] == ('Member[john]', 'place') references = sorted(workplace_mad.references(Member)) assert len(references) == 2 assert references[0] == ('Member[jane]', 'place') assert references[1] == ('Member[john]', 'place') references = sorted(workplace_mad.references({Member})) assert len(references) == 2 assert references[0] == ('Member[jane]', 'place') assert references[1] == ('Member[john]', 'place') references = sorted(workplace_mad.references([Member])) assert len(references) == 2 assert references[0] == ('Member[jane]', 'place') assert references[1] == ('Member[john]', 'place') references = sorted(workplace_mad.references((Member,))) assert len(references) == 2 assert references[0] == ('Member[jane]', 'place') assert references[1] == ('Member[john]', 'place') references = sorted(workplace_mad.references({'non-existing-model'})) assert len(references) == 0 with pytest.raises(AttributeError) as e: references = sorted(workplace_mad.references(7)) assert str(e.value) == 'filter_by_models(7) unsupported. Expected a type or a list/set of types. '\ 'Optionally, keep it as None to retrieve all the references pointing to this instance.' with pytest.raises(AttributeError) as e: references = sorted(workplace_mad.references({7})) assert str(e.value) == 'filter_by_models({7}) unsupported. Expected a type or a list/set of types. '\ 'Optionally, keep it as None to retrieve all the references pointing to this instance.' db_entries = database.dump() LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) for db_entry in db_entries: LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 13 assert db_entries[ 0] == ('set', "Member/instances", "{'Member[brad]', 'Member[jane]', 'Member[john]'}") assert db_entries[ 1] == ('dict', "Member[brad]", "{'gender': 'MALE', 'name': 'Brad', 'pk': 'brad', 'team': 'Team[admin]'}") assert db_entries[ 2] == ('dict', "Member[jane]", "{'gender': 'FEMALE', 'name': 'Jane', 'pk': 'jane', 'place': 'Workplace[mad]', "\ "'team': 'Team[dev-ops]'}") assert db_entries[ 3] == ('dict', "Member[john]", "{'gender': 'MALE', 'name': 'John', 'pk': 'john', 'place': 'Workplace[mad]', "\ "'team': 'Team[dev-ops]'}") assert db_entries[ 4] == ('set', "Team/instances", "{'Team[admin]', 'Team[dev-ops]'}") assert db_entries[ 5] == ('dict', "Team[admin]", "{'name': 'Admin', 'pk': 'admin'}") assert db_entries[ 6] == ('set' , "Team[admin]/references", "{'Member[brad]:team'}") assert db_entries[ 7] == ('dict', "Team[dev-ops]", "{'name': 'Dev Ops', 'pk': 'dev-ops'}") assert db_entries[ 8] == ('set' , "Team[dev-ops]/references", "{'Member[jane]:team', 'Member[john]:team'}") assert db_entries[ 9] == ('set', "Workplace/instances", "{'Workplace[bcn]', 'Workplace[mad]'}") assert db_entries[10] == ('dict', "Workplace[bcn]", "{'name': 'Barcelona', 'pk': 'bcn'}") assert db_entries[11] == ('dict', "Workplace[mad]", "{'name': 'Madrid', 'pk': 'mad'}") assert db_entries[12] == ('set' , "Workplace[mad]/references", "{'Member[jane]:place', 'Member[john]:place'}") Member(database, member_john.pk).delete() db_entries = database.dump() LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) for db_entry in db_entries: LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 12 assert db_entries[ 0] == ('set', "Member/instances", "{'Member[brad]', 'Member[jane]'}") assert db_entries[ 1] == ('dict', 'Member[brad]', "{'gender': 'MALE', 'name': 'Brad', 'pk': 'brad', 'team': 'Team[admin]'}") assert db_entries[ 2] == ('dict', 'Member[jane]', "{'gender': 'FEMALE', 'name': 'Jane', 'pk': 'jane', 'place': 'Workplace[mad]', "\ "'team': 'Team[dev-ops]'}") assert db_entries[ 3] == ('set', "Team/instances", "{'Team[admin]', 'Team[dev-ops]'}") assert db_entries[ 4] == ('dict', 'Team[admin]', "{'name': 'Admin', 'pk': 'admin'}") assert db_entries[ 5] == ('set', 'Team[admin]/references', "{'Member[brad]:team'}") assert db_entries[ 6] == ('dict', 'Team[dev-ops]', "{'name': 'Dev Ops', 'pk': 'dev-ops'}") assert db_entries[ 7] == ('set', 'Team[dev-ops]/references', "{'Member[jane]:team'}") assert db_entries[ 8] == ('set', "Workplace/instances", "{'Workplace[bcn]', 'Workplace[mad]'}") assert db_entries[ 9] == ('dict', 'Workplace[bcn]', "{'name': 'Barcelona', 'pk': 'bcn'}") assert db_entries[10] == ('dict', 'Workplace[mad]', "{'name': 'Madrid', 'pk': 'mad'}") assert db_entries[11] == ('set', 'Workplace[mad]/references', "{'Member[jane]:place'}")