* :sparkles: Add support for declaring a Response parameter to set headers and cookies
* :white_check_mark: Add source for docs and tests
* :memo: Add docs for setting headers, cookies and status code
* :memo: Add attribution to Hug for inspiring response parameters
Falcon is another high performance Python framework, it is designed to be minimal, and work as the foundation of other frameworks like Hug.
-It uses the previous standard for Python web frameworks (WSGI) which is synchronous, so it can't handle Websockets and other use cases. Nevertheless, it also has a very good performance.
+It uses the previous standard for Python web frameworks (WSGI) which is synchronous, so it can't handle WebSockets and other use cases. Nevertheless, it also has a very good performance.
It is designed to have functions that receive two parameters, one "request" and one "response". Then you "read" parts from the request, and "write" parts to the response. Because of this design, it is not possible to declare request parameters and bodies with standard Python type hints as function parameters.
!!! check "Inspired **FastAPI** to"
Find ways to get great performance.
+ Along with Hug (as Hug is based on Falcon) inspired **FastAPI** to declare a `response` parameter in functions.
+
+ Although in FastAPI it's optional, and is used mainly to set headers, cookies, and alternative status codes.
+
### <a href="https://moltenframework.com/" target="_blank">Molten</a>
I discovered Molten in the first stages of building **FastAPI**. And it has quite similar ideas:
Hug helped inspiring **FastAPI** to use Python type hints to declare parameters, and to generate a schema defining the API automatically.
+ Hug inspired **FastAPI** to declare a `response` parameter in functions to set headers and cookies.
### <a href="https://github.com/encode/apistar" target="_blank">APIStar</a> (<= 0.5)
--- /dev/null
+from fastapi import FastAPI
+from starlette.responses import Response
+from starlette.status import HTTP_201_CREATED
+
+app = FastAPI()
+
+tasks = {"foo": "Listen to the Bar Fighters"}
+
+
+@app.put("/get-or-create-task/{task_id}", status_code=200)
+def get_or_create_task(task_id: str, response: Response):
+ if task_id not in tasks:
+ tasks[task_id] = "This didn't exist before"
+ response.status_code = HTTP_201_CREATED
+ return tasks[task_id]
--- /dev/null
+from fastapi import FastAPI
+from starlette.responses import Response
+
+app = FastAPI()
+
+
+@app.post("/cookie-and-object/")
+def create_cookie(response: Response):
+ response.set_cookie(key="fakesession", value="fake-cookie-session-value")
+ return {"message": "Come to the dark side, we have cookies"}
--- /dev/null
+from fastapi import FastAPI
+from starlette.responses import Response
+
+app = FastAPI()
+
+
+@app.get("/headers-and-object/")
+def get_headers(response: Response):
+ response.headers["X-Cat-Dog"] = "alone in the world"
+ return {"message": "Hello World"}
--- /dev/null
+You probably read before that you can set a <a href="https://fastapi.tiangolo.com/tutorial/response-status-code/" target="_blank">default Response Status Code</a>.
+
+But in some cases you need to return a different status code than the default.
+
+## Use case
+
+For example, imagine that you want to return an HTTP status code of "OK" `200` by default.
+
+But if the data didn't exist, you want to create it, and return an HTTP status code of "CREATED" `201`.
+
+But you still want to be able to filter and convert the data you return with a `response_model`.
+
+For those cases, you can use a `Response` parameter.
+
+## Use a `Response` parameter
+
+You can declare a parameter of type `Response` in your *path operation function* (as you can do for cookies and headers).
+
+And then you can set the `status_code` in that *temporal* response object.
+
+```Python hl_lines="2 11 14"
+{!./src/response_change_status_code/tutorial001.py!}
+```
+
+And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
+
+And if you declared a `response_model`, it will still be used to filter and convert the object you returned.
+
+**FastAPI** will use that *temporal* response to extract the status code (also cookies and headers), and will put them in the final response that contains the value you returned, filtered by any `response_model`.
+
+You can also declare the `Response` parameter in dependencies, and set the status code in them. But have in mind that the last one to be set will win.
-You can create (set) Cookies in your response.
+## Use a `Response` parameter
+
+You can declare a parameter of type `Response` in your *path operation function*, the same way you can declare a `Request` parameter.
+
+And then you can set headers in that *temporal* response object.
+
+```Python hl_lines="2 8 9"
+{!./src/response_cookies/tutorial002.py!}
+```
+
+And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
+
+And if you declared a `response_model`, it will still be used to filter and convert the object you returned.
+
+**FastAPI** will use that *temporal* response to extract the cookies (also headers and status code), and will put them in the final response that contains the value you returned, filtered by any `response_model`.
+
+You can also declare the `Response` parameter in dependencies, and set cookies (and headers) in them.
+
+## Return a `Response` directly
+
+You can also create cookies when returning a `Response` directly in your code.
To do that, you can create a response as described in <a href="https://fastapi.tiangolo.com/tutorial/response-directly/" target="_blank">Return a Response directly</a>.
{!./src/response_cookies/tutorial001.py!}
```
-## More info
+!!! tip
+ Have in mind that if you return a response directly instead of using the `Response` parameter, FastAPI will return it directly.
+
+ So, you will have to make sure your data is of the correct type. E.g. it is compatible with JSON, if you are returning a `JSONResponse`.
+
+ And also that you are not sending any data that should have been filtered by a `response_model`.
+
+### More info
To see all the available parameters and options, check the <a href="https://www.starlette.io/responses/#set-cookie" target="_blank">documentation in Starlette</a>.
-You can add headers to your response.
+## Use a `Response` parameter
+
+You can declare a parameter of type `Response` in your *path operation function* (as you can do for cookies), the same way you can declare a `Request` parameter.
+
+And then you can set headers in that *temporal* response object.
+
+```Python hl_lines="2 8 9"
+{!./src/response_headers/tutorial002.py!}
+```
+
+And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
+
+And if you declared a `response_model`, it will still be used to filter and convert the object you returned.
+
+**FastAPI** will use that *temporal* response to extract the headers (also cookies and status code), and will put them in the final response that contains the value you returned, filtered by any `response_model`.
+
+You can also declare the `Response` parameter in dependencies, and set headers (and cookies) in them.
+
+## Return a `Response` directly
+
+You can also add headers when you return a `Response` directly.
Create a response as described in <a href="https://fastapi.tiangolo.com/tutorial/response-directly/" target="_blank">Return a Response directly</a> and pass the headers as an additional parameter:
{!./src/response_headers/tutorial001.py!}
```
-!!! tip
- Have in mind that custom proprietary headers can be added <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" target="_blank">using the 'X-' prefix</a>.
+## Custom Headers
+
+Have in mind that custom proprietary headers can be added <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" target="_blank">using the 'X-' prefix</a>.
- But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your <a href="https://fastapi.tiangolo.com/tutorial/cors/" target="_blank">CORS configurations</a>, using the parameter `expose_headers` documented in <a href="https://www.starlette.io/middleware/#corsmiddleware" target="_blank">Starlette's CORS docs</a>.
+But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your <a href="https://fastapi.tiangolo.com/tutorial/cors/" target="_blank">CORS configurations</a>, using the parameter `expose_headers` documented in <a href="https://www.starlette.io/middleware/#corsmiddleware" target="_blank">Starlette's CORS docs</a>.
!!! tip
To know more about each status code and which code is for what, check the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> documentation about HTTP status codes</a>.
-
## Shortcut to remember the names
Let's see the previous example again:
They are just a convenience, they hold the same number, but that way you can use the editor's autocomplete to find them:
<img src="/img/tutorial/response-status-code/image02.png">
+
+## Changing the default
+
+Later, in a more advanced part of the tutorial/user guide, you will see how to <a href="https://fastapi.tiangolo.com/tutorial/response-change-status-code/" target="_blank">return a different status code than the default</a> you are declaring here.
call: Callable = None,
request_param_name: str = None,
websocket_param_name: str = None,
+ response_param_name: str = None,
background_tasks_param_name: str = None,
security_scopes_param_name: str = None,
security_scopes: List[str] = None,
self.security_requirements = security_schemes or []
self.request_param_name = request_param_name
self.websocket_param_name = websocket_param_name
+ self.response_param_name = response_param_name
self.background_tasks_param_name = background_tasks_param_name
self.security_scopes = security_scopes
self.security_scopes_param_name = security_scopes_param_name
from starlette.concurrency import run_in_threadpool
from starlette.datastructures import FormData, Headers, QueryParams, UploadFile
from starlette.requests import Request
+from starlette.responses import Response
from starlette.websockets import WebSocket
sequence_shapes = {
elif lenient_issubclass(param.annotation, WebSocket):
dependant.websocket_param_name = param.name
return True
+ elif lenient_issubclass(param.annotation, Response):
+ dependant.response_param_name = param.name
+ return True
elif lenient_issubclass(param.annotation, BackgroundTasks):
dependant.background_tasks_param_name = param.name
return True
dependant: Dependant,
body: Dict[str, Any] = None,
background_tasks: BackgroundTasks = None,
+ response: Response = None,
dependency_overrides_provider: Any = None,
dependency_cache: Dict[Tuple[Callable, Tuple[str]], Any] = None,
) -> Tuple[
Dict[str, Any],
List[ErrorWrapper],
Optional[BackgroundTasks],
+ Response,
Dict[Tuple[Callable, Tuple[str]], Any],
]:
values: Dict[str, Any] = {}
errors: List[ErrorWrapper] = []
+ response = response or Response( # type: ignore
+ content=None, status_code=None, headers=None, media_type=None, background=None
+ )
dependency_cache = dependency_cache or {}
sub_dependant: Dependant
for sub_dependant in dependant.dependencies:
security_scopes=sub_dependant.security_scopes,
)
- sub_values, sub_errors, background_tasks, sub_dependency_cache = await solve_dependencies(
+ solved_result = await solve_dependencies(
request=request,
dependant=use_sub_dependant,
body=body,
background_tasks=background_tasks,
+ response=response,
dependency_overrides_provider=dependency_overrides_provider,
dependency_cache=dependency_cache,
)
+ sub_values, sub_errors, background_tasks, sub_response, sub_dependency_cache = (
+ solved_result
+ )
+ sub_response = cast(Response, sub_response)
+ response.headers.raw.extend(sub_response.headers.raw)
+ if sub_response.status_code:
+ response.status_code = sub_response.status_code
dependency_cache.update(sub_dependency_cache)
if sub_errors:
errors.extend(sub_errors)
if background_tasks is None:
background_tasks = BackgroundTasks()
values[dependant.background_tasks_param_name] = background_tasks
+ if dependant.response_param_name:
+ values[dependant.response_param_name] = response
if dependant.security_scopes_param_name:
values[dependant.security_scopes_param_name] = SecurityScopes(
scopes=dependant.security_scopes
)
- return values, errors, background_tasks, dependency_cache
+ return values, errors, background_tasks, response, dependency_cache
def request_params_to_args(
raise HTTPException(
status_code=400, detail="There was an error parsing the body"
) from e
- values, errors, background_tasks, _ = await solve_dependencies(
+ solved_result = await solve_dependencies(
request=request,
dependant=dependant,
body=body,
dependency_overrides_provider=dependency_overrides_provider,
)
+ values, errors, background_tasks, sub_response, _ = solved_result
if errors:
raise RequestValidationError(errors)
else:
by_alias=response_model_by_alias,
skip_defaults=response_model_skip_defaults,
)
- return response_class(
+ response = response_class(
content=response_data,
status_code=status_code,
background=background_tasks,
)
+ response.headers.raw.extend(sub_response.headers.raw)
+ if sub_response.status_code:
+ response.status_code = sub_response.status_code
+ return response
return app
dependant: Dependant, dependency_overrides_provider: Any = None
) -> Callable:
async def app(websocket: WebSocket) -> None:
- values, errors, _, _2 = await solve_dependencies(
+ solved_result = await solve_dependencies(
request=websocket,
dependant=dependant,
dependency_overrides_provider=dependency_overrides_provider,
)
+ values, errors, _, _2, _3 = solved_result
if errors:
await websocket.close(code=WS_1008_POLICY_VIOLATION)
raise WebSocketRequestValidationError(errors)
- Additional Responses in OpenAPI: 'tutorial/additional-responses.md'
- Response Cookies: 'tutorial/response-cookies.md'
- Response Headers: 'tutorial/response-headers.md'
+ - Response - Change Status Code: 'tutorial/response-change-status-code.md'
- Dependencies:
- First Steps: 'tutorial/dependencies/first-steps.md'
- Classes as Dependencies: 'tutorial/dependencies/classes-as-dependencies.md'
--- /dev/null
+from fastapi import Depends, FastAPI
+from starlette.responses import Response
+from starlette.testclient import TestClient
+
+app = FastAPI()
+
+
+async def response_status_setter(response: Response):
+ response.status_code = 201
+
+
+async def parent_dep(result=Depends(response_status_setter)):
+ return result
+
+
+@app.get("/", dependencies=[Depends(parent_dep)])
+async def get_main():
+ return {"msg": "Hello World"}
+
+
+client = TestClient(app)
+
+
+def test_dependency_set_status_code():
+ response = client.get("/")
+ assert response.status_code == 201
+ assert response.json() == {"msg": "Hello World"}
--- /dev/null
+from starlette.testclient import TestClient
+
+from response_change_status_code.tutorial001 import app
+
+client = TestClient(app)
+
+
+def test_path_operation():
+ response = client.put("/get-or-create-task/foo")
+ print(response.content)
+ assert response.status_code == 200
+ assert response.json() == "Listen to the Bar Fighters"
+ response = client.put("/get-or-create-task/bar")
+ assert response.status_code == 201
+ assert response.json() == "This didn't exist before"
--- /dev/null
+from starlette.testclient import TestClient
+
+from response_cookies.tutorial002 import app
+
+client = TestClient(app)
+
+
+def test_path_operation():
+ response = client.post("/cookie-and-object/")
+ assert response.status_code == 200
+ assert response.json() == {"message": "Come to the dark side, we have cookies"}
+ assert response.cookies["fakesession"] == "fake-cookie-session-value"
--- /dev/null
+from starlette.testclient import TestClient
+
+from response_headers.tutorial002 import app
+
+client = TestClient(app)
+
+
+def test_path_operation():
+ response = client.get("/headers-and-object/")
+ assert response.status_code == 200
+ assert response.json() == {"message": "Hello World"}
+ assert response.headers["X-Cat-Dog"] == "alone in the world"