Hay un conflicto entre el formato de variables en Python (snake_case) y el formato de variables en JavaScript (camelCase). En este artículo, te enseñaré cómo configurar FastAPI para no contradecir al PEP8 y hacer feliz a los desarrolladores de JavaScript. O dicho de otra forma, cómo recibir y devolver Camel Case mientras trabajas con Snake Case en Python.
Vamos a partir de 2 necesidades: buscar usuarios y crear usuarios. Cada uno tendrán sus verbos, inputs y outputs.
Verbo | Input | Parámetros | Output |
---|---|---|---|
GET | Parámetros (query) | firstName o lastName |
Lista de usuarios |
POST | Body (JSON) | firstName , lastName , isActive |
Confirmación de usuario creado |
Si tuvieramos que implementarlo en FastAPI, sin ningún tipo de modificación, tendríamos algo como esto:
from fastapi import FastAPI, Query
from pydantic import BaseModel
from typing import List
app = FastAPI()
@app.get('/users')
async def get_users(first_name: str, last_name: str):
...
return [
{
'first_name': 'John',
'last_name': 'Doe'
},
{
'first_name': 'David',
'last_name': 'Smith'
}
]
class User(BaseModel):
first_name: str
last_name: str
is_active: bool
@app.post('/users')
async def create_user(user: User):
...
return {
'message_status': 'User created successfully'
}
Los retornos están escritos intencionadamente en Snake Case porque estamos simulando la salida de una base de datos, tal vez mediante un ORM.
Tenemos problemas:
- Los parámetros de entrada están en Camel Case. Tanto si son query params como si son body params, deberían estar en Camel Case.
- Los retornos están en Snake Case.
Resolvamos cada uno de estos problemas.
1. BaseModel a medida
Necesitaremos crear un BaseModel
capaz de convertir las claves de un diccionario de Snake Case a Camel Case y viceversa.
Crea un archivo llamado CustomBaseModel.py
y añade el siguiente código:
from humps import camelize
from pydantic import BaseModel
def to_camel(string):
return camelize(string)
class CamelModel(BaseModel):
class Config:
alias_generator = to_camel
population_by_name = True
También necesitaremos instalar la librería humps
:
pip3 install pyhumps
Repasemos el código:
to_camel
es una función que convierte una cadena de texto de Snake Case a Camel Case. Usamos la libreríahumps
para hacer la conversión mediante la funcióncamelize
.CamelModel
es una clase que hereda deBaseModel
. Tiene una clase interna llamadaConfig
que sobreescribe el métodoalias_generator
añadiendo la funciónto_camel
como una función intermedia entre la clave original y la salida.population_by_name
habilita la opción de los alias para las claves.
Ahora transformamos los inputs de la función en FastAPI a una clase independiente que herede de CamelModel
:
from fastapi import FastAPI, Depends
from CustomBaseModel import CamelModel
app = FastAPI()
class UsersQuery(CamelModel):
first_name: str
last_name: str
@app.get('/users')
async def get_users(query: UsersQuery = Depends()):
...
return [
{
'first_name': 'John',
'last_name': 'Doe'
},
{
'first_name': 'David',
'last_name': 'Smith'
}
]
¡Ya podemos enviar parámetros en Camel Case como query params! Puedes consultar la documentación autogenerada para comprobar que los parámetros se han convertido.
2. Convertir los retornos a Camel Case
Montaremos un decorador llamado @output_keys_to_camel
que antes de devolver el resultado de la función, convertirá las claves de los diccionarios de Snake Case a Camel Case.
Crea un fichero llamado decorators.py
y añade el siguiente código:
from functools import wraps
def convert_keys_to_camel_case(item: dict | list) -> dict | list:
"""
Convert the keys of the dictionary to camel case (snake_case to camelCase) in all levels
"""
if isinstance(item, dict):
new_dict = {}
for k, v in item.items():
new_key = to_camel_case(k)
new_dict[new_key] = convert_keys_to_camel_case(v)
return new_dict
elif isinstance(item, list):
return [convert_keys_to_camel_case(item) for item in item]
else:
return item
def output_keys_to_camel(func):
"""
Convert the keys of the dictionary to camel case (snake_case to camelCase) in all levels
"""
@wraps(func)
async def wrapper(*args, **kwargs):
output = await func(*args, **kwargs)
return convert_keys_to_camel_case(output)
return wrapper
convert_keys_to_camel_case
es una función que convierte las claves de un diccionario cualquiera de Snake Case a Camel Case. Se llama recursivamente hasta que se conviertan todas las claves.output_keys_to_camel
es el decorador. Antes de devolver el resultado de la función, pasa la salida porconvert_keys_to_camel_case
.
Ya podemos importarlo y utilizarlo.
from fastapi import FastAPI, Depends
from CustomBaseModel import CamelModel
from decorators import output_keys_to_camel # Nueva línea
app = FastAPI()
class UsersQuery(CamelModel):
first_name: str
last_name: str
@app.get('/users')
@output_keys_to_camel # Nueva línea
async def get_users(query: UsersQuery = Depends()):
...
return [
{
'first_name': 'John',
'last_name': 'Doe'
},
{
'first_name': 'David',
'last_name': 'Smith'
}
]
Esta configurado, al menos con el verbo GET. Para el verbo POST, necesitarás hacer algo similar.
3. Convertir un POST Body (JSON), a Camel Case
Continuando el ejemplo anterior, volveremos a utilizar la clase CamelModel
. La única diferencia respecto a GET es que en POST irá sin ningún tipo de floritura en la función.
class NewUserModel(CamelModel):
first_name: str
last_name: str
is_active: bool
@app.post('/users')
@output_keys_to_camel
async def create_user(params: NewUserModel):
...
return {
'message_status': 'User created successfully'
}
Todo unido quedaría de tal forma.
from fastapi import FastAPI, Depends
from CustomBaseModel import CamelModel
from decorators import output_keys_to_camel
app = FastAPI()
class UsersQuery(CamelModel):
first_name: str
last_name: str
@app.get('/users')
@output_keys_to_camel
async def get_users(query: UsersQuery = Depends()):
...
return [
{
'first_name': 'John',
'last_name': 'Doe'
},
{
'first_name': 'David',
'last_name': 'Smith'
}
]
class NewUserModel(CamelModel):
first_name: str
last_name: str
is_active: bool
@app.post('/users')
@output_keys_to_camel
async def create_user(params: NewUserModel):
...
return {
'message_status': 'User created successfully'
}
¡Y listo! Ahora puedes recibir y devolver Camel Case mientras trabajas con Snake Case en Python.
{{ comments.length }} comentarios