Sync fastapi docs from b5ca1324 on 2025-12-07
Issue Manager / issue-manager (push) Has been cancelled
Build Docs / changes (push) Has been cancelled
Build Docs / langs (push) Has been cancelled
Build Docs / build-docs (push) Has been cancelled
Build Docs / docs-all-green (push) Has been cancelled
Conflict detector / main (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi) (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi-slim) (push) Has been cancelled
Test Redistribute / test-redistribute-alls-green (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / test (pydantic-v1, 3.10) (push) Has been cancelled
Test / test (pydantic-v1, 3.11) (push) Has been cancelled
Test / test (pydantic-v1, 3.13) (push) Has been cancelled
Test / test (pydantic-v1, 3.8) (push) Has been cancelled
Test / test (pydantic-v1, 3.9) (push) Has been cancelled
Test / test (pydantic-v2, 3.10) (push) Has been cancelled
Test / test (pydantic-v2, 3.11) (push) Has been cancelled
Test / test (pydantic-v2, 3.12) (push) Has been cancelled
Test / test (pydantic-v2, 3.13) (push) Has been cancelled
Test / test (pydantic-v2, 3.14) (push) Has been cancelled
Test / test (pydantic-v2, 3.8) (push) Has been cancelled
Test / test (pydantic-v2, 3.9) (push) Has been cancelled
Test / coverage-combine (push) Has been cancelled
Test / check (push) Has been cancelled
Label Approved / label-approved (push) Has been cancelled
FastAPI People Contributors / job (push) Has been cancelled
FastAPI People Sponsors / job (push) Has been cancelled
Update Topic Repos / topic-repos (push) Has been cancelled
FastAPI People / job (push) Has been cancelled
Test / test (pydantic-v1, 3.12) (push) Has been cancelled

This commit is contained in:
The Scheduler
2025-12-07 21:35:20 +00:00
commit a42b1ff04e
2600 changed files with 301870 additions and 0 deletions
@@ -0,0 +1,247 @@
# Responses Adicionales en OpenAPI
/// warning | Advertencia
Este es un tema bastante avanzado.
Si estás comenzando con **FastAPI**, puede que no lo necesites.
///
Puedes declarar responses adicionales, con códigos de estado adicionales, media types, descripciones, etc.
Esos responses adicionales se incluirán en el esquema de OpenAPI, por lo que también aparecerán en la documentación de la API.
Pero para esos responses adicionales tienes que asegurarte de devolver un `Response` como `JSONResponse` directamente, con tu código de estado y contenido.
## Response Adicional con `model`
Puedes pasar a tus *decoradores de path operation* un parámetro `responses`.
Recibe un `dict`: las claves son los códigos de estado para cada response (como `200`), y los valores son otros `dict`s con la información para cada uno de ellos.
Cada uno de esos `dict`s de response puede tener una clave `model`, conteniendo un modelo de Pydantic, así como `response_model`.
**FastAPI** tomará ese modelo, generará su JSON Schema y lo incluirá en el lugar correcto en OpenAPI.
Por ejemplo, para declarar otro response con un código de estado `404` y un modelo Pydantic `Message`, puedes escribir:
{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *}
/// note | Nota
Ten en cuenta que debes devolver el `JSONResponse` directamente.
///
/// info | Información
La clave `model` no es parte de OpenAPI.
**FastAPI** tomará el modelo de Pydantic de allí, generará el JSON Schema y lo colocará en el lugar correcto.
El lugar correcto es:
* En la clave `content`, que tiene como valor otro objeto JSON (`dict`) que contiene:
* Una clave con el media type, por ejemplo, `application/json`, que contiene como valor otro objeto JSON, que contiene:
* Una clave `schema`, que tiene como valor el JSON Schema del modelo, aquí es el lugar correcto.
* **FastAPI** agrega una referencia aquí a los JSON Schemas globales en otro lugar de tu OpenAPI en lugar de incluirlo directamente. De este modo, otras aplicaciones y clientes pueden usar esos JSON Schemas directamente, proporcionar mejores herramientas de generación de código, etc.
///
Los responses generadas en el OpenAPI para esta *path operation* serán:
```JSON hl_lines="3-12"
{
"responses": {
"404": {
"description": "Additional Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
},
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Item"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
```
Los esquemas se referencian a otro lugar dentro del esquema de OpenAPI:
```JSON hl_lines="4-16"
{
"components": {
"schemas": {
"Message": {
"title": "Message",
"required": [
"message"
],
"type": "object",
"properties": {
"message": {
"title": "Message",
"type": "string"
}
}
},
"Item": {
"title": "Item",
"required": [
"id",
"value"
],
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"value": {
"title": "Value",
"type": "string"
}
}
},
"ValidationError": {
"title": "ValidationError",
"required": [
"loc",
"msg",
"type"
],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"type": "string"
}
},
"msg": {
"title": "Message",
"type": "string"
},
"type": {
"title": "Error Type",
"type": "string"
}
}
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
}
}
}
}
```
## Media types adicionales para el response principal
Puedes usar este mismo parámetro `responses` para agregar diferentes media type para el mismo response principal.
Por ejemplo, puedes agregar un media type adicional de `image/png`, declarando que tu *path operation* puede devolver un objeto JSON (con media type `application/json`) o una imagen PNG:
{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *}
/// note | Nota
Nota que debes devolver la imagen usando un `FileResponse` directamente.
///
/// info | Información
A menos que especifiques un media type diferente explícitamente en tu parámetro `responses`, FastAPI asumirá que el response tiene el mismo media type que la clase de response principal (por defecto `application/json`).
Pero si has especificado una clase de response personalizada con `None` como su media type, FastAPI usará `application/json` para cualquier response adicional que tenga un modelo asociado.
///
## Combinando información
También puedes combinar información de response de múltiples lugares, incluyendo los parámetros `response_model`, `status_code`, y `responses`.
Puedes declarar un `response_model`, usando el código de estado predeterminado `200` (o uno personalizado si lo necesitas), y luego declarar información adicional para ese mismo response en `responses`, directamente en el esquema de OpenAPI.
**FastAPI** manterá la información adicional de `responses` y la combinará con el JSON Schema de tu modelo.
Por ejemplo, puedes declarar un response con un código de estado `404` que usa un modelo Pydantic y tiene una `description` personalizada.
Y un response con un código de estado `200` que usa tu `response_model`, pero incluye un `example` personalizado:
{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *}
Todo se combinará e incluirá en tu OpenAPI, y se mostrará en la documentación de la API:
<img src="/img/tutorial/additional-responses/image01.png">
## Combina responses predefinidos y personalizados
Es posible que desees tener algunos responses predefinidos que se apliquen a muchas *path operations*, pero que quieras combinarlos con responses personalizados necesarios por cada *path operation*.
Para esos casos, puedes usar la técnica de Python de "desempaquetar" un `dict` con `**dict_to_unpack`:
```Python
old_dict = {
"old key": "old value",
"second old key": "second old value",
}
new_dict = {**old_dict, "new key": "new value"}
```
Aquí, `new_dict` contendrá todos los pares clave-valor de `old_dict` más el nuevo par clave-valor:
```Python
{
"old key": "old value",
"second old key": "second old value",
"new key": "new value",
}
```
Puedes usar esa técnica para reutilizar algunos responses predefinidos en tus *path operations* y combinarlos con otros personalizados adicionales.
Por ejemplo:
{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
## Más información sobre responses OpenAPI
Para ver exactamente qué puedes incluir en los responses, puedes revisar estas secciones en la especificación OpenAPI:
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">Objeto de Responses de OpenAPI</a>, incluye el `Response Object`.
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">Objeto de Response de OpenAPI</a>, puedes incluir cualquier cosa de esto directamente en cada response dentro de tu parámetro `responses`. Incluyendo `description`, `headers`, `content` (dentro de este es que declaras diferentes media types y JSON Schemas), y `links`.
@@ -0,0 +1,41 @@
# Códigos de Estado Adicionales
Por defecto, **FastAPI** devolverá los responses usando un `JSONResponse`, colocando el contenido que devuelves desde tu *path operation* dentro de ese `JSONResponse`.
Usará el código de estado por defecto o el que configures en tu *path operation*.
## Códigos de estado adicionales
Si quieres devolver códigos de estado adicionales aparte del principal, puedes hacerlo devolviendo un `Response` directamente, como un `JSONResponse`, y configurando el código de estado adicional directamente.
Por ejemplo, supongamos que quieres tener una *path operation* que permita actualizar elementos, y devuelva códigos de estado HTTP de 200 "OK" cuando sea exitoso.
Pero también quieres que acepte nuevos elementos. Y cuando los elementos no existían antes, los crea y devuelve un código de estado HTTP de 201 "Created".
Para lograr eso, importa `JSONResponse`, y devuelve tu contenido allí directamente, configurando el `status_code` que deseas:
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
/// warning | Advertencia
Cuando devuelves un `Response` directamente, como en el ejemplo anterior, se devuelve directamente.
No se serializará con un modelo, etc.
Asegúrate de que tenga los datos que deseas que tenga y que los valores sean JSON válidos (si estás usando `JSONResponse`).
///
/// note | Detalles Técnicos
También podrías usar `from starlette.responses import JSONResponse`.
**FastAPI** proporciona los mismos `starlette.responses` que `fastapi.responses` solo como una conveniencia para ti, el desarrollador. Pero la mayoría de los responses disponibles provienen directamente de Starlette. Lo mismo con `status`.
///
## OpenAPI y documentación de API
Si devuelves códigos de estado adicionales y responses directamente, no se incluirán en el esquema de OpenAPI (la documentación de la API), porque FastAPI no tiene una forma de saber de antemano qué vas a devolver.
Pero puedes documentarlo en tu código, usando: [Responses Adicionales](additional-responses.md){.internal-link target=_blank}.
@@ -0,0 +1,65 @@
# Dependencias Avanzadas
## Dependencias con parámetros
Todas las dependencias que hemos visto son una función o clase fija.
Pero podría haber casos en los que quieras poder establecer parámetros en la dependencia, sin tener que declarar muchas funciones o clases diferentes.
Imaginemos que queremos tener una dependencia que revise si el parámetro de query `q` contiene algún contenido fijo.
Pero queremos poder parametrizar ese contenido fijo.
## Una *instance* "callable"
En Python hay una forma de hacer que una instance de una clase sea un "callable".
No la clase en sí (que ya es un callable), sino una instance de esa clase.
Para hacer eso, declaramos un método `__call__`:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *}
En este caso, este `__call__` es lo que **FastAPI** usará para comprobar parámetros adicionales y sub-dependencias, y es lo que llamará para pasar un valor al parámetro en tu *path operation function* más adelante.
## Parametrizar la instance
Y ahora, podemos usar `__init__` para declarar los parámetros de la instance que podemos usar para "parametrizar" la dependencia:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *}
En este caso, **FastAPI** nunca tocará ni se preocupará por `__init__`, lo usaremos directamente en nuestro código.
## Crear una instance
Podríamos crear una instance de esta clase con:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *}
Y de esa manera podemos "parametrizar" nuestra dependencia, que ahora tiene `"bar"` dentro de ella, como el atributo `checker.fixed_content`.
## Usar la instance como una dependencia
Luego, podríamos usar este `checker` en un `Depends(checker)`, en lugar de `Depends(FixedContentQueryChecker)`, porque la dependencia es la instance, `checker`, no la clase en sí.
Y al resolver la dependencia, **FastAPI** llamará a este `checker` así:
```Python
checker(q="somequery")
```
...y pasará lo que eso retorne como el valor de la dependencia en nuestra *path operation function* como el parámetro `fixed_content_included`:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *}
/// tip | Consejo
Todo esto podría parecer complicado. Y puede que no esté muy claro cómo es útil aún.
Estos ejemplos son intencionalmente simples, pero muestran cómo funciona todo.
En los capítulos sobre seguridad, hay funciones utilitarias que se implementan de esta misma manera.
Si entendiste todo esto, ya sabes cómo funcionan por debajo esas herramientas de utilidad para seguridad.
///
+99
View File
@@ -0,0 +1,99 @@
# Tests Asíncronos
Ya has visto cómo probar tus aplicaciones de **FastAPI** usando el `TestClient` proporcionado. Hasta ahora, solo has visto cómo escribir tests sincrónicos, sin usar funciones `async`.
Poder usar funciones asíncronas en tus tests puede ser útil, por ejemplo, cuando consultas tu base de datos de forma asíncrona. Imagina que quieres probar el envío de requests a tu aplicación FastAPI y luego verificar que tu backend escribió exitosamente los datos correctos en la base de datos, mientras usas un paquete de base de datos asíncrono.
Veamos cómo podemos hacer que esto funcione.
## pytest.mark.anyio
Si queremos llamar funciones asíncronas en nuestros tests, nuestras funciones de test tienen que ser asíncronas. AnyIO proporciona un plugin útil para esto, que nos permite especificar que algunas funciones de test deben ser llamadas de manera asíncrona.
## HTTPX
Incluso si tu aplicación de **FastAPI** usa funciones `def` normales en lugar de `async def`, sigue siendo una aplicación `async` por debajo.
El `TestClient` hace algo de magia interna para llamar a la aplicación FastAPI asíncrona en tus funciones de test `def` normales, usando pytest estándar. Pero esa magia ya no funciona cuando lo usamos dentro de funciones asíncronas. Al ejecutar nuestros tests de manera asíncrona, ya no podemos usar el `TestClient` dentro de nuestras funciones de test.
El `TestClient` está basado en <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, y afortunadamente, podemos usarlo directamente para probar la API.
## Ejemplo
Para un ejemplo simple, consideremos una estructura de archivos similar a la descrita en [Aplicaciones Más Grandes](../tutorial/bigger-applications.md){.internal-link target=_blank} y [Testing](../tutorial/testing.md){.internal-link target=_blank}:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   └── test_main.py
```
El archivo `main.py` tendría:
{* ../../docs_src/async_tests/main.py *}
El archivo `test_main.py` tendría los tests para `main.py`, podría verse así ahora:
{* ../../docs_src/async_tests/test_main.py *}
## Ejecútalo
Puedes ejecutar tus tests como de costumbre vía:
<div class="termy">
```console
$ pytest
---> 100%
```
</div>
## En Detalle
El marcador `@pytest.mark.anyio` le dice a pytest que esta función de test debe ser llamada asíncronamente:
{* ../../docs_src/async_tests/test_main.py hl[7] *}
/// tip | Consejo
Note que la función de test ahora es `async def` en lugar de solo `def` como antes al usar el `TestClient`.
///
Luego podemos crear un `AsyncClient` con la app y enviar requests asíncronos a ella, usando `await`.
{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
Esto es equivalente a:
```Python
response = client.get('/')
```
...que usábamos para hacer nuestros requests con el `TestClient`.
/// tip | Consejo
Nota que estamos usando async/await con el nuevo `AsyncClient`: el request es asíncrono.
///
/// warning | Advertencia
Si tu aplicación depende de eventos de lifespan, el `AsyncClient` no activará estos eventos. Para asegurarte de que se activen, usa `LifespanManager` de <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>.
///
## Otras Llamadas a Funciones Asíncronas
Al ser la función de test asíncrona, ahora también puedes llamar (y `await`) otras funciones `async` además de enviar requests a tu aplicación FastAPI en tus tests, exactamente como las llamarías en cualquier otro lugar de tu código.
/// tip | Consejo
Si encuentras un `RuntimeError: Task attached to a different loop` al integrar llamadas a funciones asíncronas en tus tests (por ejemplo, cuando usas <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MotorClient de MongoDB</a>), recuerda crear instances de objetos que necesiten un loop de eventos solo dentro de funciones async, por ejemplo, en un callback `'@app.on_event("startup")`.
///
+361
View File
@@ -0,0 +1,361 @@
# Detrás de un Proxy
En algunas situaciones, podrías necesitar usar un **proxy** como Traefik o Nginx con una configuración que añade un prefijo de path extra que no es visto por tu aplicación.
En estos casos, puedes usar `root_path` para configurar tu aplicación.
El `root_path` es un mecanismo proporcionado por la especificación ASGI (en la que está construido FastAPI, a través de Starlette).
El `root_path` se usa para manejar estos casos específicos.
Y también se usa internamente al montar subaplicaciones.
## Proxy con un prefijo de path eliminado
Tener un proxy con un prefijo de path eliminado, en este caso, significa que podrías declarar un path en `/app` en tu código, pero luego añades una capa encima (el proxy) que situaría tu aplicación **FastAPI** bajo un path como `/api/v1`.
En este caso, el path original `/app` realmente sería servido en `/api/v1/app`.
Aunque todo tu código esté escrito asumiendo que solo existe `/app`.
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *}
Y el proxy estaría **"eliminando"** el **prefijo del path** sobre la marcha antes de transmitir el request al servidor de aplicaciones (probablemente Uvicorn a través de FastAPI CLI), manteniendo a tu aplicación convencida de que está siendo servida en `/app`, así que no tienes que actualizar todo tu código para incluir el prefijo `/api/v1`.
Hasta aquí, todo funcionaría normalmente.
Pero luego, cuando abres la UI integrada de los docs (el frontend), esperaría obtener el esquema de OpenAPI en `/openapi.json`, en lugar de `/api/v1/openapi.json`.
Entonces, el frontend (que se ejecuta en el navegador) trataría de alcanzar `/openapi.json` y no podría obtener el esquema de OpenAPI.
Porque tenemos un proxy con un prefijo de path de `/api/v1` para nuestra aplicación, el frontend necesita obtener el esquema de OpenAPI en `/api/v1/openapi.json`.
```mermaid
graph LR
browser("Navegador")
proxy["Proxy en http://0.0.0.0:9999/api/v1/app"]
server["Servidor en http://127.0.0.1:8000/app"]
browser --> proxy
proxy --> server
```
/// tip | Consejo
La IP `0.0.0.0` se usa comúnmente para indicar que el programa escucha en todas las IPs disponibles en esa máquina/servidor.
///
La UI de los docs también necesitaría el esquema de OpenAPI para declarar que este API `servidor` se encuentra en `/api/v1` (detrás del proxy). Por ejemplo:
```JSON hl_lines="4-8"
{
"openapi": "3.1.0",
// Más cosas aquí
"servers": [
{
"url": "/api/v1"
}
],
"paths": {
// Más cosas aquí
}
}
```
En este ejemplo, el "Proxy" podría ser algo como **Traefik**. Y el servidor sería algo como FastAPI CLI con **Uvicorn**, ejecutando tu aplicación de FastAPI.
### Proporcionando el `root_path`
Para lograr esto, puedes usar la opción de línea de comandos `--root-path` como:
<div class="termy">
```console
$ fastapi run main.py --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Si usas Hypercorn, también tiene la opción `--root-path`.
/// note | Detalles Técnicos
La especificación ASGI define un `root_path` para este caso de uso.
Y la opción de línea de comandos `--root-path` proporciona ese `root_path`.
///
### Revisar el `root_path` actual
Puedes obtener el `root_path` actual utilizado por tu aplicación para cada request, es parte del diccionario `scope` (que es parte de la especificación ASGI).
Aquí lo estamos incluyendo en el mensaje solo con fines de demostración.
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *}
Luego, si inicias Uvicorn con:
<div class="termy">
```console
$ fastapi run main.py --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
El response sería algo como:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
### Configurar el `root_path` en la app de FastAPI
Alternativamente, si no tienes una forma de proporcionar una opción de línea de comandos como `--root-path` o su equivalente, puedes configurar el parámetro `root_path` al crear tu app de FastAPI:
{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *}
Pasar el `root_path` a `FastAPI` sería el equivalente a pasar la opción de línea de comandos `--root-path` a Uvicorn o Hypercorn.
### Acerca de `root_path`
Ten en cuenta que el servidor (Uvicorn) no usará ese `root_path` para nada, a excepción de pasárselo a la app.
Pero si vas con tu navegador a <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000/app</a> verás el response normal:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
Así que no se esperará que sea accedido en `http://127.0.0.1:8000/api/v1/app`.
Uvicorn esperará que el proxy acceda a Uvicorn en `http://127.0.0.1:8000/app`, y luego será responsabilidad del proxy añadir el prefijo extra `/api/v1` encima.
## Sobre proxies con un prefijo de path eliminado
Ten en cuenta que un proxy con prefijo de path eliminado es solo una de las formas de configurarlo.
Probablemente en muchos casos, el valor predeterminado será que el proxy no tenga un prefijo de path eliminado.
En un caso así (sin un prefijo de path eliminado), el proxy escucharía algo como `https://myawesomeapp.com`, y luego si el navegador va a `https://myawesomeapp.com/api/v1/app` y tu servidor (por ejemplo, Uvicorn) escucha en `http://127.0.0.1:8000`, el proxy (sin un prefijo de path eliminado) accedería a Uvicorn en el mismo path: `http://127.0.0.1:8000/api/v1/app`.
## Probando localmente con Traefik
Puedes ejecutar fácilmente el experimento localmente con un prefijo de path eliminado usando <a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a>.
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">Descarga Traefik</a>, es un archivo binario único, puedes extraer el archivo comprimido y ejecutarlo directamente desde la terminal.
Luego crea un archivo `traefik.toml` con:
```TOML hl_lines="3"
[entryPoints]
[entryPoints.http]
address = ":9999"
[providers]
[providers.file]
filename = "routes.toml"
```
Esto le dice a Traefik que escuche en el puerto 9999 y que use otro archivo `routes.toml`.
/// tip | Consejo
Estamos utilizando el puerto 9999 en lugar del puerto HTTP estándar 80 para que no tengas que ejecutarlo con privilegios de administrador (`sudo`).
///
Ahora crea ese otro archivo `routes.toml`:
```TOML hl_lines="5 12 20"
[http]
[http.middlewares]
[http.middlewares.api-stripprefix.stripPrefix]
prefixes = ["/api/v1"]
[http.routers]
[http.routers.app-http]
entryPoints = ["http"]
service = "app"
rule = "PathPrefix(`/api/v1`)"
middlewares = ["api-stripprefix"]
[http.services]
[http.services.app]
[http.services.app.loadBalancer]
[[http.services.app.loadBalancer.servers]]
url = "http://127.0.0.1:8000"
```
Este archivo configura Traefik para usar el prefijo de path `/api/v1`.
Y luego Traefik redireccionará sus requests a tu Uvicorn ejecutándose en `http://127.0.0.1:8000`.
Ahora inicia Traefik:
<div class="termy">
```console
$ ./traefik --configFile=traefik.toml
INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
```
</div>
Y ahora inicia tu app, utilizando la opción `--root-path`:
<div class="termy">
```console
$ fastapi run main.py --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### Revisa los responses
Ahora, si vas a la URL con el puerto para Uvicorn: <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>, verás el response normal:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
/// tip | Consejo
Nota que incluso aunque estés accediendo en `http://127.0.0.1:8000/app`, muestra el `root_path` de `/api/v1`, tomado de la opción `--root-path`.
///
Y ahora abre la URL con el puerto para Traefik, incluyendo el prefijo de path: <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>.
Obtenemos el mismo response:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
pero esta vez en la URL con el prefijo de path proporcionado por el proxy: `/api/v1`.
Por supuesto, la idea aquí es que todos accedan a la app a través del proxy, así que la versión con el prefijo de path `/api/v1` es la "correcta".
Y la versión sin el prefijo de path (`http://127.0.0.1:8000/app`), proporcionada directamente por Uvicorn, sería exclusivamente para que el _proxy_ (Traefik) la acceda.
Eso demuestra cómo el Proxy (Traefik) usa el prefijo de path y cómo el servidor (Uvicorn) usa el `root_path` de la opción `--root-path`.
### Revisa la UI de los docs
Pero aquí está la parte divertida. ✨
La forma "oficial" de acceder a la app sería a través del proxy con el prefijo de path que definimos. Así que, como esperaríamos, si intentas usar la UI de los docs servida por Uvicorn directamente, sin el prefijo de path en la URL, no funcionará, porque espera ser accedida a través del proxy.
Puedes verificarlo en <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>:
<img src="/img/tutorial/behind-a-proxy/image01.png">
Pero si accedemos a la UI de los docs en la URL "oficial" usando el proxy con puerto `9999`, en `/api/v1/docs`, ¡funciona correctamente! 🎉
Puedes verificarlo en <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>:
<img src="/img/tutorial/behind-a-proxy/image02.png">
Justo como queríamos. ✔️
Esto es porque FastAPI usa este `root_path` para crear el `server` por defecto en OpenAPI con la URL proporcionada por `root_path`.
## Servidores adicionales
/// warning | Advertencia
Este es un caso de uso más avanzado. Siéntete libre de omitirlo.
///
Por defecto, **FastAPI** creará un `server` en el esquema de OpenAPI con la URL para el `root_path`.
Pero también puedes proporcionar otros `servers` alternativos, por ejemplo, si deseas que *la misma* UI de los docs interactúe con un entorno de pruebas y de producción.
Si pasas una lista personalizada de `servers` y hay un `root_path` (porque tu API existe detrás de un proxy), **FastAPI** insertará un "server" con este `root_path` al comienzo de la lista.
Por ejemplo:
{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *}
Generará un esquema de OpenAPI como:
```JSON hl_lines="5-7"
{
"openapi": "3.1.0",
// Más cosas aquí
"servers": [
{
"url": "/api/v1"
},
{
"url": "https://stag.example.com",
"description": "Entorno de pruebas"
},
{
"url": "https://prod.example.com",
"description": "Entorno de producción"
}
],
"paths": {
// Más cosas aquí
}
}
```
/// tip | Consejo
Observa el server auto-generado con un valor `url` de `/api/v1`, tomado del `root_path`.
///
En la UI de los docs en <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> se vería como:
<img src="/img/tutorial/behind-a-proxy/image03.png">
/// tip | Consejo
La UI de los docs interactuará con el server que selecciones.
///
### Desactivar el server automático de `root_path`
Si no quieres que **FastAPI** incluya un server automático usando el `root_path`, puedes usar el parámetro `root_path_in_servers=False`:
{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *}
y entonces no lo incluirá en el esquema de OpenAPI.
## Montando una sub-aplicación
Si necesitas montar una sub-aplicación (como se describe en [Aplicaciones secundarias - Monturas](sub-applications.md){.internal-link target=_blank}) mientras usas un proxy con `root_path`, puedes hacerlo normalmente, como esperarías.
FastAPI usará internamente el `root_path` de manera inteligente, así que simplemente funcionará. ✨
+312
View File
@@ -0,0 +1,312 @@
# Response Personalizado - HTML, Stream, Archivo, otros
Por defecto, **FastAPI** devolverá los responses usando `JSONResponse`.
Puedes sobrescribirlo devolviendo un `Response` directamente como se ve en [Devolver una Response directamente](response-directly.md){.internal-link target=_blank}.
Pero si devuelves un `Response` directamente (o cualquier subclase, como `JSONResponse`), los datos no se convertirán automáticamente (incluso si declaras un `response_model`), y la documentación no se generará automáticamente (por ejemplo, incluyendo el "media type" específico, en el HTTP header `Content-Type` como parte del OpenAPI generado).
Pero también puedes declarar el `Response` que quieres usar (por ejemplo, cualquier subclase de `Response`), en el *path operation decorator* usando el parámetro `response_class`.
Los contenidos que devuelvas desde tu *path operation function* se colocarán dentro de esa `Response`.
Y si ese `Response` tiene un media type JSON (`application/json`), como es el caso con `JSONResponse` y `UJSONResponse`, los datos que devuelvas se convertirán automáticamente (y serán filtrados) con cualquier `response_model` de Pydantic que hayas declarado en el *path operation decorator*.
/// note | Nota
Si usas una clase de response sin media type, FastAPI esperará que tu response no tenga contenido, por lo que no documentará el formato del response en su OpenAPI generado.
///
## Usa `ORJSONResponse`
Por ejemplo, si estás exprimendo el rendimiento, puedes instalar y usar <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> y establecer el response como `ORJSONResponse`.
Importa la clase `Response` (sub-clase) que quieras usar y declárala en el *path operation decorator*.
Para responses grandes, devolver una `Response` directamente es mucho más rápido que devolver un diccionario.
Esto se debe a que, por defecto, FastAPI inspeccionará cada elemento dentro y se asegurará de que sea serializable como JSON, usando el mismo [Codificador Compatible con JSON](../tutorial/encoder.md){.internal-link target=_blank} explicado en el tutorial. Esto es lo que te permite devolver **objetos arbitrarios**, por ejemplo, modelos de bases de datos.
Pero si estás seguro de que el contenido que estás devolviendo es **serializable con JSON**, puedes pasarlo directamente a la clase de response y evitar la sobrecarga extra que FastAPI tendría al pasar tu contenido de retorno a través de `jsonable_encoder` antes de pasarlo a la clase de response.
{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *}
/// info | Información
El parámetro `response_class` también se utilizará para definir el "media type" del response.
En este caso, el HTTP header `Content-Type` se establecerá en `application/json`.
Y se documentará así en OpenAPI.
///
/// tip | Consejo
El `ORJSONResponse` solo está disponible en FastAPI, no en Starlette.
///
## Response HTML
Para devolver un response con HTML directamente desde **FastAPI**, usa `HTMLResponse`.
* Importa `HTMLResponse`.
* Pasa `HTMLResponse` como parámetro `response_class` de tu *path operation decorator*.
{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *}
/// info | Información
El parámetro `response_class` también se utilizará para definir el "media type" del response.
En este caso, el HTTP header `Content-Type` se establecerá en `text/html`.
Y se documentará así en OpenAPI.
///
### Devuelve una `Response`
Como se ve en [Devolver una Response directamente](response-directly.md){.internal-link target=_blank}, también puedes sobrescribir el response directamente en tu *path operation*, devolviéndolo.
El mismo ejemplo de arriba, devolviendo una `HTMLResponse`, podría verse así:
{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
/// warning | Advertencia
Una `Response` devuelta directamente por tu *path operation function* no se documentará en OpenAPI (por ejemplo, el `Content-Type` no se documentará) y no será visible en la documentación interactiva automática.
///
/// info | Información
Por supuesto, el `Content-Type` header real, el código de estado, etc., provendrán del objeto `Response` que devolviste.
///
### Documenta en OpenAPI y sobrescribe `Response`
Si quieres sobrescribir el response desde dentro de la función pero al mismo tiempo documentar el "media type" en OpenAPI, puedes usar el parámetro `response_class` Y devolver un objeto `Response`.
El `response_class` solo se usará para documentar el OpenAPI *path operation*, pero tu `Response` se usará tal cual.
#### Devuelve un `HTMLResponse` directamente
Por ejemplo, podría ser algo así:
{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *}
En este ejemplo, la función `generate_html_response()` ya genera y devuelve una `Response` en lugar de devolver el HTML en un `str`.
Al devolver el resultado de llamar a `generate_html_response()`, ya estás devolviendo una `Response` que sobrescribirá el comportamiento predeterminado de **FastAPI**.
Pero como pasaste `HTMLResponse` en el `response_class` también, **FastAPI** sabrá cómo documentarlo en OpenAPI y la documentación interactiva como HTML con `text/html`:
<img src="/img/tutorial/custom-response/image01.png">
## Responses disponibles
Aquí hay algunos de los responses disponibles.
Ten en cuenta que puedes usar `Response` para devolver cualquier otra cosa, o incluso crear una sub-clase personalizada.
/// note | Nota Técnica
También podrías usar `from starlette.responses import HTMLResponse`.
**FastAPI** proporciona los mismos `starlette.responses` como `fastapi.responses` solo como una conveniencia para ti, el desarrollador. Pero la mayoría de los responses disponibles vienen directamente de Starlette.
///
### `Response`
La clase principal `Response`, todos los otros responses heredan de ella.
Puedes devolverla directamente.
Acepta los siguientes parámetros:
* `content` - Un `str` o `bytes`.
* `status_code` - Un código de estado HTTP `int`.
* `headers` - Un `dict` de strings.
* `media_type` - Un `str` que da el media type. Por ejemplo, `"text/html"`.
FastAPI (de hecho Starlette) incluirá automáticamente un header Content-Length. También incluirá un header Content-Type, basado en el `media_type` y añadiendo un conjunto de caracteres para tipos de texto.
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
### `HTMLResponse`
Toma algún texto o bytes y devuelve un response HTML, como leíste arriba.
### `PlainTextResponse`
Toma algún texto o bytes y devuelve un response de texto plano.
{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
### `JSONResponse`
Toma algunos datos y devuelve un response codificado como `application/json`.
Este es el response predeterminado usado en **FastAPI**, como leíste arriba.
### `ORJSONResponse`
Un response JSON rápido alternativo usando <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, como leíste arriba.
/// info | Información
Esto requiere instalar `orjson`, por ejemplo, con `pip install orjson`.
///
### `UJSONResponse`
Un response JSON alternativo usando <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a>.
/// info | Información
Esto requiere instalar `ujson`, por ejemplo, con `pip install ujson`.
///
/// warning | Advertencia
`ujson` es menos cuidadoso que la implementación integrada de Python en cómo maneja algunos casos extremos.
///
{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
/// tip | Consejo
Es posible que `ORJSONResponse` sea una alternativa más rápida.
///
### `RedirectResponse`
Devuelve una redirección HTTP. Usa un código de estado 307 (Redirección Temporal) por defecto.
Puedes devolver un `RedirectResponse` directamente:
{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
---
O puedes usarlo en el parámetro `response_class`:
{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *}
Si haces eso, entonces puedes devolver la URL directamente desde tu *path operation function*.
En este caso, el `status_code` utilizado será el predeterminado para `RedirectResponse`, que es `307`.
---
También puedes usar el parámetro `status_code` combinado con el parámetro `response_class`:
{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *}
### `StreamingResponse`
Toma un generador `async` o un generador/iterador normal y transmite el cuerpo del response.
{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
#### Usando `StreamingResponse` con objetos similares a archivos
Si tienes un objeto similar a un archivo (por ejemplo, el objeto devuelto por `open()`), puedes crear una función generadora para iterar sobre ese objeto similar a un archivo.
De esa manera, no tienes que leerlo todo primero en memoria, y puedes pasar esa función generadora al `StreamingResponse`, y devolverlo.
Esto incluye muchos paquetes para interactuar con almacenamiento en la nube, procesamiento de video y otros.
{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
1. Esta es la función generadora. Es una "función generadora" porque contiene declaraciones `yield` dentro.
2. Al usar un bloque `with`, nos aseguramos de que el objeto similar a un archivo se cierre después de que la función generadora termine. Así, después de que termina de enviar el response.
3. Este `yield from` le dice a la función que itere sobre esa cosa llamada `file_like`. Y luego, para cada parte iterada, yield esa parte como proveniente de esta función generadora (`iterfile`).
Entonces, es una función generadora que transfiere el trabajo de "generar" a algo más internamente.
Al hacerlo de esta manera, podemos ponerlo en un bloque `with`, y de esa manera, asegurarnos de que el objeto similar a un archivo se cierre después de finalizar.
/// tip | Consejo
Nota que aquí como estamos usando `open()` estándar que no admite `async` y `await`, declaramos el path operation con `def` normal.
///
### `FileResponse`
Transmite un archivo asincrónicamente como response.
Toma un conjunto diferente de argumentos para crear un instance que los otros tipos de response:
* `path` - La path del archivo para el archivo a transmitir.
* `headers` - Cualquier header personalizado para incluir, como un diccionario.
* `media_type` - Un string que da el media type. Si no se establece, se usará el nombre de archivo o la path para inferir un media type.
* `filename` - Si se establece, se incluirá en el response `Content-Disposition`.
Los responses de archivos incluirán los headers apropiados `Content-Length`, `Last-Modified` y `ETag`.
{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
También puedes usar el parámetro `response_class`:
{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *}
En este caso, puedes devolver la path del archivo directamente desde tu *path operation* function.
## Clase de response personalizada
Puedes crear tu propia clase de response personalizada, heredando de `Response` y usándola.
Por ejemplo, digamos que quieres usar <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, pero con algunas configuraciones personalizadas no utilizadas en la clase `ORJSONResponse` incluida.
Digamos que quieres que devuelva JSON con sangría y formato, por lo que quieres usar la opción de orjson `orjson.OPT_INDENT_2`.
Podrías crear un `CustomORJSONResponse`. Lo principal que tienes que hacer es crear un método `Response.render(content)` que devuelva el contenido como `bytes`:
{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *}
Ahora en lugar de devolver:
```json
{"message": "Hello World"}
```
...este response devolverá:
```json
{
"message": "Hello World"
}
```
Por supuesto, probablemente encontrarás formas mucho mejores de aprovechar esto que formatear JSON. 😉
## Clase de response predeterminada
Al crear una instance de la clase **FastAPI** o un `APIRouter`, puedes especificar qué clase de response usar por defecto.
El parámetro que define esto es `default_response_class`.
En el ejemplo a continuación, **FastAPI** usará `ORJSONResponse` por defecto, en todas las *path operations*, en lugar de `JSONResponse`.
{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *}
/// tip | Consejo
Todavía puedes sobrescribir `response_class` en *path operations* como antes.
///
## Documentación adicional
También puedes declarar el media type y muchos otros detalles en OpenAPI usando `responses`: [Responses Adicionales en OpenAPI](additional-responses.md){.internal-link target=_blank}.
+95
View File
@@ -0,0 +1,95 @@
# Usando Dataclasses
FastAPI está construido sobre **Pydantic**, y te he estado mostrando cómo usar modelos de Pydantic para declarar requests y responses.
Pero FastAPI también soporta el uso de <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> de la misma manera:
{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *}
Esto sigue siendo soportado gracias a **Pydantic**, ya que tiene <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">soporte interno para `dataclasses`</a>.
Así que, incluso con el código anterior que no usa Pydantic explícitamente, FastAPI está usando Pydantic para convertir esos dataclasses estándar en su propia versión de dataclasses de Pydantic.
Y por supuesto, soporta lo mismo:
* validación de datos
* serialización de datos
* documentación de datos, etc.
Esto funciona de la misma manera que con los modelos de Pydantic. Y en realidad se logra de la misma manera internamente, utilizando Pydantic.
/// info | Información
Ten en cuenta que los dataclasses no pueden hacer todo lo que los modelos de Pydantic pueden hacer.
Así que, podrías necesitar seguir usando modelos de Pydantic.
Pero si tienes un montón de dataclasses por ahí, este es un buen truco para usarlos para potenciar una API web usando FastAPI. 🤓
///
## Dataclasses en `response_model`
También puedes usar `dataclasses` en el parámetro `response_model`:
{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
El dataclass será automáticamente convertido a un dataclass de Pydantic.
De esta manera, su esquema aparecerá en la interfaz de usuario de la documentación de la API:
<img src="/img/tutorial/dataclasses/image01.png">
## Dataclasses en Estructuras de Datos Anidadas
También puedes combinar `dataclasses` con otras anotaciones de tipos para crear estructuras de datos anidadas.
En algunos casos, todavía podrías tener que usar la versión de `dataclasses` de Pydantic. Por ejemplo, si tienes errores con la documentación de la API generada automáticamente.
En ese caso, simplemente puedes intercambiar los `dataclasses` estándar con `pydantic.dataclasses`, que es un reemplazo directo:
{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *}
1. Todavía importamos `field` de los `dataclasses` estándar.
2. `pydantic.dataclasses` es un reemplazo directo para `dataclasses`.
3. El dataclass `Author` incluye una lista de dataclasses `Item`.
4. El dataclass `Author` se usa como el parámetro `response_model`.
5. Puedes usar otras anotaciones de tipos estándar con dataclasses como el request body.
En este caso, es una lista de dataclasses `Item`.
6. Aquí estamos regresando un diccionario que contiene `items`, que es una lista de dataclasses.
FastAPI todavía es capaz de <abbr title="converting the data to a format that can be transmitted">serializar</abbr> los datos a JSON.
7. Aquí el `response_model` está usando una anotación de tipo de una lista de dataclasses `Author`.
Nuevamente, puedes combinar `dataclasses` con anotaciones de tipos estándar.
8. Nota que esta *path operation function* usa `def` regular en lugar de `async def`.
Como siempre, en FastAPI puedes combinar `def` y `async def` según sea necesario.
Si necesitas un repaso sobre cuándo usar cuál, revisa la sección _"¿Con prisa?"_ en la documentación sobre [`async` y `await`](../async.md#in-a-hurry){.internal-link target=_blank}.
9. Esta *path operation function* no está devolviendo dataclasses (aunque podría), sino una lista de diccionarios con datos internos.
FastAPI usará el parámetro `response_model` (que incluye dataclasses) para convertir el response.
Puedes combinar `dataclasses` con otras anotaciones de tipos en muchas combinaciones diferentes para formar estructuras de datos complejas.
Revisa las anotaciones en el código arriba para ver más detalles específicos.
## Aprende Más
También puedes combinar `dataclasses` con otros modelos de Pydantic, heredar de ellos, incluirlos en tus propios modelos, etc.
Para saber más, revisa la <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">documentación de Pydantic sobre dataclasses</a>.
## Versión
Esto está disponible desde la versión `0.67.0` de FastAPI. 🔖
+165
View File
@@ -0,0 +1,165 @@
# Eventos de Lifespan
Puedes definir lógica (código) que debería ser ejecutada antes de que la aplicación **inicie**. Esto significa que este código será ejecutado **una vez**, **antes** de que la aplicación **comience a recibir requests**.
De la misma manera, puedes definir lógica (código) que debería ser ejecutada cuando la aplicación esté **cerrándose**. En este caso, este código será ejecutado **una vez**, **después** de haber manejado posiblemente **muchos requests**.
Debido a que este código se ejecuta antes de que la aplicación **comience** a tomar requests, y justo después de que **termine** de manejarlos, cubre todo el **lifespan** de la aplicación (la palabra "lifespan" será importante en un momento 😉).
Esto puede ser muy útil para configurar **recursos** que necesitas usar para toda la app, y que son **compartidos** entre requests, y/o que necesitas **limpiar** después. Por ejemplo, un pool de conexiones a una base de datos, o cargando un modelo de machine learning compartido.
## Caso de Uso
Empecemos con un ejemplo de **caso de uso** y luego veamos cómo resolverlo con esto.
Imaginemos que tienes algunos **modelos de machine learning** que quieres usar para manejar requests. 🤖
Los mismos modelos son compartidos entre requests, por lo que no es un modelo por request, o uno por usuario o algo similar.
Imaginemos que cargar el modelo puede **tomar bastante tiempo**, porque tiene que leer muchos **datos del disco**. Entonces no quieres hacerlo para cada request.
Podrías cargarlo en el nivel superior del módulo/archivo, pero eso también significaría que **cargaría el modelo** incluso si solo estás ejecutando una simple prueba automatizada, entonces esa prueba sería **lenta** porque tendría que esperar a que el modelo se cargue antes de poder ejecutar una parte independiente del código.
Eso es lo que resolveremos, vamos a cargar el modelo antes de que los requests sean manejados, pero solo justo antes de que la aplicación comience a recibir requests, no mientras el código se está cargando.
## Lifespan
Puedes definir esta lógica de *startup* y *shutdown* usando el parámetro `lifespan` de la app de `FastAPI`, y un "context manager" (te mostraré lo que es en un momento).
Comencemos con un ejemplo y luego veámoslo en detalle.
Creamos una función asíncrona `lifespan()` con `yield` así:
{* ../../docs_src/events/tutorial003.py hl[16,19] *}
Aquí estamos simulando la operación costosa de *startup* de cargar el modelo poniendo la función del (falso) modelo en el diccionario con modelos de machine learning antes del `yield`. Este código será ejecutado **antes** de que la aplicación **comience a tomar requests**, durante el *startup*.
Y luego, justo después del `yield`, quitaremos el modelo de memoria. Este código será ejecutado **después** de que la aplicación **termine de manejar requests**, justo antes del *shutdown*. Esto podría, por ejemplo, liberar recursos como la memoria o una GPU.
/// tip | Consejo
El `shutdown` ocurriría cuando estás **deteniendo** la aplicación.
Quizás necesites iniciar una nueva versión, o simplemente te cansaste de ejecutarla. 🤷
///
### Función de Lifespan
Lo primero que hay que notar es que estamos definiendo una función asíncrona con `yield`. Esto es muy similar a las Dependencias con `yield`.
{* ../../docs_src/events/tutorial003.py hl[14:19] *}
La primera parte de la función, antes del `yield`, será ejecutada **antes** de que la aplicación comience.
Y la parte después del `yield` será ejecutada **después** de que la aplicación haya terminado.
### Async Context Manager
Si revisas, la función está decorada con un `@asynccontextmanager`.
Eso convierte a la función en algo llamado un "**async context manager**".
{* ../../docs_src/events/tutorial003.py hl[1,13] *}
Un **context manager** en Python es algo que puedes usar en una declaración `with`, por ejemplo, `open()` puede ser usado como un context manager:
```Python
with open("file.txt") as file:
file.read()
```
En versiones recientes de Python, también hay un **async context manager**. Lo usarías con `async with`:
```Python
async with lifespan(app):
await do_stuff()
```
Cuando creas un context manager o un async context manager como arriba, lo que hace es que, antes de entrar al bloque `with`, ejecutará el código antes del `yield`, y al salir del bloque `with`, ejecutará el código después del `yield`.
En nuestro ejemplo de código arriba, no lo usamos directamente, pero se lo pasamos a FastAPI para que lo use.
El parámetro `lifespan` de la app de `FastAPI` toma un **async context manager**, por lo que podemos pasar nuestro nuevo `lifespan` async context manager a él.
{* ../../docs_src/events/tutorial003.py hl[22] *}
## Eventos Alternativos (obsoleto)
/// warning | Advertencia
La forma recomendada de manejar el *startup* y el *shutdown* es usando el parámetro `lifespan` de la app de `FastAPI` como se describió arriba. Si proporcionas un parámetro `lifespan`, los manejadores de eventos `startup` y `shutdown` ya no serán llamados. Es solo `lifespan` o solo los eventos, no ambos.
Probablemente puedas saltarte esta parte.
///
Hay una forma alternativa de definir esta lógica para ser ejecutada durante el *startup* y durante el *shutdown*.
Puedes definir manejadores de eventos (funciones) que necesitan ser ejecutadas antes de que la aplicación se inicie, o cuando la aplicación se está cerrando.
Estas funciones pueden ser declaradas con `async def` o `def` normal.
### Evento `startup`
Para añadir una función que debería ejecutarse antes de que la aplicación inicie, declárala con el evento `"startup"`:
{* ../../docs_src/events/tutorial001.py hl[8] *}
En este caso, la función manejadora del evento `startup` inicializará los ítems de la "base de datos" (solo un `dict`) con algunos valores.
Puedes añadir más de un manejador de eventos.
Y tu aplicación no comenzará a recibir requests hasta que todos los manejadores de eventos `startup` hayan completado.
### Evento `shutdown`
Para añadir una función que debería ejecutarse cuando la aplicación se esté cerrando, declárala con el evento `"shutdown"`:
{* ../../docs_src/events/tutorial002.py hl[6] *}
Aquí, la función manejadora del evento `shutdown` escribirá una línea de texto `"Application shutdown"` a un archivo `log.txt`.
/// info | Información
En la función `open()`, el `mode="a"` significa "añadir", por lo tanto, la línea será añadida después de lo que sea que esté en ese archivo, sin sobrescribir el contenido anterior.
///
/// tip | Consejo
Nota que en este caso estamos usando una función estándar de Python `open()` que interactúa con un archivo.
Entonces, involucra I/O (entrada/salida), que requiere "esperar" para que las cosas se escriban en el disco.
Pero `open()` no usa `async` y `await`.
Por eso, declaramos la función manejadora del evento con `def` estándar en vez de `async def`.
///
### `startup` y `shutdown` juntos
Hay una gran posibilidad de que la lógica para tu *startup* y *shutdown* esté conectada, podrías querer iniciar algo y luego finalizarlo, adquirir un recurso y luego liberarlo, etc.
Hacer eso en funciones separadas que no comparten lógica o variables juntas es más difícil ya que necesitarías almacenar valores en variables globales o trucos similares.
Debido a eso, ahora se recomienda en su lugar usar el `lifespan` como se explicó arriba.
## Detalles Técnicos
Solo un detalle técnico para los nerds curiosos. 🤓
Por debajo, en la especificación técnica ASGI, esto es parte del <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Protocolo de Lifespan</a>, y define eventos llamados `startup` y `shutdown`.
/// info | Información
Puedes leer más sobre los manejadores `lifespan` de Starlette en <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">la documentación de `Lifespan` de Starlette</a>.
Incluyendo cómo manejar el estado de lifespan que puede ser usado en otras áreas de tu código.
///
## Sub Aplicaciones
🚨 Ten en cuenta que estos eventos de lifespan (startup y shutdown) solo serán ejecutados para la aplicación principal, no para [Sub Aplicaciones - Mounts](sub-applications.md){.internal-link target=_blank}.
+261
View File
@@ -0,0 +1,261 @@
# Genera Clientes
Como **FastAPI** está basado en la especificación OpenAPI, obtienes compatibilidad automática con muchas herramientas, incluyendo la documentación automática de la API (proporcionada por Swagger UI).
Una ventaja particular que no es necesariamente obvia es que puedes **generar clientes** (a veces llamados <abbr title="Software Development Kits">**SDKs**</abbr> ) para tu API, para muchos **lenguajes de programación** diferentes.
## Generadores de Clientes OpenAPI
Hay muchas herramientas para generar clientes desde **OpenAPI**.
Una herramienta común es <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>.
Si estás construyendo un **frontend**, una alternativa muy interesante es <a href="https://github.com/hey-api/openapi-ts" class="external-link" target="_blank">openapi-ts</a>.
## Generadores de Clientes y SDKs - Sponsor
También hay algunos generadores de Clientes y SDKs **respaldados por empresas** basados en OpenAPI (FastAPI), en algunos casos pueden ofrecerte **funcionalidades adicionales** además de SDKs/clientes generados de alta calidad.
Algunos de ellos también ✨ [**sponsorean FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, esto asegura el **desarrollo** continuo y saludable de FastAPI y su **ecosistema**.
Y muestra su verdadero compromiso con FastAPI y su **comunidad** (tú), ya que no solo quieren proporcionarte un **buen servicio** sino también asegurarse de que tengas un **buen y saludable framework**, FastAPI. 🙇
Por ejemplo, podrías querer probar:
* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a>
* <a href="https://www.stainlessapi.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a>
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi/?utm_source=fastapi" class="external-link" target="_blank">liblab</a>
También hay varias otras empresas que ofrecen servicios similares que puedes buscar y encontrar en línea. 🤓
## Genera un Cliente Frontend en TypeScript
Empecemos con una aplicación simple de FastAPI:
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
Nota que las *path operations* definen los modelos que usan para el payload de la petición y el payload del response, usando los modelos `Item` y `ResponseMessage`.
### Documentación de la API
Si vas a la documentación de la API, verás que tiene los **esquemas** para los datos que se enviarán en las peticiones y se recibirán en los responses:
<img src="/img/tutorial/generate-clients/image01.png">
Puedes ver esos esquemas porque fueron declarados con los modelos en la aplicación.
Esa información está disponible en el **JSON Schema** de OpenAPI de la aplicación, y luego se muestra en la documentación de la API (por Swagger UI).
Y esa misma información de los modelos que está incluida en OpenAPI es lo que puede usarse para **generar el código del cliente**.
### Genera un Cliente en TypeScript
Ahora que tenemos la aplicación con los modelos, podemos generar el código del cliente para el frontend.
#### Instalar `openapi-ts`
Puedes instalar `openapi-ts` en tu código de frontend con:
<div class="termy">
```console
$ npm install @hey-api/openapi-ts --save-dev
---> 100%
```
</div>
#### Generar el Código del Cliente
Para generar el código del cliente puedes usar la aplicación de línea de comandos `openapi-ts` que ahora estaría instalada.
Como está instalada en el proyecto local, probablemente no podrías llamar a ese comando directamente, pero podrías ponerlo en tu archivo `package.json`.
Podría verse como esto:
```JSON hl_lines="7"
{
"name": "frontend-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios"
},
"author": "",
"license": "",
"devDependencies": {
"@hey-api/openapi-ts": "^0.27.38",
"typescript": "^4.6.2"
}
}
```
Después de tener ese script de NPM `generate-client` allí, puedes ejecutarlo con:
<div class="termy">
```console
$ npm run generate-client
frontend-app@1.0.0 generate-client /home/user/code/frontend-app
> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios
```
</div>
Ese comando generará código en `./src/client` y usará `axios` (el paquete HTTP de frontend) internamente.
### Prueba el Código del Cliente
Ahora puedes importar y usar el código del cliente, podría verse así, nota que tienes autocompletado para los métodos:
<img src="/img/tutorial/generate-clients/image02.png">
También obtendrás autocompletado para el payload a enviar:
<img src="/img/tutorial/generate-clients/image03.png">
/// tip | Consejo
Nota el autocompletado para `name` y `price`, que fue definido en la aplicación de FastAPI, en el modelo `Item`.
///
Tendrás errores en línea para los datos que envíes:
<img src="/img/tutorial/generate-clients/image04.png">
El objeto de response también tendrá autocompletado:
<img src="/img/tutorial/generate-clients/image05.png">
## App de FastAPI con Tags
En muchos casos tu aplicación de FastAPI será más grande, y probablemente usarás tags para separar diferentes grupos de *path operations*.
Por ejemplo, podrías tener una sección para **items** y otra sección para **usuarios**, y podrían estar separadas por tags:
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
### Genera un Cliente TypeScript con Tags
Si generas un cliente para una aplicación de FastAPI usando tags, normalmente también separará el código del cliente basándose en los tags.
De esta manera podrás tener las cosas ordenadas y agrupadas correctamente para el código del cliente:
<img src="/img/tutorial/generate-clients/image06.png">
En este caso tienes:
* `ItemsService`
* `UsersService`
### Nombres de los Métodos del Cliente
Ahora mismo los nombres de los métodos generados como `createItemItemsPost` no se ven muy limpios:
```TypeScript
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
```
...eso es porque el generador del cliente usa el **operation ID** interno de OpenAPI para cada *path operation*.
OpenAPI requiere que cada operation ID sea único a través de todas las *path operations*, por lo que FastAPI usa el **nombre de la función**, el **path**, y el **método/operación HTTP** para generar ese operation ID, porque de esa manera puede asegurarse de que los operation IDs sean únicos.
Pero te mostraré cómo mejorar eso a continuación. 🤓
## Operation IDs Personalizados y Mejores Nombres de Métodos
Puedes **modificar** la forma en que estos operation IDs son **generados** para hacerlos más simples y tener **nombres de métodos más simples** en los clientes.
En este caso tendrás que asegurarte de que cada operation ID sea **único** de alguna otra manera.
Por ejemplo, podrías asegurarte de que cada *path operation* tenga un tag, y luego generar el operation ID basado en el **tag** y el nombre de la *path operation* **name** (el nombre de la función).
### Función Personalizada para Generar ID Único
FastAPI usa un **ID único** para cada *path operation*, se usa para el **operation ID** y también para los nombres de cualquier modelo personalizado necesario, para requests o responses.
Puedes personalizar esa función. Toma un `APIRoute` y retorna un string.
Por ejemplo, aquí está usando el primer tag (probablemente tendrás solo un tag) y el nombre de la *path operation* (el nombre de la función).
Puedes entonces pasar esa función personalizada a **FastAPI** como el parámetro `generate_unique_id_function`:
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
### Generar un Cliente TypeScript con Operation IDs Personalizados
Ahora si generas el cliente de nuevo, verás que tiene los nombres de métodos mejorados:
<img src="/img/tutorial/generate-clients/image07.png">
Como ves, los nombres de métodos ahora tienen el tag y luego el nombre de la función, ahora no incluyen información del path de la URL y la operación HTTP.
### Preprocesa la Especificación OpenAPI para el Generador de Clientes
El código generado aún tiene algo de **información duplicada**.
Ya sabemos que este método está relacionado con los **items** porque esa palabra está en el `ItemsService` (tomado del tag), pero aún tenemos el nombre del tag prefijado en el nombre del método también. 😕
Probablemente aún querremos mantenerlo para OpenAPI en general, ya que eso asegurará que los operation IDs sean **únicos**.
Pero para el cliente generado podríamos **modificar** los operation IDs de OpenAPI justo antes de generar los clientes, solo para hacer esos nombres de métodos más bonitos y **limpios**.
Podríamos descargar el JSON de OpenAPI a un archivo `openapi.json` y luego podríamos **remover ese tag prefijado** con un script como este:
{* ../../docs_src/generate_clients/tutorial004.py *}
//// tab | Node.js
```Javascript
{!> ../../docs_src/generate_clients/tutorial004.js!}
```
////
Con eso, los operation IDs serían renombrados de cosas como `items-get_items` a solo `get_items`, de esa manera el generador del cliente puede generar nombres de métodos más simples.
### Generar un Cliente TypeScript con el OpenAPI Preprocesado
Ahora como el resultado final está en un archivo `openapi.json`, modificarías el `package.json` para usar ese archivo local, por ejemplo:
```JSON hl_lines="7"
{
"name": "frontend-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios"
},
"author": "",
"license": "",
"devDependencies": {
"@hey-api/openapi-ts": "^0.27.38",
"typescript": "^4.6.2"
}
}
```
Después de generar el nuevo cliente, ahora tendrías nombres de métodos **limpios**, con todo el **autocompletado**, **errores en línea**, etc:
<img src="/img/tutorial/generate-clients/image08.png">
## Beneficios
Cuando usas los clientes generados automáticamente obtendrás **autocompletado** para:
* Métodos.
* Payloads de peticiones en el cuerpo, parámetros de query, etc.
* Payloads de responses.
También tendrás **errores en línea** para todo.
Y cada vez que actualices el código del backend, y **regeneres** el frontend, tendrás las nuevas *path operations* disponibles como métodos, las antiguas eliminadas, y cualquier otro cambio se reflejará en el código generado. 🤓
Esto también significa que si algo cambió será **reflejado** automáticamente en el código del cliente. Y si haces **build** del cliente, te dará error si tienes algún **desajuste** en los datos utilizados.
Así que, **detectarás muchos errores** muy temprano en el ciclo de desarrollo en lugar de tener que esperar a que los errores se muestren a tus usuarios finales en producción para luego intentar depurar dónde está el problema. ✨
+36
View File
@@ -0,0 +1,36 @@
# Guía avanzada del usuario
## Funcionalidades adicionales
El [Tutorial - Guía del usuario](../tutorial/index.md){.internal-link target=_blank} principal debería ser suficiente para darte un recorrido por todas las funcionalidades principales de **FastAPI**.
En las siguientes secciones verás otras opciones, configuraciones y funcionalidades adicionales.
/// tip | Consejo
Las siguientes secciones **no son necesariamente "avanzadas"**.
Y es posible que para tu caso de uso, la solución esté en una de ellas.
///
## Lee primero el Tutorial
Aún podrías usar la mayoría de las funcionalidades en **FastAPI** con el conocimiento del [Tutorial - Guía del usuario](../tutorial/index.md){.internal-link target=_blank} principal.
Y las siguientes secciones asumen que ya lo leíste y que conoces esas ideas principales.
## Cursos externos
Aunque el [Tutorial - Guía del usuario](../tutorial/index.md){.internal-link target=_blank} y esta **Guía avanzada del usuario** están escritos como un tutorial guiado (como un libro) y deberían ser suficientes para que **aprendas FastAPI**, podrías querer complementarlo con cursos adicionales.
O podría ser que simplemente prefieras tomar otros cursos porque se adaptan mejor a tu estilo de aprendizaje.
Algunos proveedores de cursos ✨ [**sponsorean FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, esto asegura el desarrollo continuo y saludable de FastAPI y su **ecosistema**.
Y muestra su verdadero compromiso con FastAPI y su **comunidad** (tú), ya que no solo quieren brindarte una **buena experiencia de aprendizaje** sino que también quieren asegurarse de que tengas un **buen y saludable framework**, FastAPI. 🙇
Podrías querer probar sus cursos:
* <a href="https://training.talkpython.fm/fastapi-courses" class="external-link" target="_blank">Talk Python Training</a>
* <a href="https://testdriven.io/courses/tdd-fastapi/" class="external-link" target="_blank">Desarrollo guiado por pruebas</a>
+96
View File
@@ -0,0 +1,96 @@
# Middleware Avanzado
En el tutorial principal leíste cómo agregar [Middleware Personalizado](../tutorial/middleware.md){.internal-link target=_blank} a tu aplicación.
Y luego también leíste cómo manejar [CORS con el `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}.
En esta sección veremos cómo usar otros middlewares.
## Agregando middlewares ASGI
Como **FastAPI** está basado en Starlette e implementa la especificación <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr>, puedes usar cualquier middleware ASGI.
Un middleware no tiene que estar hecho para FastAPI o Starlette para funcionar, siempre que siga la especificación ASGI.
En general, los middlewares ASGI son clases que esperan recibir una aplicación ASGI como primer argumento.
Entonces, en la documentación de middlewares ASGI de terceros probablemente te indicarán que hagas algo como:
```Python
from unicorn import UnicornMiddleware
app = SomeASGIApp()
new_app = UnicornMiddleware(app, some_config="rainbow")
```
Pero FastAPI (en realidad Starlette) proporciona una forma más simple de hacerlo que asegura que los middlewares internos manejen errores del servidor y los controladores de excepciones personalizadas funcionen correctamente.
Para eso, usas `app.add_middleware()` (como en el ejemplo para CORS).
```Python
from fastapi import FastAPI
from unicorn import UnicornMiddleware
app = FastAPI()
app.add_middleware(UnicornMiddleware, some_config="rainbow")
```
`app.add_middleware()` recibe una clase de middleware como primer argumento y cualquier argumento adicional que se le quiera pasar al middleware.
## Middlewares integrados
**FastAPI** incluye varios middlewares para casos de uso común, veremos a continuación cómo usarlos.
/// note | Detalles Técnicos
Para los próximos ejemplos, también podrías usar `from starlette.middleware.something import SomethingMiddleware`.
**FastAPI** proporciona varios middlewares en `fastapi.middleware` solo como una conveniencia para ti, el desarrollador. Pero la mayoría de los middlewares disponibles provienen directamente de Starlette.
///
## `HTTPSRedirectMiddleware`
Impone que todas las requests entrantes deben ser `https` o `wss`.
Cualquier request entrante a `http` o `ws` será redirigida al esquema seguro.
{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *}
## `TrustedHostMiddleware`
Impone que todas las requests entrantes tengan correctamente configurado el header `Host`, para proteger contra ataques de HTTP Host Header.
{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *}
Se soportan los siguientes argumentos:
* `allowed_hosts` - Una list de nombres de dominio que deberían ser permitidos como nombres de host. Se soportan dominios comodín como `*.example.com` para hacer coincidir subdominios. Para permitir cualquier nombre de host, usa `allowed_hosts=["*"]` u omite el middleware.
Si una request entrante no se valida correctamente, se enviará un response `400`.
## `GZipMiddleware`
Maneja responses GZip para cualquier request que incluya `"gzip"` en el header `Accept-Encoding`.
El middleware manejará tanto responses estándar como en streaming.
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
Se soportan los siguientes argumentos:
* `minimum_size` - No comprimir con GZip responses que sean más pequeñas que este tamaño mínimo en bytes. Por defecto es `500`.
* `compresslevel` - Usado durante la compresión GZip. Es un entero que varía de 1 a 9. Por defecto es `9`. Un valor más bajo resulta en una compresión más rápida pero archivos más grandes, mientras que un valor más alto resulta en una compresión más lenta pero archivos más pequeños.
## Otros middlewares
Hay muchos otros middlewares ASGI.
Por ejemplo:
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">`ProxyHeadersMiddleware` de Uvicorn</a>
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a>
Para ver otros middlewares disponibles, revisa <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">la documentación de Middleware de Starlette</a> y la <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">Lista ASGI Awesome</a>.
+186
View File
@@ -0,0 +1,186 @@
# OpenAPI Callbacks
Podrías crear una API con una *path operation* que podría desencadenar un request a una *API externa* creada por alguien más (probablemente el mismo desarrollador que estaría *usando* tu API).
El proceso que ocurre cuando tu aplicación API llama a la *API externa* se llama un "callback". Porque el software que escribió el desarrollador externo envía un request a tu API y luego tu API *responde*, enviando un request a una *API externa* (que probablemente fue creada por el mismo desarrollador).
En este caso, podrías querer documentar cómo esa API externa *debería* verse. Qué *path operation* debería tener, qué cuerpo debería esperar, qué response debería devolver, etc.
## Una aplicación con callbacks
Veamos todo esto con un ejemplo.
Imagina que desarrollas una aplicación que permite crear facturas.
Estas facturas tendrán un `id`, `title` (opcional), `customer`, y `total`.
El usuario de tu API (un desarrollador externo) creará una factura en tu API con un request POST.
Luego tu API (imaginemos):
* Enviará la factura a algún cliente del desarrollador externo.
* Recogerá el dinero.
* Enviará una notificación de vuelta al usuario de la API (el desarrollador externo).
* Esto se hará enviando un request POST (desde *tu API*) a alguna *API externa* proporcionada por ese desarrollador externo (este es el "callback").
## La aplicación normal de **FastAPI**
Primero veamos cómo sería la aplicación API normal antes de agregar el callback.
Tendrá una *path operation* que recibirá un cuerpo `Invoice`, y un parámetro de query `callback_url` que contendrá la URL para el callback.
Esta parte es bastante normal, probablemente ya estés familiarizado con la mayor parte del código:
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *}
/// tip | Consejo
El parámetro de query `callback_url` utiliza un tipo <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a> de Pydantic.
///
Lo único nuevo es el `callbacks=invoices_callback_router.routes` como un argumento para el *decorador de path operation*. Veremos qué es eso a continuación.
## Documentar el callback
El código real del callback dependerá mucho de tu propia aplicación API.
Y probablemente variará mucho de una aplicación a otra.
Podría ser solo una o dos líneas de código, como:
```Python
callback_url = "https://example.com/api/v1/invoices/events/"
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
```
Pero posiblemente la parte más importante del callback es asegurarse de que el usuario de tu API (el desarrollador externo) implemente la *API externa* correctamente, de acuerdo con los datos que *tu API* va a enviar en el request body del callback, etc.
Entonces, lo que haremos a continuación es agregar el código para documentar cómo debería verse esa *API externa* para recibir el callback de *tu API*.
Esa documentación aparecerá en la Swagger UI en `/docs` en tu API, y permitirá a los desarrolladores externos saber cómo construir la *API externa*.
Este ejemplo no implementa el callback en sí (eso podría ser solo una línea de código), solo la parte de documentación.
/// tip | Consejo
El callback real es solo un request HTTP.
Cuando implementes el callback tú mismo, podrías usar algo como <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> o <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a>.
///
## Escribir el código de documentación del callback
Este código no se ejecutará en tu aplicación, solo lo necesitamos para *documentar* cómo debería verse esa *API externa*.
Pero, ya sabes cómo crear fácilmente documentación automática para una API con **FastAPI**.
Así que vamos a usar ese mismo conocimiento para documentar cómo debería verse la *API externa*... creando la(s) *path operation(s)* que la API externa debería implementar (las que tu API va a llamar).
/// tip | Consejo
Cuando escribas el código para documentar un callback, podría ser útil imaginar que eres ese *desarrollador externo*. Y que actualmente estás implementando la *API externa*, no *tu API*.
Adoptar temporalmente este punto de vista (del *desarrollador externo*) puede ayudarte a sentir que es más obvio dónde poner los parámetros, el modelo de Pydantic para el body, para el response, etc. para esa *API externa*.
///
### Crear un `APIRouter` de callback
Primero crea un nuevo `APIRouter` que contendrá uno o más callbacks.
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *}
### Crear la *path operation* del callback
Para crear la *path operation* del callback utiliza el mismo `APIRouter` que creaste anteriormente.
Debería verse como una *path operation* normal de FastAPI:
* Probablemente debería tener una declaración del body que debería recibir, por ejemplo `body: InvoiceEvent`.
* Y también podría tener una declaración del response que debería devolver, por ejemplo `response_model=InvoiceEventReceived`.
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *}
Hay 2 diferencias principales respecto a una *path operation* normal:
* No necesita tener ningún código real, porque tu aplicación nunca llamará a este código. Solo se usa para documentar la *API externa*. Así que, la función podría simplemente tener `pass`.
* El *path* puede contener una <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">expresión OpenAPI 3</a> (ver más abajo) donde puede usar variables con parámetros y partes del request original enviado a *tu API*.
### La expresión del path del callback
El *path* del callback puede tener una <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">expresión OpenAPI 3</a> que puede contener partes del request original enviado a *tu API*.
En este caso, es el `str`:
```Python
"{$callback_url}/invoices/{$request.body.id}"
```
Entonces, si el usuario de tu API (el desarrollador externo) envía un request a *tu API* a:
```
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
```
con un JSON body de:
```JSON
{
"id": "2expen51ve",
"customer": "Mr. Richie Rich",
"total": "9999"
}
```
luego *tu API* procesará la factura, y en algún momento después, enviará un request de callback al `callback_url` (la *API externa*):
```
https://www.external.org/events/invoices/2expen51ve
```
con un JSON body que contiene algo como:
```JSON
{
"description": "Payment celebration",
"paid": true
}
```
y esperaría un response de esa *API externa* con un JSON body como:
```JSON
{
"ok": true
}
```
/// tip | Consejo
Observa cómo la URL del callback utilizada contiene la URL recibida como parámetro de query en `callback_url` (`https://www.external.org/events`) y también el `id` de la factura desde dentro del JSON body (`2expen51ve`).
///
### Agregar el router de callback
En este punto tienes las *path operation(s)* del callback necesarias (las que el *desarrollador externo* debería implementar en la *API externa*) en el router de callback que creaste antes.
Ahora usa el parámetro `callbacks` en el *decorador de path operation de tu API* para pasar el atributo `.routes` (que en realidad es solo un `list` de rutas/*path operations*) de ese router de callback:
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *}
/// tip | Consejo
Observa que no estás pasando el router en sí (`invoices_callback_router`) a `callback=`, sino el atributo `.routes`, como en `invoices_callback_router.routes`.
///
### Revisa la documentación
Ahora puedes iniciar tu aplicación e ir a <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Verás tu documentación incluyendo una sección de "Callbacks" para tu *path operation* que muestra cómo debería verse la *API externa*:
<img src="/img/tutorial/openapi-callbacks/image01.png">
+55
View File
@@ -0,0 +1,55 @@
# Webhooks de OpenAPI
Hay casos donde quieres decirle a los **usuarios** de tu API que tu aplicación podría llamar a *su* aplicación (enviando una request) con algunos datos, normalmente para **notificar** de algún tipo de **evento**.
Esto significa que en lugar del proceso normal de tus usuarios enviando requests a tu API, es **tu API** (o tu aplicación) la que podría **enviar requests a su sistema** (a su API, su aplicación).
Esto normalmente se llama un **webhook**.
## Pasos de los webhooks
El proceso normalmente es que **tú defines** en tu código cuál es el mensaje que enviarás, el **body de la request**.
También defines de alguna manera en qué **momentos** tu aplicación enviará esas requests o eventos.
Y **tus usuarios** definen de alguna manera (por ejemplo en un panel web en algún lugar) el **URL** donde tu aplicación debería enviar esas requests.
Toda la **lógica** sobre cómo registrar los URLs para webhooks y el código para realmente enviar esas requests depende de ti. Lo escribes como quieras en **tu propio código**.
## Documentando webhooks con **FastAPI** y OpenAPI
Con **FastAPI**, usando OpenAPI, puedes definir los nombres de estos webhooks, los tipos de operaciones HTTP que tu aplicación puede enviar (por ejemplo, `POST`, `PUT`, etc.) y los **bodies** de las requests que tu aplicación enviaría.
Esto puede hacer mucho más fácil para tus usuarios **implementar sus APIs** para recibir tus requests de **webhook**, incluso podrían ser capaces de autogenerar algo de su propio código de API.
/// info | Información
Los webhooks están disponibles en OpenAPI 3.1.0 y superiores, soportados por FastAPI `0.99.0` y superiores.
///
## Una aplicación con webhooks
Cuando creas una aplicación de **FastAPI**, hay un atributo `webhooks` que puedes usar para definir *webhooks*, de la misma manera que definirías *path operations*, por ejemplo con `@app.webhooks.post()`.
{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *}
Los webhooks que defines terminarán en el esquema de **OpenAPI** y en la interfaz automática de **documentación**.
/// info | Información
El objeto `app.webhooks` es en realidad solo un `APIRouter`, el mismo tipo que usarías al estructurar tu aplicación con múltiples archivos.
///
Nota que con los webhooks en realidad no estás declarando un *path* (como `/items/`), el texto que pasas allí es solo un **identificador** del webhook (el nombre del evento), por ejemplo en `@app.webhooks.post("new-subscription")`, el nombre del webhook es `new-subscription`.
Esto es porque se espera que **tus usuarios** definan el actual **URL path** donde quieren recibir la request del webhook de alguna otra manera (por ejemplo, un panel web).
### Revisa la documentación
Ahora puedes iniciar tu app e ir a <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Verás que tu documentación tiene las *path operations* normales y ahora también algunos **webhooks**:
<img src="/img/tutorial/openapi-webhooks/image01.png">
@@ -0,0 +1,204 @@
# Configuración Avanzada de Path Operation
## operationId de OpenAPI
/// warning | Advertencia
Si no eres un "experto" en OpenAPI, probablemente no necesites esto.
///
Puedes establecer el `operationId` de OpenAPI para ser usado en tu *path operation* con el parámetro `operation_id`.
Tienes que asegurarte de que sea único para cada operación.
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
### Usar el nombre de la *función de path operation* como el operationId
Si quieres usar los nombres de las funciones de tus APIs como `operationId`s, puedes iterar sobre todas ellas y sobrescribir el `operation_id` de cada *path operation* usando su `APIRoute.name`.
Deberías hacerlo después de agregar todas tus *path operations*.
{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *}
/// tip | Consejo
Si llamas manualmente a `app.openapi()`, deberías actualizar los `operationId`s antes de eso.
///
/// warning | Advertencia
Si haces esto, tienes que asegurarte de que cada una de tus *funciones de path operation* tenga un nombre único.
Incluso si están en diferentes módulos (archivos de Python).
///
## Excluir de OpenAPI
Para excluir una *path operation* del esquema OpenAPI generado (y por lo tanto, de los sistemas de documentación automática), utiliza el parámetro `include_in_schema` y configúralo en `False`:
{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *}
## Descripción avanzada desde el docstring
Puedes limitar las líneas usadas del docstring de una *función de path operation* para OpenAPI.
Añadir un `\f` (un carácter de separación de página escapado) hace que **FastAPI** trunque la salida usada para OpenAPI en este punto.
No aparecerá en la documentación, pero otras herramientas (como Sphinx) podrán usar el resto.
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *}
## Responses Adicionales
Probablemente has visto cómo declarar el `response_model` y el `status_code` para una *path operation*.
Eso define los metadatos sobre el response principal de una *path operation*.
También puedes declarar responses adicionales con sus modelos, códigos de estado, etc.
Hay un capítulo entero en la documentación sobre ello, puedes leerlo en [Responses Adicionales en OpenAPI](additional-responses.md){.internal-link target=_blank}.
## OpenAPI Extra
Cuando declaras una *path operation* en tu aplicación, **FastAPI** genera automáticamente los metadatos relevantes sobre esa *path operation* para incluirlos en el esquema de OpenAPI.
/// note | Nota
En la especificación de OpenAPI se llama el <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Objeto de Operación</a>.
///
Tiene toda la información sobre la *path operation* y se usa para generar la documentación automática.
Incluye los `tags`, `parameters`, `requestBody`, `responses`, etc.
Este esquema de OpenAPI específico de *path operation* normalmente se genera automáticamente por **FastAPI**, pero también puedes extenderlo.
/// tip | Consejo
Este es un punto de extensión de bajo nivel.
Si solo necesitas declarar responses adicionales, una forma más conveniente de hacerlo es con [Responses Adicionales en OpenAPI](additional-responses.md){.internal-link target=_blank}.
///
Puedes extender el esquema de OpenAPI para una *path operation* usando el parámetro `openapi_extra`.
### Extensiones de OpenAPI
Este `openapi_extra` puede ser útil, por ejemplo, para declarar [Extensiones de OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *}
Si abres la documentación automática de la API, tu extensión aparecerá en la parte inferior de la *path operation* específica.
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png">
Y si ves el OpenAPI resultante (en `/openapi.json` en tu API), verás tu extensión como parte de la *path operation* específica también:
```JSON hl_lines="22"
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"x-aperture-labs-portal": "blue"
}
}
}
}
```
### Esquema de *path operation* personalizada de OpenAPI
El diccionario en `openapi_extra` se combinará profundamente con el esquema de OpenAPI generado automáticamente para la *path operation*.
Por lo tanto, podrías añadir datos adicionales al esquema generado automáticamente.
Por ejemplo, podrías decidir leer y validar el request con tu propio código, sin usar las funcionalidades automáticas de FastAPI con Pydantic, pero aún podrías querer definir el request en el esquema de OpenAPI.
Podrías hacer eso con `openapi_extra`:
{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *}
En este ejemplo, no declaramos ningún modelo Pydantic. De hecho, el cuerpo del request ni siquiera se <abbr title="converted from some plain format, like bytes, into Python objects">parse</abbr> como JSON, se lee directamente como `bytes`, y la función `magic_data_reader()` sería la encargada de parsearlo de alguna manera.
Sin embargo, podemos declarar el esquema esperado para el cuerpo del request.
### Tipo de contenido personalizado de OpenAPI
Usando este mismo truco, podrías usar un modelo Pydantic para definir el esquema JSON que luego se incluye en la sección personalizada del esquema OpenAPI para la *path operation*.
Y podrías hacer esto incluso si el tipo de datos en el request no es JSON.
Por ejemplo, en esta aplicación no usamos la funcionalidad integrada de FastAPI para extraer el esquema JSON de los modelos Pydantic ni la validación automática para JSON. De hecho, estamos declarando el tipo de contenido del request como YAML, no JSON:
//// tab | Pydantic v2
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *}
////
//// tab | Pydantic v1
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *}
////
/// info | Información
En la versión 1 de Pydantic el método para obtener el esquema JSON para un modelo se llamaba `Item.schema()`, en la versión 2 de Pydantic, el método se llama `Item.model_json_schema()`.
///
Sin embargo, aunque no estamos usando la funcionalidad integrada por defecto, aún estamos usando un modelo Pydantic para generar manualmente el esquema JSON para los datos que queremos recibir en YAML.
Luego usamos el request directamente, y extraemos el cuerpo como `bytes`. Esto significa que FastAPI ni siquiera intentará parsear la carga útil del request como JSON.
Y luego en nuestro código, parseamos ese contenido YAML directamente, y nuevamente estamos usando el mismo modelo Pydantic para validar el contenido YAML:
//// tab | Pydantic v2
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *}
////
//// tab | Pydantic v1
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *}
////
/// info | Información
En la versión 1 de Pydantic el método para parsear y validar un objeto era `Item.parse_obj()`, en la versión 2 de Pydantic, el método se llama `Item.model_validate()`.
///
/// tip | Consejo
Aquí reutilizamos el mismo modelo Pydantic.
Pero de la misma manera, podríamos haberlo validado de alguna otra forma.
///
@@ -0,0 +1,31 @@
# Response - Cambiar Código de Estado
Probablemente leíste antes que puedes establecer un [Código de Estado de Response](../tutorial/response-status-code.md){.internal-link target=_blank} por defecto.
Pero en algunos casos necesitas devolver un código de estado diferente al predeterminado.
## Caso de uso
Por ejemplo, imagina que quieres devolver un código de estado HTTP de "OK" `200` por defecto.
Pero si los datos no existieran, quieres crearlos y devolver un código de estado HTTP de "CREATED" `201`.
Pero todavía quieres poder filtrar y convertir los datos que devuelves con un `response_model`.
Para esos casos, puedes usar un parámetro `Response`.
## Usa un parámetro `Response`
Puedes declarar un parámetro de tipo `Response` en tu *función de path operation* (como puedes hacer para cookies y headers).
Y luego puedes establecer el `status_code` en ese objeto de response *temporal*.
{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
Y luego puedes devolver cualquier objeto que necesites, como lo harías normalmente (un `dict`, un modelo de base de datos, etc.).
Y si declaraste un `response_model`, todavía se utilizará para filtrar y convertir el objeto que devolviste.
**FastAPI** usará ese response *temporal* para extraer el código de estado (también cookies y headers), y los pondrá en el response final que contiene el valor que devolviste, filtrado por cualquier `response_model`.
También puedes declarar el parámetro `Response` en dependencias y establecer el código de estado en ellas. Pero ten en cuenta que el último establecido prevalecerá.
+51
View File
@@ -0,0 +1,51 @@
# Cookies de Response
## Usar un parámetro `Response`
Puedes declarar un parámetro de tipo `Response` en tu *path operation function*.
Y luego puedes establecer cookies en ese objeto de response *temporal*.
{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *}
Y entonces puedes devolver cualquier objeto que necesites, como normalmente lo harías (un `dict`, un modelo de base de datos, etc).
Y si declaraste un `response_model`, todavía se utilizará para filtrar y convertir el objeto que devolviste.
**FastAPI** utilizará ese response *temporal* para extraer las cookies (también los headers y el código de estado), y las pondrá en el response final que contiene el valor que devolviste, filtrado por cualquier `response_model`.
También puedes declarar el parámetro `Response` en las dependencias, y establecer cookies (y headers) en ellas.
## Devolver una `Response` directamente
También puedes crear cookies al devolver una `Response` directamente en tu código.
Para hacer eso, puedes crear un response como se describe en [Devolver un Response Directamente](response-directly.md){.internal-link target=_blank}.
Luego establece Cookies en ella, y luego devuélvela:
{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
/// tip | Consejo
Ten en cuenta que si devuelves un response directamente en lugar de usar el parámetro `Response`, FastAPI lo devolverá directamente.
Así que tendrás que asegurarte de que tus datos son del tipo correcto. Por ejemplo, que sea compatible con JSON, si estás devolviendo un `JSONResponse`.
Y también que no estés enviando ningún dato que debería haber sido filtrado por un `response_model`.
///
### Más información
/// note | Detalles Técnicos
También podrías usar `from starlette.responses import Response` o `from starlette.responses import JSONResponse`.
**FastAPI** proporciona los mismos `starlette.responses` como `fastapi.responses` solo como una conveniencia para ti, el desarrollador. Pero la mayoría de los responses disponibles vienen directamente de Starlette.
Y como el `Response` se puede usar frecuentemente para establecer headers y cookies, **FastAPI** también lo proporciona en `fastapi.Response`.
///
Para ver todos los parámetros y opciones disponibles, revisa la <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">documentación en Starlette</a>.
@@ -0,0 +1,65 @@
# Devolver una Response Directamente
Cuando creas una *path operation* en **FastAPI**, normalmente puedes devolver cualquier dato desde ella: un `dict`, una `list`, un modelo de Pydantic, un modelo de base de datos, etc.
Por defecto, **FastAPI** convertiría automáticamente ese valor de retorno a JSON usando el `jsonable_encoder` explicado en [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}.
Luego, detrás de escena, pondría esos datos compatibles con JSON (por ejemplo, un `dict`) dentro de un `JSONResponse` que se usaría para enviar el response al cliente.
Pero puedes devolver un `JSONResponse` directamente desde tus *path operations*.
Esto podría ser útil, por ejemplo, para devolver headers o cookies personalizados.
## Devolver una `Response`
De hecho, puedes devolver cualquier `Response` o cualquier subclase de ella.
/// 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 los modelos de Pydantic, no convertirá los contenidos a ningún tipo, etc.
Esto te da mucha flexibilidad. Puedes devolver cualquier tipo de datos, sobrescribir cualquier declaración o validación de datos, etc.
## Usar el `jsonable_encoder` en una `Response`
Como **FastAPI** no realiza cambios en una `Response` que devuelves, tienes que asegurarte de que sus contenidos estén listos para ello.
Por ejemplo, no puedes poner un modelo de Pydantic en un `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 un response:
{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
/// note | Nota
También podrías usar `from starlette.responses import JSONResponse`.
**FastAPI** proporciona los mismos `starlette.responses` como `fastapi.responses` solo como una conveniencia para ti, el desarrollador. Pero la mayoría de los responses disponibles vienen directamente de Starlette.
///
## Devolver una `Response` personalizada
El ejemplo anterior muestra todas las partes que necesitas, pero aún no es muy útil, ya que podrías haber devuelto el `item` directamente, y **FastAPI** lo colocaría en un `JSONResponse` por ti, convirtiéndolo a un `dict`, etc. Todo eso por defecto.
Ahora, veamos cómo podrías usar eso para devolver un response personalizado.
Digamos que quieres devolver un response en <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a>.
Podrías poner tu contenido XML en un string, poner eso en un `Response`, y devolverlo:
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
## Notas
Cuando devuelves una `Response` directamente, sus datos no son validados, convertidos (serializados), ni documentados automáticamente.
Pero aún puedes documentarlo como se describe en [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}.
Puedes ver en secciones posteriores cómo usar/declarar estas `Response`s personalizadas mientras todavía tienes conversión automática de datos, documentación, etc.
+41
View File
@@ -0,0 +1,41 @@
# Response Headers
## Usa un parámetro `Response`
Puedes declarar un parámetro de tipo `Response` en tu *función de path operation* (como puedes hacer para cookies).
Y luego puedes establecer headers en ese objeto de response *temporal*.
{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *}
Y luego puedes devolver cualquier objeto que necesites, como harías normalmente (un `dict`, un modelo de base de datos, etc).
Y si declaraste un `response_model`, aún se usará para filtrar y convertir el objeto que devolviste.
**FastAPI** usará ese response *temporal* para extraer los headers (también cookies y el código de estado), y los pondrá en el response final que contiene el valor que devolviste, filtrado por cualquier `response_model`.
También puedes declarar el parámetro `Response` en dependencias y establecer headers (y cookies) en ellas.
## Retorna una `Response` directamente
También puedes agregar headers cuando devuelves un `Response` directamente.
Crea un response como se describe en [Retorna un Response Directamente](response-directly.md){.internal-link target=_blank} y pasa los headers como un parámetro adicional:
{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *}
/// note | Detalles Técnicos
También podrías usar `from starlette.responses import Response` o `from starlette.responses import JSONResponse`.
**FastAPI** proporciona las mismas `starlette.responses` como `fastapi.responses` solo por conveniencia para ti, el desarrollador. Pero la mayoría de los responses disponibles provienen directamente de Starlette.
Y como el `Response` se puede usar frecuentemente para establecer headers y cookies, **FastAPI** también lo proporciona en `fastapi.Response`.
///
## Headers Personalizados
Ten en cuenta que los headers propietarios personalizados se pueden agregar <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">usando el prefijo 'X-'</a>.
Pero si tienes headers personalizados que quieres que un cliente en un navegador pueda ver, necesitas agregarlos a tus configuraciones de CORS (leer más en [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), usando el parámetro `expose_headers` documentado en <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">la documentación CORS de Starlette</a>.
@@ -0,0 +1,107 @@
# HTTP Basic Auth
Para los casos más simples, puedes usar HTTP Basic Auth.
En HTTP Basic Auth, la aplicación espera un header que contiene un nombre de usuario y una contraseña.
Si no lo recibe, devuelve un error HTTP 401 "Unauthorized".
Y devuelve un header `WWW-Authenticate` con un valor de `Basic`, y un parámetro `realm` opcional.
Eso le dice al navegador que muestre el prompt integrado para un nombre de usuario y contraseña.
Luego, cuando escribes ese nombre de usuario y contraseña, el navegador los envía automáticamente en el header.
## Simple HTTP Basic Auth
* Importa `HTTPBasic` y `HTTPBasicCredentials`.
* Crea un "esquema de `security`" usando `HTTPBasic`.
* Usa ese `security` con una dependencia en tu *path operation*.
* Devuelve un objeto de tipo `HTTPBasicCredentials`:
* Contiene el `username` y `password` enviados.
{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *}
Cuando intentas abrir la URL por primera vez (o haces clic en el botón "Execute" en la documentación) el navegador te pedirá tu nombre de usuario y contraseña:
<img src="/img/tutorial/security/image12.png">
## Revisa el nombre de usuario
Aquí hay un ejemplo más completo.
Usa una dependencia para comprobar si el nombre de usuario y la contraseña son correctos.
Para esto, usa el módulo estándar de Python <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a> para verificar el nombre de usuario y la contraseña.
`secrets.compare_digest()` necesita tomar `bytes` o un `str` que solo contenga caracteres ASCII (los carácteres en inglés), esto significa que no funcionaría con caracteres como `á`, como en `Sebastián`.
Para manejar eso, primero convertimos el `username` y `password` a `bytes` codificándolos con UTF-8.
Luego podemos usar `secrets.compare_digest()` para asegurar que `credentials.username` es `"stanleyjobson"`, y que `credentials.password` es `"swordfish"`.
{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *}
Esto sería similar a:
```Python
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
# Return some error
...
```
Pero al usar `secrets.compare_digest()` será seguro contra un tipo de ataques llamados "timing attacks".
### Timing Attacks
¿Pero qué es un "timing attack"?
Imaginemos que algunos atacantes están tratando de adivinar el nombre de usuario y la contraseña.
Y envían un request con un nombre de usuario `johndoe` y una contraseña `love123`.
Entonces el código de Python en tu aplicación equivaldría a algo como:
```Python
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
...
```
Pero justo en el momento en que Python compara la primera `j` en `johndoe` con la primera `s` en `stanleyjobson`, devolverá `False`, porque ya sabe que esas dos strings no son iguales, pensando que "no hay necesidad de gastar más computación comparando el resto de las letras". Y tu aplicación dirá "Nombre de usuario o contraseña incorrectos".
Pero luego los atacantes prueban con el nombre de usuario `stanleyjobsox` y contraseña `love123`.
Y el código de tu aplicación hace algo así como:
```Python
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
...
```
Python tendrá que comparar todo `stanleyjobso` en ambos `stanleyjobsox` y `stanleyjobson` antes de darse cuenta de que ambas strings no son las mismas. Así que tomará algunos microsegundos extra para responder "Nombre de usuario o contraseña incorrectos".
#### El tiempo de respuesta ayuda a los atacantes
En ese punto, al notar que el servidor tardó algunos microsegundos más en enviar el response "Nombre de usuario o contraseña incorrectos", los atacantes sabrán que acertaron en _algo_, algunas de las letras iniciales eran correctas.
Y luego pueden intentar de nuevo sabiendo que probablemente es algo más similar a `stanleyjobsox` que a `johndoe`.
#### Un ataque "profesional"
Por supuesto, los atacantes no intentarían todo esto a mano, escribirían un programa para hacerlo, posiblemente con miles o millones de pruebas por segundo. Y obtendrían solo una letra correcta adicional a la vez.
Pero haciendo eso, en algunos minutos u horas, los atacantes habrían adivinado el nombre de usuario y la contraseña correctos, con la "ayuda" de nuestra aplicación, solo usando el tiempo tomado para responder.
#### Arréglalo con `secrets.compare_digest()`
Pero en nuestro código estamos usando realmente `secrets.compare_digest()`.
En resumen, tomará el mismo tiempo comparar `stanleyjobsox` con `stanleyjobson` que comparar `johndoe` con `stanleyjobson`. Y lo mismo para la contraseña.
De esa manera, usando `secrets.compare_digest()` en el código de tu aplicación, será seguro contra todo este rango de ataques de seguridad.
### Devuelve el error
Después de detectar que las credenciales son incorrectas, regresa un `HTTPException` con un código de estado 401 (el mismo que se devuelve cuando no se proporcionan credenciales) y agrega el header `WWW-Authenticate` para que el navegador muestre el prompt de inicio de sesión nuevamente:
{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *}
+19
View File
@@ -0,0 +1,19 @@
# Seguridad Avanzada
## Funcionalidades Adicionales
Hay algunas funcionalidades extra para manejar la seguridad aparte de las cubiertas en el [Tutorial - Guía del Usuario: Seguridad](../../tutorial/security/index.md){.internal-link target=_blank}.
/// tip | Consejo
Las siguientes secciones **no son necesariamente "avanzadas"**.
Y es posible que para tu caso de uso, la solución esté en una de ellas.
///
## Lee primero el Tutorial
Las siguientes secciones asumen que ya leíste el [Tutorial - Guía del Usuario: Seguridad](../../tutorial/security/index.md){.internal-link target=_blank}.
Todas están basadas en los mismos conceptos, pero permiten algunas funcionalidades adicionales.
@@ -0,0 +1,274 @@
# Scopes de OAuth2
Puedes usar scopes de OAuth2 directamente con **FastAPI**, están integrados para funcionar de manera fluida.
Esto te permitiría tener un sistema de permisos más detallado, siguiendo el estándar de OAuth2, integrado en tu aplicación OpenAPI (y la documentación de la API).
OAuth2 con scopes es el mecanismo usado por muchos grandes proveedores de autenticación, como Facebook, Google, GitHub, Microsoft, X (Twitter), etc. Lo usan para proporcionar permisos específicos a usuarios y aplicaciones.
Cada vez que te "logueas con" Facebook, Google, GitHub, Microsoft, X (Twitter), esa aplicación está usando OAuth2 con scopes.
En esta sección verás cómo manejar autenticación y autorización con el mismo OAuth2 con scopes en tu aplicación de **FastAPI**.
/// warning | Advertencia
Esta es una sección más o menos avanzada. Si estás comenzando, puedes saltarla.
No necesariamente necesitas scopes de OAuth2, y puedes manejar autenticación y autorización como quieras.
Pero OAuth2 con scopes se puede integrar muy bien en tu API (con OpenAPI) y en la documentación de tu API.
No obstante, tú aún impones esos scopes, o cualquier otro requisito de seguridad/autorización, como necesites, en tu código.
En muchos casos, OAuth2 con scopes puede ser un exceso.
Pero si sabes que lo necesitas, o tienes curiosidad, sigue leyendo.
///
## Scopes de OAuth2 y OpenAPI
La especificación de OAuth2 define "scopes" como una lista de strings separados por espacios.
El contenido de cada uno de estos strings puede tener cualquier formato, pero no debe contener espacios.
Estos scopes representan "permisos".
En OpenAPI (por ejemplo, en la documentación de la API), puedes definir "esquemas de seguridad".
Cuando uno de estos esquemas de seguridad usa OAuth2, también puedes declarar y usar scopes.
Cada "scope" es solo un string (sin espacios).
Normalmente se utilizan para declarar permisos de seguridad específicos, por ejemplo:
* `users:read` o `users:write` son ejemplos comunes.
* `instagram_basic` es usado por Facebook / Instagram.
* `https://www.googleapis.com/auth/drive` es usado por Google.
/// info | Información
En OAuth2 un "scope" es solo un string que declara un permiso específico requerido.
No importa si tiene otros caracteres como `:` o si es una URL.
Esos detalles son específicos de la implementación.
Para OAuth2 son solo strings.
///
## Vista global
Primero, echemos un vistazo rápido a las partes que cambian desde los ejemplos en el **Tutorial - User Guide** principal para [OAuth2 con Password (y hashing), Bearer con tokens JWT](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Ahora usando scopes de OAuth2:
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:125,129:135,140,156] *}
Ahora revisemos esos cambios paso a paso.
## Esquema de seguridad OAuth2
El primer cambio es que ahora estamos declarando el esquema de seguridad OAuth2 con dos scopes disponibles, `me` y `items`.
El parámetro `scopes` recibe un `dict` con cada scope como clave y la descripción como valor:
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}
Como ahora estamos declarando esos scopes, aparecerán en la documentación de la API cuando inicies sesión/autorices.
Y podrás seleccionar cuáles scopes quieres dar de acceso: `me` y `items`.
Este es el mismo mecanismo utilizado cuando das permisos al iniciar sesión con Facebook, Google, GitHub, etc:
<img src="/img/tutorial/security/image11.png">
## Token JWT con scopes
Ahora, modifica la *path operation* del token para devolver los scopes solicitados.
Todavía estamos usando el mismo `OAuth2PasswordRequestForm`. Incluye una propiedad `scopes` con una `list` de `str`, con cada scope que recibió en el request.
Y devolvemos los scopes como parte del token JWT.
/// danger | Peligro
Para simplificar, aquí solo estamos añadiendo los scopes recibidos directamente al token.
Pero en tu aplicación, por seguridad, deberías asegurarte de añadir solo los scopes que el usuario realmente puede tener, o los que has predefinido.
///
{* ../../docs_src/security/tutorial005_an_py310.py hl[156] *}
## Declarar scopes en *path operations* y dependencias
Ahora declaramos que la *path operation* para `/users/me/items/` requiere el scope `items`.
Para esto, importamos y usamos `Security` de `fastapi`.
Puedes usar `Security` para declarar dependencias (igual que `Depends`), pero `Security` también recibe un parámetro `scopes` con una lista de scopes (strings).
En este caso, pasamos una función de dependencia `get_current_active_user` a `Security` (de la misma manera que haríamos con `Depends`).
Pero también pasamos una `list` de scopes, en este caso con solo un scope: `items` (podría tener más).
Y la función de dependencia `get_current_active_user` también puede declarar sub-dependencias, no solo con `Depends` sino también con `Security`. Declarando su propia función de sub-dependencia (`get_current_user`), y más requisitos de scope.
En este caso, requiere el scope `me` (podría requerir más de un scope).
/// note | Nota
No necesariamente necesitas añadir diferentes scopes en diferentes lugares.
Lo estamos haciendo aquí para demostrar cómo **FastAPI** maneja scopes declarados en diferentes niveles.
///
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,140,171] *}
/// info | Información Técnica
`Security` es en realidad una subclase de `Depends`, y tiene solo un parámetro extra que veremos más adelante.
Pero al usar `Security` en lugar de `Depends`, **FastAPI** sabrá que puede declarar scopes de seguridad, usarlos internamente y documentar la API con OpenAPI.
Pero cuando importas `Query`, `Path`, `Depends`, `Security` y otros de `fastapi`, en realidad son funciones que devuelven clases especiales.
///
## Usar `SecurityScopes`
Ahora actualiza la dependencia `get_current_user`.
Esta es la que usan las dependencias anteriores.
Aquí es donde estamos usando el mismo esquema de OAuth2 que creamos antes, declarándolo como una dependencia: `oauth2_scheme`.
Porque esta función de dependencia no tiene ningún requisito de scope en sí, podemos usar `Depends` con `oauth2_scheme`, no tenemos que usar `Security` cuando no necesitamos especificar scopes de seguridad.
También declaramos un parámetro especial de tipo `SecurityScopes`, importado de `fastapi.security`.
Esta clase `SecurityScopes` es similar a `Request` (`Request` se usó para obtener el objeto request directamente).
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
## Usar los `scopes`
El parámetro `security_scopes` será del tipo `SecurityScopes`.
Tendrá una propiedad `scopes` con una lista que contiene todos los scopes requeridos por sí mismo y por todas las dependencias que lo usan como sub-dependencia. Eso significa, todos los "dependientes"... esto podría sonar confuso, se explica de nuevo más abajo.
El objeto `security_scopes` (de la clase `SecurityScopes`) también proporciona un atributo `scope_str` con un único string, que contiene esos scopes separados por espacios (lo vamos a usar).
Creamos una `HTTPException` que podemos reutilizar (`raise`) más tarde en varios puntos.
En esta excepción, incluimos los scopes requeridos (si los hay) como un string separado por espacios (usando `scope_str`). Ponemos ese string que contiene los scopes en el header `WWW-Authenticate` (esto es parte de la especificación).
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
## Verificar el `username` y la forma de los datos
Verificamos que obtenemos un `username`, y extraemos los scopes.
Y luego validamos esos datos con el modelo de Pydantic (capturando la excepción `ValidationError`), y si obtenemos un error leyendo el token JWT o validando los datos con Pydantic, lanzamos la `HTTPException` que creamos antes.
Para eso, actualizamos el modelo de Pydantic `TokenData` con una nueva propiedad `scopes`.
Al validar los datos con Pydantic podemos asegurarnos de que tenemos, por ejemplo, exactamente una `list` de `str` con los scopes y un `str` con el `username`.
En lugar de, por ejemplo, un `dict`, o algo más, ya que podría romper la aplicación en algún punto posterior, haciéndolo un riesgo de seguridad.
También verificamos que tenemos un usuario con ese username, y si no, lanzamos esa misma excepción que creamos antes.
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:128] *}
## Verificar los `scopes`
Ahora verificamos que todos los scopes requeridos, por esta dependencia y todos los dependientes (incluyendo *path operations*), estén incluidos en los scopes proporcionados en el token recibido, de lo contrario, lanzamos una `HTTPException`.
Para esto, usamos `security_scopes.scopes`, que contiene una `list` con todos estos scopes como `str`.
{* ../../docs_src/security/tutorial005_an_py310.py hl[129:135] *}
## Árbol de dependencias y scopes
Revisemos de nuevo este árbol de dependencias y los scopes.
Como la dependencia `get_current_active_user` tiene como sub-dependencia a `get_current_user`, el scope `"me"` declarado en `get_current_active_user` se incluirá en la lista de scopes requeridos en el `security_scopes.scopes` pasado a `get_current_user`.
La *path operation* en sí también declara un scope, `"items"`, por lo que esto también estará en la lista de `security_scopes.scopes` pasado a `get_current_user`.
Así es como se ve la jerarquía de dependencias y scopes:
* La *path operation* `read_own_items` tiene:
* Scopes requeridos `["items"]` con la dependencia:
* `get_current_active_user`:
* La función de dependencia `get_current_active_user` tiene:
* Scopes requeridos `["me"]` con la dependencia:
* `get_current_user`:
* La función de dependencia `get_current_user` tiene:
* No requiere scopes por sí misma.
* Una dependencia usando `oauth2_scheme`.
* Un parámetro `security_scopes` de tipo `SecurityScopes`:
* Este parámetro `security_scopes` tiene una propiedad `scopes` con una `list` que contiene todos estos scopes declarados arriba, por lo que:
* `security_scopes.scopes` contendrá `["me", "items"]` para la *path operation* `read_own_items`.
* `security_scopes.scopes` contendrá `["me"]` para la *path operation* `read_users_me`, porque está declarado en la dependencia `get_current_active_user`.
* `security_scopes.scopes` contendrá `[]` (nada) para la *path operation* `read_system_status`, porque no declaró ningún `Security` con `scopes`, y su dependencia, `get_current_user`, tampoco declara ningún `scopes`.
/// tip | Consejo
Lo importante y "mágico" aquí es que `get_current_user` tendrá una lista diferente de `scopes` para verificar para cada *path operation*.
Todo depende de los `scopes` declarados en cada *path operation* y cada dependencia en el árbol de dependencias para esa *path operation* específica.
///
## Más detalles sobre `SecurityScopes`
Puedes usar `SecurityScopes` en cualquier punto, y en múltiples lugares, no tiene que ser en la dependencia "raíz".
Siempre tendrá los scopes de seguridad declarados en las dependencias `Security` actuales y todos los dependientes para **esa específica** *path operation* y **ese específico** árbol de dependencias.
Debido a que `SecurityScopes` tendrá todos los scopes declarados por dependientes, puedes usarlo para verificar que un token tiene los scopes requeridos en una función de dependencia central, y luego declarar diferentes requisitos de scope en diferentes *path operations*.
Serán verificados independientemente para cada *path operation*.
## Revisa
Si abres la documentación de la API, puedes autenticarte y especificar qué scopes deseas autorizar.
<img src="/img/tutorial/security/image11.png">
Si no seleccionas ningún scope, estarás "autenticado", pero cuando intentes acceder a `/users/me/` o `/users/me/items/` obtendrás un error diciendo que no tienes suficientes permisos. Aún podrás acceder a `/status/`.
Y si seleccionas el scope `me` pero no el scope `items`, podrás acceder a `/users/me/` pero no a `/users/me/items/`.
Eso es lo que pasaría a una aplicación de terceros que intentara acceder a una de estas *path operations* con un token proporcionado por un usuario, dependiendo de cuántos permisos el usuario otorgó a la aplicación.
## Acerca de las integraciones de terceros
En este ejemplo estamos usando el flujo de OAuth2 "password".
Esto es apropiado cuando estamos iniciando sesión en nuestra propia aplicación, probablemente con nuestro propio frontend.
Porque podemos confiar en ella para recibir el `username` y `password`, ya que la controlamos.
Pero si estás construyendo una aplicación OAuth2 a la que otros se conectarían (es decir, si estás construyendo un proveedor de autenticación equivalente a Facebook, Google, GitHub, etc.) deberías usar uno de los otros flujos.
El más común es el flujo implícito.
El más seguro es el flujo de código, pero es más complejo de implementar ya que requiere más pasos. Como es más complejo, muchos proveedores terminan sugiriendo el flujo implícito.
/// note | Nota
Es común que cada proveedor de autenticación nombre sus flujos de una manera diferente, para hacerlos parte de su marca.
Pero al final, están implementando el mismo estándar OAuth2.
///
**FastAPI** incluye utilidades para todos estos flujos de autenticación OAuth2 en `fastapi.security.oauth2`.
## `Security` en `dependencies` del decorador
De la misma manera que puedes definir una `list` de `Depends` en el parámetro `dependencies` del decorador (como se explica en [Dependencias en decoradores de path operation](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), también podrías usar `Security` con `scopes` allí.
+346
View File
@@ -0,0 +1,346 @@
# Configuraciones y Variables de Entorno
En muchos casos, tu aplicación podría necesitar algunas configuraciones o ajustes externos, por ejemplo, claves secretas, credenciales de base de datos, credenciales para servicios de correo electrónico, etc.
La mayoría de estas configuraciones son variables (pueden cambiar), como las URLs de bases de datos. Y muchas podrían ser sensibles, como los secretos.
Por esta razón, es común proporcionarlas en variables de entorno que son leídas por la aplicación.
/// tip | Consejo
Para entender las variables de entorno, puedes leer [Variables de Entorno](../environment-variables.md){.internal-link target=_blank}.
///
## Tipos y validación
Estas variables de entorno solo pueden manejar strings de texto, ya que son externas a Python y tienen que ser compatibles con otros programas y el resto del sistema (e incluso con diferentes sistemas operativos, como Linux, Windows, macOS).
Eso significa que cualquier valor leído en Python desde una variable de entorno será un `str`, y cualquier conversión a un tipo diferente o cualquier validación tiene que hacerse en código.
## Pydantic `Settings`
Afortunadamente, Pydantic proporciona una gran utilidad para manejar estas configuraciones provenientes de variables de entorno con <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>.
### Instalar `pydantic-settings`
Primero, asegúrate de crear tu [entorno virtual](../virtual-environments.md){.internal-link target=_blank}, actívalo y luego instala el paquete `pydantic-settings`:
<div class="termy">
```console
$ pip install pydantic-settings
---> 100%
```
</div>
También viene incluido cuando instalas los extras `all` con:
<div class="termy">
```console
$ pip install "fastapi[all]"
---> 100%
```
</div>
/// info | Información
En Pydantic v1 venía incluido con el paquete principal. Ahora se distribuye como este paquete independiente para que puedas elegir si instalarlo o no si no necesitas esa funcionalidad.
///
### Crear el objeto `Settings`
Importa `BaseSettings` de Pydantic y crea una sub-clase, muy similar a un modelo de Pydantic.
De la misma forma que con los modelos de Pydantic, declaras atributos de clase con anotaciones de tipos, y posiblemente, valores por defecto.
Puedes usar todas las mismas funcionalidades de validación y herramientas que usas para los modelos de Pydantic, como diferentes tipos de datos y validaciones adicionales con `Field()`.
//// tab | Pydantic v2
{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *}
////
//// tab | Pydantic v1
/// info | Información
En Pydantic v1 importarías `BaseSettings` directamente desde `pydantic` en lugar de desde `pydantic_settings`.
///
{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *}
////
/// tip | Consejo
Si quieres algo rápido para copiar y pegar, no uses este ejemplo, usa el último más abajo.
///
Luego, cuando creas una instance de esa clase `Settings` (en este caso, en el objeto `settings`), Pydantic leerá las variables de entorno de una manera indiferente a mayúsculas y minúsculas, por lo que una variable en mayúsculas `APP_NAME` aún será leída para el atributo `app_name`.
Luego convertirá y validará los datos. Así que, cuando uses ese objeto `settings`, tendrás datos de los tipos que declaraste (por ejemplo, `items_per_user` será un `int`).
### Usar el `settings`
Luego puedes usar el nuevo objeto `settings` en tu aplicación:
{* ../../docs_src/settings/tutorial001.py hl[18:20] *}
### Ejecutar el servidor
Luego, ejecutarías el servidor pasando las configuraciones como variables de entorno, por ejemplo, podrías establecer un `ADMIN_EMAIL` y `APP_NAME` con:
<div class="termy">
```console
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
/// tip | Consejo
Para establecer múltiples variables de entorno para un solo comando, simplemente sepáralas con un espacio y ponlas todas antes del comando.
///
Y luego la configuración `admin_email` se establecería en `"deadpool@example.com"`.
El `app_name` sería `"ChimichangApp"`.
Y el `items_per_user` mantendría su valor por defecto de `50`.
## Configuraciones en otro módulo
Podrías poner esas configuraciones en otro archivo de módulo como viste en [Aplicaciones Más Grandes - Múltiples Archivos](../tutorial/bigger-applications.md){.internal-link target=_blank}.
Por ejemplo, podrías tener un archivo `config.py` con:
{* ../../docs_src/settings/app01/config.py *}
Y luego usarlo en un archivo `main.py`:
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *}
/// tip | Consejo
También necesitarías un archivo `__init__.py` como viste en [Aplicaciones Más Grandes - Múltiples Archivos](../tutorial/bigger-applications.md){.internal-link target=_blank}.
///
## Configuraciones en una dependencia
En algunas ocasiones podría ser útil proporcionar las configuraciones desde una dependencia, en lugar de tener un objeto global con `settings` que se use en todas partes.
Esto podría ser especialmente útil durante las pruebas, ya que es muy fácil sobrescribir una dependencia con tus propias configuraciones personalizadas.
### El archivo de configuración
Proveniente del ejemplo anterior, tu archivo `config.py` podría verse como:
{* ../../docs_src/settings/app02/config.py hl[10] *}
Nota que ahora no creamos una instance por defecto `settings = Settings()`.
### El archivo principal de la app
Ahora creamos una dependencia que devuelve un nuevo `config.Settings()`.
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *}
/// tip | Consejo
Hablaremos del `@lru_cache` en un momento.
Por ahora puedes asumir que `get_settings()` es una función normal.
///
Y luego podemos requerirlo desde la *path operation function* como una dependencia y usarlo donde lo necesitemos.
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *}
### Configuraciones y pruebas
Luego sería muy fácil proporcionar un objeto de configuraciones diferente durante las pruebas al sobrescribir una dependencia para `get_settings`:
{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *}
En la dependencia sobreescrita establecemos un nuevo valor para el `admin_email` al crear el nuevo objeto `Settings`, y luego devolvemos ese nuevo objeto.
Luego podemos probar que se está usando.
## Leer un archivo `.env`
Si tienes muchas configuraciones que posiblemente cambien mucho, tal vez en diferentes entornos, podría ser útil ponerlos en un archivo y luego leerlos desde allí como si fueran variables de entorno.
Esta práctica es lo suficientemente común que tiene un nombre, estas variables de entorno generalmente se colocan en un archivo `.env`, y el archivo se llama un "dotenv".
/// tip | Consejo
Un archivo que comienza con un punto (`.`) es un archivo oculto en sistemas tipo Unix, como Linux y macOS.
Pero un archivo dotenv realmente no tiene que tener ese nombre exacto.
///
Pydantic tiene soporte para leer desde estos tipos de archivos usando un paquete externo. Puedes leer más en <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>.
/// tip | Consejo
Para que esto funcione, necesitas `pip install python-dotenv`.
///
### El archivo `.env`
Podrías tener un archivo `.env` con:
```bash
ADMIN_EMAIL="deadpool@example.com"
APP_NAME="ChimichangApp"
```
### Leer configuraciones desde `.env`
Y luego actualizar tu `config.py` con:
//// tab | Pydantic v2
{* ../../docs_src/settings/app03_an/config.py hl[9] *}
/// tip | Consejo
El atributo `model_config` se usa solo para configuración de Pydantic. Puedes leer más en <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Concepts: Configuration</a>.
///
////
//// tab | Pydantic v1
{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *}
/// tip | Consejo
La clase `Config` se usa solo para configuración de Pydantic. Puedes leer más en <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.
///
////
/// info | Información
En la versión 1 de Pydantic la configuración se hacía en una clase interna `Config`, en la versión 2 de Pydantic se hace en un atributo `model_config`. Este atributo toma un `dict`, y para obtener autocompletado y errores en línea, puedes importar y usar `SettingsConfigDict` para definir ese `dict`.
///
Aquí definimos la configuración `env_file` dentro de tu clase Pydantic `Settings`, y establecemos el valor en el nombre del archivo con el archivo dotenv que queremos usar.
### Creando el `Settings` solo una vez con `lru_cache`
Leer un archivo desde el disco es normalmente una operación costosa (lenta), por lo que probablemente quieras hacerlo solo una vez y luego reutilizar el mismo objeto de configuraciones, en lugar de leerlo para cada request.
Pero cada vez que hacemos:
```Python
Settings()
```
se crearía un nuevo objeto `Settings`, y al crearse leería el archivo `.env` nuevamente.
Si la función de dependencia fuera simplemente así:
```Python
def get_settings():
return Settings()
```
crearíamos ese objeto para cada request, y estaríamos leyendo el archivo `.env` para cada request. ⚠️
Pero como estamos usando el decorador `@lru_cache` encima, el objeto `Settings` se creará solo una vez, la primera vez que se llame. ✔️
{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *}
Entonces, para cualquier llamada subsiguiente de `get_settings()` en las dependencias de los próximos requests, en lugar de ejecutar el código interno de `get_settings()` y crear un nuevo objeto `Settings`, devolverá el mismo objeto que fue devuelto en la primera llamada, una y otra vez.
#### Detalles Técnicos de `lru_cache`
`@lru_cache` modifica la función que decora para devolver el mismo valor que se devolvió la primera vez, en lugar de calcularlo nuevamente, ejecutando el código de la función cada vez.
Así que la función debajo se ejecutará una vez por cada combinación de argumentos. Y luego, los valores devueltos por cada una de esas combinaciones de argumentos se utilizarán una y otra vez cada vez que la función sea llamada con exactamente la misma combinación de argumentos.
Por ejemplo, si tienes una función:
```Python
@lru_cache
def say_hi(name: str, salutation: str = "Ms."):
return f"Hello {salutation} {name}"
```
tu programa podría ejecutarse así:
```mermaid
sequenceDiagram
participant code as Código
participant function as say_hi()
participant execute as Ejecutar función
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Camila")
function ->> execute: ejecutar código de la función
execute ->> code: devolver el resultado
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: devolver resultado almacenado
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick")
function ->> execute: ejecutar código de la función
execute ->> code: devolver el resultado
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick", salutation="Mr.")
function ->> execute: ejecutar código de la función
execute ->> code: devolver el resultado
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Rick")
function ->> code: devolver resultado almacenado
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: devolver resultado almacenado
end
```
En el caso de nuestra dependencia `get_settings()`, la función ni siquiera toma argumentos, por lo que siempre devolverá el mismo valor.
De esa manera, se comporta casi como si fuera solo una variable global. Pero como usa una función de dependencia, entonces podemos sobrescribirla fácilmente para las pruebas.
`@lru_cache` es parte de `functools`, que es parte del library estándar de Python, puedes leer más sobre él en las <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">docs de Python para `@lru_cache`</a>.
## Resumen
Puedes usar Pydantic Settings para manejar las configuraciones o ajustes de tu aplicación, con todo el poder de los modelos de Pydantic.
* Al usar una dependencia, puedes simplificar las pruebas.
* Puedes usar archivos `.env` con él.
* Usar `@lru_cache` te permite evitar leer el archivo dotenv una y otra vez para cada request, mientras te permite sobrescribirlo durante las pruebas.
+67
View File
@@ -0,0 +1,67 @@
# Sub Aplicaciones - Mounts
Si necesitas tener dos aplicaciones de **FastAPI** independientes, cada una con su propio OpenAPI independiente y su propia interfaz de docs, puedes tener una aplicación principal y "montar" una (o más) sub-aplicación(es).
## Montar una aplicación **FastAPI**
"Montar" significa añadir una aplicación completamente "independiente" en un path específico, que luego se encarga de manejar todo bajo ese path, con las _path operations_ declaradas en esa sub-aplicación.
### Aplicación de nivel superior
Primero, crea la aplicación principal de nivel superior de **FastAPI**, y sus *path operations*:
{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *}
### Sub-aplicación
Luego, crea tu sub-aplicación, y sus *path operations*.
Esta sub-aplicación es solo otra aplicación estándar de FastAPI, pero es la que se "montará":
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *}
### Montar la sub-aplicación
En tu aplicación de nivel superior, `app`, monta la sub-aplicación, `subapi`.
En este caso, se montará en el path `/subapi`:
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *}
### Revisa la documentación automática de la API
Ahora, ejecuta el comando `fastapi` con tu archivo:
<div class="termy">
```console
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Y abre la documentación en <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Verás la documentación automática de la API para la aplicación principal, incluyendo solo sus propias _path operations_:
<img src="/img/tutorial/sub-applications/image01.png">
Y luego, abre la documentación para la sub-aplicación, en <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>.
Verás la documentación automática de la API para la sub-aplicación, incluyendo solo sus propias _path operations_, todas bajo el prefijo correcto del sub-path `/subapi`:
<img src="/img/tutorial/sub-applications/image02.png">
Si intentas interactuar con cualquiera de las dos interfaces de usuario, funcionarán correctamente, porque el navegador podrá comunicarse con cada aplicación o sub-aplicación específica.
### Detalles Técnicos: `root_path`
Cuando montas una sub-aplicación como se describe arriba, FastAPI se encargará de comunicar el path de montaje para la sub-aplicación usando un mecanismo de la especificación ASGI llamado `root_path`.
De esa manera, la sub-aplicación sabrá usar ese prefijo de path para la interfaz de documentación.
Y la sub-aplicación también podría tener sus propias sub-aplicaciones montadas y todo funcionaría correctamente, porque FastAPI maneja todos estos `root_path`s automáticamente.
Aprenderás más sobre el `root_path` y cómo usarlo explícitamente en la sección sobre [Detrás de un Proxy](behind-a-proxy.md){.internal-link target=_blank}.
+126
View File
@@ -0,0 +1,126 @@
# Plantillas
Puedes usar cualquier motor de plantillas que desees con **FastAPI**.
Una elección común es Jinja2, el mismo que usa Flask y otras herramientas.
Hay utilidades para configurarlo fácilmente que puedes usar directamente en tu aplicación de **FastAPI** (proporcionadas por Starlette).
## Instalar dependencias
Asegúrate de crear un [entorno virtual](../virtual-environments.md){.internal-link target=_blank}, activarlo e instalar `jinja2`:
<div class="termy">
```console
$ pip install jinja2
---> 100%
```
</div>
## Usando `Jinja2Templates`
* Importa `Jinja2Templates`.
* Crea un objeto `templates` que puedas reutilizar más tarde.
* Declara un parámetro `Request` en la *path operation* que devolverá una plantilla.
* Usa los `templates` que creaste para renderizar y devolver un `TemplateResponse`, pasa el nombre de la plantilla, el objeto de request, y un diccionario "context" con pares clave-valor que se usarán dentro de la plantilla Jinja2.
{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *}
/// note | Nota
Antes de FastAPI 0.108.0, Starlette 0.29.0, el `name` era el primer parámetro.
Además, antes de eso, en versiones anteriores, el objeto `request` se pasaba como parte de los pares clave-valor en el contexto para Jinja2.
///
/// tip | Consejo
Al declarar `response_class=HTMLResponse`, la interfaz de usuario de la documentación podrá saber que el response será HTML.
///
/// note | Nota Técnica
También podrías usar `from starlette.templating import Jinja2Templates`.
**FastAPI** proporciona el mismo `starlette.templating` como `fastapi.templating`, solo como una conveniencia para ti, el desarrollador. Pero la mayoría de los responses disponibles vienen directamente de Starlette. Lo mismo con `Request` y `StaticFiles`.
///
## Escribiendo plantillas
Luego puedes escribir una plantilla en `templates/item.html` con, por ejemplo:
```jinja hl_lines="7"
{!../../docs_src/templates/templates/item.html!}
```
### Valores de Contexto de la Plantilla
En el HTML que contiene:
{% raw %}
```jinja
Item ID: {{ id }}
```
{% endraw %}
...mostrará el `id` tomado del `dict` de "contexto" que pasaste:
```Python
{"id": id}
```
Por ejemplo, con un ID de `42`, esto se renderizaría como:
```html
Item ID: 42
```
### Argumentos de la Plantilla `url_for`
También puedes usar `url_for()` dentro de la plantilla, toma como argumentos los mismos que usaría tu *path operation function*.
Entonces, la sección con:
{% raw %}
```jinja
<a href="{{ url_for('read_item', id=id) }}">
```
{% endraw %}
...generará un enlace hacia la misma URL que manejaría la *path operation function* `read_item(id=id)`.
Por ejemplo, con un ID de `42`, esto se renderizaría como:
```html
<a href="/items/42">
```
## Plantillas y archivos estáticos
También puedes usar `url_for()` dentro de la plantilla, y usarlo, por ejemplo, con los `StaticFiles` que montaste con el `name="static"`.
```jinja hl_lines="4"
{!../../docs_src/templates/templates/item.html!}
```
En este ejemplo, enlazaría a un archivo CSS en `static/styles.css` con:
```CSS hl_lines="4"
{!../../docs_src/templates/static/styles.css!}
```
Y porque estás usando `StaticFiles`, ese archivo CSS sería servido automáticamente por tu aplicación de **FastAPI** en la URL `/static/styles.css`.
## Más detalles
Para más detalles, incluyendo cómo testear plantillas, revisa <a href="https://www.starlette.dev/templates/" class="external-link" target="_blank">la documentación de Starlette sobre plantillas</a>.
@@ -0,0 +1,53 @@
# Probando Dependencias con Overrides
## Sobrescribir dependencias durante las pruebas
Hay algunos escenarios donde podrías querer sobrescribir una dependencia durante las pruebas.
No quieres que la dependencia original se ejecute (ni ninguna de las sub-dependencias que pueda tener).
En cambio, quieres proporcionar una dependencia diferente que se usará solo durante las pruebas (posiblemente solo algunas pruebas específicas), y que proporcionará un valor que pueda ser usado donde se usó el valor de la dependencia original.
### Casos de uso: servicio externo
Un ejemplo podría ser que tienes un proveedor de autenticación externo al que necesitas llamar.
Le envías un token y te devuelve un usuario autenticado.
Este proveedor podría estar cobrándote por cada request, y llamarlo podría tomar más tiempo adicional que si tuvieras un usuario de prueba fijo para los tests.
Probablemente quieras probar el proveedor externo una vez, pero no necesariamente llamarlo para cada test que se realice.
En este caso, puedes sobrescribir la dependencia que llama a ese proveedor y usar una dependencia personalizada que devuelva un usuario de prueba, solo para tus tests.
### Usa el atributo `app.dependency_overrides`
Para estos casos, tu aplicación **FastAPI** tiene un atributo `app.dependency_overrides`, es un simple `dict`.
Para sobrescribir una dependencia para las pruebas, colocas como clave la dependencia original (una función), y como valor, tu dependencia para sobreescribir (otra función).
Y entonces **FastAPI** llamará a esa dependencia para sobreescribir en lugar de la dependencia original.
{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *}
/// tip | Consejo
Puedes sobreescribir una dependencia utilizada en cualquier lugar de tu aplicación **FastAPI**.
La dependencia original podría ser utilizada en una *path operation function*, un *path operation decorator* (cuando no usas el valor de retorno), una llamada a `.include_router()`, etc.
FastAPI todavía podrá sobrescribirla.
///
Entonces puedes restablecer las dependencias sobreescritas configurando `app.dependency_overrides` para que sea un `dict` vacío:
```Python
app.dependency_overrides = {}
```
/// tip | Consejo
Si quieres sobrescribir una dependencia solo durante algunos tests, puedes establecer la sobrescritura al inicio del test (dentro de la función del test) y restablecerla al final (al final de la función del test).
///
+5
View File
@@ -0,0 +1,5 @@
# Testing Events: startup - shutdown
Cuando necesitas que tus manejadores de eventos (`startup` y `shutdown`) se ejecuten en tus tests, puedes usar el `TestClient` con un statement `with`:
{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *}
@@ -0,0 +1,13 @@
# Probando WebSockets
Puedes usar el mismo `TestClient` para probar WebSockets.
Para esto, usas el `TestClient` en un statement `with`, conectándote al WebSocket:
{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *}
/// note | Nota
Para más detalles, revisa la documentación de Starlette sobre <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">probando sesiones WebSocket</a>.
///
@@ -0,0 +1,56 @@
# Usar el Request Directamente
Hasta ahora, has estado declarando las partes del request que necesitas con sus tipos.
Tomando datos de:
* El path como parámetros.
* Headers.
* Cookies.
* etc.
Y al hacerlo, **FastAPI** está validando esos datos, convirtiéndolos y generando documentación para tu API automáticamente.
Pero hay situaciones donde podrías necesitar acceder al objeto `Request` directamente.
## Detalles sobre el objeto `Request`
Como **FastAPI** es en realidad **Starlette** por debajo, con una capa de varias herramientas encima, puedes usar el objeto <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">`Request`</a> de Starlette directamente cuando lo necesites.
También significa que si obtienes datos del objeto `Request` directamente (por ejemplo, leyendo el cuerpo) no serán validados, convertidos o documentados (con OpenAPI, para la interfaz automática de usuario de la API) por FastAPI.
Aunque cualquier otro parámetro declarado normalmente (por ejemplo, el cuerpo con un modelo de Pydantic) seguiría siendo validado, convertido, anotado, etc.
Pero hay casos específicos donde es útil obtener el objeto `Request`.
## Usa el objeto `Request` directamente
Imaginemos que quieres obtener la dirección IP/host del cliente dentro de tu *path operation function*.
Para eso necesitas acceder al request directamente.
{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *}
Al declarar un parámetro de *path operation function* con el tipo siendo `Request`, **FastAPI** sabrá pasar el `Request` en ese parámetro.
/// tip | Consejo
Nota que en este caso, estamos declarando un parámetro de path además del parámetro del request.
Así que, el parámetro de path será extraído, validado, convertido al tipo especificado y anotado con OpenAPI.
De la misma manera, puedes declarar cualquier otro parámetro como normalmente, y adicionalmente, obtener también el `Request`.
///
## Documentación de `Request`
Puedes leer más detalles sobre el <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">objeto `Request` en el sitio de documentación oficial de Starlette</a>.
/// note | Detalles Técnicos
Podrías también usar `from starlette.requests import Request`.
**FastAPI** lo proporciona directamente solo como conveniencia para ti, el desarrollador. Pero viene directamente de Starlette.
///
+186
View File
@@ -0,0 +1,186 @@
# WebSockets
Puedes usar <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">WebSockets</a> con **FastAPI**.
## Instalar `WebSockets`
Asegúrate de crear un [entorno virtual](../virtual-environments.md){.internal-link target=_blank}, activarlo e instalar `websockets`:
<div class="termy">
```console
$ pip install websockets
---> 100%
```
</div>
## Cliente WebSockets
### En producción
En tu sistema de producción, probablemente tengas un frontend creado con un framework moderno como React, Vue.js o Angular.
Y para comunicarte usando WebSockets con tu backend probablemente usarías las utilidades de tu frontend.
O podrías tener una aplicación móvil nativa que se comunica con tu backend de WebSocket directamente, en código nativo.
O podrías tener alguna otra forma de comunicarte con el endpoint de WebSocket.
---
Pero para este ejemplo, usaremos un documento HTML muy simple con algo de JavaScript, todo dentro de un string largo.
Esto, por supuesto, no es lo ideal y no lo usarías para producción.
En producción tendrías una de las opciones anteriores.
Pero es la forma más sencilla de enfocarse en el lado del servidor de WebSockets y tener un ejemplo funcional:
{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *}
## Crear un `websocket`
En tu aplicación de **FastAPI**, crea un `websocket`:
{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *}
/// note | Detalles Técnicos
También podrías usar `from starlette.websockets import WebSocket`.
**FastAPI** proporciona el mismo `WebSocket` directamente solo como una conveniencia para ti, el desarrollador. Pero viene directamente de Starlette.
///
## Esperar mensajes y enviar mensajes
En tu ruta de WebSocket puedes `await` para recibir mensajes y enviar mensajes.
{* ../../docs_src/websockets/tutorial001.py hl[48:52] *}
Puedes recibir y enviar datos binarios, de texto y JSON.
## Pruébalo
Si tu archivo se llama `main.py`, ejecuta tu aplicación con:
<div class="termy">
```console
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Abre tu navegador en <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
Verás una página simple como:
<img src="/img/tutorial/websockets/image01.png">
Puedes escribir mensajes en el cuadro de entrada y enviarlos:
<img src="/img/tutorial/websockets/image02.png">
Y tu aplicación **FastAPI** con WebSockets responderá de vuelta:
<img src="/img/tutorial/websockets/image03.png">
Puedes enviar (y recibir) muchos mensajes:
<img src="/img/tutorial/websockets/image04.png">
Y todos usarán la misma conexión WebSocket.
## Usando `Depends` y otros
En endpoints de WebSocket puedes importar desde `fastapi` y usar:
* `Depends`
* `Security`
* `Cookie`
* `Header`
* `Path`
* `Query`
Funcionan de la misma manera que para otros endpoints de FastAPI/*path operations*:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *}
/// info | Información
Como esto es un WebSocket no tiene mucho sentido lanzar un `HTTPException`, en su lugar lanzamos un `WebSocketException`.
Puedes usar un código de cierre de los <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">códigos válidos definidos en la especificación</a>.
///
### Prueba los WebSockets con dependencias
Si tu archivo se llama `main.py`, ejecuta tu aplicación con:
<div class="termy">
```console
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Abre tu navegador en <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
Ahí puedes establecer:
* El "ID del Ítem", usado en el path.
* El "Token" usado como un parámetro query.
/// tip | Consejo
Nota que el query `token` será manejado por una dependencia.
///
Con eso puedes conectar el WebSocket y luego enviar y recibir mensajes:
<img src="/img/tutorial/websockets/image05.png">
## Manejar desconexiones y múltiples clientes
Cuando una conexión de WebSocket se cierra, el `await websocket.receive_text()` lanzará una excepción `WebSocketDisconnect`, que puedes capturar y manejar como en este ejemplo.
{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *}
Para probarlo:
* Abre la aplicación con varias pestañas del navegador.
* Escribe mensajes desde ellas.
* Luego cierra una de las pestañas.
Eso lanzará la excepción `WebSocketDisconnect`, y todos los otros clientes recibirán un mensaje como:
```
Client #1596980209979 left the chat
```
/// tip | Consejo
La aplicación anterior es un ejemplo mínimo y simple para demostrar cómo manejar y transmitir mensajes a varias conexiones WebSocket.
Pero ten en cuenta que, como todo se maneja en memoria, en una sola lista, solo funcionará mientras el proceso esté en ejecución, y solo funcionará con un solo proceso.
Si necesitas algo fácil de integrar con FastAPI pero que sea más robusto, soportado por Redis, PostgreSQL u otros, revisa <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>.
///
## Más información
Para aprender más sobre las opciones, revisa la documentación de Starlette para:
* <a href="https://www.starlette.dev/websockets/" class="external-link" target="_blank">La clase `WebSocket`</a>.
* <a href="https://www.starlette.dev/endpoints/#websocketendpoint" class="external-link" target="_blank">Manejo de WebSocket basado en clases</a>.
+35
View File
@@ -0,0 +1,35 @@
# Incluyendo WSGI - Flask, Django, otros
Puedes montar aplicaciones WSGI como viste con [Sub Aplicaciones - Mounts](sub-applications.md){.internal-link target=_blank}, [Detrás de un Proxy](behind-a-proxy.md){.internal-link target=_blank}.
Para eso, puedes usar `WSGIMiddleware` y usarlo para envolver tu aplicación WSGI, por ejemplo, Flask, Django, etc.
## Usando `WSGIMiddleware`
Necesitas importar `WSGIMiddleware`.
Luego envuelve la aplicación WSGI (p. ej., Flask) con el middleware.
Y luego móntala bajo un path.
{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *}
## Revisa
Ahora, cada request bajo el path `/v1/` será manejado por la aplicación Flask.
Y el resto será manejado por **FastAPI**.
Si lo ejecutas y vas a <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a> verás el response de Flask:
```txt
Hello, World from Flask!
```
Y si vas a <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a> verás el response de FastAPI:
```JSON
{
"message": "Hello World"
}
```