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
- Include Data in the URL: You can include data as query parameters in the redirect URL.
- 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 yourcontext_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 thecreate_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.