I can add tables
Refactoring DbEngine Fixing unit tests Fixing unit tests Fixing unit tests Refactored DbManager for datagrid Improving front end performance I can add new table Fixed sidebar closing when clicking on it Fix drag event rebinding, improve listener options, and add debug Prevent duplicate drag event bindings with a dataset flag and ensure consistent scrollbar functionality. Change wheel event listener to passive mode for better performance. Refactor function naming for consistency, and add debug logs for event handling. Refactor Datagrid bindings and default state handling. Updated Javascript to conditionally rebind Datagrid on specific events. Improved Python components by handling empty DataFrame cases and removing redundant code. Revised default state initialization in settings for better handling of mutable fields. Added Rowindex visualisation support Working on Debugger with own implementation of JsonViewer Working on JsonViewer.py Fixed unit tests Adding unit tests I can fold and unfold fixed unit tests Adding css for debugger Added tooltip management Adding debugger functionalities Refactor serializers and improve error handling in DB engine Fixed error where tables were overwritten I can display footer menu Working on footer. Refactoring how heights are managed Refactored scrollbars management Working on footer menu I can display footer menu + fixed unit tests Fixed unit tests Updated click management I can display aggregations in footers Added docker management Refactor input handling and improve config defaults Fixed scrollbars colors Refactored tooltip management Improved tooltip management Improving FilterAll
This commit is contained in:
@@ -69,12 +69,10 @@ class DbEngine:
|
||||
Designed to keep history of the modifications
|
||||
"""
|
||||
ObjectsFolder = "objects" # group objects in the same folder
|
||||
HeadFile = "head" # used to keep track the latest version of all entries
|
||||
HeadFile = "head" # used to keep track of the latest version of all entries
|
||||
|
||||
def __init__(self, root: str = None):
|
||||
self.root = root or ".mytools_db"
|
||||
self.serializer = Serializer(RefHelper(self._get_ref_path))
|
||||
self.debug_serializer = DebugSerializer(RefHelper(self._get_ref_path))
|
||||
self.lock = RLock()
|
||||
|
||||
def is_initialized(self, user_id: str):
|
||||
@@ -196,7 +194,7 @@ class DbEngine:
|
||||
self.save(user_id, user_email, entry, entry_content)
|
||||
return True
|
||||
|
||||
def put_many(self, user_id: str, user_email, entry, items: list):
|
||||
def put_many(self, user_id: str, user_email, entry, items: list | dict):
|
||||
"""
|
||||
Save a list of item as one single snapshot
|
||||
A new snapshot will not be created if all the items already exist
|
||||
@@ -216,13 +214,24 @@ class DbEngine:
|
||||
entry_content = {}
|
||||
|
||||
is_dirty = False
|
||||
for item in items:
|
||||
key = item.get_key()
|
||||
if key in entry_content and entry_content[key] == item:
|
||||
continue
|
||||
else:
|
||||
entry_content[key] = item
|
||||
is_dirty = True
|
||||
|
||||
if isinstance(items, dict):
|
||||
for key, item in items.items():
|
||||
if key in entry_content and entry_content[key] == item:
|
||||
continue
|
||||
else:
|
||||
entry_content[key] = item
|
||||
is_dirty = True
|
||||
|
||||
else:
|
||||
|
||||
for item in items:
|
||||
key = item.get_key()
|
||||
if key in entry_content and entry_content[key] == item:
|
||||
continue
|
||||
else:
|
||||
entry_content[key] = item
|
||||
is_dirty = True
|
||||
|
||||
if is_dirty:
|
||||
self.save(user_id, user_email, entry, entry_content)
|
||||
@@ -257,7 +266,10 @@ class DbEngine:
|
||||
# return all items as list
|
||||
return [v for k, v in entry_content.items() if not k.startswith("__")]
|
||||
|
||||
return entry_content[key]
|
||||
try:
|
||||
return entry_content[key]
|
||||
except KeyError:
|
||||
raise DbException(f"Key '{key}' not found in entry '{entry}'")
|
||||
|
||||
def debug_root(self):
|
||||
"""
|
||||
@@ -287,7 +299,18 @@ class DbEngine:
|
||||
with open(target_file, 'r', encoding='utf-8') as file:
|
||||
as_dict = json.load(file)
|
||||
|
||||
return self.debug_serializer.deserialize(as_dict)
|
||||
debug_serializer = DebugSerializer(RefHelper(self._get_ref_path))
|
||||
return debug_serializer.deserialize(as_dict)
|
||||
|
||||
def debug_users(self):
|
||||
"""
|
||||
Returns a list of all user folders inside the root directory, excluding the 'refs' folder
|
||||
:return: List of folder names
|
||||
"""
|
||||
with self.lock:
|
||||
if not os.path.exists(self.root):
|
||||
return []
|
||||
return [f for f in os.listdir(self.root) if os.path.isdir(os.path.join(self.root, f)) and f != 'refs']
|
||||
|
||||
def debug_get_digest(self, user_id, entry):
|
||||
return self._get_entry_digest(user_id, entry)
|
||||
@@ -298,12 +321,15 @@ class DbEngine:
|
||||
:param obj:
|
||||
:return:
|
||||
"""
|
||||
# serializer = Serializer(RefHelper(self._get_obj_path))
|
||||
use_refs = getattr(obj, "use_refs")() if hasattr(obj, "use_refs") else None
|
||||
return self.serializer.serialize(obj, use_refs)
|
||||
with self.lock:
|
||||
serializer = Serializer(RefHelper(self._get_ref_path))
|
||||
use_refs = getattr(obj, "use_refs")() if hasattr(obj, "use_refs") else None
|
||||
return serializer.serialize(obj, use_refs)
|
||||
|
||||
def _deserialize(self, as_dict):
|
||||
return self.serializer.deserialize(as_dict)
|
||||
with self.lock:
|
||||
serializer = Serializer(RefHelper(self._get_ref_path))
|
||||
return serializer.deserialize(as_dict)
|
||||
|
||||
def _update_head(self, user_id, entry, digest):
|
||||
"""
|
||||
@@ -368,4 +394,4 @@ class DbEngine:
|
||||
:param digest:
|
||||
:return:
|
||||
"""
|
||||
return os.path.join(self.root, "refs", digest[:24], digest)
|
||||
return os.path.join(self.root, "refs", digest[:24], digest)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
import os.path
|
||||
from datetime import datetime
|
||||
|
||||
from core.dbengine import DbEngine, DbException
|
||||
from core.dbengine import DbEngine, TAG_PARENT, TAG_USER, TAG_DATE, DbException
|
||||
from core.instance_manager import NO_SESSION, NOT_LOGGED
|
||||
from core.settings_objects import *
|
||||
|
||||
@@ -20,56 +19,6 @@ class NoDefaultCls:
|
||||
NoDefault = NoDefaultCls()
|
||||
|
||||
|
||||
class DummyDbEngine:
|
||||
"""
|
||||
Dummy DB engine
|
||||
Can only serialize object defined in settings_object module
|
||||
Save everything in a single file
|
||||
"""
|
||||
|
||||
def __init__(self, setting_path="settings.json"):
|
||||
self.db_path = setting_path
|
||||
|
||||
def save(self, user_id: str, entry: str, obj: object) -> bool:
|
||||
if not hasattr(obj, "as_dict"):
|
||||
raise Exception("'as_dict' not found. Not supported")
|
||||
|
||||
as_dict = getattr(obj, "as_dict")()
|
||||
as_dict["__type__"] = type(obj).__name__
|
||||
|
||||
if os.path.exists(self.db_path):
|
||||
with open(self.db_path, "r") as settings_file:
|
||||
as_json = json.load(settings_file)
|
||||
as_json[entry] = as_dict
|
||||
with open(self.db_path, "w") as settings_file:
|
||||
json.dump(as_json, settings_file)
|
||||
else:
|
||||
as_json = {entry: as_dict}
|
||||
with open(self.db_path, "w") as settings_file:
|
||||
json.dump(as_json, settings_file)
|
||||
|
||||
return True
|
||||
|
||||
def load(self, user_id: str, entry: str, digest: str = None):
|
||||
try:
|
||||
with open(self.db_path, "r") as settings_file:
|
||||
as_json = json.load(settings_file)
|
||||
|
||||
as_dict = as_json[entry]
|
||||
obj_type = as_dict.pop("__type__")
|
||||
obj = globals()[obj_type]()
|
||||
getattr(obj, "from_dict")(as_dict)
|
||||
return obj
|
||||
except Exception as ex:
|
||||
raise DbException(f"Entry '{entry}' is not found.")
|
||||
|
||||
def is_initialized(self):
|
||||
return os.path.exists(self.db_path)
|
||||
|
||||
def init(self):
|
||||
pass
|
||||
|
||||
|
||||
class MemoryDbEngine:
|
||||
"""
|
||||
Keeps everything in memory
|
||||
@@ -81,127 +30,108 @@ class MemoryDbEngine:
|
||||
def init_db(self, entry, key, obj):
|
||||
self.db[entry] = {key: obj}
|
||||
|
||||
def save(self, user_id: str, entry: str, obj: object) -> bool:
|
||||
self.db[entry] = obj
|
||||
def save(self, user_id: str, user_email: str, entry: str, obj: object) -> bool:
|
||||
if user_id in self.db:
|
||||
self.db[user_id][entry] = obj
|
||||
self.db[user_id][TAG_PARENT] = [] # not used
|
||||
self.db[user_id][TAG_USER] = user_email
|
||||
self.db[user_id][TAG_DATE] = datetime.now().strftime('%Y%m%d %H:%M:%S %z')
|
||||
else:
|
||||
self.db[user_id] = {
|
||||
entry: obj,
|
||||
TAG_PARENT: [], # not set
|
||||
TAG_USER: user_email,
|
||||
TAG_DATE: datetime.now().strftime('%Y%m%d %H:%M:%S %z')
|
||||
}
|
||||
return True
|
||||
|
||||
def load(self, user_id: str, entry: str, digest: str = None):
|
||||
try:
|
||||
return self.db[entry]
|
||||
except KeyError:
|
||||
return {}
|
||||
return self.db[user_id][entry]
|
||||
except KeyError as e:
|
||||
raise DbException(e)
|
||||
|
||||
def get(self, user_id: str, entry: str, key: str | None = None, digest=None):
|
||||
return self.db[entry][key]
|
||||
"""
|
||||
Retrieve a specific value or the entire object from stored data based on the
|
||||
provided user ID and entry name. Optionally, a key can be specified to extract
|
||||
a particular value from the loaded object.
|
||||
|
||||
:param user_id: The unique identifier of the user associated with the data
|
||||
being retrieved.
|
||||
:param entry: The name of the entry under the user's data storage.
|
||||
:param key: Optional; Specific key for retrieving a particular value from the
|
||||
entry. If not provided, the entire object is returned.
|
||||
:param digest: Optional; Used to specify a digest or additional parameter,
|
||||
but its function should be inferred from its use, as it is not directly
|
||||
handled in this method.
|
||||
:return: The value corresponding to the specified key within the user's entry,
|
||||
or the entire entry object if no key is specified.
|
||||
"""
|
||||
obj = self.load(user_id, entry)
|
||||
try:
|
||||
return obj[key] if key else obj
|
||||
except KeyError as e:
|
||||
raise DbException(e)
|
||||
|
||||
def put(self, user_id: str, entry, key: str, value: object):
|
||||
if entry not in self.db:
|
||||
self.db[entry] = {}
|
||||
self.db[entry][key] = value
|
||||
def put(self, user_id: str, user_email: str, entry, key: str, value: object):
|
||||
obj = self.load(user_id, entry)
|
||||
obj[key] = value
|
||||
|
||||
def is_initialized(self):
|
||||
return True
|
||||
def put_many(self, user_id: str, user_email: str, entry, items):
|
||||
obj = self.load(user_id, entry)
|
||||
obj.update(items)
|
||||
|
||||
def exists(self, user_id: str, entry: str):
|
||||
return user_id in entry and entry in self.db[user_id]
|
||||
|
||||
|
||||
class SettingsManager:
|
||||
def __init__(self, engine=None):
|
||||
self._db_engine = engine or DbEngine()
|
||||
|
||||
def save(self, user_id: str, entry: str, obj: object):
|
||||
return self._db_engine.save(user_id, entry, obj)
|
||||
def save(self, session: dict, entry: str, obj: object):
|
||||
user_id, user_email = self._get_user(session)
|
||||
return self._db_engine.save(user_id, user_email, entry, obj)
|
||||
|
||||
def load(self, user_id: str, entry: str):
|
||||
return self._db_engine.load(user_id, entry)
|
||||
|
||||
def get_all(self, user_id: str, entry: str):
|
||||
""""
|
||||
Returns all the items of an entry
|
||||
"""
|
||||
return self._db_engine.get(user_id, entry, None)
|
||||
|
||||
def put(self, session: dict, key: str, value: object):
|
||||
"""
|
||||
Inserts or updates a key-value pair in the database for the current user session.
|
||||
The method extracts the user ID and email from the session dictionary and
|
||||
utilizes the database engine to perform the storage operation.
|
||||
|
||||
:param session: A dictionary containing session-specific details,
|
||||
including 'user_id' and 'user_email'.
|
||||
:type session: dict
|
||||
:param key: The key under which the value should be stored in the database.
|
||||
:type key: str
|
||||
:param value: The value to be stored, associated with the specified key.
|
||||
:type value: object
|
||||
:return: The result of the database engine's put operation.
|
||||
:rtype: object
|
||||
"""
|
||||
user_id = session["user_id"] if session else NO_SESSION
|
||||
user_email = session["user_email"] if session else NOT_LOGGED
|
||||
return self._db_engine.put(user_email, str(user_id), key, value)
|
||||
|
||||
def get(self, session: dict, key: str | None = None, default=NoDefault):
|
||||
"""
|
||||
Fetches a value associated with a specific key for a user session from the
|
||||
database. If the key is not found in the database and a default value is
|
||||
provided, returns the default value. If no default is provided and the key
|
||||
is not found, raises a KeyError.
|
||||
|
||||
:param session: A dictionary containing session data. Must include "user_id"
|
||||
and "user_email" keys.
|
||||
:type session: dict
|
||||
:param key: The key to fetch from the database for the given session user.
|
||||
Defaults to None if not specified.
|
||||
:type key: str | None
|
||||
:param default: The default value to return if the key is not found in the
|
||||
database. If not provided, raises KeyError when the key is missing.
|
||||
:type default: Any
|
||||
:return: The value associated with the key for the user session if found in
|
||||
the database, or the provided default value if the key is not found.
|
||||
"""
|
||||
def load(self, session: dict, entry: str, default=NoDefault):
|
||||
user_id, _ = self._get_user(session)
|
||||
try:
|
||||
user_id = session["user_id"] if session else NO_SESSION
|
||||
user_email = session["user_email"] if session else NOT_LOGGED
|
||||
|
||||
return self._db_engine.get(user_email, str(user_id), key)
|
||||
except KeyError:
|
||||
return self._db_engine.load(user_id, entry)
|
||||
except DbException:
|
||||
return default
|
||||
|
||||
def put(self, session: dict, entry: str, key: str, value: object):
|
||||
user_id, user_email = self._get_user(session)
|
||||
return self._db_engine.put(user_id, user_email, entry, key, value)
|
||||
|
||||
def put_many(self, session: dict, entry: str, items: list | dict):
|
||||
user_id, user_email = self._get_user(session)
|
||||
return self._db_engine.put_many(user_id, user_email, entry, items)
|
||||
|
||||
def get(self, session: dict, entry: str, key: str | None = None, default=NoDefault):
|
||||
try:
|
||||
user_id, _ = self._get_user(session)
|
||||
return self._db_engine.get(user_id, entry, key)
|
||||
except DbException:
|
||||
if default is NoDefault:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
|
||||
def remove(self, session: dict, key: str):
|
||||
user_id = session["user_id"] if session else NO_SESSION
|
||||
user_email = session["user_email"] if session else NOT_LOGGED
|
||||
return self._db_engine.remove(user_email, user_id, key)
|
||||
def exists(self, session: dict, entry: str):
|
||||
user_id, _ = self._get_user(session)
|
||||
|
||||
def update(self, session: dict, old_key: str, key: str, value: object):
|
||||
user_id = session["user_id"] if session else NO_SESSION
|
||||
user_email = session["user_email"] if session else NOT_LOGGED
|
||||
|
||||
def _update_helper(_old_key, _key, _value):
|
||||
pass
|
||||
|
||||
if hasattr(self._db_engine, "lock"):
|
||||
with self._db_engine.lock:
|
||||
_update_helper(old_key, key, value)
|
||||
else:
|
||||
_update_helper(old_key, key, value)
|
||||
|
||||
def init_user(self, user_id: str, user_email: str):
|
||||
"""
|
||||
Init the settings block space for a user
|
||||
:param user_id:
|
||||
:param user_email:
|
||||
:return:
|
||||
"""
|
||||
if not self._db_engine.exists(user_id):
|
||||
self._db_engine.save(user_email, user_id, {})
|
||||
|
||||
def get_db_engine_root(self):
|
||||
return os.path.abspath(self._db_engine.root)
|
||||
return self._db_engine.exists(user_id, entry)
|
||||
|
||||
def get_db_engine(self):
|
||||
return self._db_engine
|
||||
|
||||
@staticmethod
|
||||
def _get_user(session):
|
||||
user_id = str(session.get("user_id", NOT_LOGGED)) if session else NO_SESSION
|
||||
user_email = session.get("user_email", NOT_LOGGED) if session else NO_SESSION
|
||||
return user_id, user_email
|
||||
|
||||
|
||||
class SettingsTransaction:
|
||||
@@ -223,6 +153,31 @@ class SettingsTransaction:
|
||||
if exc_type is None:
|
||||
self._settings_manager.save(self._user_email, self._user_id, self._entries)
|
||||
|
||||
#
|
||||
# settings_manager = SettingsManager()
|
||||
# settings_manager.init()
|
||||
|
||||
class GenericDbManager:
|
||||
def __init__(self, session, settings_manager: SettingsManager, obj_entry, obj_type):
|
||||
self.__dict__["_session"] = session
|
||||
self.__dict__["_settings_manager"] = settings_manager
|
||||
self.__dict__["_obj_entry"] = obj_entry
|
||||
self.__dict__["_obj_type"] = obj_type
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key.startswith("_"):
|
||||
super().__setattr__(key, value)
|
||||
|
||||
settings = self._settings_manager.load(self._session, self._obj_entry, self._obj_type())
|
||||
if not (hasattr(settings, key)):
|
||||
raise AttributeError(f"Settings {self._obj_entry.__name__} has no attribute {key}")
|
||||
|
||||
setattr(settings, key, value)
|
||||
self._settings_manager.save(self._session, self._obj_entry, settings)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith("_"):
|
||||
return super().__getattribute__(item)
|
||||
|
||||
settings = self._settings_manager.load(self._session, self._obj_entry, self._obj_type())
|
||||
if not (hasattr(settings, item)):
|
||||
raise AttributeError(f"Settings {self._obj_entry.__name__} has no attribute {item}")
|
||||
|
||||
return getattr(settings, item)
|
||||
|
||||
@@ -199,7 +199,7 @@ def snake_case_to_capitalized_words(s: str) -> str:
|
||||
return transformed_name
|
||||
|
||||
|
||||
def make_column_id(s: str | None):
|
||||
def make_safe_id(s: str | None):
|
||||
if s is None:
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user