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

Nur die Werte einer Liste von Datensätzen aus FastAPI zurückgeben

Indem wir nur Werte anstelle von Wörterbüchern zurückgeben, minimieren wir den Umfang und die Zeit der Datenübertragung.

6 Juli 2023
In API, FastAPI
post main image
https://www.pexels.com/nl-nl/@hellokellybrito/

In Python ist alles eine Klasse, was bedeutet, dass die Modelldaten einem Wörterbuch ähnlich sind. Aber Wörterbücher haben Schlüssel. Und wenn Sie eine Liste mit vielen Wörterbüchern aus FastAPI zurückgeben, ist die Größe der Daten, Schlüssel und Werte, in der Regel viel mehr als doppelt so groß wie die der Werte. Größere Daten und mehr Zeit bedeuten, dass unsere Anwendung nicht sehr effizient ist und langsamer als nötig arbeitet. Es bedeutet auch, dass sie mehr Energie verbraucht, was bedeutet, dass sie nicht sehr nachhaltig ist (klingt gut ... igitt).

Im Folgenden werden zwei Möglichkeiten vorgestellt und verglichen, wie Daten aus FastAPI zurückgegeben werden können:

  • Eine Liste von Wörterbüchern
  • Eine Liste von tuples mit Wörterbuchwerten

Um die Sache noch spannender zu machen, besteht die Antwort aus einem "Meta"-Teil und einem "Daten"-Teil. Sie können dies selbst ausprobieren, den Code finden Sie unten. Wie immer laufe ich auf Ubuntu 22.04.

Die ListResponse-Klasse

Ich habe die ListResponse-Klasse bereits in einem früheren Beitrag erwähnt. Was wir zurückgeben wollen, sind zwei Elemente, "meta" und "data".

    return {
        'meta': ListMetaResponse(
            page=1,
            per_page=10,
            count=count,
            total=total,
        ),
        'data': <list-of-dicts or list-of-tuples>,
    }

Die Klasse ListResponse erstellt ein einzelnes Antwortobjekt, das die Klasse ListMetaResponse und das Datenmodell verwendet. Um sie zu verwenden:

    response_model=ListResponse(Item)

1. Rückgabe einer Liste von Dictionaries

Das Modell entnehmen Sie bitte dem Dokument FastAPI 'Response Model - Return Type', siehe Links unten.
Wir verwenden das folgende Modell und die folgenden Elemente:

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

Die Rückgabe der Daten ist sehr einfach; ich werde Sie hier nicht damit belästigen.

2. Rückgabe einer Liste von tuples , die die Werte enthält

In diesem Fall verwenden wir eine tuple für die Werte eines Wörterbuchs. Wir können keine Liste verwenden, da es keine Python Typing-Unterstützung für positionale Elemente in einer Liste gibt. Die Werte des Wörterbuchs müssen an festen Positionen in der tuple platziert werden. Dies bedeutet, dass das Modell ist, siehe oben:

Tuple[str, Optional[str], float, Optional[float], Optional[list[str]]]

Um die Werte in den tuples zu bekommen, kommt es auf Ihre Anwendung an, woher die Daten kommen. Hier nehme ich die 'items' wie oben gezeigt an, das sind 'unvollständige' Wörterbücher. Wir können dies zu einer Liste von tuples auf folgende Weise verarbeiten:

    # 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))

Wenn die "Elemente" aus einer Datenbank stammen, können Sie alle Felder in der Abfrage auswählen. Das Ergebnis wird eine Liste von tuples sein, und es besteht keine Notwendigkeit für diese Verarbeitung.

Vergleich der Daten von JSON

1. Liste der Wörterbücher

Um die Daten von JSON zu erhalten, führen Sie diese in einem anderen Terminal aus:

curl http://127.0.0.1:8888/items

Ergebnis:

{"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":[]}]}

Und nur der Datenteil:

[{"name":"Portal Gun","description":null,"price":42.0,"tax":null,"tags":[]},{"name":"Plumbus","description":null,"price":32.0,"tax":null,"tags":[]}]

Anzahl der bytes der Daten: 148.

2. Liste der tuples mit den Werten

Um die Daten von JSON zu erhalten:

curl http://127.0.0.1:8888/items-values

Ergebnis:

{"meta":{"page":1,"per_page":10,"count":2,"total":2},"data":[["Portal Gun",null,42.0,null,null],["Plumbus",null,32.0,null,null]]}

Und nur der Datenteil:

[["Portal Gun",null,42.0,null,null],["Plumbus",null,32.0,null,null]]

Anzahl der bytes der Daten: 68.

Das ist eine Reduzierung um 54%!

Der Code

Nachfolgend finden Sie den Code, falls Sie ihn ausprobieren möchten. Erstellen Sie eine virtual environment, dann:

pip install fastapi
pip install uvicorn

Starten Sie die Anwendung:

python main.py

Um die Posten anzuzeigen, geben Sie in Ihren Browser ein:

http://127.0.0.1:8888/items

Um die Item-Werte anzuzeigen, geben Sie in Ihren Browser ein:

http://127.0.0.1:8888/items-values

Den Code:

# 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)

Zusammenfassung

Die Verringerung der zu übertragenden Datenmenge macht Ihre Anwendung effizienter und reaktionsschneller. Python Typing unterstützt keine positionsbezogenen Elemente in einer Liste, was bedeutet, dass wir tuples verwenden. In vielen Fällen, z. B. bei der Auswahl von Daten aus einer Datenbank, benötigen wir keine Operation, um die Werte zu extrahieren, da die zurückgegebenen Zeilen bereits tuples sind.

Links / Impressum

FastAPI - Response Model - Return Type
https://fastapi.tiangolo.com/tutorial/response-model

Pydantic
https://docs.pydantic.dev/latest

Mehr erfahren

API FastAPI

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.