Parent is now mandatory when creating a new BaseInstance class
This commit is contained in:
@@ -7,7 +7,7 @@ from myfasthtml.controls.Layout import Layout
|
|||||||
from myfasthtml.controls.TabsManager import TabsManager
|
from myfasthtml.controls.TabsManager import TabsManager
|
||||||
from myfasthtml.controls.helpers import Ids, mk
|
from myfasthtml.controls.helpers import Ids, mk
|
||||||
from myfasthtml.core.commands import Command
|
from myfasthtml.core.commands import Command
|
||||||
from myfasthtml.core.instances import InstancesManager
|
from myfasthtml.core.instances import InstancesManager, RootInstance
|
||||||
from myfasthtml.myfastapp import create_app
|
from myfasthtml.myfastapp import create_app
|
||||||
|
|
||||||
with open('logging.yaml', 'r') as f:
|
with open('logging.yaml', 'r') as f:
|
||||||
@@ -27,10 +27,10 @@ app, rt = create_app(protect_routes=True,
|
|||||||
|
|
||||||
@rt("/")
|
@rt("/")
|
||||||
def index(session):
|
def index(session):
|
||||||
layout = InstancesManager.get(session, Ids.Layout, Layout, "Testing Layout")
|
layout = InstancesManager.get(session, Ids.Layout, Layout, RootInstance, "Testing Layout")
|
||||||
layout.set_footer("Goodbye World")
|
layout.set_footer("Goodbye World")
|
||||||
|
|
||||||
tabs_manager = TabsManager(session, _id="main")
|
tabs_manager = TabsManager(layout, _id=f"{Ids.TabsManager}-main")
|
||||||
|
|
||||||
btn_show_right_drawer = mk.button("show",
|
btn_show_right_drawer = mk.button("show",
|
||||||
command=Command("ShowRightDrawer",
|
command=Command("ShowRightDrawer",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class Boundaries(SingleInstance):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session, owner, container_id: str = None, on_resize=None):
|
def __init__(self, session, owner, container_id: str = None, on_resize=None):
|
||||||
super().__init__(session, Ids.Boundaries)
|
super().__init__(session, Ids.Boundaries, owner)
|
||||||
self._owner = owner
|
self._owner = owner
|
||||||
self._container_id = container_id or owner.get_id()
|
self._container_id = container_id or owner.get_id()
|
||||||
self._on_resize = on_resize
|
self._on_resize = on_resize
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class Layout(SingleInstance):
|
|||||||
def get_content(self):
|
def get_content(self):
|
||||||
return self._content
|
return self._content
|
||||||
|
|
||||||
def __init__(self, session, app_name):
|
def __init__(self, session, app_name, parent=None):
|
||||||
"""
|
"""
|
||||||
Initialize the Layout component.
|
Initialize the Layout component.
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ class Layout(SingleInstance):
|
|||||||
left_drawer (bool): Enable left drawer. Default is True.
|
left_drawer (bool): Enable left drawer. Default is True.
|
||||||
right_drawer (bool): Enable right drawer. Default is True.
|
right_drawer (bool): Enable right drawer. Default is True.
|
||||||
"""
|
"""
|
||||||
super().__init__(session, Ids.Layout)
|
super().__init__(session, Ids.Layout, parent)
|
||||||
self.app_name = app_name
|
self.app_name = app_name
|
||||||
|
|
||||||
# Content storage
|
# Content storage
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from fasthtml.components import *
|
|||||||
from myfasthtml.controls.BaseCommands import BaseCommands
|
from myfasthtml.controls.BaseCommands import BaseCommands
|
||||||
from myfasthtml.controls.helpers import Ids, mk
|
from myfasthtml.controls.helpers import Ids, mk
|
||||||
from myfasthtml.core.commands import Command
|
from myfasthtml.core.commands import Command
|
||||||
from myfasthtml.core.instances import MultipleInstance
|
from myfasthtml.core.instances import MultipleInstance, BaseInstance
|
||||||
from myfasthtml.core.matching_utils import subsequence_matching, fuzzy_matching
|
from myfasthtml.core.matching_utils import subsequence_matching, fuzzy_matching
|
||||||
|
|
||||||
logger = logging.getLogger("Search")
|
logger = logging.getLogger("Search")
|
||||||
@@ -22,7 +22,7 @@ class Commands(BaseCommands):
|
|||||||
|
|
||||||
class Search(MultipleInstance):
|
class Search(MultipleInstance):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
session,
|
parent: BaseInstance,
|
||||||
_id=None,
|
_id=None,
|
||||||
items_names=None, # what is the name of the items to filter
|
items_names=None, # what is the name of the items to filter
|
||||||
items=None, # first set of items to filter
|
items=None, # first set of items to filter
|
||||||
@@ -42,7 +42,7 @@ class Search(MultipleInstance):
|
|||||||
function that returns the item as is.
|
function that returns the item as is.
|
||||||
:param template: Callable function to render the filtered items. Defaults to a Div rendering function.
|
:param template: Callable function to render the filtered items. Defaults to a Div rendering function.
|
||||||
"""
|
"""
|
||||||
super().__init__(session, Ids.Search, _id=_id)
|
super().__init__(Ids.Search, parent, _id=_id)
|
||||||
self.items_names = items_names or ''
|
self.items_names = items_names or ''
|
||||||
self.items = items or []
|
self.items = items or []
|
||||||
self.filtered = self.items.copy()
|
self.filtered = self.items.copy()
|
||||||
|
|||||||
@@ -77,12 +77,12 @@ class Commands(BaseCommands):
|
|||||||
class TabsManager(MultipleInstance):
|
class TabsManager(MultipleInstance):
|
||||||
_tab_count = 0
|
_tab_count = 0
|
||||||
|
|
||||||
def __init__(self, session, _id=None):
|
def __init__(self, parent, _id=None):
|
||||||
super().__init__(session, Ids.TabsManager, _id=_id)
|
super().__init__(Ids.TabsManager, parent, _id=_id)
|
||||||
self._state = TabsManagerState(self)
|
self._state = TabsManagerState(self)
|
||||||
self.commands = Commands(self)
|
self.commands = Commands(self)
|
||||||
self._boundaries = Boundaries()
|
self._boundaries = Boundaries()
|
||||||
self._search = Search(self._session,
|
self._search = Search(self,
|
||||||
items=self._get_tab_list(),
|
items=self._get_tab_list(),
|
||||||
get_attr=lambda x: x["label"],
|
get_attr=lambda x: x["label"],
|
||||||
template=self._mk_tab_button)
|
template=self._mk_tab_button)
|
||||||
@@ -102,7 +102,7 @@ class TabsManager(MultipleInstance):
|
|||||||
tab_config = self._state.tabs[tab_id]
|
tab_config = self._state.tabs[tab_id]
|
||||||
if tab_config["component_type"] is None:
|
if tab_config["component_type"] is None:
|
||||||
return None
|
return None
|
||||||
return InstancesHelper.dynamic_get(self._session, tab_config["component_type"], tab_config["component_id"])
|
return InstancesHelper.dynamic_get(self, tab_config["component_type"], tab_config["component_id"])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_tab_count():
|
def _get_tab_count():
|
||||||
@@ -114,7 +114,7 @@ class TabsManager(MultipleInstance):
|
|||||||
logger.debug(f"on_new_tab {label=}, {component=}, {auto_increment=}")
|
logger.debug(f"on_new_tab {label=}, {component=}, {auto_increment=}")
|
||||||
if auto_increment:
|
if auto_increment:
|
||||||
label = f"{label}_{self._get_tab_count()}"
|
label = f"{label}_{self._get_tab_count()}"
|
||||||
component = component or VisNetwork(self._session, nodes=vis_nodes, edges=vis_edges)
|
component = component or VisNetwork(self, nodes=vis_nodes, edges=vis_edges)
|
||||||
tab_id = self.add_tab(label, component)
|
tab_id = self.add_tab(label, component)
|
||||||
return (
|
return (
|
||||||
self._mk_tabs_controller(),
|
self._mk_tabs_controller(),
|
||||||
@@ -328,7 +328,7 @@ class TabsManager(MultipleInstance):
|
|||||||
tab_content = self._mk_tab_content(active_tab, content)
|
tab_content = self._mk_tab_content(active_tab, content)
|
||||||
self._state._tabs_content[active_tab] = tab_content
|
self._state._tabs_content[active_tab] = tab_content
|
||||||
else:
|
else:
|
||||||
tab_content = self._mk_tab_content("", None)
|
tab_content = self._mk_tab_content(None, None)
|
||||||
|
|
||||||
return Div(
|
return Div(
|
||||||
tab_content,
|
tab_content,
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ class Commands(BaseCommands):
|
|||||||
|
|
||||||
|
|
||||||
class UserProfile(SingleInstance):
|
class UserProfile(SingleInstance):
|
||||||
def __init__(self, session):
|
def __init__(self, session, parent=None):
|
||||||
super().__init__(session, Ids.UserProfile)
|
super().__init__(session, Ids.UserProfile, parent)
|
||||||
self._state = UserProfileState(self)
|
self._state = UserProfileState(self)
|
||||||
self._commands = Commands(self)
|
self._commands = Commands(self)
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ class VisNetworkState(DbObject):
|
|||||||
|
|
||||||
|
|
||||||
class VisNetwork(MultipleInstance):
|
class VisNetwork(MultipleInstance):
|
||||||
def __init__(self, session, _id=None, nodes=None, edges=None, options=None):
|
def __init__(self, parent, _id=None, nodes=None, edges=None, options=None):
|
||||||
super().__init__(session, Ids.VisNetwork, _id=_id)
|
super().__init__(Ids.VisNetwork, parent, _id=_id)
|
||||||
logger.debug(f"VisNetwork created with id: {self._id}")
|
logger.debug(f"VisNetwork created with id: {self._id}")
|
||||||
|
|
||||||
self._state = VisNetworkState(self)
|
self._state = VisNetworkState(self)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class Ids:
|
|||||||
Boundaries = "mf-boundaries"
|
Boundaries = "mf-boundaries"
|
||||||
DbManager = "mf-dbmanager"
|
DbManager = "mf-dbmanager"
|
||||||
Layout = "mf-layout"
|
Layout = "mf-layout"
|
||||||
|
Root = "mf-root"
|
||||||
Search = "mf-search"
|
Search = "mf-search"
|
||||||
TabsManager = "mf-tabs-manager"
|
TabsManager = "mf-tabs-manager"
|
||||||
UserProfile = "mf-user-profile"
|
UserProfile = "mf-user-profile"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from myfasthtml.auth.utils import login_user, save_user_info, register_user
|
from myfasthtml.auth.utils import login_user, save_user_info, register_user
|
||||||
from myfasthtml.controls.helpers import Ids
|
from myfasthtml.controls.helpers import Ids
|
||||||
from myfasthtml.core.instances import special_session, UniqueInstance
|
from myfasthtml.core.instances import UniqueInstance, RootInstance
|
||||||
|
|
||||||
|
|
||||||
class AuthProxy(UniqueInstance):
|
class AuthProxy(UniqueInstance):
|
||||||
def __init__(self, base_url: str = None):
|
def __init__(self, base_url: str = None):
|
||||||
super().__init__(special_session, Ids.AuthProxy)
|
super().__init__(Ids.AuthProxy, RootInstance)
|
||||||
self._base_url = base_url
|
self._base_url = base_url
|
||||||
|
|
||||||
def login_user(self, email: str, password: str):
|
def login_user(self, email: str, password: str):
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ from myfasthtml.core.utils import retrieve_user_info
|
|||||||
|
|
||||||
|
|
||||||
class DbManager(SingleInstance):
|
class DbManager(SingleInstance):
|
||||||
def __init__(self, session, root=".myFastHtmlDb", auto_register: bool = True):
|
def __init__(self, session, parent=None, root=".myFastHtmlDb", auto_register: bool = True):
|
||||||
super().__init__(session, Ids.DbManager, auto_register=auto_register)
|
super().__init__(session, Ids.DbManager, parent, auto_register=auto_register)
|
||||||
self.db = DbEngine(root=root)
|
self.db = DbEngine(root=root)
|
||||||
|
|
||||||
def save(self, entry, obj):
|
def save(self, entry, obj):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
from myfasthtml.controls.helpers import Ids
|
from myfasthtml.controls.helpers import Ids
|
||||||
|
|
||||||
@@ -17,10 +18,11 @@ class BaseInstance:
|
|||||||
Base class for all instances (manageable by InstancesManager)
|
Base class for all instances (manageable by InstancesManager)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session: dict, prefix: str, _id: str, auto_register: bool = True):
|
def __init__(self, session: dict, prefix: str, _id: str, parent: Self, auto_register: bool = True):
|
||||||
self._session = session
|
self._session = session
|
||||||
self._id = _id
|
self._id = _id
|
||||||
self._prefix = prefix
|
self._prefix = prefix
|
||||||
|
self._parent = parent
|
||||||
if auto_register:
|
if auto_register:
|
||||||
InstancesManager.register(session, self)
|
InstancesManager.register(session, self)
|
||||||
|
|
||||||
@@ -39,8 +41,8 @@ class SingleInstance(BaseInstance):
|
|||||||
Base class for instances that can only have one instance at a time.
|
Base class for instances that can only have one instance at a time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session: dict, prefix: str, auto_register: bool = True):
|
def __init__(self, session: dict, prefix: str, parent, auto_register: bool = True):
|
||||||
super().__init__(session, prefix, prefix, auto_register)
|
super().__init__(session, prefix, prefix, parent, auto_register)
|
||||||
|
|
||||||
|
|
||||||
class UniqueInstance(BaseInstance):
|
class UniqueInstance(BaseInstance):
|
||||||
@@ -49,8 +51,8 @@ class UniqueInstance(BaseInstance):
|
|||||||
Does not throw exception if the instance already exists, it simply overwrites it.
|
Does not throw exception if the instance already exists, it simply overwrites it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session: dict, prefix: str, auto_register: bool = True):
|
def __init__(self, prefix: str, parent: BaseInstance, auto_register: bool = True):
|
||||||
super().__init__(session, prefix, prefix, auto_register)
|
super().__init__(parent.get_session(), prefix, prefix, parent, auto_register)
|
||||||
self._prefix = prefix
|
self._prefix = prefix
|
||||||
|
|
||||||
|
|
||||||
@@ -59,8 +61,8 @@ class MultipleInstance(BaseInstance):
|
|||||||
Base class for instances that can have multiple instances at a time.
|
Base class for instances that can have multiple instances at a time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session: dict, prefix: str, auto_register: bool = True, _id=None):
|
def __init__(self, prefix: str, parent: BaseInstance, auto_register: bool = True, _id=None):
|
||||||
super().__init__(session, prefix, _id or f"{prefix}-{str(uuid.uuid4())}", auto_register)
|
super().__init__(parent.get_session(), prefix, _id or f"{prefix}-{str(uuid.uuid4())}", parent, auto_register)
|
||||||
self._prefix = prefix
|
self._prefix = prefix
|
||||||
|
|
||||||
|
|
||||||
@@ -84,12 +86,13 @@ class InstancesManager:
|
|||||||
return instance
|
return instance
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get(session: dict, instance_id: str, instance_type: type = None, *args, **kwargs):
|
def get(session: dict, instance_id: str, instance_type: type = None, parent: BaseInstance = None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Get or create an instance of the given type (from its id)
|
Get or create an instance of the given type (from its id)
|
||||||
:param session:
|
:param session:
|
||||||
:param instance_id:
|
:param instance_id:
|
||||||
:param instance_type:
|
:param instance_type:
|
||||||
|
:param parent:
|
||||||
:param args:
|
:param args:
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return:
|
:return:
|
||||||
@@ -100,7 +103,9 @@ class InstancesManager:
|
|||||||
return InstancesManager.instances[key]
|
return InstancesManager.instances[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if instance_type:
|
if instance_type:
|
||||||
return instance_type(session, *args, **kwargs) # it will be automatically registered
|
if not issubclass(instance_type, SingleInstance):
|
||||||
|
assert parent is not None, "Parent instance must be provided if not SingleInstance"
|
||||||
|
return instance_type(session, parent=parent, *args, **kwargs) # it will be automatically registered
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -119,3 +124,6 @@ class InstancesManager:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def reset():
|
def reset():
|
||||||
return InstancesManager.instances.clear()
|
return InstancesManager.instances.clear()
|
||||||
|
|
||||||
|
|
||||||
|
RootInstance = SingleInstance(special_session, Ids.Root, None)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from myfasthtml.controls.VisNetwork import VisNetwork
|
from myfasthtml.controls.VisNetwork import VisNetwork
|
||||||
from myfasthtml.controls.helpers import Ids
|
from myfasthtml.controls.helpers import Ids
|
||||||
|
from myfasthtml.core.instances import BaseInstance
|
||||||
|
|
||||||
|
|
||||||
class InstancesHelper:
|
class InstancesHelper:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def dynamic_get(session, component_type: str, instance_id: str):
|
def dynamic_get(parent: BaseInstance, component_type: str, instance_id: str):
|
||||||
if component_type == Ids.VisNetwork:
|
if component_type == Ids.VisNetwork:
|
||||||
return VisNetwork(session, _id=instance_id)
|
return VisNetwork(parent, _id=instance_id)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from myfasthtml.core.instances import SingleInstance
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def session():
|
def session():
|
||||||
@@ -14,3 +16,8 @@ def session():
|
|||||||
'updated_at': '2025-11-10T15:52:59.006213'
|
'updated_at': '2025-11-10T15:52:59.006213'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def root_instance(session):
|
||||||
|
return SingleInstance(session, "TestRoot", None)
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ from fasthtml.xtend import Script
|
|||||||
|
|
||||||
from myfasthtml.controls.TabsManager import TabsManager
|
from myfasthtml.controls.TabsManager import TabsManager
|
||||||
from myfasthtml.core.instances import InstancesManager
|
from myfasthtml.core.instances import InstancesManager
|
||||||
from myfasthtml.test.matcher import matches, NoChildren, StartsWith
|
from myfasthtml.test.matcher import matches, NoChildren
|
||||||
from .conftest import session
|
from .conftest import session
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def tabs_manager(session):
|
def tabs_manager(root_instance):
|
||||||
yield TabsManager(session)
|
yield TabsManager(root_instance)
|
||||||
|
|
||||||
InstancesManager.reset()
|
InstancesManager.reset()
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ class TestTabsManagerRender:
|
|||||||
id=f"{tabs_manager.get_id()}-header-wrapper"
|
id=f"{tabs_manager.get_id()}-header-wrapper"
|
||||||
),
|
),
|
||||||
Div(
|
Div(
|
||||||
Div(id=StartsWith(tabs_manager.get_id())),
|
Div("Content 3"), # active tab content
|
||||||
# Lasy loading for the other contents
|
# Lasy loading for the other contents
|
||||||
id=f"{tabs_manager.get_id()}-content-wrapper"
|
id=f"{tabs_manager.get_id()}-content-wrapper"
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user