Créer des applications plus grandes - Plusieurs fichiers¶
🌐 Traduction par IA et humains
Cette traduction a été réalisée par une IA guidée par des humains. 🤝
Elle peut contenir des erreurs d'interprétation du sens original, ou paraître peu naturelle, etc. 🤖
Vous pouvez améliorer cette traduction en nous aidant à mieux guider le LLM d'IA.
Si vous créez une application ou une API web, il est rare que vous puissiez tout mettre dans un seul fichier.
FastAPI fournit un outil pratique pour structurer votre application tout en conservant toute la flexibilité.
Info
Si vous venez de Flask, cela équivaut aux Blueprints de Flask.
Exemple de structure de fichiers¶
Supposons que vous ayez une structure de fichiers comme ceci :
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
Astuce
Il y a plusieurs fichiers __init__.py : un dans chaque répertoire ou sous-répertoire.
C'est cela qui permet d'importer du code d'un fichier dans un autre.
Par exemple, dans app/main.py vous pourriez avoir une ligne comme :
from app.routers import items
- Le répertoire
appcontient tout. Et il a un fichier videapp/__init__.py, c'est donc un « package Python » (une collection de « modules Python ») :app. - Il contient un fichier
app/main.py. Comme il se trouve dans un package Python (un répertoire avec un fichier__init__.py), c'est un « module » de ce package :app.main. - Il y a aussi un fichier
app/dependencies.py, tout commeapp/main.py, c'est un « module » :app.dependencies. - Il y a un sous-répertoire
app/routers/avec un autre fichier__init__.py, c'est donc un « sous-package Python » :app.routers. - Le fichier
app/routers/items.pyest dans un package,app/routers/, c'est donc un sous-module :app.routers.items. - De même pour
app/routers/users.py, c'est un autre sous-module :app.routers.users. - Il y a aussi un sous-répertoire
app/internal/avec un autre fichier__init__.py, c'est donc un autre « sous-package Python » :app.internal. - Et le fichier
app/internal/admin.pyest un autre sous-module :app.internal.admin.
La même structure de fichiers avec des commentaires :
.
├── app # "app" est un package Python
│ ├── __init__.py # ce fichier fait de "app" un "package Python"
│ ├── main.py # module "main", ex. import app.main
│ ├── dependencies.py # module "dependencies", ex. import app.dependencies
│ └── routers # "routers" est un "sous-package Python"
│ │ ├── __init__.py # fait de "routers" un "sous-package Python"
│ │ ├── items.py # sous-module "items", ex. import app.routers.items
│ │ └── users.py # sous-module "users", ex. import app.routers.users
│ └── internal # "internal" est un "sous-package Python"
│ ├── __init__.py # fait de "internal" un "sous-package Python"
│ └── admin.py # sous-module "admin", ex. import app.internal.admin
APIRouter¶
Supposons que le fichier dédié à la gestion des utilisateurs soit le sous-module /app/routers/users.py.
Vous voulez séparer les chemins d'accès liés à vos utilisateurs du reste du code pour le garder organisé.
Mais cela fait toujours partie de la même application/API web FastAPI (cela fait partie du même « package Python »).
Vous pouvez créer les chemins d'accès pour ce module à l'aide de APIRouter.
Importer APIRouter¶
Vous l'importez et créez une « instance » de la même manière que vous le feriez avec la classe FastAPI :
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
Déclarer des chemins d'accès avec APIRouter¶
Puis vous l'utilisez pour déclarer vos chemins d'accès.
Utilisez-le de la même manière que vous utiliseriez la classe FastAPI :
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
Vous pouvez considérer APIRouter comme une « mini FastAPI ».
Toutes les mêmes options sont prises en charge.
Tous les mêmes parameters, responses, dependencies, tags, etc.
Astuce
Dans cet exemple, la variable s'appelle router, mais vous pouvez la nommer comme vous le souhaitez.
Nous allons inclure ce APIRouter dans l'application principale FastAPI, mais d'abord, examinons les dépendances et un autre APIRouter.
Gérer les dépendances¶
Nous voyons que nous allons avoir besoin de certaines dépendances utilisées à plusieurs endroits de l'application.
Nous les mettons donc dans leur propre module dependencies (app/dependencies.py).
Nous allons maintenant utiliser une dépendance simple pour lire un en-tête personnalisé X-Token :
from typing import Annotated
from fastapi import Header, HTTPException
async def get_token_header(x_token: Annotated[str, Header()]):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def get_query_token(token: str):
if token != "jessica":
raise HTTPException(status_code=400, detail="No Jessica token provided")
Astuce
Nous utilisons un en-tête inventé pour simplifier cet exemple.
Mais dans les cas réels, vous obtiendrez de meilleurs résultats en utilisant les utilitaires de sécurité intégrés.
Créer un autre module avec APIRouter¶
Supposons que vous ayez également les endpoints dédiés à la gestion des « items » de votre application dans le module app/routers/items.py.
Vous avez des chemins d'accès pour :
/items//items/{item_id}
C'est exactement la même structure que pour app/routers/users.py.
Mais nous voulons être plus malins et simplifier un peu le code.
Nous savons que tous les chemins d'accès de ce module ont les mêmes éléments :
- Préfixe de chemin
prefix:/items. tags: (un seul tag :items).responsessupplémentaires.dependencies: ils ont tous besoin de la dépendanceX-Tokenque nous avons créée.
Donc, au lieu d'ajouter tout cela à chaque chemin d'accès, nous pouvons l'ajouter au APIRouter.
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = 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("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(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"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
Comme le chemin de chaque chemin d'accès doit commencer par /, comme dans :
@router.get("/{item_id}")
async def read_item(item_id: str):
...
... le préfixe ne doit pas inclure un / final.
Ainsi, le préfixe dans ce cas est /items.
Nous pouvons également ajouter une liste de tags et des responses supplémentaires qui seront appliqués à tous les chemins d'accès inclus dans ce routeur.
Et nous pouvons ajouter une liste de dependencies qui seront ajoutées à tous les chemins d'accès du routeur et seront exécutées/résolues pour chaque requête qui leur est faite.
Astuce
Notez que, tout comme pour les dépendances dans les décorateurs de chemin d'accès, aucune valeur ne sera transmise à votre fonction de chemin d'accès.
Le résultat final est que les chemins d'item sont désormais :
/items//items/{item_id}
... comme prévu.
- Ils seront marqués avec une liste de tags qui contient une seule chaîne « items ».
- Ces « tags » sont particulièrement utiles pour les systèmes de documentation interactive automatique (utilisant OpenAPI).
- Ils incluront tous les
responsesprédéfinies. - Tous ces chemins d'accès auront la liste des
dependenciesévaluées/exécutées avant eux.- Si vous déclarez également des dépendances dans un chemin d'accès spécifique, elles seront aussi exécutées.
- Les dépendances du routeur sont exécutées en premier, puis les
dependenciesdans le décorateur, puis les dépendances des paramètres normaux. - Vous pouvez également ajouter des
Securitydependencies avec desscopes.
Astuce
Avoir des dependencies dans le APIRouter peut servir, par exemple, à exiger une authentification pour tout un groupe de chemins d'accès. Même si les dépendances ne sont pas ajoutées individuellement à chacun d'eux.
Vérifications
Les paramètres prefix, tags, responses et dependencies sont (comme dans de nombreux autres cas) simplement une fonctionnalité de FastAPI pour vous aider à éviter la duplication de code.
Importer les dépendances¶
Ce code se trouve dans le module app.routers.items, le fichier app/routers/items.py.
Et nous devons récupérer la fonction de dépendance depuis le module app.dependencies, le fichier app/dependencies.py.
Nous utilisons donc un import relatif avec .. pour les dépendances :
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = 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("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(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"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
Comprendre le fonctionnement des imports relatifs¶
Astuce
Si vous savez parfaitement comment fonctionnent les imports, passez à la section suivante ci-dessous.
Un seul point ., comme dans :
from .dependencies import get_token_header
signifierait :
- En partant du même package dans lequel vit ce module (le fichier
app/routers/items.py) (le répertoireapp/routers/)... - trouver le module
dependencies(un fichier imaginaireapp/routers/dependencies.py)... - et en importer la fonction
get_token_header.
Mais ce fichier n'existe pas, nos dépendances sont dans un fichier app/dependencies.py.
Rappelez-vous à quoi ressemble la structure de notre app/fichiers :
Les deux points .., comme dans :
from ..dependencies import get_token_header
veulent dire :
- En partant du même package dans lequel vit ce module (le fichier
app/routers/items.py) (le répertoireapp/routers/)... - aller au package parent (le répertoire
app/)... - et là, trouver le module
dependencies(le fichierapp/dependencies.py)... - et en importer la fonction
get_token_header.
Cela fonctionne correctement ! 🎉
De la même manière, si nous avions utilisé trois points ..., comme dans :
from ...dependencies import get_token_header
cela voudrait dire :
- En partant du même package dans lequel vit ce module (le fichier
app/routers/items.py) (le répertoireapp/routers/)... - aller au package parent (le répertoire
app/)... - puis aller au parent de ce package (il n'y a pas de package parent,
appest le niveau supérieur 😱)... - et là, trouver le module
dependencies(le fichierapp/dependencies.py)... - et en importer la fonction
get_token_header.
Cela ferait référence à un package au-dessus de app/, avec son propre fichier __init__.py, etc. Mais nous n'avons pas cela. Donc, cela lèverait une erreur dans notre exemple. 🚨
Mais maintenant vous savez comment cela fonctionne, vous pouvez donc utiliser des imports relatifs dans vos propres applications, aussi complexes soient-elles. 🤓
Ajouter des tags, responses et dependencies personnalisés¶
Nous n'ajoutons pas le préfixe /items ni tags=["items"] à chaque chemin d'accès parce que nous les avons ajoutés au APIRouter.
Mais nous pouvons toujours ajouter davantage de tags qui seront appliqués à un chemin d'accès spécifique, ainsi que des responses supplémentaires propres à ce chemin d'accès :
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = 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("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(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"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
Astuce
Ce dernier chemin d'accès aura la combinaison de tags : ["items", "custom"].
Et il aura également les deux réponses dans la documentation, une pour 404 et une pour 403.
Créer l'application FastAPI principale¶
Voyons maintenant le module app/main.py.
C'est ici que vous importez et utilisez la classe FastAPI.
Ce sera le fichier principal de votre application qui reliera tout ensemble.
Et comme la plupart de votre logique vivra désormais dans son propre module, le fichier principal sera assez simple.
Importer FastAPI¶
Vous importez et créez une classe FastAPI comme d'habitude.
Et nous pouvons même déclarer des dépendances globales qui seront combinées avec les dépendances de chaque APIRouter :
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = 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("/")
async def root():
return {"message": "Hello Bigger Applications!"}
Importer les APIRouter¶
Nous importons maintenant les autres sous-modules qui ont des APIRouter :
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = 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("/")
async def root():
return {"message": "Hello Bigger Applications!"}
Comme les fichiers app/routers/users.py et app/routers/items.py sont des sous-modules qui font partie du même package Python app, nous pouvons utiliser un seul point . pour les importer en utilisant des « imports relatifs ».
Comprendre le fonctionnement de l'import¶
La section :
from .routers import items, users
signifie :
- En partant du même package dans lequel vit ce module (le fichier
app/main.py) (le répertoireapp/)... - chercher le sous-package
routers(le répertoireapp/routers/)... - et en importer le sous-module
items(le fichierapp/routers/items.py) etusers(le fichierapp/routers/users.py)...
Le module items aura une variable router (items.router). C'est celle que nous avons créée dans le fichier app/routers/items.py, c'est un objet APIRouter.
Nous faisons ensuite la même chose pour le module users.
Nous pourrions aussi les importer ainsi :
from app.routers import items, users
Info
La première version est un « import relatif » :
from .routers import items, users
La deuxième version est un « import absolu » :
from app.routers import items, users
Pour en savoir plus sur les Packages et Modules Python, lisez la documentation officielle de Python sur les modules.
Éviter les collisions de noms¶
Nous importons le sous-module items directement, au lieu d'importer uniquement sa variable router.
C'est parce que nous avons également une autre variable nommée router dans le sous-module users.
Si nous les avions importées l'une après l'autre, comme :
from .routers.items import router
from .routers.users import router
le router de users écraserait celui de items et nous ne pourrions pas les utiliser en même temps.
Donc, pour pouvoir utiliser les deux dans le même fichier, nous importons directement les sous-modules :
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = 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("/")
async def root():
return {"message": "Hello Bigger Applications!"}
Inclure les APIRouter pour users et items¶
Incluons maintenant les router des sous-modules users et items :
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = 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("/")
async def root():
return {"message": "Hello Bigger Applications!"}
Info
users.router contient le APIRouter à l'intérieur du fichier app/routers/users.py.
Et items.router contient le APIRouter à l'intérieur du fichier app/routers/items.py.
Avec app.include_router(), nous pouvons ajouter chaque APIRouter à l'application principale FastAPI.
Cela inclura toutes les routes de ce routeur comme faisant partie de l'application.
Détails techniques
En interne, cela créera en fait un chemin d'accès pour chaque chemin d'accès qui a été déclaré dans le APIRouter.
Donc, en coulisses, cela fonctionnera comme si tout faisait partie d'une seule et même application.
Vérifications
Vous n'avez pas à vous soucier de la performance lors de l'inclusion de routeurs.
Cela prendra des microsecondes et ne se produira qu'au démarrage.
Donc cela n'affectera pas la performance. ⚡
Inclure un APIRouter avec un prefix, des tags, des responses et des dependencies personnalisés¶
Imaginons maintenant que votre organisation vous ait fourni le fichier app/internal/admin.py.
Il contient un APIRouter avec quelques chemins d'accès d'administration que votre organisation partage entre plusieurs projets.
Pour cet exemple, il sera très simple. Mais supposons que, parce qu'il est partagé avec d'autres projets de l'organisation, nous ne puissions pas le modifier et ajouter un prefix, des dependencies, des tags, etc. directement au APIRouter :
from fastapi import APIRouter
router = APIRouter()
@router.post("/")
async def update_admin():
return {"message": "Admin getting schwifty"}
Mais nous voulons quand même définir un prefix personnalisé lors de l'inclusion du APIRouter afin que tous ses chemins d'accès commencent par /admin, nous voulons le sécuriser avec les dependencies que nous avons déjà pour ce projet, et nous voulons inclure des tags et des responses.
Nous pouvons déclarer tout cela sans avoir à modifier le APIRouter d'origine en passant ces paramètres à app.include_router() :
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = 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("/")
async def root():
return {"message": "Hello Bigger Applications!"}
De cette façon, le APIRouter original restera inchangé, afin que nous puissions toujours partager ce même fichier app/internal/admin.py avec d'autres projets de l'organisation.
Le résultat est que, dans notre application, chacun des chemins d'accès du module admin aura :
- Le préfixe
/admin. - Le tag
admin. - La dépendance
get_token_header. - La réponse
418. 🍵
Mais cela n'affectera que ce APIRouter dans notre application, pas dans tout autre code qui l'utilise.
Ainsi, par exemple, d'autres projets pourraient utiliser le même APIRouter avec une méthode d'authentification différente.
Inclure un chemin d'accès¶
Nous pouvons également ajouter des chemins d'accès directement à l'application FastAPI.
Ici, nous le faisons... juste pour montrer que nous le pouvons 🤷 :
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = 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("/")
async def root():
return {"message": "Hello Bigger Applications!"}
et cela fonctionnera correctement, avec tous les autres chemins d'accès ajoutés avec app.include_router().
Détails très techniques
Note : c'est un détail très technique que vous pouvez probablement simplement ignorer.
Les APIRouter ne sont pas « montés », ils ne sont pas isolés du reste de l'application.
C'est parce que nous voulons inclure leurs chemins d'accès dans le schéma OpenAPI et les interfaces utilisateur.
Comme nous ne pouvons pas simplement les isoler et les « monter » indépendamment du reste, les chemins d'accès sont « clonés » (recréés), pas inclus directement.
Consulter la documentation API automatique¶
Maintenant, exécutez votre application :
$ fastapi dev app/main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Et ouvrez les documents à http://127.0.0.1:8000/docs.
Vous verrez la documentation API automatique, incluant les chemins de tous les sous-modules, utilisant les bons chemins (et préfixes) et les bons tags :

Inclure le même routeur plusieurs fois avec des prefix différents¶
Vous pouvez aussi utiliser .include_router() plusieurs fois avec le même routeur en utilisant des préfixes différents.
Cela peut être utile, par exemple, pour exposer la même API sous des préfixes différents, p. ex. /api/v1 et /api/latest.
C'est un usage avancé dont vous n'aurez peut-être pas vraiment besoin, mais il est là au cas où.
Inclure un APIRouter dans un autre¶
De la même manière que vous pouvez inclure un APIRouter dans une application FastAPI, vous pouvez inclure un APIRouter dans un autre APIRouter en utilisant :
router.include_router(other_router)
Vous devez vous assurer de le faire avant d'inclure router dans l'application FastAPI, afin que les chemins d'accès de other_router soient également inclus.