Skip to content

SOURCE CODE pipen.template DOCS

"""Template adaptor for pipen"""
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Mapping, Type

from liquid import Liquid

from .defaults import TEMPLATE_ENTRY_GROUP
from .exceptions import NoSuchTemplateEngineError, WrongTemplateEnginTypeError
from .utils import is_subclass, load_entrypoints

__all__ = [
    "Template",
    "TemplateLiquid",
    "TemplateJinja2",
    "get_template_engine",
]


class Template(ABC):DOCS
    """Base class wrapper to wrap template for pipen"""

    def __init__(
        self,
        source: Any,
        **kwargs: Any,
    ):
        """Template construct"""
        self.engine: Any = None

    def render(self, data: Mapping[str, Any] = None) -> str:DOCS
        """
        Render the template
        @parmas:
            data (dict): The data used to render
        """
        return self._render(data or {})

    @abstractmethod
    def _render(self, data: Mapping[str, Any]) -> str:
        """Implement rendering"""


class TemplateLiquid(Template):DOCS
    """Liquidpy template wrapper."""

    name = "liquid"

    def __init__(
        self,
        source: Any,
        **kwargs: Any,
    ):
        """Initiate the engine with source and envs

        Args:
            source: The souce text
            envs: The env data
            **kwargs: Other arguments for Liquid
        """
        super().__init__(source)
        self.engine = Liquid(
            source,
            from_file=False,
            mode="wild",
            **kwargs,
        )

    def _render(self, data: Mapping[str, Any]) -> str:
        """Render the template

        Args:
            data: The data used for rendering

        Returns
            The rendered string
        """
        return self.engine.render(data)


class TemplateJinja2(Template):DOCS
    """Jinja2 template wrapper"""

    name = "jinja2"

    def __init__(
        self,
        source: Any,
        **kwargs: Any,
    ):
        """Initiate the engine with source and envs

        Args:
            source: The souce text
            envs: The env data
            **kwargs: Other arguments for jinja2.Template
        """
        import jinja2

        super().__init__(source)
        filters = kwargs.pop("filters", {})
        envs = kwargs.pop("globals", {})
        filters = kwargs.pop("filters", {})
        self.engine = jinja2.Template(source, **kwargs)
        self.engine.globals.update(envs)
        self.engine.environment.filters.update(filters)

    def _render(self, data: Mapping[str, Any]) -> str:
        """Render the template

        Args:
            data: The data used for rendering

        Retuens:
            The rendered string
        """
        return self.engine.render(data)


def get_template_engine(template: str | Type[Template]) -> Type[Template]:DOCS
    """Get the template engine by name or the template engine itself

    Args:
        template: The name of the template engine or the template engine itself

    Returns:
        The template engine
    """
    if is_subclass(template, Template):
        return template  # type: ignore

    if template == "liquid":
        return TemplateLiquid

    if template == "jinja2":
        return TemplateJinja2

    for name, obj in load_entrypoints(
        TEMPLATE_ENTRY_GROUP
    ):  # pragma: no cover
        if name == template:
            if not is_subclass(obj, Template):
                raise WrongTemplateEnginTypeError(
                    "Template engine should be a subclass of "
                    "pipen.templates.Template."
                )
            return obj

    raise NoSuchTemplateEngineError(str(template))