Skip to content

Commit

Permalink
Improve types around template rendering (#258)
Browse files Browse the repository at this point in the history
Big improvements here are:

- `template.Template.render` now returns `SafeText` instead of `Any`.
- [`DjangoDivFormRenderer`](https://docs.djangoproject.com/en/4.2/ref/forms/renderers/#django.forms.renderers.DjangoDivFormRenderer) and [`Jinja2DivFormRenderer`](https://docs.djangoproject.com/en/4.2/ref/forms/renderers/#django.forms.renderers.Jinja2DivFormRenderer) in `forms.renderers`.
- A `template.backends.base._BaseTemplate` [protocol class](https://docs.python.org/3/library/typing.html#typing.Protocol) that imitates the abstract [`BaseTemplate` class from DEP 182](https://github.com/django/deps/blob/main/final/0182-multiple-template-engines.rst#backends-api). This is needed because there is no real parent class from which each template backend inherits from, they implement their own `Template` classes from scratch. So, `BaseEngine` needed a return type for its abstract methods, which is what `_BaseTemplate` provides.

Other changes include reducing repetition, adding more specific Template types for the different backends, using more generic `Mapping` type instead of `dict` for template contexts.
  • Loading branch information
noelleleigh authored Aug 8, 2024
1 parent d826549 commit b36b99f
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 40 deletions.
39 changes: 26 additions & 13 deletions django-stubs/forms/renderers.pyi
Original file line number Diff line number Diff line change
@@ -1,27 +1,40 @@
from collections.abc import Mapping
from typing import Any
from typing_extensions import override

from django.template import Template
from django.template.backends.base import BaseEngine
from django.http.request import HttpRequest
from django.template.backends.base import BaseEngine, _BaseTemplate
from django.template.backends.django import Template as DjangoTemplate
from django.template.backends.jinja2 import Template as Jinja2Template
from django.utils.safestring import SafeText

ROOT: Any

def get_default_renderer() -> DjangoTemplates: ...
def get_default_renderer() -> BaseRenderer: ...

class BaseRenderer:
def get_template(self, template_name: str) -> Any: ...
form_template_name: str
formset_template_name: str
def get_template(self, template_name: str) -> _BaseTemplate: ...
def render(
self, template_name: str, context: dict[str, Any], request: None = ...
) -> str: ...
self,
template_name: str,
context: Mapping[str, Any],
request: HttpRequest | None = ...,
) -> SafeText: ...

class EngineMixin:
def get_template(self, template_name: str) -> Any: ...
backend: BaseEngine
def get_template(self, template_name: str) -> _BaseTemplate: ...
@property
def engine(self) -> BaseEngine: ...

class DjangoTemplates(EngineMixin, BaseRenderer):
backend: Any = ...
@override
def get_template(self, template_name: str) -> DjangoTemplate: ...

class Jinja2(EngineMixin, BaseRenderer):
backend: Any = ...
@override
def get_template(self, template_name: str) -> Jinja2Template: ...

class TemplatesSetting(BaseRenderer):
def get_template(self, template_name: str) -> Template: ...
class DjangoDivFormRenderer(DjangoTemplates): ...
class Jinja2DivFormRenderer(Jinja2): ...
class TemplatesSetting(BaseRenderer): ...
19 changes: 14 additions & 5 deletions django-stubs/template/backends/base.pyi
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
from collections.abc import Iterator, Mapping
from typing import Any
from typing import Any, Protocol

from django.template.base import Template
from django.http.request import HttpRequest
from django.utils.safestring import SafeText

# Source: https://github.com/django/deps/blob/main/final/0182-multiple-template-engines.rst#backends-api
class _BaseTemplate(Protocol):
def render(
self,
context: Mapping[str, Any] | None = ...,
request: HttpRequest | None = ...,
) -> SafeText | str: ...

class BaseEngine:
name: str = ...
dirs: list[str] = ...
app_dirs: bool = ...
def __init__(self, params: Mapping[str, Any]) -> None: ...
@property
def app_dirname(self) -> str | None: ...
def from_string(self, template_code: str) -> Template: ...
def get_template(self, template_name: str) -> Template | None: ...
def app_dirname(self) -> str: ...
def from_string(self, template_code: str) -> _BaseTemplate: ...
def get_template(self, template_name: str) -> _BaseTemplate: ...
@property
def template_dirs(self) -> tuple[str]: ...
def iter_template_filenames(self, template_name: str) -> Iterator[str]: ...
24 changes: 18 additions & 6 deletions django-stubs/template/backends/django.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from collections.abc import Iterator
from typing import Any
from collections.abc import Iterator, Mapping
from typing import Any, NoReturn
from typing_extensions import override

from django.http.request import HttpRequest
from django.template import base
from django.template.engine import Engine
from django.template.exceptions import TemplateDoesNotExist
from django.utils.safestring import SafeText

from .base import BaseEngine

Expand All @@ -13,18 +16,27 @@ class DjangoTemplates(BaseEngine):
def get_templatetag_libraries(
self, custom_libraries: dict[str, str]
) -> dict[str, str]: ...
@override
def from_string(self, template_code: str) -> Template: ...
@override
def get_template(self, template_name: str) -> Template: ...

class Template:
template: base.Template
backend: BaseEngine
def __init__(self, template: base.Template, backend: BaseEngine) -> None: ...
backend: DjangoTemplates
def __init__(self, template: base.Template, backend: DjangoTemplates) -> None: ...
@property
def origin(self) -> base.Origin: ...
def render(self, context: Any = ..., request: Any = ...) -> str: ...
def render(
self,
context: Mapping[str, Any] | None = ...,
request: HttpRequest | None = ...,
) -> SafeText: ...

def copy_exception(
exc: TemplateDoesNotExist, backend: DjangoTemplates | None = ...
) -> TemplateDoesNotExist: ...
def reraise(exc: TemplateDoesNotExist, backend: DjangoTemplates) -> Any: ...
def reraise(exc: TemplateDoesNotExist, backend: DjangoTemplates) -> NoReturn: ...
def get_template_tag_modules() -> Iterator[tuple[str, str]]: ...
def get_installed_libraries() -> dict[str, str]: ...
def get_package_libraries(pkg: Any) -> Iterator[str]: ...
13 changes: 7 additions & 6 deletions django-stubs/template/backends/dummy.pyi
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import string
from collections.abc import Mapping
from typing import Any
from typing_extensions import override

from django.http.request import HttpRequest

from .base import BaseEngine

class TemplateStrings(BaseEngine):
template_dirs: tuple[str]
def __init__(
self, params: dict[str, dict[Any, Any] | list[Any] | bool | str]
) -> None: ...
@override
def from_string(self, template_code: str) -> Template: ...
@override
def get_template(self, template_name: str) -> Template: ...

class Template(string.Template):
template: str
def render(
self,
context: dict[str, str] | None = ...,
context: Mapping[str, Any] | None = ...,
request: HttpRequest | None = ...,
) -> str: ...
22 changes: 20 additions & 2 deletions django-stubs/template/backends/jinja2.pyi
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
from collections.abc import Callable
from collections.abc import Callable, Mapping
from typing import Any
from typing_extensions import override

from django.http.request import HttpRequest
from django.template import base
from django.template.exceptions import TemplateSyntaxError
from django.utils.safestring import SafeText

from .base import BaseEngine

class Jinja2(BaseEngine):
context_processors: list[str] = ...
def __init__(self, params: dict[str, Any]) -> None: ...
@override
def from_string(self, template_code: str) -> Template: ...
@override
def get_template(self, template_name: str) -> Template: ...
@property
def template_context_processors(self) -> list[Callable[..., Any]]: ...

class Template:
template: base.Template
backend: Jinja2
origin: Origin
def __init__(self, template: base.Template, backend: Jinja2) -> None: ...
def render(
self,
context: Mapping[str, Any] | None = ...,
request: HttpRequest | None = ...,
) -> SafeText: ...

class Origin:
name: str = ...
template_name: str | None = ...
Expand Down
6 changes: 3 additions & 3 deletions django-stubs/template/backends/utils.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import Any
from collections.abc import Callable

from django.http.request import HttpRequest
from django.utils.safestring import SafeText

def csrf_input(request: HttpRequest) -> SafeText: ...

csrf_input_lazy: Any
csrf_token_lazy: Any
csrf_input_lazy: Callable[[HttpRequest], SafeText]
csrf_token_lazy: Callable[[HttpRequest], SafeText]
2 changes: 1 addition & 1 deletion django-stubs/template/base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class Template:
self,
context: Context | dict[str, Any] | None = ...,
request: HttpRequest | None = ...,
) -> Any: ...
) -> SafeText: ...
def compile_nodelist(self) -> NodeList: ...
def get_exception_info(
self, exception: Exception, token: Token
Expand Down
9 changes: 5 additions & 4 deletions django-stubs/template/loader.pyi
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
from typing import Any

from django.http.request import HttpRequest
from django.template.backends.django import Template
from django.template.backends.base import _BaseTemplate
from django.template.exceptions import ( # noqa: F401
TemplateDoesNotExist as TemplateDoesNotExist,
)
from django.utils.safestring import SafeText

from . import engines as engines # noqa: F401

def get_template(template_name: str, using: str | None = ...) -> Template: ...
def get_template(template_name: str, using: str | None = ...) -> _BaseTemplate: ...
def select_template(
template_name_list: list[str] | str, using: str | None = ...
) -> Template: ...
) -> _BaseTemplate: ...
def render_to_string(
template_name: list[str] | str,
context: dict[str, Any] | None = ...,
request: HttpRequest | None = ...,
using: str | None = ...,
) -> str: ...
) -> SafeText: ...

0 comments on commit b36b99f

Please sign in to comment.