angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Вернуть только значения списка записей из FastAPI

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

6 июля 2023
В API, FastAPI
post main image
https://www.pexels.com/nl-nl/@hellokellybrito/

В 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

Подробнее

API FastAPI

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.