Skip to content

Commit

Permalink
feat: Improve performance during rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
paveldedik committed May 30, 2024
1 parent b32ae2b commit 0059332
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 33 deletions.
26 changes: 10 additions & 16 deletions ludic/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import ABCMeta
from collections.abc import Callable, Iterator, Mapping, MutableMapping, Sequence
from collections.abc import Iterator, Mapping, MutableMapping, Sequence
from typing import (
Any,
ClassVar,
Expand Down Expand Up @@ -121,7 +121,7 @@ def _format_attributes(
) -> str:
attrs: dict[str, Any]
if is_html:
attrs = format_attrs(type(self), dict(self.attrs), is_html=True)
attrs = format_attrs(type(self), self.attrs, is_html=True)
else:
attrs = self.aliased_attrs

Expand All @@ -136,29 +136,23 @@ def _format_attributes(
for key, value in attrs.items()
)

def _format_children(
self,
format_fun: Callable[[Any], str] = format_element,
) -> str:
formatted = []
def _format_children(self) -> str:
formatted = ""
for child in self.children:
if isinstance(child, BaseElement):
if self.context and isinstance(child, BaseElement):
child.context.update(self.context)
formatted.append(format_fun(child))
return "".join(formatted)
formatted += format_element(child)
return formatted

@property
def aliased_attrs(self) -> dict[str, Any]:
"""Attributes as a dict with keys renamed to their aliases."""
return format_attrs(type(self), dict(self.attrs))
return format_attrs(type(self), self.attrs)

@property
def text(self) -> str:
"""Get the text content of the element."""
return "".join(
child.text if isinstance(child, BaseElement) else str(child)
for child in self.children
)
return "".join(getattr(child, "text", str(child)) for child in self.children)

@property
def theme(self) -> Theme:
Expand Down Expand Up @@ -215,7 +209,7 @@ def to_html(self) -> str:
dom = self
classes = list(dom.classes)

while dom != (rendered_dom := dom.render()):
while id(dom) != id(rendered_dom := dom.render()):
rendered_dom.context.update(dom.context)
dom = rendered_dom
classes += dom.classes
Expand Down
31 changes: 22 additions & 9 deletions ludic/format.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import html
import random
import re
from collections.abc import Mapping
from contextvars import ContextVar
from typing import Annotated, Any, Final, TypeVar, get_args, get_origin

Expand All @@ -20,25 +21,37 @@ def format_attr_value(key: str, value: Any, is_html: bool = False) -> str:
Returns:
str: The formatted HTML attribute.
"""

def _html_escape(value: Any) -> str:
if (
is_html
and value
and isinstance(value, str)
and getattr(value, "escape", True)
):
return html.escape(value, False) # type: ignore
return str(value)

if isinstance(value, dict):
value = ";".join(
f"{dict_key}:{html.escape(dict_value, False)}"
formatted_value = ";".join(
f"{dict_key}:{_html_escape(dict_value)}"
for dict_key, dict_value in value.items()
)
elif isinstance(value, list):
value = " ".join(html.escape(v, False) for v in value)
formatted_value = " ".join(map(_html_escape, value))
elif isinstance(value, bool):
if is_html and not key.startswith("hx"):
value = html.escape(key, False) if value else ""
formatted_value = html.escape(key, False) if value else ""
else:
value = "true" if value else "false"
elif isinstance(value, str) and getattr(value, "escape", True):
value = html.escape(value, False)
return str(value)
formatted_value = "true" if value else "false"
else:
formatted_value = _html_escape(value)

return formatted_value


def format_attrs(
attrs_type: Any, attrs: dict[str, Any], is_html: bool = False
attrs_type: Any, attrs: Mapping[str, Any], is_html: bool = False
) -> dict[str, Any]:
"""Format the given attributes according to the element's attributes.
Expand Down
16 changes: 9 additions & 7 deletions ludic/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections.abc import Hashable
from functools import lru_cache
from typing import (
Annotated,
Expand All @@ -11,43 +12,44 @@
_T = TypeVar("_T", covariant=True)


def get_element_generic_args(cls_or_obj: Any) -> tuple[type, ...] | None:
@lru_cache
def get_element_generic_args(obj_or_type: Hashable) -> tuple[type, ...] | None:
"""Get the generic arguments of the element class.
Args:
cls_or_obj (Any): The element to get the generic arguments of.
obj_or_type (Any): The element to get the generic arguments of.
Returns:
dict[str, Any] | None: The generic arguments or :obj:`None`.
"""
from ludic.base import BaseElement

if hints := getattr(cls_or_obj, "__annotations__", None):
if hints := getattr(obj_or_type, "__annotations__", None):
children = hints.get("children")
attrs = hints.get("attrs")
if attrs and children:
return (children, attrs)

for base in getattr(cls_or_obj, "__orig_bases__", []):
for base in getattr(obj_or_type, "__orig_bases__", []):
if issubclass(get_origin(base), BaseElement):
return get_args(base)
return None


@lru_cache
def get_element_attrs_annotations(
cls_or_obj: Any, include_extras: bool = False
obj_or_type: Hashable, include_extras: bool = False
) -> dict[str, Any]:
"""Get the annotations of the element.
Args:
cls_or_obj (type[Any]): The element to get the annotations of.
obj_or_type (Hashable): The element to get the annotations of.
include_extras (bool): Whether to include extra annotation info.
Returns:
dict[str, Any]: The attributes' annotations of the element.
"""
if (args := get_element_generic_args(cls_or_obj)) is not None:
if (args := get_element_generic_args(obj_or_type)) is not None:
return get_type_hints(args[-1], include_extras=include_extras)
return {}

Expand Down
2 changes: 1 addition & 1 deletion ludic/web/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def url_for(self, endpoint: type[RoutedProtocol] | str, **path_params: Any) -> U

if inspect.isclass(endpoint) and issubclass(endpoint, Endpoint):
endpoint_generic_args = get_element_generic_args(endpoint)
self_generic_args = get_element_generic_args(self)
self_generic_args = get_element_generic_args(type(self))

if (
endpoint_generic_args
Expand Down

0 comments on commit 0059332

Please sign in to comment.