You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.7 KiB

Extramodelle

Im Anschluss an das vorherige Beispiel ist es üblich, mehr als ein zusammenhängendes Modell zu haben.

Dies gilt insbesondere für Benutzermodelle, denn:

  • Das Eingabemodell muss ein Passwort enthalten können.
  • Das Ausgabemodell sollte kein Passwort haben.
  • Das Datenbankmodell müsste wahrscheinlich ein gehashtes Passwort haben.

/// danger | Gefahr

Speichern Sie niemals das Klartextpasswort eines Benutzers. Speichern Sie immer einen „sicheren Hash“, den Sie dann verifizieren können.

Wenn Sie nicht wissen, was das ist, werden Sie in den Sicherheitskapiteln{.internal-link target=_blank} lernen, was ein „Passworthash“ ist.

///

Mehrere Modelle

Hier ist eine allgemeine Idee, wie die Modelle mit ihren Passwortfeldern aussehen könnten und an welchen Stellen sie verwendet werden:

{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}

/// info | Hinweis

In Pydantic v1 hieß die Methode .dict(), in Pydantic v2 wurde sie als veraltet markiert (aber weiterhin unterstützt) und in .model_dump() umbenannt.

Die Beispiele hier verwenden .dict() für die Kompatibilität mit Pydantic v1, aber Sie sollten .model_dump() verwenden, wenn Sie Pydantic v2 verwenden können.

///

Über **user_in.dict()

Die .dict() Methode von Pydantic

user_in ist ein Pydantic-Modell der Klasse UserIn.

Pydantic-Modelle haben eine .dict()-Methode, die ein dict mit den Daten des Modells zurückgibt.

Wenn wir also ein Pydantic-Objekt user_in erstellen, etwa so:

user_in = UserIn(username="john", password="secret", email="john.doe@example.com")

und dann aufrufen:

user_dict = user_in.dict()

haben wir jetzt ein dict mit den Daten in der Variablen user_dict (es ist ein dict statt eines Pydantic-Modellobjekts).

Und wenn wir aufrufen:

print(user_dict)

würden wir ein Python-dict erhalten mit:

{
    'username': 'john',
    'password': 'secret',
    'email': 'john.doe@example.com',
    'full_name': None,
}

Ein dict entpacken

Wenn wir ein dict wie user_dict nehmen und es einer Funktion (oder Klasse) mit **user_dict übergeben, wird Python es „entpacken“. Es wird die Schlüssel und Werte von user_dict direkt als Schlüsselwort-Argumente übergeben.

Setzen wir also das user_dict von oben ein, dann wird:

UserInDB(**user_dict)

etwas Ähnliches wie:

UserInDB(
    username="john",
    password="secret",
    email="john.doe@example.com",
    full_name=None,
)

Oder genauer gesagt, indem user_dict direkt verwendet wird, mit welchen Inhalten es auch immer in der Zukunft haben mag:

UserInDB(
    username = user_dict["username"],
    password = user_dict["password"],
    email = user_dict["email"],
    full_name = user_dict["full_name"],
)

Ein Pydantic-Modell aus dem Inhalt eines anderen

Da wir im obigen Beispiel user_dict von user_in.dict() bekommen haben, wäre dieser Code:

user_dict = user_in.dict()
UserInDB(**user_dict)

gleichwertig zu:

UserInDB(**user_in.dict())

...weil user_in.dict() ein dict ist, und dann lassen wir Python es "entpacken", indem wir es an UserInDB mit vorangestelltem ** übergeben.

Auf diese Weise erhalten wir ein Pydantic-Modell aus den Daten eines anderen Pydantic-Modells.

Ein dict entpacken und zusätzliche Schlüsselwort-Argumente

Und dann fügen wir das zusätzliche Schlüsselwort-Argument hashed_password=hashed_password hinzu, wie in:

UserInDB(**user_in.dict(), hashed_password=hashed_password)

...führt am Ende zu:

UserInDB(
    username = user_dict["username"],
    password = user_dict["password"],
    email = user_dict["email"],
    full_name = user_dict["full_name"],
    hashed_password = hashed_password,
)

/// warning | Achtung

Die unterstützenden zusätzlichen Funktionen fake_password_hasher und fake_save_user dienen nur zur Demo eines möglichen Datenflussses, bieten jedoch natürlich keine echte Sicherheit.

///

Verdopplung vermeiden

Die Reduzierung von Code-Verdoppelung ist eine der Kernideen von FastAPI.

Da die Verdopplung von Code die Wahrscheinlichkeit von Fehlern, Sicherheitsproblemen, Probleme mit der Desynchronisation des Codes (wenn Sie an einer Stelle, aber nicht an der anderen aktualisieren) usw. erhöht.

Und diese Modelle teilen alle eine Menge der Daten und verdoppeln Attributnamen und -typen.

Wir könnten es besser machen.

Wir können ein UserBase-Modell deklarieren, das als Basis für unsere anderen Modelle dient. Und dann können wir Unterklassen dieses Modells erstellen, die seine Attribute (Typdeklarationen, Validierung usw.) erben.

Alle Datenkonvertierungen, Validierungen, Dokumentationen usw. werden immer noch wie gewohnt funktionieren.

Auf diese Weise können wir nur die Unterschiede zwischen den Modellen (mit Klartext-password, mit hashed_password und ohne Passwort) deklarieren:

{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}

Union oder anyOf

Sie können deklarieren, dass eine Response eine Union mehrerer Typen ist, das bedeutet, dass die Response einer von ihnen ist.

Dies wird in OpenAPI mit anyOf definiert.

Um das zu tun, verwenden Sie den Standard-Python-Typhinweis typing.Union:

/// note | Hinweis

Wenn Sie eine Union definieren, listen Sie den spezifischeren Typ zuerst auf, gefolgt vom weniger spezifischen Typ. Im Beispiel unten steht PlaneItem vor CarItem in Union[PlaneItem, CarItem].

///

{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}

Union in Python 3.10

In diesem Beispiel übergeben wir Union[PlaneItem, CarItem] als Wert des Arguments response_model.

Da wir es als Wert an ein Argument übergeben, anstatt es in einer Typannotation zu verwenden, müssen wir Union verwenden, sogar in Python 3.10.

Wäre es eine Typannotation gewesen, hätten wir den vertikalen Strich verwenden können, wie in:

some_variable: PlaneItem | CarItem

Aber wenn wir das in der Zuweisung response_model=PlaneItem | CarItem machen, würden wir einen Fehler erhalten, weil Python versuchen würde, eine ungültige Operation zwischen PlaneItem und CarItem auszuführen, anstatt es als Typannotation zu interpretieren.

Liste von Modellen

Auf die gleiche Weise können Sie Responses von Listen von Objekten deklarieren.

Dafür verwenden Sie das Standard-Python typing.List (oder nur list in Python 3.9 und höher):

{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}

Response mit beliebigem dict

Sie können auch eine Response deklarieren, die ein beliebiges dict zurückgibt, indem Sie nur die Typen der Schlüssel und Werte ohne ein Pydantic-Modell deklarieren.

Dies ist nützlich, wenn Sie die gültigen Feld-/Attributnamen nicht im Voraus kennen (die für ein Pydantic-Modell benötigt werden würden).

In diesem Fall können Sie typing.Dict verwenden (oder nur dict in Python 3.9 und höher):

{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}

Zusammenfassung

Verwenden Sie gerne mehrere Pydantic-Modelle und vererben Sie je nach Bedarf.

Sie brauchen kein einzelnes Datenmodell pro Einheit, wenn diese Einheit in der Lage sein muss, verschiedene „Zustände“ zu haben. Wie im Fall der Benutzer-„Einheit“ mit einem Zustand einschließlich password, password_hash und ohne Passwort.