Saltar al contenido

Parámetros de Ruta

Puedes declarar "parámetros" o "variables" de ruta con la misma sintaxis que usan las cadenas de formato 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 ruta 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 una respuesta de:

{"item_id":"foo"}

Parámetros de ruta con tipos

Puedes declarar el tipo de un parámetro de ruta en la función, usando las anotaciones de tipo 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 está declarado como un int.

Consejo

Esto te dará soporte en el editor dentro de tu función, con verificación 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 una respuesta de:

{"item_id":3}

Consejo

Notarás que el valor que tu función recibió (y devolvió) es 3, como un int de Python, no como una cadena "3".

Así que, con esa declaración de tipo, FastAPI te da "análisis" automático de la petición.

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"
    }
  ]
}

porque el parámetro de ruta 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

Consejo

Así que, con la misma declaración de tipo de Python, FastAPI te da validación de datos.

Notarás que el error también indica claramente el punto exacto donde no pasó la validación.

Esto es increíblemente útil al desarrollar y depurar 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 automática e interactiva de la API como:

Consejo

De nuevo, solo con esa misma declaración de tipo de Python, FastAPI te da documentación automática e interactiva (integrando Swagger UI).

Notarás que el parámetro de ruta está declarado como un entero.

Beneficios basados en estándares, documentación alternativa

Y como el esquema generado es del estándar OpenAPI, hay muchas herramientas compatibles.

Por esto, FastAPI mismo proporciona 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 se realiza por debajo por Pydantic, así que obtienes todos los beneficios de esto. 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.

Varias de estas se exploran en los próximos capítulos del tutorial.

El orden importa

Al crear operaciones de ruta, puedes encontrarte con situaciones donde tienes una ruta fija.

Como /users/me, digamos que es para obtener datos sobre el usuario actual.

Y luego también puedes tener una ruta /users/{user_id} para obtener datos sobre un usuario específico por algún ID de usuario.

Como las operaciones de ruta se evalúan en orden, necesitas asegurarte de que la ruta para /users/me se declare antes que la 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, la ruta para /users/{user_id} también coincidiría con /users/me, "pensando" que está recibiendo un parámetro user_id con un valor de "me".

De manera similar, no puedes redefinir una operación de ruta:

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 se usará ya que la ruta coincide primero.

Valores predefinidos

Si tienes una operación de ruta que recibe un parámetro de ruta, pero quieres que los posibles valores válidos del parámetro de ruta 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á renderizarse 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"}

Consejo

Si te lo preguntas, "AlexNet", "ResNet", y "LeNet" son solo nombres de modelos de Machine Learning.

Declarar un parámetro de ruta

Luego crea un parámetro de ruta 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"}

Revisar la documentación

Como los valores disponibles para el parámetro de ruta están predefinidos, la documentación interactiva puede mostrarlos bien:

Trabajando con enumeraciones de Python

El valor del parámetro de ruta será un miembro de enumeración.

Comparar miembros de enumeración

Puedes compararlo con el miembro de enumeración en tu enum creado 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 real (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 operación de ruta, incluso anidados en un cuerpo JSON (por ejemplo, un dict).

Se convertirán 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 obtendrás una respuesta JSON como:

{
  "model_name": "alexnet",
  "message": "Deep Learning FTW!"
}

Parámetros de ruta que contienen rutas

Digamos que tienes una operación de ruta con una ruta /files/{file_path}.

Pero necesitas que file_path en sí mismo contenga una ruta, como home/johndoe/myfile.txt.

Así que, la URL para ese archivo sería algo como: /files/home/johndoe/myfile.txt.

Soporte de OpenAPI

OpenAPI no soporta una forma de declarar un parámetro de ruta que contenga una ruta dentro, ya que eso podría llevar a escenarios difíciles de probar y definir.

Sin embargo, aún puedes hacerlo en FastAPI, usando una de las herramientas internas de Starlette.

Y la documentación seguiría funcionando, aunque sin añadir ninguna documentación que indique que el parámetro debe contener una ruta.

Convertidor de ruta

Usando una opción directamente de Starlette puedes declarar un parámetro de ruta que contenga una ruta 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 indica que el parámetro debe coincidir con cualquier ruta.

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 inicial (/).

En ese caso, la URL sería: /files//home/johndoe/myfile.txt, con una doble barra (//) entre files y home.

Resumen

Con FastAPI, al usar declaraciones de tipo cortas, intuitivas y estándar de Python, obtienes:

  • Soporte del editor: verificación de errores, autocompletado, etc.
  • "Análisis" de datos
  • Validación de datos
  • Anotaciones de API y documentación automática

Y solo tienes que declararlos una vez.

Esa es probablemente la principal ventaja visible de FastAPI comparada con frameworks alternativos (aparte del rendimiento bruto).