From c4e10bee5e096e71a5bc4fac4928f69e5c04f2fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:50:09 +0000 Subject: [PATCH] fix: adapt TemplateResponse calls to Starlette 1.0 new-style API Starlette 1.0.0 changed TemplateResponse signature from (name, context_dict) to (request, name, context=dict). - Update base.py wrapper to convert old-style calls to new-style - Update main.py error handler TemplateResponse calls - Update local_auth.py, billing.py, auth.py, share.py calls - Update test mocks for new calling convention Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/7b5f7e0d-89ad-43be-b68d-a9c0c5407a7e --- app/api/billing.py | 2 +- app/api/local_auth.py | 18 +++++++++--------- app/auth.py | 4 ++-- app/main.py | 7 ++++--- app/views/base.py | 33 ++++++++++++++++++++++++++++----- app/views/share.py | 3 ++- tests/test_dark_mode.py | 8 ++++---- tests/test_social_login.py | 4 ++-- 8 files changed, 52 insertions(+), 27 deletions(-) diff --git a/app/api/billing.py b/app/api/billing.py index 9c5582b0..85528608 100644 --- a/app/api/billing.py +++ b/app/api/billing.py @@ -260,7 +260,7 @@ async def stripe_webhook(request: Request, db: Session = Depends(get_db)) -> dic @require_login async def billing_success(request: Request) -> Any: """Show a success page after a completed Stripe Checkout.""" - return _templates.TemplateResponse("billing_success.html", {"request": request}) + return _templates.TemplateResponse(request, "billing_success.html") # --------------------------------------------------------------------------- diff --git a/app/api/local_auth.py b/app/api/local_auth.py index 2b68003f..e946f484 100644 --- a/app/api/local_auth.py +++ b/app/api/local_auth.py @@ -101,9 +101,9 @@ async def signup_page(request: Request) -> Any: if not settings.allow_local_signup: return RedirectResponse(url="/login?error=Registration+is+not+enabled", status_code=302) return templates.TemplateResponse( + request, "signup.html", - { - "request": request, + context={ "csrf_token": getattr(request.state, "csrf_token", ""), "app_version": settings.version, }, @@ -113,16 +113,16 @@ async def signup_page(request: Request) -> Any: @router.get("/verify-email-sent", include_in_schema=False) async def verify_email_sent_page(request: Request) -> Any: """Render the verify-email-sent confirmation page.""" - return templates.TemplateResponse("verify_email_sent.html", {"request": request}) + return templates.TemplateResponse(request, "verify_email_sent.html") @router.get("/forgot-username", include_in_schema=False) async def forgot_username_page(request: Request) -> Any: """Render the forgot-username page where users can request a username reminder email.""" return templates.TemplateResponse( + request, "forgot_username.html", - { - "request": request, + context={ "csrf_token": getattr(request.state, "csrf_token", ""), "app_version": settings.version, }, @@ -133,9 +133,9 @@ async def forgot_username_page(request: Request) -> Any: async def forgot_password_page(request: Request) -> Any: """Render the forgot-password page where users can request a reset email.""" return templates.TemplateResponse( + request, "forgot_password.html", - { - "request": request, + context={ "csrf_token": getattr(request.state, "csrf_token", ""), "app_version": settings.version, }, @@ -147,9 +147,9 @@ async def reset_password_page(request: Request) -> Any: """Render the password reset form page.""" token = request.query_params.get("token", "") return templates.TemplateResponse( + request, "password_reset_form.html", - { - "request": request, + context={ "token": token, "csrf_token": getattr(request.state, "csrf_token", ""), "app_version": settings.version, diff --git a/app/auth.py b/app/auth.py index 694d08cc..eb0254be 100644 --- a/app/auth.py +++ b/app/auth.py @@ -536,9 +536,9 @@ async def login(request: Request): return RedirectResponse(url="/oauth-login", status_code=status.HTTP_302_FOUND) return templates.TemplateResponse( + request, "login.html", - { - "request": request, + context={ "error": error, "message": message, "show_oauth": show_oauth, diff --git a/app/main.py b/app/main.py index 89477ceb..3e2ee632 100644 --- a/app/main.py +++ b/app/main.py @@ -425,14 +425,14 @@ async def http_exception_handler(request: Request, exc: HTTPException): # Handle 404 errors with a custom template if exc.status_code == 404: return _error_templates.TemplateResponse( - "404.html", {"request": request}, status_code=status.HTTP_404_NOT_FOUND + request, "404.html", status_code=status.HTTP_404_NOT_FOUND ) # For other HTTP errors, we could create specific templates or use a generic one # For now, return a simple error page return _error_templates.TemplateResponse( + request, "404.html", # Reuse 404 template for other errors, or create a generic error template - {"request": request}, status_code=exc.status_code, ) @@ -452,8 +452,9 @@ async def custom_500_handler(request: Request, exc: Exception): # Serve the 500 template for non-API routes return _error_templates.TemplateResponse( + request, "500.html", - {"request": request, "exc": exc}, + context={"exc": exc}, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) diff --git a/app/views/base.py b/app/views/base.py index 6a116733..0c4d2684 100644 --- a/app/views/base.py +++ b/app/views/base.py @@ -162,12 +162,35 @@ 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""" - # If context dict is provided, add version to it - if len(args) >= 2 and isinstance(args[1], dict): - _inject_global_context(args[1]) - elif "context" in kwargs and isinstance(kwargs["context"], dict): + """Wrapper for TemplateResponse to include version and CSRF token in all templates. + + Handles both old-style and new-style Starlette TemplateResponse calls: + - Old-style (Starlette <1.0): TemplateResponse(name, {"request": req, ...}, ...) + - New-style (Starlette 1.0+): TemplateResponse(request, name, context={...}, ...) + """ + if len(args) >= 1 and isinstance(args[0], str): + # Old-style call: first positional arg is the template name (string). + # Convert to new-style: (request, name, context=..., ...) + name = args[0] + if len(args) >= 2 and isinstance(args[1], dict): + context = args[1] + remaining_args = args[2:] + else: + context = kwargs.pop("context", {}) + remaining_args = args[1:] + request_obj = context.pop("request", None) + if request_obj is not None: + context["request"] = request_obj + _inject_global_context(context) + if request_obj is not None: + return original_template_response(request_obj, name, context=context, *remaining_args, **kwargs) + return original_template_response(name, context=context, *remaining_args, **kwargs) + + # New-style call: (request, name, context=..., ...) + if "context" in kwargs and isinstance(kwargs["context"], dict): _inject_global_context(kwargs["context"]) + elif len(args) >= 3 and isinstance(args[2], dict): + _inject_global_context(args[2]) return original_template_response(*args, **kwargs) diff --git a/app/views/share.py b/app/views/share.py index 118e0ee9..925342f6 100644 --- a/app/views/share.py +++ b/app/views/share.py @@ -23,6 +23,7 @@ templates = Jinja2Templates(directory=str(_templates_dir)) async def shared_link_view(request: Request, token: str): """Render the public share landing page for a given token.""" return templates.TemplateResponse( + request, "shared_link_view.html", - {"request": request, "token": token}, + context={"token": token}, ) diff --git a/tests/test_dark_mode.py b/tests/test_dark_mode.py index 82514a20..a39752c7 100644 --- a/tests/test_dark_mode.py +++ b/tests/test_dark_mode.py @@ -55,8 +55,8 @@ class TestDarkModeTemplateInjection: captured = {} - def fake_original(name, ctx, **kw): - captured.update(ctx) + def fake_original(request_obj, name, context=None, **kw): + captured.update(context or {}) with patch("app.views.base.original_template_response", side_effect=fake_original): mock_request = MagicMock() @@ -73,8 +73,8 @@ class TestDarkModeTemplateInjection: captured = {} - def fake_original(name, ctx, **kw): - captured.update(ctx) + def fake_original(request_obj, name, context=None, **kw): + captured.update(context or {}) with patch("app.views.base.original_template_response", side_effect=fake_original): mock_request = MagicMock() diff --git a/tests/test_social_login.py b/tests/test_social_login.py index 54c81931..f46c874f 100644 --- a/tests/test_social_login.py +++ b/tests/test_social_login.py @@ -345,7 +345,7 @@ class TestLoginPageSocialProviders: mock_templates.TemplateResponse.assert_called_once() call_args = mock_templates.TemplateResponse.call_args - context = call_args[0][1] + context = call_args.kwargs.get("context") or call_args[0][2] if len(call_args[0]) > 2 else {} assert context["social_providers"] == mock_providers @pytest.mark.asyncio @@ -371,7 +371,7 @@ class TestLoginPageSocialProviders: mock_templates.TemplateResponse.assert_called_once() call_args = mock_templates.TemplateResponse.call_args - context = call_args[0][1] + context = call_args.kwargs.get("context") or call_args[0][2] if len(call_args[0]) > 2 else {} assert context["social_providers"] == {}