How to redirect to dynamic URL inside a FastAPI endpoint?

Question:

I’m doing a feature where the user on their profile page makes changes (not related to the user model). Everything is implemented through static HTML templates. I need the user to click on the button and return to the same page (i.e., their profile page) after processing the request.

Html template

<td><a href="{{ url_for('decline_event_invite', pk=invite.id) }}" class="btn blue lighten-2">Accept</a></td>

endpoints.py

@router.get('/invite/{pk}/decline')
async def decline_event_invite(
        request: Request,
        pk: int,
        user_id: str = Depends(get_current_user),
        service: InviteService = Depends(),
):
    await service.invite_decline(pk)
    ...
    --> here I want redirect to user profile page 
    return RedirectResponse('DYNAMIC URL WITH ARGS')

profile.py

@router.get('/{pk}')
async def user_profile(
        request: Request,
        pk: int,
        service: UserService = Depends()
):
    user = await service.get_user_info(pk)
    events_invites = await service.get_user_events_invite_list(pk)
    return templates.TemplateResponse(
        'profile.html',
        context=
        {
            'request': request,
            'user': user,
            'events_invites': events_invites,
        }
    )

But I can’t find anywhere how to do a redirect similar to the logic that applies to templates. For example:

<a href="{{ url_for('user_profile', pk=pk) }}">Sender</a>
Asked By: Jekson

||

Answers:

You can use url_for() function and pass the (**kwargs) path parameters.

import uvicorn
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse
import urllib
from fastapi import APIRouter

router = APIRouter()

templates = Jinja2Templates(directory="templates")

@router.get('/invite/{pk}/decline')
def decline_event_invite(request: Request, pk: int):
    redirect_url = request.url_for('user_profile', **{ 'pk' : pk})
    return RedirectResponse(redirect_url)    

@router.get('/{pk}')
def user_profile(request: Request, pk: int):
    return templates.TemplateResponse("profile.html", {"request": request, "pk": pk})
    
if __name__ == "__main__":
    uvicorn.run(router, host='127.0.0.1', port=8000, debug=True)

To add query params

In case you had to pass query params as well, you could use the following code (make sure to import urllib). Alternatively, you could use the CustomURLProcessor, as described in this and this answer (which pretty much follows the same approach).

If the endpoint expected query params, for example:

@router.get('/invite/{pk}/decline')
def decline_event_invite(request: Request, pk: int, message: str):
    pass

you could use:

redirect_url = request.url_for('user_profile', pk=pk)
parsed = list(urllib.parse.urlparse(redirect_url))
parsed[4] = urllib.parse.urlencode({**{ 'message' : "Success!"}})
redirect_url = urllib.parse.urlunparse(parsed)

or even use:

message = 'Success!'
redirect_url = request.url_for('user_profile', pk=pk) + f'?message={message}'

Update

Another solution would be to use Starlette’s starlette.datastructures.URL, which now provides a method to include_query_params. Example:

from starlette.datastructures import URL

redirect_url = URL(request.url_for('user_profile', pk=pk)).include_query_params(message="Success!")
Answered By: Chris
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.