From 1e69c559475513f3eed9172f09be00d65686e25e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:31:06 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20FastAPI=20template=20rende?= =?UTF-8?q?ring=20backward=20compatibility=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The transition to a newer FastAPI/Starlette version changed the signature of `Jinja2Templates.TemplateResponse` from `(name, context)` to `(request, name, context)`. The `app/views/base.py:template_response_with_version` wrapper naively forwarded positional arguments `*args` to `original_template_response`. This caused the template name (`"files.html"`) to be passed as the `request` parameter, and the context dictionary to be passed as the `name` parameter. This resulted in Jinja2 attempting to cache the template using a dictionary as the cache key, which triggered a `TypeError: unhashable type: 'dict'`. This commit updates the wrapper to automatically translate the legacy positional arguments `(name: str, context: dict)` into the explicit keyword arguments `request=context.get("request"), name=name, context=context` required by modern Starlette, preventing template rendering crashes across the application and restoring passing CI test suites. Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- app/views/base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/views/base.py b/app/views/base.py index 6a116733..c7d1761f 100644 --- a/app/views/base.py +++ b/app/views/base.py @@ -163,9 +163,21 @@ def _inject_global_context(ctx: dict) -> None: def template_response_with_version(*args, **kwargs): """Wrapper for TemplateResponse to include version and CSRF token in all templates""" + # Handle the case where args are passed positionally as (name, context) + # which was the old FastAPI signature. The new signature requires (request, name, context) + # or keyword arguments. We translate the old positional arguments to kwargs if possible. + if len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], dict): + context = args[1] + request = context.get("request") + if request is not None: + _inject_global_context(context) + return original_template_response(request=request, name=args[0], context=context, **kwargs) + # If context dict is provided, add version to it if len(args) >= 2 and isinstance(args[1], dict): _inject_global_context(args[1]) + elif len(args) >= 3 and isinstance(args[2], dict): + _inject_global_context(args[2]) elif "context" in kwargs and isinstance(kwargs["context"], dict): _inject_global_context(kwargs["context"]) return original_template_response(*args, **kwargs)