Improved Command class management.

This commit is contained in:
2025-12-19 21:12:55 +01:00
parent b26abc4257
commit 1347f12618
23 changed files with 349 additions and 169 deletions

View File

@@ -1,7 +1,6 @@
from fasthtml.xtend import Script
from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.helpers import Ids
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import SingleInstance

View File

@@ -1,6 +1,6 @@
from myfasthtml.controls.VisNetwork import VisNetwork
from myfasthtml.core.commands import CommandsManager
from myfasthtml.core.instances import SingleInstance
from myfasthtml.core.instances import SingleInstance, InstancesManager
from myfasthtml.core.network_utils import from_parent_child_list
@@ -9,22 +9,18 @@ class CommandsDebugger(SingleInstance):
Represents a debugger designed for visualizing and managing commands in a parent-child
hierarchical structure.
"""
def __init__(self, parent, _id=None):
super().__init__(parent, _id=_id)
def render(self):
commands = self._get_commands()
nodes, edges = from_parent_child_list(commands,
id_getter=lambda x: str(x.id),
label_getter=lambda x: x.name,
parent_getter=lambda x: str(self.get_command_parent(x))
)
nodes, edges = self._get_nodes_and_edges()
vis_network = VisNetwork(self, nodes=nodes, edges=edges)
return vis_network
@staticmethod
def get_command_parent(command):
def get_command_parent_from_ft(command):
if (ft := command.get_ft()) is None:
return None
if hasattr(ft, "get_id") and callable(ft.get_id):
@@ -36,6 +32,30 @@ class CommandsDebugger(SingleInstance):
return None
@staticmethod
def get_command_parent_from_instance(command):
if command.owner is None:
return None
return command.owner.get_full_id()
def _get_nodes_and_edges(self):
commands = self._get_commands()
nodes, edges = from_parent_child_list(commands,
id_getter=lambda x: str(x.id),
label_getter=lambda x: x.name,
parent_getter=lambda x: str(self.get_command_parent_from_instance(x)),
ghost_label_getter=lambda x: InstancesManager.get(*x.split("#")).get_id()
)
for edge in edges:
edge["color"] = "blue"
edge["arrows"] = {"to": {"enabled": False, "type": "circle"}}
for node in nodes:
node["shape"] = "box"
return nodes, edges
def _get_commands(self):
return list(CommandsManager.commands.values())

View File

@@ -10,7 +10,7 @@ from myfasthtml.controls.Panel import Panel
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.controls.TreeView import TreeView, TreeNode
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.commands import Command, LambdaCommand
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance, InstancesManager
from myfasthtml.icons.fluent_p1 import table_add20_regular
@@ -51,22 +51,29 @@ class Commands(BaseCommands):
"Open from Excel",
self._owner,
self._owner.open_from_excel,
tab_id,
file_upload).htmx(target=f"#{self._owner._tree.get_id()}")
args=[tab_id,
file_upload]).htmx(target=f"#{self._owner._tree.get_id()}")
def clear_tree(self):
return Command("ClearTree",
"Clear tree",
self._owner,
self._owner.clear_tree).htmx(target=f"#{self._owner._tree.get_id()}")
def show_document(self):
return LambdaCommand(self._owner, lambda: print("show_document"))
class DataGridsManager(MultipleInstance):
def __init__(self, parent, _id=None):
if not getattr(self, "_is_new_instance", False):
# Skip __init__ if instance already existed
return
super().__init__(parent, _id=_id)
self.commands = Commands(self)
self._state = DataGridsState(self)
self._tree = self._mk_tree()
self._tree.bind_command("SelectNode", self.commands.show_document())
self._tabs_manager = InstancesManager.get_by_type(self._session, TabsManager)
def upload_from_source(self):
@@ -86,7 +93,7 @@ class DataGridsManager(MultipleInstance):
name=file_upload.get_sheet_name(),
type="excel",
tab_id=tab_id,
datagrid_id=None
datagrid_id=dg.get_id()
)
self._state.elements = self._state.elements + [document]
parent_id = self._tree.ensure_path(document.namespace)
@@ -110,7 +117,7 @@ class DataGridsManager(MultipleInstance):
tree = TreeView(self, _id="-treeview")
for element in self._state.elements:
parent_id = tree.ensure_path(element.namespace)
tree.add_node(TreeNode(label=element.name, type=element.type, parent=parent_id))
tree.add_node(TreeNode(label=element.name, type=element.type, parent=parent_id, id=element.datagrid_id))
return tree
def render(self):

View File

@@ -34,6 +34,7 @@ class Dropdown(MultipleInstance):
The dropdown provides functionality to manage its state, including opening, closing, and
handling user interactions.
"""
def __init__(self, parent, content=None, button=None, _id=None):
super().__init__(parent, _id=_id)
self.button = Div(button) if not isinstance(button, FT) else button

View File

@@ -37,7 +37,7 @@ class InstancesDebugger(SingleInstance):
instances,
id_getter=lambda x: x.get_full_id(),
label_getter=lambda x: f"{x.get_id()}",
parent_getter=lambda x: x.get_full_parent_id()
parent_getter=lambda x: x.get_parent_full_id()
)
for edge in edges:
edge["color"] = "green"

View File

@@ -2,7 +2,7 @@ import json
from fasthtml.xtend import Script
from myfasthtml.core.commands import BaseCommand
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import MultipleInstance
@@ -17,7 +17,7 @@ class Keyboard(MultipleInstance):
super().__init__(parent, _id=_id)
self.combinations = combinations or {}
def add(self, sequence: str, command: BaseCommand):
def add(self, sequence: str, command: Command):
self.combinations[sequence] = command
return self

View File

@@ -40,7 +40,8 @@ class Commands(BaseCommands):
return Command("ToggleDrawer",
f"Toggle {side} layout drawer",
self._owner,
self._owner.toggle_drawer, side)
self._owner.toggle_drawer,
args=[side])
def update_drawer_width(self, side: Literal["left", "right"], width: int = None):
"""
@@ -57,7 +58,7 @@ class Commands(BaseCommands):
f"Update {side} drawer width",
self._owner,
self._owner.update_drawer_width,
side)
args=[side])
class Layout(SingleInstance):

View File

@@ -2,7 +2,7 @@ import json
from fasthtml.xtend import Script
from myfasthtml.core.commands import BaseCommand
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import MultipleInstance
@@ -17,7 +17,7 @@ class Mouse(MultipleInstance):
super().__init__(parent, _id=_id)
self.combinations = combinations or {}
def add(self, sequence: str, command: BaseCommand):
def add(self, sequence: str, command: Command):
self.combinations[sequence] = command
return self

View File

@@ -21,7 +21,7 @@ class Commands(BaseCommands):
f"Toggle {side} side panel",
self._owner,
self._owner.toggle_side,
side)
args=[side])
def update_side_width(self, side: Literal["left", "right"]):
"""
@@ -37,7 +37,7 @@ class Commands(BaseCommands):
f"Update {side} side panel width",
self._owner,
self._owner.update_side_width,
side)
args=[side])
class Panel(MultipleInstance):

View File

@@ -62,25 +62,25 @@ class Commands(BaseCommands):
"Activate or show a specific tab",
self._owner,
self._owner.show_tab,
tab_id,
True,
False).htmx(target=f"#{self._id}-controller", swap="outerHTML")
args=[tab_id,
True,
False]).htmx(target=f"#{self._id}-controller", swap="outerHTML")
def close_tab(self, tab_id):
return Command(f"{self._prefix}CloseTab",
"Close a specific tab",
self._owner,
self._owner.close_tab,
tab_id).htmx(target=f"#{self._id}-controller", swap="outerHTML")
args=[tab_id]).htmx(target=f"#{self._id}-controller", swap="outerHTML")
def add_tab(self, label: str, component: Any, auto_increment=False):
return Command(f"{self._prefix}AddTab",
"Add a new tab",
self._owner,
self._owner.on_new_tab,
label,
component,
auto_increment).htmx(target=f"#{self._id}-controller", swap="outerHTML")
args=[label,
component,
auto_increment]).htmx(target=f"#{self._id}-controller", swap="outerHTML")
class TabsManager(MultipleInstance):

View File

@@ -13,7 +13,7 @@ from fasthtml.components import Div, Input, Span
from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.Keyboard import Keyboard
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.commands import Command, CommandTemplate
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance
from myfasthtml.icons.fluent_p1 import chevron_right20_regular, edit20_regular
@@ -37,7 +37,7 @@ class TreeNode:
type: str = "default"
parent: Optional[str] = None
children: list[str] = field(default_factory=list)
bag: Optional[dict] = None # to keep extra info
bag: Optional[dict] = None # to keep extra info
class TreeViewState(DbObject):
@@ -71,7 +71,9 @@ class Commands(BaseCommands):
f"Toggle node {node_id}",
self._owner,
self._owner._toggle_node,
node_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-ToggleNode"
).htmx(target=f"#{self._owner.get_id()}")
def add_child(self, parent_id: str):
"""Create command to add a child node."""
@@ -79,7 +81,9 @@ class Commands(BaseCommands):
f"Add child to {parent_id}",
self._owner,
self._owner._add_child,
parent_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"parent_id": parent_id},
key=f"{self._owner.get_safe_parent_key()}-AddChild"
).htmx(target=f"#{self._owner.get_id()}")
def add_sibling(self, node_id: str):
"""Create command to add a sibling node."""
@@ -87,7 +91,8 @@ class Commands(BaseCommands):
f"Add sibling to {node_id}",
self._owner,
self._owner._add_sibling,
node_id
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-AddSibling"
).htmx(target=f"#{self._owner.get_id()}")
def start_rename(self, node_id: str):
@@ -96,7 +101,9 @@ class Commands(BaseCommands):
f"Start renaming {node_id}",
self._owner,
self._owner._start_rename,
node_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-StartRename"
).htmx(target=f"#{self._owner.get_id()}")
def save_rename(self, node_id: str):
"""Create command to save renamed node."""
@@ -104,14 +111,18 @@ class Commands(BaseCommands):
f"Save rename for {node_id}",
self._owner,
self._owner._save_rename,
node_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-SaveRename"
).htmx(target=f"#{self._owner.get_id()}")
def cancel_rename(self):
"""Create command to cancel renaming."""
return Command("CancelRename",
"Cancel rename",
self._owner,
self._owner._cancel_rename).htmx(target=f"#{self._owner.get_id()}")
self._owner._cancel_rename,
key=f"{self._owner.get_safe_parent_key()}-CancelRename"
).htmx(target=f"#{self._owner.get_id()}")
def delete_node(self, node_id: str):
"""Create command to delete a node."""
@@ -119,7 +130,9 @@ class Commands(BaseCommands):
f"Delete node {node_id}",
self._owner,
self._owner._delete_node,
node_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-DeleteNode"
).htmx(target=f"#{self._owner.get_id()}")
def select_node(self, node_id: str):
"""Create command to select a node."""
@@ -127,7 +140,9 @@ class Commands(BaseCommands):
f"Select node {node_id}",
self._owner,
self._owner._select_node,
node_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-SelectNode"
).htmx(target=f"#{self._owner.get_id()}")
class TreeView(MultipleInstance):
@@ -394,7 +409,7 @@ class TreeView(MultipleInstance):
name="node_label",
value=node.label,
cls="mf-treenode-input input input-sm"
), command=self.commands.save_rename(node_id))
), command=CommandTemplate("TreeView.SaveRename", self.commands.save_rename, args=[node_id]))
else:
label_element = mk.mk(
Span(node.label, cls="mf-treenode-label text-sm"),

View File

@@ -1,7 +1,7 @@
from fasthtml.components import *
from myfasthtml.core.bindings import Binding
from myfasthtml.core.commands import Command
from myfasthtml.core.commands import Command, CommandTemplate
from myfasthtml.core.utils import merge_classes
@@ -14,7 +14,7 @@ class Ids:
class mk:
@staticmethod
def button(element, command: Command = None, binding: Binding = None, **kwargs):
def button(element, command: Command | CommandTemplate = None, binding: Binding = None, **kwargs):
"""
Defines a static method for creating a Button object with specific configurations.
@@ -33,7 +33,7 @@ class mk:
@staticmethod
def dialog_buttons(ok_title: str = "OK",
cancel_title: str = "Cancel",
on_ok: Command = None,
on_ok: Command | CommandTemplate = None,
on_cancel: Command = None,
cls=None):
return Div(
@@ -52,7 +52,7 @@ class mk:
can_hover=False,
tooltip=None,
cls='',
command: Command = None,
command: Command | CommandTemplate = None,
binding: Binding = None,
**kwargs):
"""
@@ -92,7 +92,7 @@ class mk:
icon=None,
size: str = "sm",
cls='',
command: Command = None,
command: Command | CommandTemplate = None,
binding: Binding = None,
**kwargs):
merged_cls = merge_classes("flex", cls, kwargs)
@@ -109,7 +109,10 @@ class mk:
replace("xl", "32"))
@staticmethod
def manage_command(ft, command: Command):
def manage_command(ft, command: Command | CommandTemplate):
if isinstance(command, CommandTemplate):
command = command.command
if command:
ft = command.bind_ft(ft)
@@ -130,7 +133,7 @@ class mk:
return ft
@staticmethod
def mk(ft, command: Command = None, binding: Binding = None, init_binding=True):
def mk(ft, command: Command | CommandTemplate = None, binding: Binding = None, init_binding=True):
ft = mk.manage_command(ft, command) if command else ft
ft = mk.manage_binding(ft, binding, init_binding=init_binding) if binding else ft
return ft

View File

@@ -3,12 +3,13 @@ import json
import uuid
from typing import Optional
from myutils.observable import NotObservableError, ObservableEvent, add_event_listener, remove_event_listener
from myutils.observable import NotObservableError, ObservableResultCollector
from myfasthtml.core.constants import Routes, ROUTE_ROOT
from myfasthtml.core.utils import flatten
class BaseCommand:
class Command:
"""
Represents the base command class for defining executable actions.
@@ -25,28 +26,80 @@ class BaseCommand:
:type description: str
"""
def __init__(self, name, description, owner=None, auto_register=True):
def __init__(self, name,
description,
owner=None,
callback=None,
args: list = None,
kwargs: dict = None,
key=None,
auto_register=True):
self.id = uuid.uuid4()
self.name = name
self.description = description
self.owner = owner
self.callback = callback
self.default_args = args or []
self.default_kwargs = kwargs or {}
self._htmx_extra = {}
self._bindings = []
self._ft = None
self._callback_parameters = dict(inspect.signature(callback).parameters) if callback else {}
self._key = key
# register the command
if auto_register:
CommandsManager.register(self)
if key in CommandsManager.commands_by_key:
self.id = CommandsManager.commands_by_key[key].id
else:
CommandsManager.register(self)
def get_key(self):
return self._key
def get_htmx_params(self):
return {
res = {
"hx-post": f"{ROUTE_ROOT}{Routes.Commands}",
"hx-swap": "outerHTML",
"hx-vals": {"c_id": f"{self.id}"},
} | self._htmx_extra
}
for k, v in self._htmx_extra.items():
if k == "hx-post":
continue # cannot override this one
elif k == "hx-vals":
res["hx-vals"] |= v
else:
res[k] = v
# kwarg are given to the callback as values
res["hx-vals"] |= self.default_kwargs
return res
def execute(self, client_response: dict = None):
raise NotImplementedError
with ObservableResultCollector(self._bindings) as collector:
kwargs = self._create_kwargs(self.default_kwargs,
client_response,
{"client_response": client_response or {}})
ret = self.callback(*self.default_args, **kwargs)
ret_from_bound_commands = []
if self.owner:
for command in self.owner.get_bound_commands(self.name):
r = command.execute(client_response)
ret_from_bound_commands.append(r) # it will be flatten if needed later
all_ret = flatten(ret, ret_from_bound_commands, collector.results)
# Set the hx-swap-oob attribute on all elements returned by the callback
for r in all_ret[1:]:
if (hasattr(r, 'attrs')
and "hx-swap-oob" not in r.attrs
and r.get("id", None) is not None):
r.attrs["hx-swap-oob"] = r.attrs.get("hx-swap-oob", "true")
return all_ret[0] if len(all_ret) == 1 else all_ret
def htmx(self, target: Optional[str] = "this", swap="outerHTML", trigger=None):
# Note that the default value is the same than in get_htmx_params()
@@ -101,49 +154,22 @@ class BaseCommand:
return f"{ROUTE_ROOT}{Routes.Commands}?c_id={self.id}"
def ajax_htmx_options(self):
return {
res = {
"url": self.url,
"target": self._htmx_extra.get("hx-target", "this"),
"swap": self._htmx_extra.get("hx-swap", "outerHTML"),
"values": {}
"values": self.default_kwargs
}
res["values"]["c_id"] = f"{self.id}" # cannot be overridden
return res
def get_ft(self):
return self._ft
def __str__(self):
return f"Command({self.name})"
class Command(BaseCommand):
"""
Represents a command that encapsulates a callable action with parameters.
This class is designed to hold a defined action (callback) alongside its arguments
and keyword arguments.
:ivar name: The name of the command.
:type name: str
:ivar description: A brief description of the command.
:type description: str
:ivar callback: The function or callable to be executed.
:type callback: Callable
:ivar args: Positional arguments to be passed to the callback.
:type args: tuple
:ivar kwargs: Keyword arguments to be passed to the callback.
:type kwargs: dict
"""
def __init__(self, name, description, owner, callback, *args, **kwargs):
super().__init__(name, description, owner=owner)
self.callback = callback
self.callback_parameters = dict(inspect.signature(callback).parameters) if callback else {}
self.args = args
self.kwargs = kwargs
def _cast_parameter(self, key, value):
if key in self.callback_parameters:
param = self.callback_parameters[key]
if key in self._callback_parameters:
param = self._callback_parameters[key]
if param.annotation == bool:
return value == "true"
elif param.annotation == int:
@@ -156,72 +182,59 @@ class Command(BaseCommand):
return json.loads(value)
return value
def ajax_htmx_options(self):
res = super().ajax_htmx_options()
if self.kwargs:
res["values"] |= self.kwargs
res["values"]["c_id"] = f"{self.id}" # cannot be overridden
def _create_kwargs(self, *args):
"""
Try to recreate the requested kwargs from the client response and the default kwargs.
:param args:
:return:
"""
all_args = {}
for arg in [arg for arg in args if arg is not None]:
all_args |= arg
res = {}
for k, v in self._callback_parameters.items():
if k in all_args:
res[k] = self._cast_parameter(k, all_args[k])
return res
def execute(self, client_response: dict = None):
ret_from_bindings = []
def binding_result_callback(attr, old, new, results):
ret_from_bindings.extend(results)
for data in self._bindings:
add_event_listener(ObservableEvent.AFTER_PROPERTY_CHANGE, data, "", binding_result_callback)
new_kwargs = self.kwargs.copy()
if client_response:
for k, v in client_response.items():
if k in self.callback_parameters:
new_kwargs[k] = self._cast_parameter(k, v)
if 'client_response' in self.callback_parameters:
new_kwargs['client_response'] = client_response
ret = self.callback(*self.args, **new_kwargs)
for data in self._bindings:
remove_event_listener(ObservableEvent.AFTER_PROPERTY_CHANGE, data, "", binding_result_callback)
# Set the hx-swap-oob attribute on all elements returned by the callback
if isinstance(ret, (list, tuple)):
for r in ret[1:]:
if (hasattr(r, 'attrs')
and "hx-swap-oob" not in r.attrs
and r.get("id", None) is not None):
r.attrs["hx-swap-oob"] = r.attrs.get("hx-swap-oob", "true")
if not ret_from_bindings:
return ret
if isinstance(ret, (list, tuple)):
return list(ret) + ret_from_bindings
else:
return [ret] + ret_from_bindings
def __str__(self):
return f"Command({self.name})"
class LambdaCommand(Command):
def __init__(self, owner, delegate, name="LambdaCommand", description="Lambda Command"):
super().__init__(name, description, owner, delegate)
self.htmx(target=None)
def execute(self, client_response: dict = None):
return self.callback(client_response)
class CommandTemplate:
def __init__(self, key, command_type, args: list = None, kwargs: dict = None):
self.key = key
args = args or []
kwargs = kwargs or {}
self.command = CommandsManager.get_command_by_key(key) or command_type(*args, **kwargs)
class CommandsManager:
commands = {}
commands = {} # by_id
commands_by_key = {}
@staticmethod
def register(command: BaseCommand):
def register(command: Command):
CommandsManager.commands[str(command.id)] = command
if (key := command.get_key()) is not None:
CommandsManager.commands_by_key[key] = command
@staticmethod
def get_command(command_id: str) -> Optional[BaseCommand]:
def get_command(command_id: str) -> Optional[Command]:
return CommandsManager.commands.get(command_id)
@staticmethod
def get_command_by_key(key):
return CommandsManager.commands_by_key.get(key, None)
@staticmethod
def reset():
return CommandsManager.commands.clear()
CommandsManager.commands.clear()
CommandsManager.commands_by_key.clear()

View File

@@ -67,6 +67,7 @@ class BaseInstance:
self._session = session or (parent.get_session() if parent else None)
self._id = self.compute_id(_id, parent)
self._prefix = self._id if isinstance(self, (UniqueInstance, SingleInstance)) else self.compute_prefix()
self._bound_commands = {}
if auto_register:
InstancesManager.register(self._session, self)
@@ -80,16 +81,26 @@ class BaseInstance:
def get_parent(self) -> Optional['BaseInstance']:
return self._parent
def get_safe_parent_key(self):
return self.get_parent_full_id() if self.get_parent() else self.get_full_id()
def get_prefix(self) -> str:
return self._prefix
def get_full_id(self) -> str:
return f"{InstancesManager.get_session_id(self._session)}#{self._id}"
def get_full_parent_id(self) -> Optional[str]:
def get_parent_full_id(self) -> Optional[str]:
parent = self.get_parent()
return parent.get_full_id() if parent else None
def bind_command(self, command, command_to_bind):
command_name = command.name if hasattr(command, "name") else command
self._bound_commands.setdefault(command_name, []).append(command_to_bind)
def get_bound_commands(self, command_name):
return self._bound_commands.get(command_name, [])
@classmethod
def compute_prefix(cls):
return f"mf-{pascal_to_snake(cls.__name__)}"

View File

@@ -150,6 +150,7 @@ def from_parent_child_list(
id_getter: Callable = None,
label_getter: Callable = None,
parent_getter: Callable = None,
ghost_label_getter: Callable = lambda node: str(node),
ghost_color: str = GHOST_COLOR,
root_color: str | None = ROOT_COLOR
) -> tuple[list, list]:
@@ -161,6 +162,7 @@ def from_parent_child_list(
id_getter: callback to extract node ID
label_getter: callback to extract node label
parent_getter: callback to extract parent ID
ghost_label_getter: callback to extract label for ghost nodes
ghost_color: color for ghost nodes (referenced parents)
root_color: color for root nodes (nodes without parent)
@@ -225,7 +227,7 @@ def from_parent_child_list(
ghost_nodes.add(parent_id)
nodes.append({
"id": parent_id,
"label": str(parent_id),
"label": ghost_label_getter(parent_id),
"color": ghost_color
})

View File

@@ -262,6 +262,28 @@ def snake_to_pascal(name: str) -> str:
return ''.join(word.capitalize() for word in parts if word)
def flatten(*args):
"""
Flattens nested lists or tuples into a single list. This utility function takes
any number of arguments, iterating recursively through any nested lists or
tuples, and returns a flat list containing all the elements.
:param args: Arbitrary number of arguments, which can include nested lists or
tuples to be flattened.
:type args: Any
:return: A flat list containing all the elements from the input, preserving the
order of elements as they are recursively extracted from nested
structures.
:rtype: list
"""
res = []
for arg in args:
if isinstance(arg, (list, tuple)):
res.extend(flatten(*arg))
else:
res.append(arg)
return res
@utils_rt(Routes.Commands)
def post(session, c_id: str, client_response: dict = None):
"""

View File

@@ -5,7 +5,7 @@ from typing import Optional, Any
from fastcore.basics import NotStr
from fastcore.xml import FT
from myfasthtml.core.commands import BaseCommand
from myfasthtml.core.commands import Command
from myfasthtml.core.utils import quoted_str, snake_to_pascal
from myfasthtml.test.testclient import MyFT
@@ -160,7 +160,7 @@ class AttributeForbidden(ChildrenPredicate):
class HasHtmx(ChildrenPredicate):
def __init__(self, command: BaseCommand = None, **htmx_params):
def __init__(self, command: Command = None, **htmx_params):
super().__init__(None)
self.command = command
if command: