120 lines
3.8 KiB
Python
120 lines
3.8 KiB
Python
from contextlib import contextmanager
|
|
from types import SimpleNamespace
|
|
|
|
from dbengine.dbengine import DbEngine
|
|
|
|
from myfasthtml.core.instances import SingleInstance, BaseInstance
|
|
from myfasthtml.core.utils import retrieve_user_info
|
|
|
|
|
|
class DbManager(SingleInstance):
|
|
def __init__(self, parent, root=".myFastHtmlDb", auto_register: bool = True):
|
|
super().__init__(parent, auto_register=auto_register)
|
|
|
|
self.db = DbEngine(root=root)
|
|
|
|
def save(self, entry, obj):
|
|
self.db.save(self.get_tenant(), self.get_user(), entry, obj)
|
|
|
|
def load(self, entry):
|
|
return self.db.load(self.get_tenant(), entry)
|
|
|
|
def exists_entry(self, entry):
|
|
return self.db.exists(self.get_tenant(), entry)
|
|
|
|
def get_tenant(self):
|
|
return retrieve_user_info(self._session)["id"]
|
|
|
|
def get_user(self):
|
|
return retrieve_user_info(self._session)["email"]
|
|
|
|
|
|
class DbObject:
|
|
"""
|
|
When you set the attribute, it persists in DB
|
|
It loads from DB at startup
|
|
"""
|
|
_initializing = False
|
|
_forbidden_attrs = {"_initializing", "_db_manager", "_name", "_owner", "_forbidden_attrs"}
|
|
|
|
def __init__(self, owner: BaseInstance, name=None, db_manager=None):
|
|
self._owner = owner
|
|
self._name = name or self.__class__.__name__
|
|
self._db_manager = db_manager or DbManager(self._owner)
|
|
|
|
self._finalize_initialization()
|
|
|
|
@contextmanager
|
|
def initializing(self):
|
|
old_state = getattr(self, "_initializing", False)
|
|
self._initializing = True
|
|
try:
|
|
yield
|
|
finally:
|
|
self._finalize_initialization()
|
|
self._initializing = old_state
|
|
|
|
def __setattr__(self, name: str, value: str):
|
|
if name.startswith("_") or name.startswith("ns") or getattr(self, "_initializing", False):
|
|
super().__setattr__(name, value)
|
|
return
|
|
|
|
old_value = getattr(self, name, None)
|
|
if old_value == value:
|
|
return
|
|
|
|
super().__setattr__(name, value)
|
|
self._save_self()
|
|
|
|
def _finalize_initialization(self):
|
|
if self._db_manager.exists_entry(self._name):
|
|
props = self._db_manager.load(self._name)
|
|
self.update(props)
|
|
else:
|
|
self._save_self()
|
|
|
|
def _save_self(self):
|
|
props = {k: getattr(self, k) for k, v in self._get_properties().items() if
|
|
not k.startswith("_") and not k.startswith("ns")}
|
|
if props:
|
|
self._db_manager.save(self._name, props)
|
|
|
|
def _get_properties(self):
|
|
"""
|
|
Retrieves all the properties of the current object, combining both the properties defined in
|
|
the class and the instance attributes.
|
|
:return: A dictionary containing the properties of the object, where keys are property names
|
|
and values are their corresponding values.
|
|
"""
|
|
props = {k: getattr(self, k) for k, v in self.__class__.__dict__.items()} # for dataclass
|
|
props |= {k: getattr(self, k) for k, v in self.__dict__.items()} # for dataclass
|
|
props = {k: v for k, v in props.items() if not k.startswith("__")}
|
|
return props
|
|
|
|
def update(self, *args, **kwargs):
|
|
if len(args) > 1:
|
|
raise ValueError("Only one argument is allowed")
|
|
|
|
properties = {}
|
|
if args:
|
|
arg = args[0]
|
|
if not isinstance(arg, (dict, SimpleNamespace)):
|
|
raise ValueError("Only dict or Expando are allowed as argument")
|
|
properties |= vars(arg) if isinstance(arg, SimpleNamespace) else arg
|
|
|
|
properties |= kwargs
|
|
|
|
# save the new state
|
|
old_state = getattr(self, "_initializing", False)
|
|
self._initializing = True
|
|
for k, v in properties.items():
|
|
if hasattr(self, k) and k not in DbObject._forbidden_attrs: # internal variables cannot be updated
|
|
setattr(self, k, v)
|
|
self._save_self()
|
|
self._initializing = old_state
|
|
|
|
def copy(self):
|
|
as_dict = self._get_properties().copy()
|
|
as_dict = {k: v for k, v in as_dict.items() if k not in DbObject._forbidden_attrs}
|
|
return SimpleNamespace(**as_dict)
|