Fixed FormattingRules not being applied

This commit is contained in:
2026-03-15 16:50:21 +01:00
parent feb9da50b2
commit 0c9c8bc7fa
7 changed files with 671 additions and 416 deletions

View File

@@ -220,7 +220,10 @@ class DataGrid(MultipleInstance):
name, namespace = (conf.name, conf.namespace) if conf else ("No name", "__default__")
self._settings = DatagridSettings(self, save_state=save_state, name=name, namespace=namespace)
self._state = DatagridState(self, save_state=self._settings.save_state)
self._formatting_engine = FormattingEngine()
self._formatting_provider = DatagridMetadataProvider(self._parent)
self._formatting_engine = FormattingEngine(
rule_presets_provider=lambda: self._formatting_provider.rule_presets
)
self._columns = None
self.commands = Commands(self)
@@ -268,8 +271,7 @@ class DataGrid(MultipleInstance):
# self._columns_manager.bind_command("SaveColumnDetails", self.commands.on_column_changed())
if self._settings.enable_formatting:
provider = DatagridMetadataProvider(self._parent)
completion_engine = FormattingCompletionEngine(provider, self.get_table_name())
completion_engine = FormattingCompletionEngine(self._formatting_provider, self.get_table_name())
editor_conf = DslEditorConf(engine_id=completion_engine.get_id())
dsl = FormattingDSL()
self._formatting_editor = DataGridFormattingEditor(self,

View File

@@ -140,7 +140,7 @@ class HierarchicalCanvasGraph(MultipleInstance):
self._query.bind_command("CancelQuery", self.commands.apply_filter())
# Add Menu
self._menu = Menu(self, conf=MenuConf(["ResetView"]))
self._menu = Menu(self, conf=MenuConf(["ResetView"]), _id="-menu")
logger.debug(f"HierarchicalCanvasGraph created with id={self._id}, "
f"nodes={len(conf.nodes)}, edges={len(conf.edges)}")

View File

@@ -15,12 +15,12 @@ from myfasthtml.core.formatting.dataclasses import RulePreset
from myfasthtml.core.formatting.presets import (
DEFAULT_FORMATTER_PRESETS, DEFAULT_STYLE_PRESETS, DEFAULT_RULE_PRESETS,
)
from myfasthtml.core.instances import SingleInstance, InstancesManager
from myfasthtml.core.instances import UniqueInstance, InstancesManager
logger = logging.getLogger(__name__)
class DatagridMetadataProvider(SingleInstance, BaseMetadataProvider):
class DatagridMetadataProvider(UniqueInstance, BaseMetadataProvider):
"""Concrete session-scoped metadata provider for DataGrid DSL engines.
Implements BaseMetadataProvider by delegating live data queries to
@@ -36,8 +36,7 @@ class DatagridMetadataProvider(SingleInstance, BaseMetadataProvider):
all_tables_formats: Global format rules applied to all tables.
"""
def __init__(self, parent=None, session: Optional[dict] = None,
_id: Optional[str] = None):
def __init__(self, parent=None, session: Optional[dict] = None, _id: Optional[str] = None):
super().__init__(parent, session, _id)
self.style_presets: dict = DEFAULT_STYLE_PRESETS.copy()
self.formatter_presets: dict = DEFAULT_FORMATTER_PRESETS.copy()

View File

@@ -31,7 +31,8 @@ class FormattingEngine:
style_presets: dict = None,
formatter_presets: dict = None,
rule_presets: dict = None,
lookup_resolver: Callable[[str, str, str], dict] = None
lookup_resolver: Callable[[str, str, str], dict] = None,
rule_presets_provider: Callable[[], dict] = None,
):
"""
Initialize the FormattingEngine.
@@ -41,11 +42,20 @@ class FormattingEngine:
formatter_presets: Custom formatter presets. If None, uses defaults.
rule_presets: Named rule presets (list of FormatRule dicts). If None, uses defaults.
lookup_resolver: Function for resolving enum datagrid sources.
rule_presets_provider: Callable returning the current rule_presets dict.
When provided, takes precedence over rule_presets on every apply_format call.
Use this to keep the engine in sync with a shared provider.
"""
self._condition_evaluator = ConditionEvaluator()
self._style_resolver = StyleResolver(style_presets)
self._formatter_resolver = FormatterResolver(formatter_presets, lookup_resolver)
self._rule_presets = rule_presets if rule_presets is not None else DEFAULT_RULE_PRESETS
self._rule_presets_provider = rule_presets_provider
def _get_rule_presets(self) -> dict:
if self._rule_presets_provider is not None:
return self._rule_presets_provider()
return self._rule_presets
def apply_format(
self,
@@ -99,8 +109,8 @@ class FormattingEngine:
"""
Replace any FormatRule that references a rule preset with the preset's rules.
A rule is a rule preset reference when its formatter has a preset name
that exists in rule_presets (and not in formatter_presets).
A rule is a rule preset reference when its formatter or style has a preset name
that exists in rule_presets.
Args:
rules: Original list of FormatRule
@@ -108,22 +118,26 @@ class FormattingEngine:
Returns:
Expanded list with preset references replaced by their FormatRules
"""
rule_presets = self._get_rule_presets()
expanded = []
for rule in rules:
preset_name = self._get_rule_preset_name(rule)
preset_name = self._get_rule_preset_name(rule, rule_presets)
if preset_name:
expanded.extend(self._rule_presets[preset_name].rules)
expanded.extend(rule_presets[preset_name].rules)
else:
expanded.append(rule)
return expanded
def _get_rule_preset_name(self, rule: FormatRule) -> str | None:
"""Return the preset name if the rule's formatter references a rule preset, else None."""
if rule.formatter is None:
return None
preset = getattr(rule.formatter, "preset", None)
if preset and preset in self._rule_presets:
return preset
def _get_rule_preset_name(self, rule: FormatRule, rule_presets: dict) -> str | None:
"""Return the preset name if the rule references a rule preset via format() or style(), else None."""
if rule.formatter is not None:
preset = getattr(rule.formatter, "preset", None)
if preset and preset in rule_presets:
return preset
if rule.style is not None:
preset = getattr(rule.style, "preset", None)
if preset and preset in rule_presets:
return preset
return None
def _get_matching_rules(

View File

@@ -5,7 +5,7 @@ from typing import Optional, Literal
from myfasthtml.controls.helpers import Ids
from myfasthtml.core.commands import BoundCommand, Command
from myfasthtml.core.constants import NO_DEFAULT_VALUE
from myfasthtml.core.utils import pascal_to_snake, get_class, snake_to_pascal
from myfasthtml.core.utils import pascal_to_snake, get_class, snake_to_pascal, debug_session
VERBOSE_VERBOSE = False
@@ -36,7 +36,13 @@ class BaseInstance:
session = args[1] if len(args) > 1 and isinstance(args[1], dict) else kwargs.get("session", None)
_id = args[2] if len(args) > 2 and isinstance(args[2], str) else kwargs.get("_id", None)
if VERBOSE_VERBOSE:
logger.debug(f" parent={parent}, session={session}, _id={_id}")
logger.debug(f" parent={parent}, session={debug_session(session)}, _id={_id}")
# for UniqueInstance, the parent is always the ultimate root parent
if issubclass(cls, UniqueInstance):
parent = BaseInstance.get_ultimate_root_parent(parent)
if VERBOSE_VERBOSE:
logger.debug(f" UniqueInstance detected. parent is set to ultimate root {parent=}")
# Compute _id
_id = cls.compute_id(_id, parent)
@@ -163,7 +169,7 @@ class BaseInstance:
def compute_id(cls, _id: Optional[str], parent: Optional['BaseInstance']):
if _id is None:
prefix = cls.compute_prefix()
if issubclass(cls, SingleInstance):
if issubclass(cls, (SingleInstance, UniqueInstance)):
_id = prefix
else:
_id = f"{prefix}-{str(uuid.uuid4())}"
@@ -173,6 +179,17 @@ class BaseInstance:
return f"{parent.get_id()}{_id}"
return _id
@staticmethod
def get_ultimate_root_parent(instance):
if instance is None:
return None
parent = instance
while True:
if parent.get_parent() is None:
return parent
parent = parent.get_parent()
class SingleInstance(BaseInstance):
@@ -200,7 +217,7 @@ class UniqueInstance(BaseInstance):
_id: Optional[str] = None,
auto_register: bool = True,
on_init=None):
super().__init__(parent, session, _id, auto_register)
super().__init__(BaseInstance.get_ultimate_root_parent(parent), session, _id, auto_register)
if on_init is not None:
on_init()