From 0f43c9643cf6c67b5fa0ee7b8f74bd9c41c74238 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Wed, 9 Oct 2024 15:53:28 +0200 Subject: [PATCH 1/8] Annotate `context` argument of `make_context()` The implementation of `make_context()` explicitly rejects everything other than `dict` or `None` by raising `TypeError`. --- django-stubs/template/context.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django-stubs/template/context.pyi b/django-stubs/template/context.pyi index a9c0302cc..e61e6e607 100644 --- a/django-stubs/template/context.pyi +++ b/django-stubs/template/context.pyi @@ -88,4 +88,4 @@ class RequestContext(Context): def bind_template(self, template: Template) -> Iterator[None]: ... def new(self, values: _ContextValues | None = None) -> RequestContext: ... -def make_context(context: Any, request: HttpRequest | None = None, **kwargs: Any) -> Context: ... +def make_context(context: dict[str, Any] | None, request: HttpRequest | None = None, **kwargs: Any) -> Context: ... From 2a070e0a3306875b2d8e436ca4f96083ae36224b Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Wed, 9 Oct 2024 16:00:05 +0200 Subject: [PATCH 2/8] Drop `Context` from allowed types of template render method [DEP 182](https://github.com/django/deps/blob/main/final/0182-multiple-template-engines.rst#backends-api) states: "If `context` is provided, it must be a `dict`.". While the `Jinja2` and `TemplateStrings` backends support arbitrary mappings, `DjangoTemplates` calls `make_context()`, which only supports `dict` (and `None`). --- django-stubs/template/backends/base.pyi | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/django-stubs/template/backends/base.pyi b/django-stubs/template/backends/base.pyi index 476b1854e..f9b58e427 100644 --- a/django-stubs/template/backends/base.pyi +++ b/django-stubs/template/backends/base.pyi @@ -2,7 +2,6 @@ from collections.abc import Iterator, Mapping from typing import Any, Protocol, type_check_only from django.http.request import HttpRequest -from django.template.base import Context from django.utils.functional import cached_property from django.utils.safestring import SafeString @@ -23,6 +22,6 @@ class BaseEngine: class _EngineTemplate(Protocol): def render( self, - context: Context | dict[str, Any] | None = ..., + context: dict[str, Any] | None = ..., request: HttpRequest | None = ..., ) -> SafeString: ... From 0b0913dc22a1fdac41d54789170a5a6d8d1ddb40 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Wed, 9 Oct 2024 16:32:12 +0200 Subject: [PATCH 3/8] Do not require template render method to return a `SafeString` Only the Django Template Language backend outputs `SafeString`. --- django-stubs/template/backends/base.pyi | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/django-stubs/template/backends/base.pyi b/django-stubs/template/backends/base.pyi index f9b58e427..76c1a194d 100644 --- a/django-stubs/template/backends/base.pyi +++ b/django-stubs/template/backends/base.pyi @@ -3,7 +3,6 @@ from typing import Any, Protocol, type_check_only from django.http.request import HttpRequest from django.utils.functional import cached_property -from django.utils.safestring import SafeString class BaseEngine: name: str @@ -24,4 +23,4 @@ class _EngineTemplate(Protocol): self, context: dict[str, Any] | None = ..., request: HttpRequest | None = ..., - ) -> SafeString: ... + ) -> str: ... From 9511a8546082b239aeddc765863598150434a66a Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Wed, 9 Oct 2024 16:36:42 +0200 Subject: [PATCH 4/8] Provide arguments types for Jinja2 template render method All of the argument types are Django types, so there is no need to avoid annotating them. Only the Jinja2 native template type is unknown to us. --- django-stubs/template/backends/jinja2.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django-stubs/template/backends/jinja2.pyi b/django-stubs/template/backends/jinja2.pyi index cae83ba8d..54c35e710 100644 --- a/django-stubs/template/backends/jinja2.pyi +++ b/django-stubs/template/backends/jinja2.pyi @@ -2,6 +2,7 @@ from collections.abc import Callable from typing import Any from _typeshed import Incomplete +from django.http.request import HttpRequest from django.template.exceptions import TemplateSyntaxError from django.utils.functional import cached_property @@ -24,6 +25,6 @@ class Template: backend: Jinja2 origin: Origin def __init__(self, template: Incomplete, backend: Jinja2) -> None: ... - def render(self, context: Incomplete | None = ..., request: Incomplete | None = ...) -> Incomplete: ... + def render(self, context: dict[str, Any] | None = ..., request: HttpRequest | None = ...) -> str: ... def get_exception_info(exception: TemplateSyntaxError) -> dict[str, Any]: ... From 292e517fb5dae340df3823878e3eb38348e7ea6d Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Wed, 9 Oct 2024 16:10:46 +0200 Subject: [PATCH 5/8] Allow non-string values in dummy template render context The implementation of the `render()` method uses the standard library method `string.Template.safe_substitute()` to do the actual formatting, which accepts other value types than `str` and simply calls `__str__()` on them. The `safe_substitute()` method is annotated in typeshed with `object` as the value type for the mapping. However, as `_EngineTemplate` uses `Any` instead, I went with that. --- django-stubs/template/backends/dummy.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django-stubs/template/backends/dummy.pyi b/django-stubs/template/backends/dummy.pyi index a1fb2b083..a334f7ea1 100644 --- a/django-stubs/template/backends/dummy.pyi +++ b/django-stubs/template/backends/dummy.pyi @@ -10,4 +10,4 @@ class TemplateStrings(BaseEngine): class Template(string.Template): template: str - def render(self, context: dict[str, str] | None = ..., request: HttpRequest | None = ...) -> str: ... + def render(self, context: dict[str, Any] | None = ..., request: HttpRequest | None = ...) -> str: ... From 509e36813f0ed418f8cbeba9b529292b678ecb49 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Wed, 9 Oct 2024 16:40:51 +0200 Subject: [PATCH 6/8] Make it explicit that the dummy template class adheres to the protocol --- django-stubs/template/backends/dummy.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django-stubs/template/backends/dummy.pyi b/django-stubs/template/backends/dummy.pyi index a334f7ea1..f95a2210d 100644 --- a/django-stubs/template/backends/dummy.pyi +++ b/django-stubs/template/backends/dummy.pyi @@ -3,11 +3,11 @@ from typing import Any from django.http.request import HttpRequest -from .base import BaseEngine +from .base import BaseEngine, _EngineTemplate class TemplateStrings(BaseEngine): def __init__(self, params: dict[str, dict[Any, Any] | list[Any] | bool | str]) -> None: ... -class Template(string.Template): +class Template(string.Template, _EngineTemplate): template: str def render(self, context: dict[str, Any] | None = ..., request: HttpRequest | None = ...) -> str: ... From 01d0f81d96fff83f0d18770eebac425761e42f25 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Wed, 9 Oct 2024 14:57:05 +0200 Subject: [PATCH 7/8] Make `from_string()` and `get_template()` return backend-specific template --- django-stubs/template/backends/django.pyi | 2 ++ django-stubs/template/backends/dummy.pyi | 2 ++ django-stubs/template/backends/jinja2.pyi | 2 ++ 3 files changed, 6 insertions(+) diff --git a/django-stubs/template/backends/django.pyi b/django-stubs/template/backends/django.pyi index 7f88df5ab..6e99b01d9 100644 --- a/django-stubs/template/backends/django.pyi +++ b/django-stubs/template/backends/django.pyi @@ -11,6 +11,8 @@ from .base import BaseEngine, _EngineTemplate class DjangoTemplates(BaseEngine): engine: Engine def __init__(self, params: dict[str, Any]) -> None: ... + def from_string(self, template_code: str) -> Template: ... + def get_template(self, template_name: str) -> Template: ... def get_templatetag_libraries(self, custom_libraries: dict[str, str]) -> dict[str, str]: ... def copy_exception(exc: TemplateDoesNotExist, backend: DjangoTemplates | None = None) -> TemplateDoesNotExist: ... diff --git a/django-stubs/template/backends/dummy.pyi b/django-stubs/template/backends/dummy.pyi index f95a2210d..c881f8ffa 100644 --- a/django-stubs/template/backends/dummy.pyi +++ b/django-stubs/template/backends/dummy.pyi @@ -7,6 +7,8 @@ from .base import BaseEngine, _EngineTemplate class TemplateStrings(BaseEngine): def __init__(self, params: dict[str, dict[Any, Any] | list[Any] | bool | str]) -> None: ... + def from_string(self, template_code: str) -> Template: ... + def get_template(self, template_name: str) -> Template: ... class Template(string.Template, _EngineTemplate): template: str diff --git a/django-stubs/template/backends/jinja2.pyi b/django-stubs/template/backends/jinja2.pyi index 54c35e710..68db8daaf 100644 --- a/django-stubs/template/backends/jinja2.pyi +++ b/django-stubs/template/backends/jinja2.pyi @@ -12,6 +12,8 @@ class Jinja2(BaseEngine): env: Any context_processors: list[str] def __init__(self, params: dict[str, Any]) -> None: ... + def from_string(self, template_code: str) -> Template: ... + def get_template(self, template_name: str) -> Template: ... @cached_property def template_context_processors(self) -> list[Callable]: ... From 90c2a6f18915525c6ba6693f9ac4469bfe483d4c Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Wed, 9 Oct 2024 16:57:39 +0200 Subject: [PATCH 8/8] Revert "Make it explicit that the dummy template class adheres to the protocol" This reverts commit 509e36813f0ed418f8cbeba9b529292b678ecb49. Inheriting from both `string.Template` and `typing.Protocol` causes mypy to report a metaclass conflict on Python 3.8. --- django-stubs/template/backends/dummy.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django-stubs/template/backends/dummy.pyi b/django-stubs/template/backends/dummy.pyi index c881f8ffa..41a5f5dfc 100644 --- a/django-stubs/template/backends/dummy.pyi +++ b/django-stubs/template/backends/dummy.pyi @@ -3,13 +3,13 @@ from typing import Any from django.http.request import HttpRequest -from .base import BaseEngine, _EngineTemplate +from .base import BaseEngine class TemplateStrings(BaseEngine): def __init__(self, params: dict[str, dict[Any, Any] | list[Any] | bool | str]) -> None: ... def from_string(self, template_code: str) -> Template: ... def get_template(self, template_name: str) -> Template: ... -class Template(string.Template, _EngineTemplate): +class Template(string.Template): template: str def render(self, context: dict[str, Any] | None = ..., request: HttpRequest | None = ...) -> str: ...