Source code for pantra.components.render.renderer_base

from __future__ import annotations

import typing
from abc import ABC, abstractmethod
from contextlib import contextmanager
from dataclasses import dataclass

from pantra.compiler import ContextInitFailed
from ..template import MacroCode
from ..context import HTMLElement, Slot

if typing.TYPE_CHECKING:
    from typing import *
    from types import CodeType
    from pantra.session import Session
    from ..context import Context
    from ..template import HTMLTemplate
    from .render_node import RenderNode

ValueOrCode = typing.Any | MacroCode

__all__ = ['RendererBase', 'ValueOrCode', 'ForLoopType']

@dataclass(slots=True)
class ForLoopType:
    parent: Self
    counter: int = 0
    counter0: int = 0
    index: Hashable = 0


[docs] class RendererBase(ABC): """Renderer base class Attributes: ctx: Local context of component templates: (class variable) dictionary of all loaded templates """ templates: ClassVar[dict[str, Any]] = {} __slots__ = ['ctx'] def __init__(self, ctx: Context): self.ctx: Context = ctx
[docs] @classmethod @abstractmethod def collect_template(cls, name: str, session: Optional[Session] = None, app: Optional[str] = None) -> Optional[HTMLTemplate | CodeType]: """Abstract method to collect component's template by given name Arguments: name: component name session: Optional session instance, used for loading feedback messages app: Optional application instance if session is not defined Returns: :class:`HTMLTemplate` or factory """
[docs] def add(self, tag_name: str, parent: RenderNode) -> HTMLElement: """Add HTML element with specified tag to this context. Arguments: tag_name: tag name ("div", "p", etc.) parent: parent node inside this context, if specified """ node = HTMLElement(tag_name, parent, context=self.ctx) return node
@contextmanager def override_ns_type(self, slot: Slot): if self.ctx.ns_type: save_ns_type = slot.ctx.ns_type slot.ctx.ns_type = self.ctx.ns_type yield slot.ctx.ns_type = save_ns_type else: yield
[docs] def build(self): """Build this node by associated template and prepare for continues rendering""" try: self.build_node(self.ctx.template, self.ctx) except ContextInitFailed: self.ctx.remove()
def trace_eval(self, macro: MacroCode, node: RenderNode, bind_ctx: Context = None): ctx = bind_ctx or self.ctx with ctx.session.node_context(node): if isinstance(macro.code, str): return ctx.locals[macro.code] if type(macro.code) == list: var_name = macro.code[0] if len(macro.code) == 1: return ctx.locals[var_name] if var_name == 'ctx': value = ctx elif var_name == 'this': value = node else: value = ctx.locals[var_name] for chunk in macro.code[1:]: value = getattr(value, chunk) return value return eval(macro.code, {'ctx': ctx, 'this': node}, ctx.locals) def makeup_value(self, source: ValueOrCode, node: RenderNode, evaluate_once: bool = False) -> Any: ctx = self.ctx # save ctx to lambda instead of self, as ctx could be temporarily changed by slot if type(source) is MacroCode: if evaluate_once or source.evaluated: return self.trace_eval(source, node, ctx) if source.reactive: ctx.locals.register_reactions(source.vars, node) return lambda: self.trace_eval(source, node, ctx) else: return source
[docs] @abstractmethod def build_node(self, template: Any, parent: Optional[RenderNode] = None) -> Optional[RenderNode]: """Build child node from given template within current context Note: Not necessary to call this method directly. Use :meth:`build` and :meth:`render` instead. Arguments: template: template node or factory parent: parent node inside this context, if specified """
[docs] def update_children(self, node: RenderNode): """Update all child nodes recursively Arguments: node: node to update """ for child in node.children: # type: RenderNode if child.context == self.ctx: self.update(child, True) else: child.update_tree()
[docs] @abstractmethod def update(self, node: RenderNode, recursive: bool = False): """Update rendered node, to sync changes Arguments: node: node to update recursive: update children recursively """
[docs] def render(self, template: str | HTMLTemplate | CodeType, parent: RenderNode = None, locals: dict = None, build: bool = True): """Render new node. Arguments: template: template to render or code parent: parent node inside this context, if specified locals: locals dict for current context build: whether to build the node """ from ..context import Context if not parent: parent = self.ctx c = Context(template, parent, locals=locals) if build: c.renderer.build() return c