Skip to content

Commit

Permalink
Remove file operations from renderers
Browse files Browse the repository at this point in the history
  • Loading branch information
bartfeenstra committed Aug 29, 2024
1 parent d5b8625 commit 416d817
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 20 deletions.
30 changes: 26 additions & 4 deletions betty/jinja2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@
Mapping,
)
from threading import Lock
from typing import Callable, Any, cast, TYPE_CHECKING, TypeAlias, final, Awaitable, Self
from typing import (
Callable,
Any,
cast,
TYPE_CHECKING,
TypeAlias,
final,
Awaitable,
Self,
TypeVar,
)

import aiofiles
from aiofiles import os as aiofiles_os
Expand All @@ -34,9 +44,8 @@
from betty.locale.localizable import Localizable, plain
from betty.locale.localizer import DEFAULT_LOCALIZER
from betty.locale.localizer import Localizer
from betty.plugin import Plugin
from betty.project.factory import ProjectDependentFactory
from betty.render import Renderer
from betty.render import RendererPlugin, MediaTypey
from betty.serde.dump import Dumpable, DumpMapping, VoidableDump, Dump
from betty.typing import Void

Expand All @@ -54,6 +63,8 @@
MutableSequence,
)

_MediaTypeyT = TypeVar("_MediaTypeyT", bound=MediaTypey)


def context_project(context: Context) -> Project:
"""
Expand Down Expand Up @@ -396,7 +407,7 @@ def _init_extensions(self) -> None:


@final
class Jinja2Renderer(Renderer, ProjectDependentFactory, Plugin):
class Jinja2Renderer(RendererPlugin, ProjectDependentFactory):
"""
Render content as Jinja2 templates.
"""
Expand Down Expand Up @@ -425,6 +436,17 @@ def new_for_project(cls, project: Project) -> Self:
def file_extensions(self) -> set[str]:
return {".j2"}

@override
async def render(
self,
content: str,
media_typey: _MediaTypeyT,
*,
job_context: Context | None = None,
localizer: Localizer | None = None,
) -> tuple[str, _MediaTypeyT]:
pass

@override
async def render_file(
self,
Expand Down
58 changes: 46 additions & 12 deletions betty/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,88 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import final, TYPE_CHECKING
from pathlib import Path
from typing import final, TYPE_CHECKING, Union, TypeAlias

from mypy.graph_utils import TypeVar
from typing_extensions import override

from betty.media_type import MediaType
from betty.plugin import Plugin
from betty.plugin.entry_point import EntryPointPluginRepository
from betty.typing import internal

if TYPE_CHECKING:
from betty.plugin import PluginRepository, Plugin
from betty.plugin import PluginRepository
from betty.locale.localizer import Localizer
from betty.job import Context
from pathlib import Path
from collections.abc import Sequence


MediaTypey: TypeAlias = Union[MediaType, Path]
_MediaTypeyT = TypeVar("_MediaTypeyT", bound=MediaTypey)


class Renderer(ABC):
"""
Render content to HTML.
Read more about :doc:`/development/plugin/renderer`.
See also :py:class:`betty.render.RendererPlugin`.
"""

@property
@abstractmethod
def media_types(self) -> set[MediaType]:
"""
The media types this renderer can render.
"""
pass

@property
@abstractmethod
def file_extensions(self) -> set[str]:
"""
The extensions of the files this renderer can render.
The extensions (including leading dot) of the files this renderer can render.
"""
pass

# @todo How truthfully can we return a changed media type?
# @todo With file names, we can resolve nested extensions, and therefore nested renderers
# @todo But also, we kind of assume each renderer returns HTML.
# @todo So, maybe, no longer allow renderers to be chained.
# @todo However, we do a few *.json.j2 files still. We'd have to convert those to Python code first
# @todo and then restrict renderers to HTML only.
@abstractmethod
async def render_file(
async def render(
self,
file_path: Path,
content: str,
media_typey: _MediaTypeyT,
*,
job_context: Context | None = None,
localizer: Localizer | None = None,
) -> Path:
) -> tuple[str, _MediaTypeyT]:
"""
Render a single file.
Render content.
:return: The file's new path, which may have been changed, e.g. a
renderer-specific extension may have been stripped from the end.
:return: The rendered content, and its new media type or path, which may have been changed.
"""
pass


RENDERER_REPOSITORY: PluginRepository[Renderer & Plugin] = EntryPointPluginRepository(
class RendererPlugin(Renderer, Plugin):
"""
A renderer as a plugin.
Read more about :doc:`/development/plugin/renderer`.
"""

@override
@property
def media_types(self) -> set[MediaType]:
return {f"text/x.betty.{self.plugin_id()}"}


RENDERER_REPOSITORY: PluginRepository[RendererPlugin] = EntryPointPluginRepository(
"betty.renderer"
)
"""
Expand Down
7 changes: 3 additions & 4 deletions documentation/development/plugin/renderer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,15 @@ Renderers convert textual content to HTML. A renderer is often built to support
Creating a renderer
-------------------

#. Create a new class that extends both :py:class:`betty.render.Renderer` and :py:class:`betty.plugin.Plugin` and implements the abstract methods,
#. Create a new class that extends :py:class:`betty.render.RendererPlugin` and implements the abstract methods,
for example:

.. code-block:: python
from typing import override
from betty.plugin import Plugin
from betty.render import Renderer
from betty.render import RendererPlugin
class MyRenderer(Renderer, Plugin):
class MyRenderer(RendererPlugin):
@override
@classmethod
def plugin_id(cls) -> str:
Expand Down

0 comments on commit 416d817

Please sign in to comment.