Вернуть только значения списка записей из FastAPI
Возвращая только значения вместо словарей, мы минимизируем размер и время передачи данных.

В Python все является классом, что означает, что данные модели похожи на словарь. Но у словарей есть ключи. И когда вы возвращаете список из многих словарей из FastAPI, размер данных, ключей и значений, обычно в два раза больше, чем размер значений. Больший размер и большее время означает, что наше приложение не очень эффективно, медленнее, чем нужно. Это также означает, что оно потребляет больше энергии, что означает, что оно не очень устойчиво (звучит хорошо... ух).
Ниже я представляю и сравниваю два способа возврата данных из FastAPI:
- Список словарей
- Список tuples , содержащий значения словарей.
Чтобы сделать вещи более интересными, ответ состоит из части "meta" и части "data". Вы можете попробовать это сами, код приведен ниже. Как всегда, я работаю на Ubuntu 22.04.
Класс ListResponse
Я уже упоминал класс ListResponse в предыдущем сообщении. Мы хотим вернуть два элемента, 'meta' и 'data'.
return {
'meta': ListMetaResponse(
page=1,
per_page=10,
count=count,
total=total,
),
'data': <list-of-dicts or list-of-tuples>,
}
Класс ListResponse создает один объект ответа, используя класс ListMetaResponse и модель данных. Чтобы использовать его, выполните следующие действия:
response_model=ListResponse(Item)
1. Возвращение списка словарей
Пожалуйста, обратитесь к документу FastAPI 'Response Model - Return Type' для модели, см. ссылки ниже.
Мы используем следующую модель и элементы:
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
items = [
{
'name': 'Portal Gun',
'price': 42.0
},
{
'name': 'Plumbus',
'price': 32.0
},
]
Возврат данных очень прост, я не буду вас об этом беспокоить.
2. Возвращение списка tuples , содержащего значения.
В данном случае мы используем tuple для значений словаря. Мы не можем использовать список, потому что нет поддержки Python Typing для позиционных элементов в списке. Значения словаря должны быть размещены в фиксированных позициях в tuple. Это означает, что модель является, см. выше:
Tuple[str, Optional[str], float, Optional[float], Optional[list[str]]]
Получение значений в tuples зависит от вашего приложения, откуда берутся данные. Здесь я предполагаю, что "элементы", как показано выше, это "неполные" словари. Мы можем обработать это до списка tuples следующим образом:
# extract values from items
def get_item_values(item):
d = {
'name': None,
'description': None,
'price': None,
'tax': None,
'tags': None,
} | item
return tuple(d.values())
items_values = list(map(get_item_values, items))
Если "элементы" поступают из базы данных, можно выбрать все поля в запросе. Результатом будет список tuples , и в этой обработке нет необходимости.
Сравнение данных JSON
1. Список словарей
Чтобы получить данные JSON , запустите в другом терминале:
curl http://127.0.0.1:8888/items
Результат:
{"meta":{"page":1,"per_page":10,"count":2,"total":2},"data":[{"name":"Portal Gun","description":null,"price":42.0,"tax":null,"tags":[]},{"name":"Plumbus","description":null,"price":32.0,"tax":null,"tags":[]}]}
Только часть данных:
[{"name":"Portal Gun","description":null,"price":42.0,"tax":null,"tags":[]},{"name":"Plumbus","description":null,"price":32.0,"tax":null,"tags":[]}]
Количество bytes данных: 148.
2. Список tuples , содержащих значения.
Получить данные JSON :
curl http://127.0.0.1:8888/items-values
Результат:
{"meta":{"page":1,"per_page":10,"count":2,"total":2},"data":[["Portal Gun",null,42.0,null,null],["Plumbus",null,32.0,null,null]]}
Только часть данных:
[["Portal Gun",null,42.0,null,null],["Plumbus",null,32.0,null,null]]
Количество bytes данных: 68.
Это сокращение на 54%!
Код
Ниже приведен код на случай, если вы захотите попробовать. Создайте virtual environment, затем:
pip install fastapi
pip install uvicorn
Запустите приложение:
python main.py
Чтобы показать элементы, наберите в браузере:
http://127.0.0.1:8888/items
Чтобы показать элементы-значения, введите в браузере:
http://127.0.0.1:8888/items-values
Код:
# main.py
import datetime
from functools import lru_cache
from typing import Any, List, Optional, Tuple, Union
from fastapi import FastAPI, status as fastapi_status
from pydantic import BaseModel, Field, create_model as create_pydantic_model
import uvicorn
# see also:
# https://fastapi.tiangolo.com/tutorial/response-model
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
items = [
{
'name': 'Portal Gun',
'price': 42.0
},
{
'name': 'Plumbus',
'price': 32.0
},
]
class ListMetaResponse(BaseModel):
page: int = Field(..., title='Pagination page number', example='2')
per_page: int = Field(..., title='Pagination items per page', example='10')
count: int = Field(..., title='Number of items returned', example='10')
total: int = Field(..., title='Total number of items', example='100')
class ListResponse(BaseModel):
def __init__(self, data_model=None):
pass
# KeyError when using the Pydantic model dynamically created by created_model in two Generic Model as response model #3464
# https://github.com/tiangolo/fastapi/issues/3464
@lru_cache(None)
def __new__(cls, data_model=None):
if hasattr(data_model, '__name__'):
data_model_name = data_model.__name__
else:
data_model_name = '???'
print(f'data_model_name = {data_model_name}')
return create_pydantic_model(
data_model_name + 'ListResponse',
meta=(ListMetaResponse, ...),
data=(List[data_model], ...),
__base__=BaseModel,
)
app = FastAPI()
@app.get('/')
def index():
return 'Hello index'
@app.get(
'/items',
name='Get items',
status_code=fastapi_status.HTTP_200_OK,
response_model=ListResponse(Item)
)
async def return_items() -> Any:
return {
'meta': ListMetaResponse(
page=1,
per_page=10,
count=len(items),
total=len(items),
),
'data': items
}
@app.get(
'/items-values',
name='Get item values',
status_code=fastapi_status.HTTP_200_OK,
response_model=ListResponse(Tuple[str, Optional[str], float, Optional[float], Optional[list[str]]]),
)
async def return_items_values() -> Any:
# extract values from items
def get_item_values(item):
d = {
'name': None,
'description': None,
'price': None,
'tax': None,
'tags': None,
} | item
return tuple(d.values())
items_values = list(map(get_item_values, items))
print(f'items_values = {items_values}')
return {
'meta': ListMetaResponse(
page=1,
per_page=10,
count=len(items),
total=len(items),
),
'data': items_values,
}
if __name__ == "__main__":
uvicorn.run("main:app", host='127.0.0.1', port=8888, reload=True)
Резюме
Сокращение объема передаваемых данных делает ваше приложение более эффективным и отзывчивым. Python Typing не поддерживает позиционные элементы в списке, поэтому мы используем tuples. Во многих случаях, например, при выборе данных из базы данных, нам не нужна операция извлечения значений, потому что возвращаемые строки уже являются tuples.
Ссылки / кредиты
FastAPI - Response Model - Return Type
https://fastapi.tiangolo.com/tutorial/response-model
Pydantic
https://docs.pydantic.dev/latest
Недавний
- График временного ряда с Flask, Bootstrap и Chart.js
- Использование IPv6 с Microk8s
- Использование Ingress для доступа к RabbitMQ на кластере Microk8s
- Простая видеогалерея с Flask, Jinja, Bootstrap и JQuery
- Базовое планирование заданий с помощью APScheduler
- Коммутатор базы данных с HAProxy и HAProxy Runtime API
Большинство просмотренных
- Использование PyInstaller и Cython для создания исполняемого файла Python
- Уменьшение времени отклика на запросы на странице Flask SQLAlchemy веб-сайта
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Подключение к службе на хосте Docker из контейнера Docker
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов