Zum Inhalt springen

FastAPI Response Model mit SQLModel

Jetzt zeige ich Ihnen, wie Sie das response_model von FastAPI mit SQLModel verwenden.

Interaktive API-Dokumentation

Bis jetzt weiß die API-Dokumentation mit dem von uns verwendeten Code, welche Daten die Clients senden müssen.

Interactive API docs UI

Diese interaktive Dokumentations-UI wird von Swagger UI betrieben. Swagger UI liest einen großen JSON-Inhalt, der die API mit allen Datenschemata (Datenformen) gemäß dem Standard OpenAPI definiert und sie in dieser schönen UI anzeigt.

FastAPI generiert diese OpenAPI automatisch, damit Swagger UI sie lesen kann.

Und es generiert sie basierend auf dem Code, den Sie schreiben, unter Verwendung der Pydantic-Modelle (in diesem Fall SQLModel-Modelle) und Typ-Annotationen, um die Schemata der Daten zu kennen, die die API verarbeitet.

Antwortdaten

Aber bis jetzt kennt die API-Dokumentations-UI das Schema der *Antworten*, die unsere App zurücksendet, nicht.

Sie sehen, dass es eine mögliche "Erfolgreiche Antwort" mit dem Code 200 gibt, aber wir haben keine Ahnung, wie die Antwortdaten aussehen würden.

API docs UI without response data schemas

Im Moment teilen wir FastAPI nur die Daten mit, die wir empfangen möchten, aber wir teilen ihm noch nicht die Daten mit, die wir zurücksenden möchten.

Machen wir das jetzt. 🤓

Verwenden von response_model

Wir können response_model verwenden, um FastAPI das Schema der Daten mitzuteilen, die wir zurücksenden möchten.

Zum Beispiel können wir dieselbe SQLModel-Klasse Hero übergeben (da sie auch ein Pydantic-Modell ist).

# Code above omitted 👆

@app.post("/heroes/", response_model=Hero)
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero

# Code below omitted 👇
👀 Vorschau der vollständigen Datei
from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/", response_model=Hero)
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero


@app.get("/heroes/", response_model=list[Hero])
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes
🤓 Andere Versionen und Varianten
from typing import Optional

from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/", response_model=Hero)
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero


@app.get("/heroes/", response_model=list[Hero])
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes
from typing import List, Optional

from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/", response_model=Hero)
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero


@app.get("/heroes/", response_model=List[Hero])
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes

Liste von Helden in response_model

Wir können auch andere Typ-Annotationen verwenden, genauso wie wir es mit Pydantic-Feldern tun können. Zum Beispiel können wir eine Liste von Heros übergeben.

Dazu deklarieren wir response_model mit list[Hero].

# Code above omitted 👆

@app.get("/heroes/", response_model=list[Hero])
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes
👀 Vorschau der vollständigen Datei
from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/", response_model=Hero)
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero


@app.get("/heroes/", response_model=list[Hero])
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes
🤓 Andere Versionen und Varianten
from typing import Optional

from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/", response_model=Hero)
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero


@app.get("/heroes/", response_model=list[Hero])
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes
from typing import List, Optional

from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/", response_model=Hero)
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero


@app.get("/heroes/", response_model=List[Hero])
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes

FastAPI und Response Model

FastAPI führt mit diesem response_model eine Datenvalidierung und -filterung der Antwort durch.

Dies funktioniert also wie ein Vertrag zwischen unserer Anwendung und dem Client.

Mehr dazu erfahren Sie in der FastAPI-Dokumentation zu response_model.

Neue API-Dokumentations-UI

Nun können wir zurück zur Dokumentations-UI gehen und sehen, dass sie jetzt das Schema der Antwort anzeigen, die wir erhalten werden.

API docs UI without response data schemas

Die Clients wissen, welche Daten sie erwarten sollten.

Automatische Clients

Der sichtbarste Vorteil der Verwendung von response_model ist, dass es in der API-Dokumentations-UI angezeigt wird.

Es gibt aber auch andere Vorteile, wie z.B. dass FastAPI mit diesem Modell eine automatische Datenvalidierung und -filterung der Antwortdaten durchführt.

Darüber hinaus können viele Tools davon profitieren, da die Schemata standardkonform definiert sind.

Zum Beispiel Client-Generatoren, die automatisch den notwendigen Code zum Kommunizieren mit Ihrer API in vielen Sprachen erstellen können.

Info

Wenn Sie neugierig auf die Standards sind: FastAPI generiert OpenAPI, das intern JSON Schema verwendet.

All das können Sie in der FastAPI-Dokumentation - Erste Schritte nachlesen.

Zusammenfassung

Verwenden Sie response_model, um FastAPI das Schema der Daten mitzuteilen, die Sie zurücksenden möchten, und erstellen Sie großartige Daten-APIs. 😎