import shutil from dataclasses import dataclass import pytest from myfasthtml.core.dbmanager import DbManager, DbObject from myfasthtml.core.instances import SingleInstance, BaseInstance @pytest.fixture(scope="session") def session(): return { "user_info": { "id": "test_tenant_id", "email": "test@email.com", "username": "test user", "role": [], } } @pytest.fixture def parent(session): return SingleInstance(session=session, _id="test_parent_id") @pytest.fixture def db_manager(parent): shutil.rmtree("TestDb", ignore_errors=True) db_manager_instance = DbManager(parent, root="TestDb", auto_register=False) yield db_manager_instance shutil.rmtree("TestDb", ignore_errors=True) def simplify(res: dict) -> dict: return {k: v for k, v in res.items() if not k.startswith("_")} def test_i_can_init(parent, db_manager): class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) with self.initializing(): self.value: str = "hello" self.number: int = 42 self.none_value: None = None dummy = DummyObject(parent) props = dummy._get_properties() in_db = db_manager.load("DummyObject") history = db_manager.db.history(db_manager.get_tenant(), "DummyObject") assert simplify(in_db) == {"value": "hello", "number": 42, "none_value": None} assert len(history) == 1 def test_i_can_init_from_dataclass(parent, db_manager): @dataclass class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) value: str = "hello" number: int = 42 none_value: None = None DummyObject(parent) in_db = db_manager.load("DummyObject") history = db_manager.db.history(db_manager.get_tenant(), "DummyObject") assert simplify(in_db) == {"value": "hello", "number": 42, "none_value": None} assert len(history) == 1 def test_i_can_init_from_db_with(parent, db_manager): class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) with self.initializing(): self.value: str = "hello" self.number: int = 42 # insert other values in db db_manager.save("DummyObject", {"value": "other_value", "number": 34}) dummy = DummyObject(parent) assert dummy.value == "other_value" assert dummy.number == 34 def test_i_can_init_from_db_with_dataclass(parent, db_manager): @dataclass class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) value: str = "hello" number: int = 42 # insert other values in db db_manager.save("DummyObject", {"value": "other_value", "number": 34}) dummy = DummyObject(parent) assert dummy.value == "other_value" assert dummy.number == 34 def test_i_do_not_save_when_prefixed_by_underscore_or_ns(parent, db_manager): class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) with self.initializing(): self.to_save: str = "value" self._not_to_save: str = "value" self.ns_not_to_save: str = "value" to_save: str = "value" _not_to_save: str = "value" ns_not_to_save: str = "value" dummy = DummyObject(parent) dummy.to_save = "other_value" dummy.ns_not_to_save = "other_value" dummy._not_to_save = "other_value" in_db = db_manager.load("DummyObject") assert in_db["to_save"] == "other_value" assert "_not_to_save" not in in_db assert "ns_not_to_save" not in in_db def test_i_do_not_save_when_prefixed_by_underscore_or_ns_with_dataclass(parent, db_manager): @dataclass class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) to_save: str = "value" _not_to_save: str = "value" ns_not_to_save: str = "value" dummy = DummyObject(parent) dummy.to_save = "other_value" dummy.ns_not_to_save = "other_value" dummy._not_to_save = "other_value" in_db = db_manager.load("DummyObject") assert in_db["to_save"] == "other_value" assert "_not_to_save" not in in_db assert "ns_not_to_save" not in in_db def test_db_is_updated_when_attribute_is_modified(parent, db_manager): @dataclass class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) value: str = "hello" number: int = 42 dummy = DummyObject(parent) dummy.value = "other_value" assert simplify(db_manager.load("DummyObject")) == {"value": "other_value", "number": 42} def test_i_do_not_save_in_db_when_value_is_the_same(parent, db_manager): @dataclass class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) value: str = "hello" number: int = 42 dummy = DummyObject(parent) dummy.value = "other_value" in_db_1 = db_manager.load("DummyObject") dummy.value = "other_value" in_db_2 = db_manager.load("DummyObject") assert in_db_1["__parent__"] == in_db_2["__parent__"] def test_i_can_update(parent, db_manager): @dataclass class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) value: str = "hello" number: int = 42 dummy = DummyObject(parent) clone = dummy.copy() clone.number = 34 clone.value = "other_value" clone.other_attr = "some_value" dummy.update(clone) assert simplify(db_manager.load("DummyObject")) == {"value": "other_value", "number": 34} def test_forbidden_attributes_are_not_the_copy(parent, db_manager): class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) with self.initializing(): self.value: str = "hello" self.number: int = 42 self.none_value: None = None dummy = DummyObject(parent) clone = dummy.copy() for k in DbObject._forbidden_attrs: assert not hasattr(clone, k), f"Clone should not have forbidden attribute '{k}'" def test_forbidden_attributes_are_not_the_copy_for_dataclass(parent, db_manager): @dataclass class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) value: str = "hello" number: int = 42 none_value: None = None dummy = DummyObject(parent) clone = dummy.copy() for k in DbObject._forbidden_attrs: assert not hasattr(clone, k), f"Clone should not have forbidden attribute '{k}'" def test_i_cannot_update_a_forbidden_attribute(parent, db_manager): @dataclass class DummyObject(DbObject): def __init__(self, owner: BaseInstance): super().__init__(owner, "DummyObject", db_manager) value: str = "hello" number: int = 42 none_value: None = None dummy = DummyObject(parent) dummy.update(_owner="other_value") assert dummy._owner is parent