logo cosasdedevs
Cómo utilizar Form de FastAPI para recuperar parámetros enviados por formulario

Cómo utilizar Form de FastAPI para recuperar parámetros enviados por formulario



My Profile
Ene 26, 2022

👋 ¡Hola! Como ya comenté, sigo trabajando en la ruta de aprendizaje que estoy construyendo para FastAPI así que esta semana vamos a ver cositas nuevas 😊. En este tutorial me quiero enfocar en aprender como recibir los parámetros de un formulario con FastAPI así que vamos al lío 👇.

Para ver este ejemplo me voy a crear un proyecto muy sencillo en el que necesitaremos instalar FastAPI, uvicorn, python-multipart y requests. La librería python-multipart la necesitaremos para poder usar la función Form de FastAPI y requests la utilizaremos para realizar peticiones a nuestra API. Como siempre voy a crear un entorno virtual y lo voy a activar:

python -m venv env
# Activación windows
env/Scrips/activate
# Activación en mac/linux
source env/bin/activate

Una vez creado, instalamos los paquetes necesarios:

pip install fastapi uvicorn python-multipart requests

Recibir la información de un formulario con FastAPI

Ahora que ya tenemos listo el proyecto, creamos el archivo main.py y añadimos el siguiente contenido:

from fastapi import FastAPI
from fastapi import Form

app = FastAPI()

@app.post("/contact")
async def contact(subject: str = Form(...), msg: str = Form(...)):
    return {
        "subject": subject,
        "message": msg
    }

En este archivo he generado un endpoint de ejemplo para recibir un formulario de contacto. Para poder capturar los parámetros que vienen de un formulario, necesitamos la función Form de FastAPI así que la importamos.

En la función contact he añadido dos parámetros, subject y msg los cuales serán de tipo string e igual a Form para indicar que son campos que vienen de un formulario, si quisiéramos añadir más parámetros de tipo Form podríamos hacerlo sin problema.

Recordad que los ... (tres puntos) significan que son campos obligatorios.

Por último, para comprobar que todo funciona correctamente, solo necesitamos levantar el servicio con uvicorn y probarlo:

uvicorn main:app --reload

Para ejecutar esta prueba podríamos hacerlo directamente desde la documentación que genera FastAPI en la ruta /docs o como lo voy a hacer yo que es empleando la librería requests de Python. Para ello me he creado un archivo llamado send_form.py con el siguiente contenido:

import requests

url = 'http://127.0.0.1:8000/contact'

headers = {
    'Content-type': 'application/x-www-form-urlencoded'
}

params = {
    'subject': 'Asunto del formulario',
    'msg': 'Mensaje del formulario',
}

r = requests.post(url, headers=headers, data=params)

print(f'Status http: {r.status_code}')

print(f'Response: {r.json()}')

En este ejemplo indicamos la url a la que queremos atacar y después declaramos las cabeceras que queremos enviar junto con la petición. Recordad que siempre que enviéis un formulario, debéis de añadir la cabecera "content-type: application/x-www-form-urlencoded" para que funcione correctamente.

Posteriormente, declaramos los parámetros dentro de un diccionario y por último lanzamos la petición que será de tipo post con la url, las cabeceras y los parámetros.

Si todo ha ido bien, al lanzar nuestro script por la terminal deberíamos ver esto:

Status http: 200
Response: {'subject': 'Asunto del formulario', 'message': 'Mensaje del formulario'}

Recibir archivos por formulario con FastAPI

Ahora vamos a complicarlo un poco. En el caso de querer recibir archivos debemos añadir algunos cambios así que vamos a crear un nuevo endpoint que por ejemplo se va a encargar de crear un usuario, este recibirá el username, password y un archivo que será una foto. Para ello volvemos al archivo main.py y añadimos los siguientes cambios:

from fastapi import FastAPI, HTTPException, status
from fastapi import Form, File, UploadFile

app = FastAPI()

@app.post("/contact")
async def contact(subject: str = Form(...), msg: str = Form(...)):
    return {
        "subject": subject,
        "message": msg
    }

@app.post("/create_user")
async def create_user(username: str = Form(...), password: str = Form(...), photo: UploadFile = File(...)):

    if photo.content_type != 'image/jpeg':
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail='Only valid images with format jpg'
        )

    return {
        "username": username,
        "password": password,
        "photo": {
            'filename': photo.filename,
            'content_type': photo.content_type
        }
    }

Los dos nuevos imports en los que os debéis fijar son File y UploadFile. El primero hereda directamente de Form, pero en este caso lo usamos para indicar que vamos a recibir un archivo. El segundo lo utilizaremos para tipar el parámetro que contendrá la foto. También lo podemos tipar como bytes tal y como podemos ver en la documentación, pero empleando UploadFile podremos obtener información adicional para facilitarnos el trabajo.

El siguiente punto importante es ya la función create_user. El parámetro username y password son de tipo string e igual a Form y por último tenemos el campo photo el cual será de tipo UploadFile e igual a File.

Después y gracias a tipar la foto como UploadFile, podemos obtener el content_type el cual me ha servido para añadir un control que lanzará una excepción si la imagen no es de tipo jpg.

Por último, retorno la información recibida para que podamos comprobar que recibimos la información correctamente. Aquí podéis observar que también podemos obtener el nombre del archivo enviado gracias a filename.

Guardar archivos enviados desde un formulario con FastAPI

Ahora vamos a añadir una mejora en este endpoint. Ya recibimos la imagen, pero no la estamos guardando en ningún sitio. Para resolver esto, primero vamos a crear una carpeta en nuestro proyecto donde guardar las imágenes a la que yo voy a nombrar como tmp.

Ahora volvemos al archivo main.py y reemplazamos el código existente por el siguiente:

import os
import time
from fastapi import FastAPI, HTTPException, status
from fastapi import Form, File, UploadFile

app = FastAPI()

@app.post("/contact")
async def contact(subject: str = Form(...), msg: str = Form(...)):
    return {
        "subject": subject,
        "message": msg
    }

@app.post("/create_user")
async def create_user(username: str = Form(...), password: str = Form(...), photo: UploadFile = File(...)):

    if photo.content_type != 'image/jpeg':
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f'Invalid format {photo.content_type} only valid images with format jpg'
        )

    await save_image(photo)

    return {
        "username": username,
        "password": password,
        "photo": {
            'filename': photo.filename,
            'content_type': photo.content_type
        }
    }

async def save_image(photo):
    tmp_folder = './tmp'
    if not os.path.exists(tmp_folder):
        os.mkdir(tmp_folder)

    content = await photo.read()

    print(f'{tmp_folder}/{time.time()}.jpg')
    with open(f'{tmp_folder}/{time.time()}.jpg', 'wb') as f:
        f.write(content)

Como veis, he añadido una nueva función llamada save_image la cual recibe por parámetro el archivo. Verifica que exista la carpeta donde guardaremos las imágenes y si no es así la genera.

Después guardamos la información en binario dentro de variable content y por último generamos el archivo el cual yo renombro como el timestamp actual más la extensión. Una vez hecho esto, abre el archivo y guarda la información dentro de él.

Para probar que funciona correctamente podéis levantar el servicio con uvicorn y testarlo desde la documentación que genera automáticamente FastAPI en la ruta /docs. También os dejo un ejemplo de como realizar la petición con la librería requests:

import requests

url = 'http://127.0.0.1:8000/create_user'

"""
Para enviar archivos hay que añadir esta cabecera
pero al enviar el parámetro files al realizar la petición
ya nos añade automáticamente la cabecera por lo que no sería necesario añadirla
de todas formas os dejo por aquí como sería
"""
# headers = {
#     'Content-type': 'multipart/form-data'
# }

params = {
    'username': 'requestalber',
    'password': 'admin123',
}

filename = 'fastapi.jpg'
route_file = f'./tmp/{filename}'

files = {
    'photo': (filename, open(route_file, 'rb'), 'image/jpeg')
}

r = requests.post(url, data=params, files=files)

print(f'Status http: {r.status_code}')
print(f'Response: {r.json()}')

Recibir múltiples archivos desde un formulario con FastAPI

Por último vamos a ver como enviar múltiples archivos. Para ello vamos al archivo main.py y reemplazamos el código por el siguiente:

from typing import List
from fastapi import FastAPI
from fastapi import File, UploadFile

app = FastAPI()

# Python 3.6 o mayor
@app.post("/upload_multiple_files/")
async def create_upload_files(files: List[UploadFile] = File(...)):
    return {"filenames": [file.filename for file in files]}

# A partir de Python 3.9
@app.post("/upload_multiple_files_v2/")
async def create_upload_files_v2(files: list[UploadFile] = File(...)):
    return {"filenames": [file.filename for file in files]}

En este caso he creado dos endpoints. Si estamos en una versión anterior a la 3.9 de Python, necesitaremos importarnos List de typing para poder tipar el listado de archivos como una lista de tipo UploadFile y si estamos en la versión 3.9 de Python ya de forma nativa podemos utilizar list así que según la versión con la que estéis trabajando podéis escoger una versión u otra.

Si os fijáis, la diferencia entre una y otra es que en el List que importamos está en mayúsculas y el nativo en minúsculas.

Como en ejemplos anteriores, podéis probar el endpoints desde la ruta /docs o con requests como en el ejemplo que podéis ver a continuación:

import requests

url = 'http://127.0.0.1:8000/upload_multiple_files'

files = [
    ('files', ('fastapi.jpg', open('./tmp/fastapi.jpg', 'rb'), 'image/jpeg')),
    ('files', ('fastapi2.jpg', open('./tmp/fastapi2.jpg', 'rb'), 'image/jpeg')),
]

r = requests.post(url, files=files)

print(f'Status http: {r.status_code}')
print(f'Response: {r.json()}')

Y eso es todo por este tutorial, si tenéis algún problema con el proyecto, os dejo el enlace al repositorio y recordad que podéis dejar vuestras dudas en la caja de comentarios.

Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.

Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.

Nos leemos 👋.

700 vistas

🐍 Sígueme en Twitter

Si te gusta el contenido que subo y no quieres perderte nada, sígueme en Twitter y te avisaré cada vez que cree contenido nuevo 💪
Luego ¡Te sigo!

Nos tomamos en serio tu privacidad

Utilizamos cookies propias y de terceros para mejorar la experiencia del usuario a través de su navegación. Si pulsas entendido aceptas su uso. Ver política de cookies.