FastAPI: How to redirect from a POST endpoint to a GET endpoint

ghz 11hours ago ⋅ 7 views

I can’t understand how I can pass additional data to the redirect response.

in HTML file script:

function cmb_pos() {
        var data = {
            'dep_id': dep_id, 'div_id': div_id,
        }
        fetch('/job-history/add-new/intermediate/{{persona_id}}', {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
        })
            .then(response => response.json())
            .then(response => console.log(JSON.stringify(response)))
    };

first: (a function that accepts a POST, performs actions and redirects to another function)

from starlette.datastructures import URL

@router.post(path="/add-new/intermediate/{persona_id}",
    response_class=responses.RedirectResponse,
)
async def my_test(request: Request, item: Item, persona_id: int, service: Related):
    staff_schedule = await service.get_item_by_where(....)
    context_ext = {}
    context_ext["request"] = request
    context_ext["staff_schedule"] = staff_schedule
    context_ext["flag"] = True

    redirect_url = URL(request.url_for("create_job_history_init", persona_id=persona_id))
                  .include_query_params(context_ext=context_ext)
    return responses.RedirectResponse(
        url=redirect_url,
        status_code=status.HTTP_303_SEE_OTHER,
    )

second: (function to which the redirect occurs)

@router.get(path="/persona_id={persona_id}/add-new",
    response_class=responses.HTMLResponse,
)
async def create_job_history_init(request: Request, persona_id: int,
    schedule_service: CommonsDep, context_ext: Optional[dict] = None,
):
    context = {}
    schedule = await schedule_service.get_all_item()
    related = ......
    context = schedule | related
    context["request"] = request
    context["persona_id"] = persona_id
    context["context_ext"] = context_ext

    return backend.webapps.templates.TemplateResponse(
        name="job_history/create_job_history.html",
        context=context,
    )

I got this error:

File "/Users/pas/------/backend/webapps/job_history.py", line 276, in my_test ).include_query_params(context_ext=context_ext)

File "/Users/pas/-------/.my_pers/lib/python3.11/site-packages/starlette/datastructures.py", line 143, in include_query_params params = MultiDict(parse_qsl(self.query, keep_blank_values=True))

File "/Users/pas/---------/.my_pers/lib/python3.11/site-packages/starlette/datastructures.py", line 83, in query return self.components.query

File "/Users/pas/---------/.my_pers/lib/python3.11/site-packages/starlette/datastructures.py", line 66, in components self._components = urlsplit(self._url)

TypeError: unhashable type: 'URL'

what am I doing wrong? Is it possible to transfer additional data via a redirect?

thank you very much in advance

update:

return tuple(x.decode(encoding, errors) if x else '' for x in args)

AttributeError: 'URL' object has no attribute 'decode'

parsed = list(urllib.parse.urlparse(redirect_url))

for me the right solution is this:

parsed = list(urllib.parse.urlparse(redirect_url ._url )) after adding ._url I was able to parse

Answer

The issue you're facing stems from a misunderstanding of how to work with query parameters and how to include additional data during a redirect in FastAPI. The URL object from starlette doesn't allow direct inclusion of additional data (like context_ext) via the include_query_params method because it doesn't expect a dictionary as an argument.

In FastAPI, when you need to pass additional data in a redirect, you typically include the data as query parameters in the URL. However, FastAPI's RedirectResponse doesn't support adding arbitrary data directly to the URL object, which is why you're encountering issues with include_query_params.

Let me walk you through a more standard approach to handling this situation.

Correct Approach to Redirect with Data

  1. Include Data in the URL: You can include data as query parameters in the redirect URL.
  2. Handle Data in the Target Route: Extract the data from the query parameters in the target route.

Step 1: Redirect with Query Parameters

To include additional data, like context_ext, in the redirect URL, you should manually construct the query parameters and append them to the URL. Here's how to do it:

my_test Endpoint (POST Method)

from fastapi import APIRouter, Request, responses, status
from starlette.datastructures import URL
from urllib.parse import urlencode

router = APIRouter()

@router.post("/add-new/intermediate/{persona_id}", response_class=responses.RedirectResponse)
async def my_test(request: Request, item: dict, persona_id: int):
    # Create the context_ext dictionary
    context_ext = {"flag": True, "extra_data": "some_value"}
    
    # Create the redirect URL and include the data as query parameters
    redirect_url = request.url_for("create_job_history_init", persona_id=persona_id)
    query_params = urlencode(context_ext)
    redirect_url_with_params = f"{redirect_url}?{query_params}"

    # Redirect to the new URL with the query parameters
    return responses.RedirectResponse(url=redirect_url_with_params, status_code=status.HTTP_303_SEE_OTHER)

Explanation:

  • urlencode(context_ext) encodes your context_ext dictionary as query parameters (e.g., flag=True&extra_data=some_value).
  • You append this encoded query string to the original redirect URL.
  • This is a simple way to pass additional data via a redirect, making it accessible to the target route.

Step 2: Handle Data in the Target Route

In your target route, you can now easily retrieve the query parameters using FastAPI's dependency injection system.

create_job_history_init Endpoint (GET Method)

from fastapi import APIRouter, Request
from typing import Optional

router = APIRouter()

@router.get("/persona_id={persona_id}/add-new", response_class=responses.HTMLResponse)
async def create_job_history_init(request: Request, persona_id: int, context_ext: Optional[dict] = None):
    # Retrieve the context_ext data from the query parameters
    if context_ext:
        flag = context_ext.get("flag")
        extra_data = context_ext.get("extra_data")
        print(f"Flag: {flag}, Extra Data: {extra_data}")
    
    # Create the context for rendering the template
    context = {"persona_id": persona_id, "context_ext": context_ext}
    return templates.TemplateResponse("job_history/create_job_history.html", context)

Explanation:

  • The context_ext query parameter is passed as a dictionary to the create_job_history_init function.
  • The data from the query parameters (flag, extra_data) is now available in the target route for further processing.

Full Example:

Here’s a simplified full example with a POST method redirecting to a GET method that receives additional data:

from fastapi import FastAPI, APIRouter, Request, responses, status
from starlette.responses import HTMLResponse
from urllib.parse import urlencode
from typing import Optional

app = FastAPI()
router = APIRouter()

@router.post("/add-new/intermediate/{persona_id}", response_class=responses.RedirectResponse)
async def my_test(request: Request, persona_id: int):
    context_ext = {"flag": True, "extra_data": "some_value"}
    redirect_url = request.url_for("create_job_history_init", persona_id=persona_id)
    query_params = urlencode(context_ext)
    redirect_url_with_params = f"{redirect_url}?{query_params}"
    
    return responses.RedirectResponse(url=redirect_url_with_params, status_code=status.HTTP_303_SEE_OTHER)

@router.get("/persona_id={persona_id}/add-new", response_class=HTMLResponse)
async def create_job_history_init(request: Request, persona_id: int, context_ext: Optional[dict] = None):
    if context_ext:
        flag = context_ext.get("flag")
        extra_data = context_ext.get("extra_data")
        print(f"Flag: {flag}, Extra Data: {extra_data}")
    
    context = {"persona_id": persona_id, "context_ext": context_ext}
    return HTMLResponse(content=f"Persona ID: {persona_id}, Context: {context}")

app.include_router(router)

Explanation of the Key Concepts:

  • POST to GET redirect: The POST method does some processing and then redirects the user to a new route, passing along data as query parameters.
  • Query Parameters: The data you want to pass (e.g., flag, extra_data) is encoded into query parameters and appended to the redirect URL.
  • Accessing Query Parameters: In the GET route, the query parameters are automatically parsed and passed to the function. You can then use this data as needed.

Conclusion:

In FastAPI, you can easily pass additional data via query parameters in a redirect by constructing the URL with urlencode and appending the parameters manually. In the target route, you can access these parameters as part of the request.

This method avoids the need for any complex object manipulations or using include_query_params, which is not meant for the use case you're trying to achieve.