Compare commits

...

17 Commits

Author SHA1 Message Date
Sebastián Ramírez
7e9d5aacf8 🔖 Release version 0.66.0 2021-07-04 20:59:46 +02:00
Sebastián Ramírez
d27a218bc3 📝 Update release notes 2021-07-04 20:58:49 +02:00
github-actions
0c4ded88fe 📝 Update release notes 2021-07-04 18:54:19 +00:00
Rubikoid
0e0931d308 🐛 Fix include/exclude for dicts in jsonable_encoder (#2016)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-07-04 20:53:40 +02:00
github-actions
dcfa9eb8fe 📝 Update release notes 2021-07-04 17:35:19 +00:00
Camila Gutierrez
6ebf60b175 🌐 Add Spanish translation for tutorial/query-params.md (#2243)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-07-04 19:34:46 +02:00
github-actions
a4d0724a97 📝 Update release notes 2021-07-04 17:06:51 +00:00
Juan Funez
c235e9b78c 🌐 Add Spanish translation for advanced/response-directly.md (#1253)
Co-authored-by: Camila Gutierrez <mariacamilagl30@gmail.com>
2021-07-04 19:06:18 +02:00
github-actions
5fa3e239db 📝 Update release notes 2021-07-04 14:15:04 +00:00
Juan Funez
3d1b107d70 🌐 Add Spanish translation for advanced/additional-status-codes.md (#1252)
Co-authored-by: Camila Gutierrez <mariacamilagl30@gmail.com>
2021-07-04 16:14:29 +02:00
github-actions
18e0828daf 📝 Update release notes 2021-07-04 12:50:02 +00:00
Juan Funez
f16c7729bd 🌐 Add Spanish translation for advanced/path-operation-advanced-configuration.md (#1251)
Co-authored-by: Camila Gutierrez <mariacamilagl@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
Co-authored-by: Camila Gutierrez <mariacamilagl30@gmail.com>
2021-07-04 14:49:31 +02:00
Sebastián Ramírez
9f3c8cd139 📝 Update release notes, add links to docs 2021-07-03 21:59:39 +02:00
github-actions
a7a35aee61 📝 Update release notes 2021-07-03 19:52:03 +00:00
Sebastián Ramírez
dc5a966548 Allow setting the response_class to RedirectResponse and returning the URL from the function (#3457) 2021-07-03 21:51:28 +02:00
github-actions
ea8d7f689e 📝 Update release notes 2021-07-03 17:16:33 +00:00
Jacob Magnusson
0ed6c92341 🐛 Support custom OpenAPI / JSON Schema fields in the generated output OpenAPI (#1429)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-07-03 19:15:59 +02:00
24 changed files with 825 additions and 32 deletions

View File

@@ -161,10 +161,33 @@ An alternative JSON response using <a href="https://github.com/ultrajson/ultrajs
Returns an HTTP redirect. Uses a 307 status code (Temporary Redirect) by default.
You can return a `RedirectResponse` directly:
```Python hl_lines="2 9"
{!../../../docs_src/custom_response/tutorial006.py!}
```
---
Or you can use it in the `response_class` parameter:
```Python hl_lines="2 7 9"
{!../../../docs_src/custom_response/tutorial006b.py!}
```
If you do that, then you can return the URL directly from your *path operation* function.
In this case, the `status_code` used will be the default one for the `RedirectResponse`, which is `307`.
---
You can also use the `status_code` parameter combined with the `response_class` parameter:
```Python hl_lines="2 7 9"
{!../../../docs_src/custom_response/tutorial006c.py!}
```
### `StreamingResponse`
Takes an async generator or a normal generator/iterator and streams the response body.
@@ -203,6 +226,14 @@ File responses will include appropriate `Content-Length`, `Last-Modified` and `E
{!../../../docs_src/custom_response/tutorial009.py!}
```
You can also use the `response_class` parameter:
```Python hl_lines="2 8 10"
{!../../../docs_src/custom_response/tutorial009b.py!}
```
In this case, you can return the file path directly from your *path operation* function.
## Default response class
When creating a **FastAPI** class instance or an `APIRouter` you can specify which response class to use by default.

View File

@@ -3,6 +3,24 @@
## Latest Changes
## 0.66.0
### Features
* ✨ Allow setting the `response_class` to `RedirectResponse` and returning the URL from the function. New and updated docs are in the tutorial section **Custom Response - HTML, Stream, File, others**, in [RedirectResponse](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse) and in [FileResponse](https://fastapi.tiangolo.com/advanced/custom-response/#fileresponse). PR [#3457](https://github.com/tiangolo/fastapi/pull/3457) by [@tiangolo](https://github.com/tiangolo).
### Fixes
* 🐛 Fix include/exclude for dicts in `jsonable_encoder`. PR [#2016](https://github.com/tiangolo/fastapi/pull/2016) by [@Rubikoid](https://github.com/Rubikoid).
* 🐛 Support custom OpenAPI / JSON Schema fields in the generated output OpenAPI. PR [#1429](https://github.com/tiangolo/fastapi/pull/1429) by [@jmagnusson](https://github.com/jmagnusson).
### Translations
* 🌐 Add Spanish translation for `tutorial/query-params.md`. PR [#2243](https://github.com/tiangolo/fastapi/pull/2243) by [@mariacamilagl](https://github.com/mariacamilagl).
* 🌐 Add Spanish translation for `advanced/response-directly.md`. PR [#1253](https://github.com/tiangolo/fastapi/pull/1253) by [@jfunez](https://github.com/jfunez).
* 🌐 Add Spanish translation for `advanced/additional-status-codes.md`. PR [#1252](https://github.com/tiangolo/fastapi/pull/1252) by [@jfunez](https://github.com/jfunez).
* 🌐 Add Spanish translation for `advanced/path-operation-advanced-configuration.md`. PR [#1251](https://github.com/tiangolo/fastapi/pull/1251) by [@jfunez](https://github.com/jfunez).
## 0.65.3
### Fixes

View File

@@ -0,0 +1,37 @@
# Códigos de estado adicionales
Por defecto, **FastAPI** devolverá las respuestas utilizando una `JSONResponse`, poniendo el contenido que devuelves en tu *operación de path* dentro de esa `JSONResponse`.
Utilizará el código de estado por defecto, o el que hayas asignado en tu *operación de path*.
## Códigos de estado adicionales
Si quieres devolver códigos de estado adicionales además del principal, puedes hacerlo devolviendo directamente una `Response`, como una `JSONResponse`, y asignar directamente el código de estado adicional.
Por ejemplo, digamos que quieres tener una *operación de path* que permita actualizar ítems y devolver códigos de estado HTTP 200 "OK" cuando sea exitosa.
Pero también quieres que acepte nuevos ítems. Cuando los ítems no existan anteriormente, serán creados y devolverá un código de estado HTTP 201 "Created".
Para conseguir esto importa `JSONResponse` y devuelve ahí directamente tu contenido, asignando el `status_code` que quieras:
```Python hl_lines="2 19"
{!../../../docs_src/additional_status_codes/tutorial001.py!}
```
!!! warning "Advertencia"
Cuando devuelves directamente una `Response`, como en los ejemplos anteriores, será devuelta directamente.
No será serializado con el modelo, etc.
Asegurate de que la respuesta tenga los datos que quieras, y que los valores sean JSON válidos (si estás usando `JSONResponse`).
!!! note "Detalles Técnicos"
También podrías utilizar `from starlette.responses import JSONResponse`.
**FastAPI** provee las mismas `starlette.responses` que `fastapi.responses` simplemente como una convención para ti, el desarrollador. Pero la mayoría de las respuestas disponibles vienen directamente de Starlette. Lo mismo con `status`.
## OpenAPI y documentación de API
Si quieres devolver códigos de estado y respuestas adicionales directamente, estas no estarán incluidas en el schema de OpenAPI (documentación de API), porque FastAPI no tiene una manera de conocer de antemano lo que vas a devolver.
Pero puedes documentar eso en tu código usando [Respuestas Adicionales](additional-responses.md){.internal-link target=_blank}.

View File

@@ -0,0 +1,52 @@
# Configuración avanzada de las operaciones de path
## OpenAPI operationId
!!! warning "Advertencia"
Si no eres una persona "experta" en OpenAPI, probablemente no necesitas leer esto.
Puedes asignar el `operationId` de OpenAPI para ser usado en tu *operación de path* con el parámetro `operation_id`.
En este caso tendrías que asegurarte de que sea único para cada operación.
```Python hl_lines="6"
{!../../../docs_src/path_operation_advanced_configuration/tutorial001.py!}
```
### Usando el nombre de la *función de la operación de path* en el operationId
Si quieres usar tus nombres de funciones de API como `operationId`s, puedes iterar sobre todos ellos y sobrescribir `operation_id` de cada *operación de path* usando su `APIRoute.name`.
Deberías hacerlo después de adicionar todas tus *operaciones de path*.
```Python hl_lines="2 12 13 14 15 16 17 18 19 20 21 24"
{!../../../docs_src/path_operation_advanced_configuration/tutorial002.py!}
```
!!! tip "Consejo"
Si llamas manualmente a `app.openapi()`, debes actualizar el `operationId`s antes de hacerlo.
!!! warning "Advertencia"
Si haces esto, debes asegurarte de que cada una de tus *funciones de las operaciones de path* tenga un nombre único.
Incluso si están en diferentes módulos (archivos Python).
## Excluir de OpenAPI
Para excluir una *operación de path* del esquema OpenAPI generado (y por tanto del la documentación generada automáticamente), usa el parámetro `include_in_schema` y asigna el valor como `False`;
```Python hl_lines="6"
{!../../../docs_src/path_operation_advanced_configuration/tutorial003.py!}
```
## Descripción avanzada desde el docstring
Puedes limitar las líneas usadas desde el docstring de una *operación de path* para OpenAPI.
Agregar un `\f` (un carácter de "form feed" escapado) hace que **FastAPI** trunque el output utilizada para OpenAPI en ese punto.
No será mostrado en la documentación, pero otras herramientas (como Sphinx) serán capaces de usar el resto.
```Python hl_lines="19 20 21 22 23 24 25 26 27 28 29"
{!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!}
```

View File

@@ -0,0 +1,63 @@
# Devolver una respuesta directamente
Cuando creas una *operación de path* normalmente puedes devolver cualquier dato: un `dict`, una `list`, un modelo Pydantic, un modelo de base de datos, etc.
Por defecto, **FastAPI** convertiría automáticamente ese valor devuelto a JSON usando el `jsonable_encoder` explicado en [Codificador Compatible JSON](../tutorial/encoder.md){.internal-link target=_blank}.
Luego, tras bastidores, pondría esos datos compatibles con JSON (por ejemplo, un `dict`) dentro de una `JSONResponse` que se usaría para enviar la respuesta al cliente.
Pero puedes devolver una `JSONResponse` directamente de tu *operación de path*.
Esto puede ser útil, por ejemplo, para devolver cookies o headers personalizados.
## Devolver una `Response`
De hecho, puedes devolver cualquier `Response` o cualquier subclase de la misma.
!!! tip "Consejo"
`JSONResponse` en sí misma es una subclase de `Response`.
Y cuando devuelves una `Response`, **FastAPI** la pasará directamente.
No hará ninguna conversión de datos con modelos Pydantic, no convertirá el contenido a ningún tipo, etc.
Esto te da mucha flexibilidad. Puedes devolver cualquier tipo de dato, sobrescribir cualquer declaración de datos o validación, etc.
## Usando el `jsonable_encoder` en una `Response`
Como **FastAPI** no realiza ningún cambio en la `Response` que devuelves, debes asegurarte de que el contenido está listo.
Por ejemplo, no puedes poner un modelo Pydantic en una `JSONResponse` sin primero convertirlo a un `dict` con todos los tipos de datos (como `datetime`, `UUID`, etc) convertidos a tipos compatibles con JSON.
Para esos casos, puedes usar el `jsonable_encoder` para convertir tus datos antes de pasarlos a la respuesta:
```Python hl_lines="4 6 20 21"
{!../../../docs_src/response_directly/tutorial001.py!}
```
!!! note "Detalles Técnicos"
También puedes usar `from starlette.responses import JSONResponse`.
**FastAPI** provee `starlette.responses` como `fastapi.responses`, simplemente como una conveniencia para ti, el desarrollador. Pero la mayoría de las respuestas disponibles vienen directamente de Starlette.
## Devolviendo una `Response` personalizada
El ejemplo anterior muestra las partes que necesitas, pero no es muy útil todavía, dado que podrías simplemente devolver el `item` directamente, y **FastAPI** lo pondría en una `JSONResponse` por ti, convirtiéndolo en un `dict`, etc. Todo esto por defecto.
Ahora, veamos cómo puedes usarlo para devolver una respuesta personalizada.
Digamos que quieres devolver una respuesta <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a>.
Podrías poner tu contenido XML en un string, ponerlo en una `Response` y devolverlo:
```Python hl_lines="1 18"
{!../../../docs_src/response_directly/tutorial002.py!}
```
## Notas
Cuando devuelves una `Response` directamente, los datos no son validados, convertidos (serializados), ni documentados automáticamente.
Pero todavía es posible documentarlo como es descrito en [Respuestas adicionales en OpenAPI](additional-responses.md){.internal-link target=_blank}.
Puedes ver en secciones posteriores como usar/declarar esas `Response`s personalizadas aún teniendo conversión automática de datos, documentación, etc.

View File

@@ -0,0 +1,197 @@
# Parámetros de query
Cuando declaras otros parámetros de la función que no hacen parte de los parámetros de path estos se interpretan automáticamente como parámetros de "query".
```Python hl_lines="9"
{!../../../docs_src/query_params/tutorial001.py!}
```
El query es el conjunto de pares de key-value que van después del `?` en la URL, separados por caracteres `&`.
Por ejemplo, en la URL:
```
http://127.0.0.1:8000/items/?skip=0&limit=10
```
...los parámetros de query son:
* `skip`: con un valor de `0`
* `limit`: con un valor de `10`
Dado que son parte de la URL son strings "naturalmente".
Pero cuando los declaras con tipos de Python (en el ejemplo arriba, como `int`) son convertidos a ese tipo y son validados con él.
Todo el proceso que aplicaba a los parámetros de path también aplica a los parámetros de query:
* Soporte del editor (obviamente)
* <abbr title="convertir el string que viene de un HTTP request a datos de Python">"Parsing"</abbr> de datos
* Validación de datos
* Documentación automática
## Configuraciones por defecto
Como los parámetros de query no están fijos en una parte del path pueden ser opcionales y pueden tener valores por defecto.
El ejemplo arriba tiene `skip=0` y `limit=10` como los valores por defecto.
Entonces, si vas a la URL:
```
http://127.0.0.1:8000/items/
```
Sería lo mismo que ir a:
```
http://127.0.0.1:8000/items/?skip=0&limit=10
```
Pero, si por ejemplo vas a:
```
http://127.0.0.1:8000/items/?skip=20
```
Los valores de los parámetros en tu función serán:
* `skip=20`: porque lo definiste en la URL
* `limit=10`: porque era el valor por defecto
## Parámetros opcionales
Del mismo modo puedes declarar parámetros de query opcionales definiendo el valor por defecto como `None`:
```Python hl_lines="9"
{!../../../docs_src/query_params/tutorial002.py!}
```
En este caso el parámetro de la función `q` será opcional y será `None` por defecto.
!!! check "Revisa"
También puedes notar que **FastAPI** es lo suficientemente inteligente para darse cuenta de que el parámetro de path `item_id` es un parámetro de path y que `q` no lo es, y por lo tanto es un parámetro de query.
!!! note "Nota"
FastAPI sabrá que `q` es opcional por el `= None`.
El `Optional` en `Optional[str]` no es usado por FastAPI (FastAPI solo usará la parte `str`), pero el `Optional[str]` le permitirá a tu editor ayudarte a encontrar errores en tu código.
## Conversión de tipos de parámetros de query
También puedes declarar tipos `bool` y serán convertidos:
```Python hl_lines="9"
{!../../../docs_src/query_params/tutorial003.py!}
```
En este caso, si vas a:
```
http://127.0.0.1:8000/items/foo?short=1
```
o
```
http://127.0.0.1:8000/items/foo?short=True
```
o
```
http://127.0.0.1:8000/items/foo?short=true
```
o
```
http://127.0.0.1:8000/items/foo?short=on
```
o
```
http://127.0.0.1:8000/items/foo?short=yes
```
o cualquier otra variación (mayúsculas, primera letra en mayúscula, etc.) tu función verá el parámetro `short` con un valor `bool` de `True`. Si no, lo verá como `False`.
## Múltiples parámetros de path y query
Puedes declarar múltiples parámetros de path y parámetros de query al mismo tiempo. **FastAPI** sabe cuál es cuál.
No los tienes que declarar en un orden específico.
Serán detectados por nombre:
```Python hl_lines="8 10"
{!../../../docs_src/query_params/tutorial004.py!}
```
## Parámetros de query requeridos
Cuando declaras un valor por defecto para los parámetros que no son de path (por ahora solo hemos visto parámetros de query), entonces no es requerido.
Si no quieres añadir un valor específico sino solo hacerlo opcional, pon el valor por defecto como `None`.
Pero cuando quieres hacer que un parámetro de query sea requerido, puedes simplemente no declararle un valor por defecto:
```Python hl_lines="6-7"
{!../../../docs_src/query_params/tutorial005.py!}
```
Aquí el parámetro de query `needy` es un parámetro de query requerido, del tipo `str`.
Si abres tu navegador en una URL como:
```
http://127.0.0.1:8000/items/foo-item
```
...sin añadir el parámetro `needy` requerido, verás un error como:
```JSON
{
"detail": [
{
"loc": [
"query",
"needy"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
```
Dado que `needy` es un parámetro requerido necesitarías declararlo en la URL:
```
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
```
...esto funcionaría:
```JSON
{
"item_id": "foo-item",
"needy": "sooooneedy"
}
```
Por supuesto que también puedes definir algunos parámetros como requeridos, con un valor por defecto y otros completamente opcionales:
```Python hl_lines="10"
{!../../../docs_src/query_params/tutorial006.py!}
```
En este caso hay 3 parámetros de query:
* `needy`, un `str` requerido.
* `skip`, un `int` con un valor por defecto de `0`.
* `limit`, un `int` opcional.
!!! tip "Consejo"
También podrías usar los `Enum`s de la misma manera que con los [Parámetros de path](path-params.md#predefined-values){.internal-link target=_blank}.

View File

@@ -58,6 +58,7 @@ nav:
- tutorial/index.md
- tutorial/first-steps.md
- tutorial/path-params.md
- tutorial/query-params.md
- Guía de Usuario Avanzada:
- advanced/index.md
- async.md

View File

@@ -5,5 +5,5 @@ app = FastAPI()
@app.get("/typer")
async def read_typer():
async def redirect_typer():
return RedirectResponse("https://typer.tiangolo.com")

View File

@@ -0,0 +1,9 @@
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/fastapi", response_class=RedirectResponse)
async def redirect_fastapi():
return "https://fastapi.tiangolo.com"

View File

@@ -0,0 +1,9 @@
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/pydantic", response_class=RedirectResponse, status_code=302)
async def redirect_pydantic():
return "https://pydantic-docs.helpmanual.io/"

View File

@@ -0,0 +1,10 @@
from fastapi import FastAPI
from fastapi.responses import FileResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
@app.get("/", response_class=FileResponse)
async def main():
return some_file_path

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.65.3"
__version__ = "0.66.0"
from starlette import status as status

View File

@@ -206,7 +206,7 @@ class FastAPI(Starlette):
endpoint: Callable[..., Coroutine[Any, Any, Response]],
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
@@ -258,7 +258,7 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
@@ -351,7 +351,7 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
@@ -400,7 +400,7 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
@@ -449,7 +449,7 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
@@ -498,7 +498,7 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
@@ -547,7 +547,7 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
@@ -596,7 +596,7 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
@@ -645,7 +645,7 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
@@ -694,7 +694,7 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,

View File

@@ -36,9 +36,9 @@ def jsonable_encoder(
custom_encoder: Dict[Any, Callable[[Any], Any]] = {},
sqlalchemy_safe: bool = True,
) -> Any:
if include is not None and not isinstance(include, set):
if include is not None and not isinstance(include, (set, dict)):
include = set(include)
if exclude is not None and not isinstance(exclude, set):
if exclude is not None and not isinstance(exclude, (set, dict)):
exclude = set(exclude)
if isinstance(obj, BaseModel):
encoder = getattr(obj.__config__, "json_encoders", {})

View File

@@ -117,6 +117,9 @@ class SchemaBase(BaseModel):
example: Optional[Any] = None
deprecated: Optional[bool] = None
class Config:
extra: str = "allow"
class Schema(SchemaBase):
allOf: Optional[List[SchemaBase]] = None

View File

@@ -1,4 +1,5 @@
import http.client
import inspect
from enum import Enum
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, cast
@@ -218,7 +219,19 @@ def get_openapi_path(
)
callbacks[callback.name] = {callback.path: cb_path}
operation["callbacks"] = callbacks
status_code = str(route.status_code)
if route.status_code is not None:
status_code = str(route.status_code)
else:
# It would probably make more sense for all response classes to have an
# explicit default status_code, and to extract it from them, instead of
# doing this inspection tricks, that would probably be in the future
# TODO: probably make status_code a default class attribute for all
# responses in Starlette
response_signature = inspect.signature(current_response_class.__init__)
status_code_param = response_signature.parameters.get("status_code")
if status_code_param is not None:
if isinstance(status_code_param.default, int):
status_code = str(status_code_param.default)
operation.setdefault("responses", {}).setdefault(status_code, {})[
"description"
] = route.response_description

View File

@@ -154,7 +154,7 @@ async def run_endpoint_function(
def get_request_handler(
dependant: Dependant,
body_field: Optional[ModelField] = None,
status_code: int = 200,
status_code: Optional[int] = None,
response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse),
response_field: Optional[ModelField] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
@@ -232,11 +232,12 @@ def get_request_handler(
exclude_none=response_model_exclude_none,
is_coroutine=is_coroutine,
)
response = actual_response_class(
content=response_data,
status_code=status_code,
background=background_tasks, # type: ignore # in Starlette
)
response_args: Dict[str, Any] = {"background": background_tasks}
# If status_code was set, use it, otherwise use the default from the
# response class, in the case of redirect it's 307
if status_code is not None:
response_args["status_code"] = status_code
response = actual_response_class(response_data, **response_args)
response.headers.raw.extend(sub_response.headers.raw)
if sub_response.status_code:
response.status_code = sub_response.status_code
@@ -293,7 +294,7 @@ class APIRoute(routing.Route):
endpoint: Callable[..., Any],
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
@@ -469,7 +470,7 @@ class APIRouter(routing.Router):
endpoint: Callable[..., Any],
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
@@ -541,7 +542,7 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
@@ -719,7 +720,7 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
@@ -769,7 +770,7 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
@@ -819,7 +820,7 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
@@ -869,7 +870,7 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
@@ -919,7 +920,7 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
@@ -969,7 +970,7 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
@@ -1019,7 +1020,7 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
@@ -1069,7 +1070,7 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
status_code: Optional[int] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,

View File

@@ -0,0 +1,51 @@
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
class Config:
schema_extra = {
"x-something-internal": {"level": 4},
}
@app.get("/foo", response_model=Item)
def foo():
return {"name": "Foo item"}
client = TestClient(app)
item_schema = {
"title": "Item",
"required": ["name"],
"type": "object",
"x-something-internal": {
"level": 4,
},
"properties": {
"name": {
"title": "Name",
"type": "string",
}
},
}
def test_custom_response_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json()["components"]["schemas"]["Item"] == item_schema
def test_response():
# For coverage
response = client.get("/foo")
assert response.status_code == 200, response.text
assert response.json() == {"name": "Foo item"}

View File

@@ -0,0 +1,174 @@
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
class Test(BaseModel):
foo: str
bar: str
class Test2(BaseModel):
test: Test
baz: str
class Test3(BaseModel):
name: str
age: int
test2: Test2
app = FastAPI()
@app.get(
"/simple_include",
response_model=Test2,
response_model_include={"baz": ..., "test": {"foo"}},
)
def simple_include():
return Test2(
test=Test(foo="simple_include test foo", bar="simple_include test bar"),
baz="simple_include test2 baz",
)
@app.get(
"/simple_include_dict",
response_model=Test2,
response_model_include={"baz": ..., "test": {"foo"}},
)
def simple_include_dict():
return {
"test": {
"foo": "simple_include_dict test foo",
"bar": "simple_include_dict test bar",
},
"baz": "simple_include_dict test2 baz",
}
@app.get(
"/simple_exclude",
response_model=Test2,
response_model_exclude={"test": {"bar"}},
)
def simple_exclude():
return Test2(
test=Test(foo="simple_exclude test foo", bar="simple_exclude test bar"),
baz="simple_exclude test2 baz",
)
@app.get(
"/simple_exclude_dict",
response_model=Test2,
response_model_exclude={"test": {"bar"}},
)
def simple_exclude_dict():
return {
"test": {
"foo": "simple_exclude_dict test foo",
"bar": "simple_exclude_dict test bar",
},
"baz": "simple_exclude_dict test2 baz",
}
@app.get(
"/mixed",
response_model=Test3,
response_model_include={"test2", "name"},
response_model_exclude={"test2": {"baz"}},
)
def mixed():
return Test3(
name="mixed test3 name",
age=3,
test2=Test2(
test=Test(foo="mixed test foo", bar="mixed test bar"), baz="mixed test2 baz"
),
)
@app.get(
"/mixed_dict",
response_model=Test3,
response_model_include={"test2", "name"},
response_model_exclude={"test2": {"baz"}},
)
def mixed_dict():
return {
"name": "mixed_dict test3 name",
"age": 3,
"test2": {
"test": {"foo": "mixed_dict test foo", "bar": "mixed_dict test bar"},
"baz": "mixed_dict test2 baz",
},
}
client = TestClient(app)
def test_nested_include_simple():
response = client.get("/simple_include")
assert response.status_code == 200, response.text
assert response.json() == {
"baz": "simple_include test2 baz",
"test": {"foo": "simple_include test foo"},
}
def test_nested_include_simple_dict():
response = client.get("/simple_include_dict")
assert response.status_code == 200, response.text
assert response.json() == {
"baz": "simple_include_dict test2 baz",
"test": {"foo": "simple_include_dict test foo"},
}
def test_nested_exclude_simple():
response = client.get("/simple_exclude")
assert response.status_code == 200, response.text
assert response.json() == {
"baz": "simple_exclude test2 baz",
"test": {"foo": "simple_exclude test foo"},
}
def test_nested_exclude_simple_dict():
response = client.get("/simple_exclude_dict")
assert response.status_code == 200, response.text
assert response.json() == {
"baz": "simple_exclude_dict test2 baz",
"test": {"foo": "simple_exclude_dict test foo"},
}
def test_nested_include_mixed():
response = client.get("/mixed")
assert response.status_code == 200, response.text
assert response.json() == {
"name": "mixed test3 name",
"test2": {
"test": {"foo": "mixed test foo", "bar": "mixed test bar"},
},
}
def test_nested_include_mixed_dict():
response = client.get("/mixed_dict")
assert response.status_code == 200, response.text
assert response.json() == {
"name": "mixed_dict test3 name",
"test2": {
"test": {"foo": "mixed_dict test foo", "bar": "mixed_dict test bar"},
},
}

View File

@@ -5,6 +5,32 @@ from docs_src.custom_response.tutorial006 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/typer": {
"get": {
"summary": "Redirect Typer",
"operationId": "redirect_typer_typer_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_get():
response = client.get("/typer", allow_redirects=False)
assert response.status_code == 307, response.text

View File

@@ -0,0 +1,32 @@
from fastapi.testclient import TestClient
from docs_src.custom_response.tutorial006b import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/fastapi": {
"get": {
"summary": "Redirect Fastapi",
"operationId": "redirect_fastapi_fastapi_get",
"responses": {"307": {"description": "Successful Response"}},
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_redirect_response_class():
response = client.get("/fastapi", allow_redirects=False)
assert response.status_code == 307
assert response.headers["location"] == "https://fastapi.tiangolo.com"

View File

@@ -0,0 +1,32 @@
from fastapi.testclient import TestClient
from docs_src.custom_response.tutorial006c import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/pydantic": {
"get": {
"summary": "Redirect Pydantic",
"operationId": "redirect_pydantic_pydantic_get",
"responses": {"302": {"description": "Successful Response"}},
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_redirect_status_code():
response = client.get("/pydantic", allow_redirects=False)
assert response.status_code == 302
assert response.headers["location"] == "https://pydantic-docs.helpmanual.io/"

View File

@@ -0,0 +1,17 @@
from pathlib import Path
from fastapi.testclient import TestClient
from docs_src.custom_response import tutorial009
from docs_src.custom_response.tutorial009 import app
client = TestClient(app)
def test_get(tmp_path: Path):
file_path: Path = tmp_path / "large-video-file.mp4"
tutorial009.some_file_path = str(file_path)
test_content = b"Fake video bytes"
file_path.write_bytes(test_content)
response = client.get("/")
assert response.content == test_content

View File

@@ -0,0 +1,17 @@
from pathlib import Path
from fastapi.testclient import TestClient
from docs_src.custom_response import tutorial009b
from docs_src.custom_response.tutorial009b import app
client = TestClient(app)
def test_get(tmp_path: Path):
file_path: Path = tmp_path / "large-video-file.mp4"
tutorial009b.some_file_path = str(file_path)
test_content = b"Fake video bytes"
file_path.write_bytes(test_content)
response = client.get("/")
assert response.content == test_content