Repository: encode/starlette

Issue:
Index Errors in old-style Jinja2Templates.TemplateResponse
The parsing logic for the backward-compatible (pre-0.29.0) list of positional arguments uses index 2 twice:

https://github.com/encode/starlette/blob/c8a46925366361e40b65b117473db1342895b904/starlette/templating.py#L168-L173

`headers`, `media_type` and `background` should be at indices 3, 4 and 5, respectively.


Discussion:
cc @alex-oleshkevich 
Fixed by https://github.com/encode/starlette/pull/2909



Patch:
diff --git a/starlette/templating.py b/starlette/templating.py
index 6b01aac92..f764858b8 100644
--- a/starlette/templating.py
+++ b/starlette/templating.py
@@ -168,9 +168,9 @@ def TemplateResponse(self, *args: typing.Any, **kwargs: typing.Any) -> _Template
                 name = args[0]
                 context = args[1] if len(args) > 1 else kwargs.get("context", {})
                 status_code = args[2] if len(args) > 2 else kwargs.get("status_code", 200)
-                headers = args[2] if len(args) > 2 else kwargs.get("headers")
-                media_type = args[3] if len(args) > 3 else kwargs.get("media_type")
-                background = args[4] if len(args) > 4 else kwargs.get("background")
+                headers = args[3] if len(args) > 3 else kwargs.get("headers")
+                media_type = args[4] if len(args) > 4 else kwargs.get("media_type")
+                background = args[5] if len(args) > 5 else kwargs.get("background")
 
                 if "request" not in context:
                     raise ValueError('context must include a "request" key')


Test patch:
diff --git a/tests/test_templates.py b/tests/test_templates.py
index 6b2080c17..e182cb82b 100644
--- a/tests/test_templates.py
+++ b/tests/test_templates.py
@@ -279,6 +279,127 @@ def page(request: Request) -> Response:
     spy.assert_called()
 
 
+class TestTemplatesArgsOnly:
+    # MAINTAINERS: remove after 1.0
+    def test_name_and_context(self, tmpdir: Path, test_client_factory: TestClientFactory) -> None:
+        path = os.path.join(tmpdir, "index.html")
+        with open(path, "w") as file:
+            file.write("value: {{ a }}")
+        templates = Jinja2Templates(directory=str(tmpdir))
+
+        def page(request: Request) -> Response:
+            return templates.TemplateResponse(
+                "index.html",
+                {"a": "b", "request": request},
+            )
+
+        app = Starlette(routes=[Route("/", page)])
+        client = test_client_factory(app)
+        with pytest.warns(DeprecationWarning):
+            response = client.get("/")
+
+        assert response.text == "value: b"  # context was rendered
+        assert response.status_code == 200
+
+    def test_status_code(self, tmpdir: Path, test_client_factory: TestClientFactory) -> None:
+        path = os.path.join(tmpdir, "index.html")
+        with open(path, "w") as file:
+            file.write("value: {{ a }}")
+        templates = Jinja2Templates(directory=str(tmpdir))
+
+        def page(request: Request) -> Response:
+            return templates.TemplateResponse(
+                "index.html",
+                {"a": "b", "request": request},
+                201,
+            )
+
+        app = Starlette(routes=[Route("/", page)])
+        client = test_client_factory(app)
+        with pytest.warns(DeprecationWarning):
+            response = client.get("/")
+
+        assert response.text == "value: b"  # context was rendered
+        assert response.status_code == 201
+
+    def test_headers(self, tmpdir: Path, test_client_factory: TestClientFactory) -> None:
+        path = os.path.join(tmpdir, "index.html")
+        with open(path, "w") as file:
+            file.write("value: {{ a }}")
+        templates = Jinja2Templates(directory=str(tmpdir))
+
+        def page(request: Request) -> Response:
+            return templates.TemplateResponse(
+                "index.html",
+                {"a": "b", "request": request},
+                201,
+                {"x-key": "value"},
+            )
+
+        app = Starlette(routes=[Route("/", page)])
+        client = test_client_factory(app)
+        with pytest.warns(DeprecationWarning):
+            response = client.get("/")
+
+        assert response.text == "value: b"  # context was rendered
+        assert response.status_code == 201
+        assert response.headers["x-key"] == "value"
+
+    def test_media_type(self, tmpdir: Path, test_client_factory: TestClientFactory) -> None:
+        path = os.path.join(tmpdir, "index.html")
+        with open(path, "w") as file:
+            file.write("value: {{ a }}")
+        templates = Jinja2Templates(directory=str(tmpdir))
+
+        def page(request: Request) -> Response:
+            return templates.TemplateResponse(
+                "index.html",
+                {"a": "b", "request": request},
+                201,
+                {"x-key": "value"},
+                "text/plain",
+            )
+
+        app = Starlette(routes=[Route("/", page)])
+        client = test_client_factory(app)
+        with pytest.warns(DeprecationWarning):
+            response = client.get("/")
+
+        assert response.text == "value: b"  # context was rendered
+        assert response.status_code == 201
+        assert response.headers["x-key"] == "value"
+        assert response.headers["content-type"] == "text/plain; charset=utf-8"
+
+    def test_all_args(self, tmpdir: Path, test_client_factory: TestClientFactory) -> None:
+        path = os.path.join(tmpdir, "index.html")
+        with open(path, "w") as file:
+            file.write("value: {{ a }}")
+        templates = Jinja2Templates(directory=str(tmpdir))
+
+        spy = mock.MagicMock()
+
+        def page(request: Request) -> Response:
+            return templates.TemplateResponse(
+                "index.html",
+                {"a": "b", "request": request},
+                201,
+                {"x-key": "value"},
+                "text/plain",
+                BackgroundTask(func=spy),
+            )
+
+        app = Starlette(routes=[Route("/", page)])
+        client = test_client_factory(app)
+        with pytest.warns(DeprecationWarning):
+            response = client.get("/")
+
+        assert response.text == "value: b"  # context was rendered
+        assert response.status_code == 201
+        assert response.headers["x-key"] == "value"
+        assert response.headers["content-type"] == "text/plain; charset=utf-8"
+        spy.assert_called()
+
+
 def test_templates_when_first_argument_is_request(tmpdir: Path, test_client_factory: TestClientFactory) -> None:
     # MAINTAINERS: remove after 1.0
     path = os.path.join(tmpdir, "index.html")