Source code for jinja2_fragments

from __future__ import annotations

import typing
from asyncio import AbstractEventLoop

from jinja2 import Environment, pass_context
from jinja2.runtime import Context
from markupsafe import Markup


[docs] class BlockNotFoundError(Exception):
[docs] def __init__(self, block_name: str, template_name: str, message: str | None = None): self.block_name = block_name self.template_name = template_name super().__init__( message or f"Block {self.block_name!r} not found in template {self.template_name!r}" )
[docs] async def render_block_async( environment: Environment, template_name: str, block_name: str, *args: typing.Any, **kwargs: typing.Any, ) -> str: """ This works similar to :func:`render_block` but returns a coroutine that when awaited returns the entire rendered template block string. This requires the environment async feature to be enabled. """ if not environment.is_async: raise RuntimeError("The environment was not created with async mode enabled.") template = environment.get_template(template_name) try: block_render_func = template.blocks[block_name] except KeyError: raise BlockNotFoundError(block_name, template_name) ctx = template.new_context(dict(*args, **kwargs)) try: return environment.concat( # type: ignore [n async for n in block_render_func(ctx)] # type: ignore ) except Exception: return environment.handle_exception()
[docs] async def render_blocks_async( environment: Environment, template_name: str, block_names: typing.Sequence[str], *args: typing.Any, **kwargs: typing.Any, ) -> str: """ This works similar to :func:`render_blocks` but returns a coroutine that when awaited returns every rendered template block as a single string. This requires the environment async feature to be enabled. """ if not environment.is_async: raise RuntimeError("The environment was not created with async mode enabled.") return await _render_template_blocks_async( environment, template_name, block_names, *args, **kwargs )
[docs] def render_block( environment: Environment, template_name: str, block_name: str, *args: typing.Any, **kwargs: typing.Any, ) -> str: """ Render a specific block from a Jinja2 template with the given context. This function renders a named block from a template located in the application's template folder. The context variables passed are available within the rendered block. Custom signals are triggered before and after rendering to allow integration with additional logic. Args: template_name: The name of the Jinja2 template file. This should include only the template file's name, without the path. block_name: The name of the block defined in the template that you want to render. **context: Additional variables to be included in the template's context. These key-value pairs will be accessible inside the block. Returns: The rendered HTML output of the block. Raises: RuntimeError: If the Flask application or Jinja2 environment is not properly initialized. TemplateNotFound: If the template specified does not exist. KeyError: If the block specified is not defined in the provided template. Signals: before_render_template_block: A signal sent before rendering the block. Includes the `template_name`, `block_name`, and `context`. template_block_rendered: A signal sent after rendering the block. Includes the `template_name`, `block_name`, and `context`. """ if environment.is_async: loop, close = _get_loop() try: return loop.run_until_complete( render_block_async( environment, template_name, block_name, *args, **kwargs ) ) finally: if close: loop.close() template = environment.get_template(template_name) try: block_render_func = template.blocks[block_name] except KeyError: raise BlockNotFoundError(block_name, template_name) ctx = template.new_context(dict(*args, **kwargs)) try: return environment.concat(block_render_func(ctx)) # type: ignore except Exception: environment.handle_exception()
[docs] def render_blocks( environment: Environment, template_name: str, block_names: typing.Sequence[str], *args: typing.Any, **kwargs: typing.Any, ) -> str: """ Render multiple blocks from a Jinja2 template with the given context. This function processes and renders the specified list of blocks from a single Jinja2 template file in the template folder. The given context variables are shared across all the blocks. Args: template_name: The name of the Jinja2 template file. This should include only the template file's name, without the path. block_names: A sequence of block names from the template that you want to render. These blocks are rendered sequentially in the given order. **context: Additional variables passed as context for rendering the blocks. These variables will be available across all the blocks. Returns: A combined HTML string containing the rendered content of all blocks. Raises: RuntimeError: If the Flask application or Jinja2 environment is not properly initialized. TemplateNotFound: If the template specified does not exist. KeyError: If any of the blocks specified in `block_names` are not defined in the template. Signals: before_render_template_blocks: Triggered before rendering the list of blocks. Includes the `template_name`, `block_names`, and `context`. template_blocks_rendered: Triggered after rendering the blocks. Includes the `template_name`, `block_names`, and `context`. """ if environment.is_async: loop, close = _get_loop() try: return loop.run_until_complete( render_blocks_async( environment, template_name, block_names, *args, **kwargs ) ) finally: if close: loop.close() return _render_template_blocks( environment, template_name, block_names, *args, **kwargs )
def _render_template_blocks( environment: Environment, template_name: str, block_names: typing.Sequence[str], *args: typing.Any, **kwargs: typing.Any, ) -> str: contents: list[str] = [] template = environment.get_template(template_name) for block_name in block_names: try: block_render_func = template.blocks[block_name] except KeyError: raise BlockNotFoundError(block_name, template_name) ctx = template.new_context(dict(*args, **kwargs)) try: contents.append(environment.concat(block_render_func(ctx))) # type: ignore except Exception: environment.handle_exception() return "".join(contents) async def _render_template_blocks_async( environment: Environment, template_name: str, block_names: typing.Sequence[str], *args: typing.Any, **kwargs: typing.Any, ) -> str: if not environment.is_async: raise RuntimeError("The environment was not created with async mode enabled.") contents: list[str] = [] template = environment.get_template(template_name) for block_name in block_names: try: block_render_func = template.blocks[block_name] except KeyError: raise BlockNotFoundError(block_name, template_name) ctx = template.new_context(dict(*args, **kwargs)) try: contents.append( environment.concat( # type: ignore [n async for n in block_render_func(ctx)] # type: ignore ) ) except Exception: environment.handle_exception() return "".join(contents) def _get_loop() -> tuple[AbstractEventLoop, bool]: import asyncio close = False try: return (asyncio.get_running_loop(), close) except RuntimeError: close = True return (asyncio.new_event_loop(), close) @pass_context def _render_block_callable( context: Context, template_name: str, block_name: str, **kwargs: typing.Any, ) -> Markup: """Jinja2 template global that renders a single block from another template. The current template context is forwarded to the target block and can be overridden with keyword arguments. """ merged = {**context.get_all(), **kwargs} return Markup(render_block(context.environment, template_name, block_name, merged)) @pass_context def _render_blocks_callable( context: Context, template_name: str, block_names: typing.Sequence[str], **kwargs: typing.Any, ) -> Markup: """Jinja2 template global that renders multiple blocks from another template. The current template context is forwarded to the target blocks and can be overridden with keyword arguments. """ merged = {**context.get_all(), **kwargs} return Markup( render_blocks(context.environment, template_name, block_names, merged) ) @pass_context async def _async_render_block_callable( context: Context, template_name: str, block_name: str, **kwargs: typing.Any, ) -> Markup: """Async version of :func:`_render_block_callable`.""" merged = {**context.get_all(), **kwargs} return Markup( await render_block_async(context.environment, template_name, block_name, merged) ) @pass_context async def _async_render_blocks_callable( context: Context, template_name: str, block_names: typing.Sequence[str], **kwargs: typing.Any, ) -> Markup: """Async version of :func:`_render_blocks_callable`.""" merged = {**context.get_all(), **kwargs} return Markup( await render_blocks_async( context.environment, template_name, block_names, merged ) )
[docs] def setup_globals(environment: Environment) -> None: """Install ``render_block`` and ``render_blocks`` as Jinja2 template globals. After calling this function, templates rendered with *environment* can use ``render_block`` and ``render_blocks`` directly in expressions: .. code-block:: jinja {{ render_block("other_template.html", "block_name", key=value) }} {{ render_blocks("other_template.html", ["block_a", "block_b"], key=value) }} The current template context is automatically forwarded; extra keyword arguments override individual context variables. The correct sync or async callable is chosen based on ``environment.is_async``. Existing globals named ``render_block`` and ``render_blocks`` are preserved. Args: environment: The Jinja2 :class:`~jinja2.Environment` to modify. """ if environment.is_async: environment.globals.setdefault("render_block", _async_render_block_callable) environment.globals.setdefault("render_blocks", _async_render_blocks_callable) else: environment.globals.setdefault("render_block", _render_block_callable) environment.globals.setdefault("render_blocks", _render_blocks_callable)