Es gibt mehrere __init__.py-Dateien: eine in jedem Verzeichnis oder Unterverzeichnis.
Das ermöglicht den Import von Code aus einer Datei in eine andere.
In app/main.py könnten Sie beispielsweise eine Zeile wie diese haben:
from app.routers import items
Das Verzeichnis app enthält alles. Und es hat eine leere Datei app/__init__.py, es handelt sich also um ein „Python-Package“ (eine Sammlung von „Python-Modulen“): app.
Es enthält eine Datei app/main.py. Da sie sich in einem Python-Package (einem Verzeichnis mit einer Datei __init__.py) befindet, ist sie ein „Modul“ dieses Packages: app.main.
Es gibt auch eine Datei app/dependencies.py, genau wie app/main.py ist sie ein „Modul“: app.dependencies.
Es gibt ein Unterverzeichnis app/routers/ mit einer weiteren Datei __init__.py, es handelt sich also um ein „Python-Subpackage“: app.routers.
Die Datei app/routers/items.py befindet sich in einem Package, app/routers/, also ist sie ein Submodul: app.routers.items.
Das Gleiche gilt für app/routers/users.py, es ist ein weiteres Submodul: app.routers.users.
Es gibt auch ein Unterverzeichnis app/internal/ mit einer weiteren Datei __init__.py, es handelt sich also um ein weiteres „Python-Subpackage“: app.internal.
Und die Datei app/internal/admin.py ist ein weiteres Submodul: app.internal.admin.
Die gleiche Dateistruktur mit Kommentaren:
.
├── app # „app“ ist ein Python-Package
│ ├── __init__.py # diese Datei macht „app“ zu einem „Python-Package“
│ ├── main.py # „main“-Modul, z. B. import app.main
│ ├── dependencies.py # „dependencies“-Modul, z. B. import app.dependencies
│ └── routers # „routers“ ist ein „Python-Subpackage“
│ │ ├── __init__.py # macht „routers“ zu einem „Python-Subpackage“
│ │ ├── items.py # „items“-Submodul, z. B. import app.routers.items
│ │ └── users.py # „users“-Submodul, z. B. import app.routers.users
│ └── internal # „internal“ ist ein „Python-Subpackage“
│ ├── __init__.py # macht „internal“ zu einem „Python-Subpackage“
│ └── admin.py # „admin“-Submodul, z. B. import app.internal.admin
Nehmen wir an, Sie haben im Modul unter app/routers/items.py auch die Endpunkte, die für die Verarbeitung von Artikeln („Items“) aus Ihrer Anwendung vorgesehen sind.
Sie haben Pfadoperationen für:
/items/
/items/{item_id}
Es ist alles die gleiche Struktur wie bei app/routers/users.py.
Aber wir wollen schlauer sein und den Code etwas vereinfachen.
Wir wissen, dass alle Pfadoperationen in diesem Modul folgendes haben:
Pfad-prefix: /items.
tags: (nur ein Tag: items).
Zusätzliche responses.
dependencies: Sie alle benötigen die von uns erstellte X-Token-Abhängigkeit.
Anstatt also alles zu jeder Pfadoperation hinzuzufügen, können wir es dem APIRouter hinzufügen.
app/routers/items.py
fromfastapiimportAPIRouter,Depends,HTTPExceptionfrom..dependenciesimportget_token_headerrouter=APIRouter(prefix="/items",tags=["items"],dependencies=[Depends(get_token_header)],responses={404:{"description":"Not found"}},)fake_items_db={"plumbus":{"name":"Plumbus"},"gun":{"name":"Portal Gun"}}@router.get("/")asyncdefread_items():returnfake_items_db@router.get("/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinfake_items_db:raiseHTTPException(status_code=404,detail="Item not found")return{"name":fake_items_db[item_id]["name"],"item_id":item_id}@router.put("/{item_id}",tags=["custom"],responses={403:{"description":"Operation forbidden"}},)asyncdefupdate_item(item_id:str):ifitem_id!="plumbus":raiseHTTPException(status_code=403,detail="You can only update the item: plumbus")return{"item_id":item_id,"name":"The great Plumbus"}
Da der Pfad jeder Pfadoperation mit / beginnen muss, wie in:
... darf das Präfix kein abschließendes / enthalten.
Das Präfix lautet in diesem Fall also /items.
Wir können auch eine Liste von tags und zusätzliche responses hinzufügen, die auf alle in diesem Router enthaltenen Pfadoperationen angewendet werden.
Und wir können eine Liste von dependencies hinzufügen, die allen Pfadoperationen im Router hinzugefügt und für jeden an sie gerichteten Request ausgeführt/aufgelöst werden.
dependencies im APIRouter können beispielsweise verwendet werden, um eine Authentifizierung für eine ganze Gruppe von Pfadoperationen zu erfordern. Selbst wenn die Abhängigkeiten nicht jeder einzeln hinzugefügt werden.
Check
Die Parameter prefix, tags, responses und dependencies sind (wie in vielen anderen Fällen) nur ein Feature von FastAPI, um Ihnen dabei zu helfen, Codeverdoppelung zu vermeiden.
Der folgende Code befindet sich im Modul app.routers.items, also in der Datei app/routers/items.py.
Und wir müssen die Abhängigkeitsfunktion aus dem Modul app.dependencies importieren, also aus der Datei app/dependencies.py.
Daher verwenden wir einen relativen Import mit .. für die Abhängigkeiten:
app/routers/items.py
fromfastapiimportAPIRouter,Depends,HTTPExceptionfrom..dependenciesimportget_token_headerrouter=APIRouter(prefix="/items",tags=["items"],dependencies=[Depends(get_token_header)],responses={404:{"description":"Not found"}},)fake_items_db={"plumbus":{"name":"Plumbus"},"gun":{"name":"Portal Gun"}}@router.get("/")asyncdefread_items():returnfake_items_db@router.get("/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinfake_items_db:raiseHTTPException(status_code=404,detail="Item not found")return{"name":fake_items_db[item_id]["name"],"item_id":item_id}@router.put("/{item_id}",tags=["custom"],responses={403:{"description":"Operation forbidden"}},)asyncdefupdate_item(item_id:str):ifitem_id!="plumbus":raiseHTTPException(status_code=403,detail="You can only update the item: plumbus")return{"item_id":item_id,"name":"The great Plumbus"}
Wenn Sie genau wissen, wie Importe funktionieren, fahren Sie mit dem nächsten Abschnitt unten fort.
Ein einzelner Punkt ., wie in:
from.dependenciesimportget_token_header
würde bedeuten:
Beginnend im selben Package, in dem sich dieses Modul (die Datei app/routers/items.py) befindet (das Verzeichnis app/routers/) ...
finde das Modul dependencies (eine imaginäre Datei unter app/routers/dependencies.py) ...
und importiere daraus die Funktion get_token_header.
Aber diese Datei existiert nicht, unsere Abhängigkeiten befinden sich in einer Datei unter app/dependencies.py.
Erinnern Sie sich, wie unsere Anwendungs-/Dateistruktur aussieht:
Die beiden Punkte .., wie in:
from..dependenciesimportget_token_header
bedeuten:
Beginnend im selben Package, in dem sich dieses Modul (die Datei app/routers/items.py) befindet (das Verzeichnis app/routers/) ...
gehe zum übergeordneten Package (das Verzeichnis app/) ...
und finde dort das Modul dependencies (die Datei unter app/dependencies.py) ...
und importiere daraus die Funktion get_token_header.
Das funktioniert korrekt! 🎉
Das Gleiche gilt, wenn wir drei Punkte ... verwendet hätten, wie in:
from...dependenciesimportget_token_header
Das würde bedeuten:
Beginnend im selben Package, in dem sich dieses Modul (die Datei app/routers/items.py) befindet (das Verzeichnis app/routers/) ...
gehe zum übergeordneten Package (das Verzeichnis app/) ...
gehe dann zum übergeordneten Package dieses Packages (es gibt kein übergeordnetes Package, app ist die oberste Ebene 😱) ...
und finde dort das Modul dependencies (die Datei unter app/dependencies.py) ...
und importiere daraus die Funktion get_token_header.
Das würde sich auf ein Paket oberhalb von app/ beziehen, mit seiner eigenen Datei __init__.py, usw. Aber das haben wir nicht. Das würde in unserem Beispiel also einen Fehler auslösen. 🚨
Aber jetzt wissen Sie, wie es funktioniert, sodass Sie relative Importe in Ihren eigenen Anwendungen verwenden können, egal wie komplex diese sind. 🤓
Einige benutzerdefinierte tags, responses, und dependencies hinzufügen¶
Wir fügen weder das Präfix /items noch tags=["items"] zu jeder Pfadoperation hinzu, da wir sie zum APIRouter hinzugefügt haben.
Aber wir können immer noch mehrtags hinzufügen, die auf eine bestimmte Pfadoperation angewendet werden, sowie einige zusätzliche responses, die speziell für diese Pfadoperation gelten:
app/routers/items.py
fromfastapiimportAPIRouter,Depends,HTTPExceptionfrom..dependenciesimportget_token_headerrouter=APIRouter(prefix="/items",tags=["items"],dependencies=[Depends(get_token_header)],responses={404:{"description":"Not found"}},)fake_items_db={"plumbus":{"name":"Plumbus"},"gun":{"name":"Portal Gun"}}@router.get("/")asyncdefread_items():returnfake_items_db@router.get("/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinfake_items_db:raiseHTTPException(status_code=404,detail="Item not found")return{"name":fake_items_db[item_id]["name"],"item_id":item_id}@router.put("/{item_id}",tags=["custom"],responses={403:{"description":"Operation forbidden"}},)asyncdefupdate_item(item_id:str):ifitem_id!="plumbus":raiseHTTPException(status_code=403,detail="You can only update the item: plumbus")return{"item_id":item_id,"name":"The great Plumbus"}
Tipp
Diese letzte Pfadoperation wird eine Kombination von Tags haben: ["items", "custom"].
Und sie wird auch beide Responses in der Dokumentation haben, eine für 404 und eine für 403.
Sie importieren und erstellen wie gewohnt eine FastAPI-Klasse.
Und wir können sogar globale Abhängigkeiten deklarieren, die mit den Abhängigkeiten für jeden APIRouter kombiniert werden:
app/main.py
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}
Jetzt importieren wir die anderen Submodule, die APIRouter haben:
app/main.py
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}
Da es sich bei den Dateien app/routers/users.py und app/routers/items.py um Submodule handelt, die Teil desselben Python-Packages app sind, können wir einen einzelnen Punkt . verwenden, um sie mit „relativen Imports“ zu importieren.
Beginnend im selben Package, in dem sich dieses Modul (die Datei app/main.py) befindet (das Verzeichnis app/) ...
Suche nach dem Subpackage routers (das Verzeichnis unter app/routers/) ...
und importiere daraus die Submodule items (die Datei unter app/routers/items.py) und users (die Datei unter app/routers/users.py) ...
Das Modul items verfügt über eine Variable router (items.router). Das ist dieselbe, die wir in der Datei app/routers/items.py erstellt haben, es ist ein APIRouter-Objekt.
Und dann machen wir das gleiche für das Modul users.
würde der router von users den von items überschreiben und wir könnten sie nicht gleichzeitig verwenden.
Um also beide in derselben Datei verwenden zu können, importieren wir die Submodule direkt:
app/main.py
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}
Inkludieren wir nun die router aus diesen Submodulen users und items:
app/main.py
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}
Info
users.router enthält den APIRouter in der Datei app/routers/users.py.
Und items.router enthält den APIRouter in der Datei app/routers/items.py.
Mit app.include_router() können wir jeden APIRouter zur Hauptanwendung FastAPI hinzufügen.
Es wird alle Routen von diesem Router als Teil von dieser inkludieren.
Technische Details
Tatsächlich wird intern eine Pfadoperation für jede Pfadoperation erstellt, die im APIRouter deklariert wurde.
Hinter den Kulissen wird es also tatsächlich so funktionieren, als ob alles dieselbe einzige Anwendung wäre.
Check
Bei der Einbindung von Routern müssen Sie sich keine Gedanken über die Performanz machen.
Dies dauert Mikrosekunden und geschieht nur beim Start.
Es hat also keinen Einfluss auf die Leistung. ⚡
Einen APIRouter mit benutzerdefinierten prefix, tags, responses und dependencies einfügen¶
Stellen wir uns nun vor, dass Ihre Organisation Ihnen die Datei app/internal/admin.py gegeben hat.
Sie enthält einen APIRouter mit einigen administrativen Pfadoperationen, die Ihre Organisation zwischen mehreren Projekten teilt.
In diesem Beispiel wird es ganz einfach sein. Nehmen wir jedoch an, dass wir, da sie mit anderen Projekten in der Organisation geteilt wird, sie nicht ändern und kein prefix, dependencies, tags, usw. direkt zum APIRouter hinzufügen können:
Aber wir möchten immer noch ein benutzerdefiniertes prefix festlegen, wenn wir den APIRouter einbinden, sodass alle seine Pfadoperationen mit /admin beginnen, wir möchten es mit den dependencies sichern, die wir bereits für dieses Projekt haben, und wir möchten tags und responses hinzufügen.
Wir können das alles deklarieren, ohne den ursprünglichen APIRouter ändern zu müssen, indem wir diese Parameter an app.include_router() übergeben:
app/main.py
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}
Auf diese Weise bleibt der ursprüngliche APIRouter unverändert, sodass wir dieselbe app/internal/admin.py-Datei weiterhin mit anderen Projekten in der Organisation teilen können.
Das Ergebnis ist, dass in unserer Anwendung jede der Pfadoperationen aus dem Modul admin Folgendes haben wird:
Das Präfix /admin.
Den Tag admin.
Die Abhängigkeit get_token_header.
Die Response 418. 🍵
Dies wirkt sich jedoch nur auf diesen APIRouter in unserer Anwendung aus, nicht auf anderen Code, der ihn verwendet.
So könnten beispielsweise andere Projekte denselben APIRouter mit einer anderen Authentifizierungsmethode verwenden.
Wir können Pfadoperationen auch direkt zur FastAPI-App hinzufügen.
Hier machen wir es ... nur um zu zeigen, dass wir es können 🤷:
app/main.py
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}
und es wird korrekt funktionieren, zusammen mit allen anderen Pfadoperationen, die mit app.include_router() hinzugefügt wurden.
Sehr technische Details
Hinweis: Dies ist ein sehr technisches Detail, das Sie wahrscheinlich einfach überspringen können.
Die APIRouter sind nicht „gemountet“, sie sind nicht vom Rest der Anwendung isoliert.
Das liegt daran, dass wir deren Pfadoperationen in das OpenAPI-Schema und die Benutzeroberflächen einbinden möchten.
Da wir sie nicht einfach isolieren und unabhängig vom Rest „mounten“ können, werden die Pfadoperationen „geklont“ (neu erstellt) und nicht direkt einbezogen.
Es in der automatischen API-Dokumentation ansehen¶
Führen Sie nun uvicorn aus, indem Sie das Modul app.main und die Variable app verwenden:
fast →uvicorn app.main:app --reload INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Sie sehen die automatische API-Dokumentation, einschließlich der Pfade aller Submodule, mit den richtigen Pfaden (und Präfixen) und den richtigen Tags:
Den gleichen Router mehrmals mit unterschiedlichem prefix inkludieren¶
Sie können .include_router() auch mehrmals mit demselben Router und unterschiedlichen Präfixen verwenden.
Dies könnte beispielsweise nützlich sein, um dieselbe API unter verschiedenen Präfixen verfügbar zu machen, z. B. /api/v1 und /api/latest.
Dies ist eine fortgeschrittene Verwendung, die Sie möglicherweise nicht wirklich benötigen, aber für den Fall, dass Sie sie benötigen, ist sie vorhanden.
Auf die gleiche Weise, wie Sie einen APIRouter in eine FastAPI-Anwendung einbinden können, können Sie einen APIRouter in einen anderen APIRouter einbinden, indem Sie Folgendes verwenden:
router.include_router(other_router)
Stellen Sie sicher, dass Sie dies tun, bevor Sie router in die FastAPI-App einbinden, damit auch die Pfadoperationen von other_router inkludiert werden.