Retourneer alleen de waarden van een lijst met records uit FastAPI
Door alleen waarden terug te sturen in plaats van woordenboeken minimaliseren we de grootte en tijd van gegevensoverdracht.
In Python is alles een klasse, wat betekent dat modelgegevens lijken op een woordenboek. Maar woordenboeken hebben sleutels. En als je een lijst met veel woordenboeken uit FastAPI retourneert, is de grootte van de gegevens, sleutels en waarden, meestal veel meer dan twee keer zo groot als de grootte van de waarden. Een grotere omvang en meer tijd betekent dat onze toepassing niet erg efficiënt is, langzamer dan nodig. Het betekent ook dat het meer energie verbruikt, wat betekent dat het niet erg duurzaam is (klinkt goed ... ugh).
Hieronder presenteer en vergelijk ik twee manieren om gegevens terug te sturen van FastAPI:
- Een lijst van woordenboeken
- Een lijst van tuples met woordenboekwaarden
Om het nog spannender te maken, bestaat het antwoord uit een "meta"-gedeelte en een "data"-gedeelte. Je kunt dit zelf proberen, de code staat hieronder. Zoals altijd draai ik op Ubuntu 22.04.
De klasse ListResponse
Ik heb de ListResponse klasse al in een eerdere post genoemd. Wat we willen retourneren zijn twee items, 'meta' en 'data'.
return {
'meta': ListMetaResponse(
page=1,
per_page=10,
count=count,
total=total,
),
'data': <list-of-dicts or list-of-tuples>,
}
De klasse ListResponse maakt een enkel antwoordobject, met behulp van de klasse ListMetaResponse en het gegevensmodel. Om het te gebruiken:
response_model=ListResponse(Item)
1. Een lijst met woordenboeken teruggeven
Raadpleeg het FastAPI document 'Response Model - Return Type' voor het model, zie onderstaande links.
We gebruiken het volgende model en de volgende items:
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
},
]
Het retourneren van de gegevens is heel eenvoudig; ik zal je hier niet mee lastigvallen.
2. Een lijst met tuples met de waarden teruggeven.
In dit geval gebruiken we een tuple voor de waarden van een woordenboek. We kunnen geen lijst gebruiken omdat er geen Python typeringsondersteuning is voor positionele items in een lijst. De waarden van het woordenboek moeten op vaste posities in de tuple worden geplaatst. Dit betekent dat het model is, zie hierboven:
Tuple[str, Optional[str], float, Optional[float], Optional[list[str]]]
Om de waarden in de tuples te krijgen is afhankelijk van je toepassing, waar komt de data vandaan. Hier ga ik uit van de 'items' zoals hierboven weergegeven, dit zijn 'incomplete' woordenboeken. Dit kunnen we op de volgende manier verwerken tot een lijst van 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))
Als de 'items' uit een database komen, kun je alle velden in de query selecteren. Het resultaat zal een lijst van tuples zijn en deze bewerking is niet nodig.
De gegevens van JSON vergelijken
1. Lijst van woordenboeken
Voer in een andere terminal uit om de JSON gegevens te krijgen:
curl http://127.0.0.1:8888/items
Resultaat:
{"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":[]}]}
En alleen het gegevensgedeelte:
[{"name":"Portal Gun","description":null,"price":42.0,"tax":null,"tags":[]},{"name":"Plumbus","description":null,"price":32.0,"tax":null,"tags":[]}]
Aantal bytes van de gegevens: 148.
2. Lijst van tuples met de waarden
Om de gegevens van JSON te krijgen:
curl http://127.0.0.1:8888/items-values
Resultaat:
{"meta":{"page":1,"per_page":10,"count":2,"total":2},"data":[["Portal Gun",null,42.0,null,null],["Plumbus",null,32.0,null,null]]}
En alleen het gegevensgedeelte:
[["Portal Gun",null,42.0,null,null],["Plumbus",null,32.0,null,null]]
Aantal bytes van de gegevens: 68.
Dit is een reductie van 54%!
De code
Hieronder staat de code voor het geval je het wilt proberen. Maak een virtual environment en vervolgens:
pip install fastapi
pip install uvicorn
Start de toepassing:
python main.py
Om de items weer te geven, typ je in je browser:
http://127.0.0.1:8888/items
Om de items-waarden te tonen, typ je in je browser:
http://127.0.0.1:8888/items-values
De 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)
Samenvatting
Het verminderen van de hoeveelheid over te dragen gegevens maakt je applicatie efficiënter en responsiever. Python Typen ondersteunt geen positionele items in een lijst, waardoor we tuples gebruiken. In veel gevallen, zoals het selecteren van gegevens uit een database, hebben we geen bewerking nodig om de waarden te extraheren, omdat de geretourneerde rijen al tuples zijn.
Links / credits
FastAPI - Response Model - Return Type
https://fastapi.tiangolo.com/tutorial/response-model
Pydantic
https://docs.pydantic.dev/latest
Recent
- Database UUID primaire sleutels van je webapplicatie verbergen
- Don't Repeat Yourself (DRY) met Jinja2
- SQLAlchemy, PostgreSQL, maximum aantal rijen per user
- Toon de waarden in SQLAlchemy dynamische filters
- Veilige gegevensoverdracht met Public Key versleuteling en pyNaCl
- rqlite: een alternatief voor SQLite met hoge beschikbaarheid en distributed
Meest bekeken
- Met behulp van Python's pyOpenSSL om SSL-certificaten die van een host zijn gedownload te controleren
- Gebruik van UUIDs in plaats van Integer Autoincrement Primary Keys met SQLAlchemy en MariaDb
- Maak verbinding met een dienst op een Docker host vanaf een Docker container
- PyInstaller en Cython gebruiken om een Python executable te maken
- SQLAlchemy: Gebruik van Cascade Deletes om verwante objecten te verwijderen
- Flask RESTful API verzoekparametervalidatie met Marshmallow-schema's