Separate OpenAPI-Schemas für Eingabe und Ausgabe oder nicht¶
Bei Verwendung von Pydantic v2 ist die generierte OpenAPI etwas genauer und korrekter als zuvor. 😎
Tatsächlich gibt es in einigen Fällen sogar zwei JSON-Schemas in OpenAPI für dasselbe Pydantic-Modell für Eingabe und Ausgabe, je nachdem, ob sie Defaultwerte haben.
Sehen wir uns an, wie das funktioniert und wie Sie es bei Bedarf ändern können.
fromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:str|None=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportOptionalfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Optional[str]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportList,UnionfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Union[str,None]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->List[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:str|None=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportOptionalfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Optional[str]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportList,UnionfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Union[str,None]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->List[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
... dann ist das Feld descriptionnicht erforderlich. Weil es den Defaultwert None hat.
Wenn Sie jedoch dasselbe Modell als Ausgabe verwenden, wie hier:
fromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:str|None=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportOptionalfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Optional[str]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportList,UnionfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Union[str,None]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->List[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
... dann, weil description einen Defaultwert hat, wird es, wenn Sie für dieses Feld nichts zurückgeben, immer noch diesen Defaultwert haben.
Wenn Sie mit der Dokumentation interagieren und die Response überprüfen, enthält die JSON-Response den Defaultwert (null), obwohl der Code nichts in eines der description-Felder geschrieben hat:
Das bedeutet, dass es immer einen Wert hat, der Wert kann jedoch manchmal None sein (oder null in JSON).
Das bedeutet, dass Clients, die Ihre API verwenden, nicht prüfen müssen, ob der Wert vorhanden ist oder nicht. Sie können davon ausgehen, dass das Feld immer vorhanden ist. In einigen Fällen hat es jedoch nur den Defaultwert None.
Um dies in OpenAPI zu kennzeichnen, markieren Sie dieses Feld als erforderlich, da es immer vorhanden sein wird.
Aus diesem Grund kann das JSON-Schema für ein Modell unterschiedlich sein, je nachdem, ob es für Eingabe oder Ausgabe verwendet wird:
für die Eingabe ist descriptionnicht erforderlich
für die Ausgabe ist es erforderlich (und möglicherweise None oder, in JSON-Begriffen, null)
Sie können das Ausgabemodell auch in der Dokumentation überprüfen. Sowohlnameals auchdescription sind mit einem roten Sternchen als erforderlich markiert:
Und wenn Sie alle verfügbaren Schemas (JSON-Schemas) in OpenAPI überprüfen, werden Sie feststellen, dass es zwei gibt, ein Item-Input und ein Item-Output.
Für Item-Input ist descriptionnicht erforderlich, es hat kein rotes Sternchen.
Aber für Item-Output ist descriptionerforderlich, es hat ein rotes Sternchen.
Mit dieser Funktion von Pydantic v2 ist Ihre API-Dokumentation präziser, und wenn Sie über automatisch generierte Clients und SDKs verfügen, sind diese auch präziser, mit einer besseren Entwicklererfahrung und Konsistenz. 🎉
Nun gibt es einige Fälle, in denen Sie möglicherweise dasselbe Schema für Eingabe und Ausgabe haben möchten.
Der Hauptanwendungsfall hierfür besteht wahrscheinlich darin, dass Sie das mal tun möchten, wenn Sie bereits über einige automatisch generierte Client-Codes/SDKs verfügen und im Moment nicht alle automatisch generierten Client-Codes/SDKs aktualisieren möchten, möglicherweise später, aber nicht jetzt.
In diesem Fall können Sie diese Funktion in FastAPI mit dem Parameter separate_input_output_schemas=False deaktivieren.
Info
Unterstützung für separate_input_output_schemas wurde in FastAPI 0.102.0 hinzugefügt. 🤓
fromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:str|None=Noneapp=FastAPI(separate_input_output_schemas=False)@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportOptionalfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Optional[str]=Noneapp=FastAPI(separate_input_output_schemas=False)@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportList,UnionfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Union[str,None]=Noneapp=FastAPI(separate_input_output_schemas=False)@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->List[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
Gleiches Schema für Eingabe- und Ausgabemodelle in der Dokumentation¶
Und jetzt wird es ein einziges Schema für die Eingabe und Ausgabe des Modells geben, nur Item, und es wird description als nicht erforderlich kennzeichnen:
Dies ist das gleiche Verhalten wie in Pydantic v1. 🤓