Parámetros de path¶
Puedes declarar los "parámetros" o "variables" con la misma sintaxis que usan 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
será pasado a tu función como el argumento item_id
.
Entonces, si corres este ejemplo y vas a http://127.0.0.1:8000/items/foo, verás una respuesta 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 las 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
es declarado como un int
.
Revisa
Esto te dará soporte en el editor dentro de tu función, con chequeos de errores, autocompletado, etc.
Conversión de datos¶
Si corres este ejemplo y abres tu navegador en http://127.0.0.1:8000/items/3 verás una respuesta de:
{"item_id":3}
Revisa
Observa que el valor que recibió (y devolvió) tu función es 3
, como un Python int
, y no un string "3"
.
Entonces, con esa declaración de tipos FastAPI te da "parsing" automático del request.
Validación de datos¶
Pero si abres tu navegador en http://127.0.0.1:8000/items/foo verás este lindo error de HTTP:
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
debido a que el parámetro de path item_id
tenía el valor "foo"
, que no es un int
.
El mismo error aparecería si pasaras un float
en vez de un int
como en: http://127.0.0.1:8000/items/4.2
Revisa
Así, con la misma declaración de tipo de Python, FastAPI te da validación de datos.
Observa que el error también muestra claramente el punto exacto en el que no pasó la validación.
Esto es increíblemente útil cuando estás desarrollando y debugging código que interactúa con tu API.
Documentación¶
Cuando abras tu navegador en http://127.0.0.1:8000/docs verás la documentación automática e interactiva del API como:
Revisa
Nuevamente, con la misma declaración de tipo de Python, FastAPI te da documentación automática e interactiva (integrándose con Swagger UI)
Observa que el parámetro de path está declarado como un integer.
Beneficios basados en estándares, documentación alternativa¶
Debido a que el schema generado es del estándar OpenAPI hay muchas herramientas compatibles.
Es por esto que FastAPI mismo provee una documentación alternativa de la API (usando ReDoc), a la que 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 es realizada tras bastidores por Pydantic, así que obtienes todos sus beneficios. Así sabes que estás en buenas manos.
Puedes usar las mismas declaraciones de tipos con str
, float
, bool
y otros tipos de datos más complejos.
Exploraremos varios de estos tipos en los próximos capítulos del tutorial.
El orden importa¶
Cuando creas operaciones de path puedes encontrarte con situaciones en las que tengas un path fijo.
Digamos algo como /users/me
que sea para obtener datos del usuario actual.
... y luego puedes tener el path /users/{user_id}
para obtener los datos sobre un usuario específico asociados a un ID de usuario.
Porque las operaciones de path son evaluadas en orden, tienes que asegurarte de que el path para /users/me
sea declarado antes que el path para /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 otra manera el path para /users/{user_id}
coincidiría también con /users/me
"pensando" que está recibiendo el parámetro user_id
con el valor "me"
.
Valores predefinidos¶
Si tienes una operación de path que recibe un parámetro de path pero quieres que los valores posibles del parámetro de path sean predefinidos puedes usar un Enum
estándar de Python.
Crea una clase Enum
¶
Importa Enum
y crea una sub-clase que herede desde str
y desde Enum
.
Al heredar desde str
la documentación de la API podrá saber que los valores deben ser de tipo string
y podrá mostrarlos correctamente.
Luego crea atributos de clase con valores fijos, que serán los valores disponibles válidos:
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 Enumerations (o enums) están disponibles en Python desde la versión 3.4.
Consejo
Si lo estás dudando, "AlexNet", "ResNet", y "LeNet" son solo nombres de modelos de Machine Learning.
Declara un parámetro de path¶
Luego, crea un parámetro de path con anotaciones de tipos 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¶
Debido a que los valores disponibles para el parámetro de path están predefinidos, la documentación interactiva los puede mostrar bien:
Trabajando con los enumerations de Python¶
El valor del parámetro de path será un enumeration member.
Compara enumeration members¶
Puedes compararlo con el enumeration member en el enum (ModelName
) que creaste:
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"}
Obtén el enumeration value¶
Puedes obtener el valor exacto (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 obtener el valor "lenet"
con ModelName.lenet.value
.
Devuelve enumeration members¶
Puedes devolver enum members desde tu operación de path inclusive en un body de JSON anidado (por ejemplo, un dict
).
Ellos serán convertidos a sus valores correspondientes (strings 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 obtendrás una respuesta en JSON como:
{
"model_name": "alexnet",
"message": "Deep Learning FTW!"
}
Parámetros de path parameters que contienen paths¶
Digamos que tienes una operación de path con un path /files/{file_path}
.
Pero necesitas que el mismo file_path
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 parámetro de path que contenga un path, dado que esto podría llevar a escenarios que son difíciles de probar y definir.
Sin embargo, lo puedes hacer en FastAPI usando una de las herramientas internas de Starlette.
La documentación seguirá funcionando, aunque no añadirá ninguna información diciendo que el parámetro debería contener un path.
Convertidor de path¶
Usando una opción directamente desde Starlette puedes declarar un parámetro de path que contenga 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
, le dice que el parámetro debería coincidir con cualquier path.
Entonces lo puedes usar 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 un slash inicial (/
).
En este caso la URL sería /files//home/johndoe/myfile.txt
con un slash doble (//
) entre files
y home
.
Repaso¶
Con FastAPI, usando declaraciones de tipo de Python intuitivas y estándares, obtienes:
- Soporte en el editor: chequeos de errores, auto-completado, etc.
- "Parsing" de datos
- Validación de datos
- Anotación de la API y documentación automática
Solo tienes que declararlos una vez.
Esa es probablemente la principal ventaja visible de FastAPI sobre otros frameworks alternativos (aparte del rendimiento puro).