Parámetros de Path¶
Puedes declarar "parámetros" o "variables" de path con la misma sintaxis que se usa en los format strings de Python:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id):
return {"item_id": item_id}
El valor del parámetro de path item_id
se pasará a tu función como el argumento item_id
.
Así que, si ejecutas este ejemplo y vas a http://127.0.0.1:8000/items/foo, verás un response de:
{"item_id":"foo"}
Parámetros de path con tipos¶
Puedes declarar el tipo de un parámetro de path en la función, usando anotaciones de tipos estándar de Python:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
En este caso, item_id
se declara como un int
.
Revisa
Esto te dará soporte del editor dentro de tu función, con chequeo de errores, autocompletado, etc.
Conversión de datos¶
Si ejecutas este ejemplo y abres tu navegador en http://127.0.0.1:8000/items/3, verás un response de:
{"item_id":3}
Revisa
Nota que el valor que tu función recibió (y devolvió) es 3
, como un int
de Python, no un string "3"
.
Entonces, con esa declaración de tipo, FastAPI te ofrece "parsing" automático de requests.
Validación de datos¶
Pero si vas al navegador en http://127.0.0.1:8000/items/foo, verás un bonito error HTTP de:
{
"detail": [
{
"type": "int_parsing",
"loc": [
"path",
"item_id"
],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
"url": "https://errors.pydantic.dev/2.1/v/int_parsing"
}
]
}
porque el parámetro de path item_id
tenía un valor de "foo"
, que no es un int
.
El mismo error aparecería si proporcionaras un float
en lugar de un int
, como en: http://127.0.0.1:8000/items/4.2
Revisa
Entonces, con la misma declaración de tipo de Python, FastAPI te ofrece validación de datos.
Nota que el error también indica claramente el punto exacto donde la validación falló.
Esto es increíblemente útil mientras desarrollas y depuras código que interactúa con tu API.
Documentación¶
Y cuando abras tu navegador en http://127.0.0.1:8000/docs, verás una documentación de API automática e interactiva como:
Revisa
Nuevamente, solo con esa misma declaración de tipo de Python, FastAPI te ofrece documentación automática e interactiva (integrando Swagger UI).
Nota que el parámetro de path está declarado como un entero.
Beneficios basados en estándares, documentación alternativa¶
Y porque el esquema generado es del estándar OpenAPI, hay muchas herramientas compatibles.
Debido a esto, el propio FastAPI proporciona una documentación de API alternativa (usando ReDoc), a la cual puedes acceder en http://127.0.0.1:8000/redoc:
De la misma manera, hay muchas herramientas compatibles. Incluyendo herramientas de generación de código para muchos lenguajes.
Pydantic¶
Toda la validación de datos se realiza internamente con Pydantic, así que obtienes todos los beneficios de esta. Y sabes que estás en buenas manos.
Puedes usar las mismas declaraciones de tipo con str
, float
, bool
y muchos otros tipos de datos complejos.
Varios de estos se exploran en los siguientes capítulos del tutorial.
El orden importa¶
Al crear path operations, puedes encontrarte en situaciones donde tienes un path fijo.
Como /users/me
, imaginemos que es para obtener datos sobre el usuario actual.
Y luego también puedes tener un path /users/{user_id}
para obtener datos sobre un usuario específico por algún ID de usuario.
Debido a que las path operations se evalúan en orden, necesitas asegurarte de que el path para /users/me
se declara antes que el de /users/{user_id}
:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/me")
async def read_user_me():
return {"user_id": "the current user"}
@app.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}
De lo contrario, el path para /users/{user_id}
también coincidiría para /users/me
, "pensando" que está recibiendo un parámetro user_id
con un valor de "me"
.
De manera similar, no puedes redefinir una path operation:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users")
async def read_users():
return ["Rick", "Morty"]
@app.get("/users")
async def read_users2():
return ["Bean", "Elfo"]
La primera siempre será utilizada ya que el path coincide primero.
Valores predefinidos¶
Si tienes una path operation que recibe un path parameter, pero quieres que los valores posibles válidos del path parameter estén predefinidos, puedes usar un Enum
estándar de Python.
Crear una clase Enum
¶
Importa Enum
y crea una subclase que herede de str
y de Enum
.
Al heredar de str
, la documentación de la API podrá saber que los valores deben ser de tipo string
y podrá representarlos correctamente.
Luego crea atributos de clase con valores fijos, que serán los valores válidos disponibles:
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
return {"model_name": model_name, "message": "Have some residuals"}
Información
Las enumeraciones (o enums) están disponibles en Python desde la versión 3.4.
Consejo
Si te estás preguntando, "AlexNet", "ResNet" y "LeNet" son solo nombres de modelos de Machine Learning.
Declarar un path parameter¶
Luego crea un path parameter con una anotación de tipo usando la clase enum que creaste (ModelName
):
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
return {"model_name": model_name, "message": "Have some residuals"}
Revisa la documentación¶
Como los valores disponibles para el path parameter están predefinidos, la documentación interactiva puede mostrarlos de manera ordenada:
Trabajando con enumeraciones de Python¶
El valor del path parameter será un miembro de enumeración.
Comparar miembros de enumeraciones¶
Puedes compararlo con el miembro de enumeración en tu enum creada ModelName
:
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
return {"model_name": model_name, "message": "Have some residuals"}
Obtener el valor de enumeración¶
Puedes obtener el valor actual (un str
en este caso) usando model_name.value
, o en general, your_enum_member.value
:
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
return {"model_name": model_name, "message": "Have some residuals"}
Consejo
También podrías acceder al valor "lenet"
con ModelName.lenet.value
.
Devolver miembros de enumeración¶
Puedes devolver miembros de enum desde tu path operation, incluso anidados en un cuerpo JSON (por ejemplo, un dict
).
Serán convertidos a sus valores correspondientes (cadenas en este caso) antes de devolverlos al cliente:
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
return {"model_name": model_name, "message": "Have some residuals"}
En tu cliente recibirás un response JSON como:
{
"model_name": "alexnet",
"message": "Deep Learning FTW!"
}
Parámetros de path conteniendo paths¶
Imaginemos que tienes una path operation con un path /files/{file_path}
.
Pero necesitas que file_path
en sí mismo contenga un path, como home/johndoe/myfile.txt
.
Entonces, la URL para ese archivo sería algo como: /files/home/johndoe/myfile.txt
.
Soporte de OpenAPI¶
OpenAPI no soporta una manera de declarar un path parameter para que contenga un path dentro, ya que eso podría llevar a escenarios que son difíciles de probar y definir.
Sin embargo, todavía puedes hacerlo en FastAPI, usando una de las herramientas internas de Starlette.
Y la documentación seguiría funcionando, aunque no agregue ninguna documentación indicando que el parámetro debe contener un path.
Convertidor de Path¶
Usando una opción directamente de Starlette puedes declarar un path parameter conteniendo un path usando una URL como:
/files/{file_path:path}
En este caso, el nombre del parámetro es file_path
, y la última parte, :path
, indica que el parámetro debería coincidir con cualquier path.
Así que, puedes usarlo con:
from fastapi import FastAPI
app = FastAPI()
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {"file_path": file_path}
Consejo
Podrías necesitar que el parámetro contenga /home/johndoe/myfile.txt
, con una barra inclinada (/
) inicial.
En ese caso, la URL sería: /files//home/johndoe/myfile.txt
, con una doble barra inclinada (//
) entre files
y home
.
Resumen¶
Con FastAPI, al usar declaraciones de tipo estándar de Python, cortas e intuitivas, obtienes:
- Soporte del editor: chequeo de errores, autocompletado, etc.
- "parsing" de datos
- Validación de datos
- Anotación de API y documentación automática
Y solo tienes que declararlos una vez.
Probablemente esa sea la principal ventaja visible de FastAPI en comparación con otros frameworks alternativos (aparte del rendimiento bruto).