Movatterモバイル変換


[0]ホーム

URL:


Skip to content
Join theFastAPI Cloud waiting list 🚀
Follow@fastapi onX (Twitter) to stay updated
FollowFastAPI onLinkedIn to stay updated
Subscribe to theFastAPI and friends newsletter 🎉
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor

Bigger Applications - Multiple Files

If you are building an application or a web API, it's rarely the case that you can put everything in a single file.

FastAPI provides a convenience tool to structure your application while keeping all the flexibility.

Info

If you come from Flask, this would be the equivalent of Flask's Blueprints.

An example file structure

Let's say you have a file structure like this:

.├── app│   ├── __init__.py│   ├── main.py│   ├── dependencies.py│   └── routers│   │   ├── __init__.py│   │   ├── items.py│   │   └── users.py│   └── internal│       ├── __init__.py│       └── admin.py

Tip

There are several__init__.py files: one in each directory or subdirectory.

This is what allows importing code from one file into another.

For example, inapp/main.py you could have a line like:

from app.routers import items
  • Theapp directory contains everything. And it has an empty fileapp/__init__.py, so it is a "Python package" (a collection of "Python modules"):app.
  • It contains anapp/main.py file. As it is inside a Python package (a directory with a file__init__.py), it is a "module" of that package:app.main.
  • There's also anapp/dependencies.py file, just likeapp/main.py, it is a "module":app.dependencies.
  • There's a subdirectoryapp/routers/ with another file__init__.py, so it's a "Python subpackage":app.routers.
  • The fileapp/routers/items.py is inside a package,app/routers/, so, it's a submodule:app.routers.items.
  • The same withapp/routers/users.py, it's another submodule:app.routers.users.
  • There's also a subdirectoryapp/internal/ with another file__init__.py, so it's another "Python subpackage":app.internal.
  • And the fileapp/internal/admin.py is another submodule:app.internal.admin.

The same file structure with comments:

.├──app# "app" is a Python package  ├──__init__.py# this file makes "app" a "Python package"  ├──main.py# "main" module, e.g. import app.main  ├──dependencies.py# "dependencies" module, e.g. import app.dependencies  └──routers# "routers" is a "Python subpackage"  ├──__init__.py# makes "routers" a "Python subpackage"  ├──items.py# "items" submodule, e.g. import app.routers.items  └──users.py# "users" submodule, e.g. import app.routers.users  └──internal# "internal" is a "Python subpackage"  ├──__init__.py# makes "internal" a "Python subpackage"  └──admin.py# "admin" submodule, e.g. import app.internal.admin

APIRouter

Let's say the file dedicated to handling just users is the submodule at/app/routers/users.py.

You want to have thepath operations related to your users separated from the rest of the code, to keep it organized.

But it's still part of the sameFastAPI application/web API (it's part of the same "Python Package").

You can create thepath operations for that module usingAPIRouter.

ImportAPIRouter

You import it and create an "instance" the same way you would with the classFastAPI:

app/routers/users.py
fromfastapiimportAPIRouterrouter=APIRouter()@router.get("/users/",tags=["users"])asyncdefread_users():return[{"username":"Rick"},{"username":"Morty"}]@router.get("/users/me",tags=["users"])asyncdefread_user_me():return{"username":"fakecurrentuser"}@router.get("/users/{username}",tags=["users"])asyncdefread_user(username:str):return{"username":username}

Path operations withAPIRouter

And then you use it to declare yourpath operations.

Use it the same way you would use theFastAPI class:

app/routers/users.py
fromfastapiimportAPIRouterrouter=APIRouter()@router.get("/users/",tags=["users"])asyncdefread_users():return[{"username":"Rick"},{"username":"Morty"}]@router.get("/users/me",tags=["users"])asyncdefread_user_me():return{"username":"fakecurrentuser"}@router.get("/users/{username}",tags=["users"])asyncdefread_user(username:str):return{"username":username}

You can think ofAPIRouter as a "miniFastAPI" class.

All the same options are supported.

All the sameparameters,responses,dependencies,tags, etc.

Tip

In this example, the variable is calledrouter, but you can name it however you want.

We are going to include thisAPIRouter in the mainFastAPI app, but first, let's check the dependencies and anotherAPIRouter.

Dependencies

We see that we are going to need some dependencies used in several places of the application.

So we put them in their owndependencies module (app/dependencies.py).

We will now use a simple dependency to read a customX-Token header:

app/dependencies.py
fromtypingimportAnnotatedfromfastapiimportHeader,HTTPExceptionasyncdefget_token_header(x_token:Annotated[str,Header()]):ifx_token!="fake-super-secret-token":raiseHTTPException(status_code=400,detail="X-Token header invalid")asyncdefget_query_token(token:str):iftoken!="jessica":raiseHTTPException(status_code=400,detail="No Jessica token provided")

Tip

We are using an invented header to simplify this example.

But in real cases you will get better results using the integratedSecurity utilities.

Another module withAPIRouter

Let's say you also have the endpoints dedicated to handling "items" from your application in the module atapp/routers/items.py.

You havepath operations for:

  • /items/
  • /items/{item_id}

It's all the same structure as withapp/routers/users.py.

But we want to be smarter and simplify the code a bit.

We know all thepath operations in this module have the same:

  • Pathprefix:/items.
  • tags: (just one tag:items).
  • Extraresponses.
  • dependencies: they all need thatX-Token dependency we created.

So, instead of adding all that to eachpath operation, we can add it to theAPIRouter.

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"}

As the path of eachpath operation has to start with/, like in:

@router.get("/{item_id}")asyncdefread_item(item_id:str):...

...the prefix must not include a final/.

So, the prefix in this case is/items.

We can also add a list oftags and extraresponses that will be applied to all thepath operations included in this router.

And we can add a list ofdependencies that will be added to all thepath operations in the router and will be executed/solved for each request made to them.

Tip

Note that, much likedependencies inpath operation decorators, no value will be passed to yourpath operation function.

The end result is that the item paths are now:

  • /items/
  • /items/{item_id}

...as we intended.

  • They will be marked with a list of tags that contain a single string"items".
    • These "tags" are especially useful for the automatic interactive documentation systems (using OpenAPI).
  • All of them will include the predefinedresponses.
  • All thesepath operations will have the list ofdependencies evaluated/executed before them.

Tip

Havingdependencies in theAPIRouter can be used, for example, to require authentication for a whole group ofpath operations. Even if the dependencies are not added individually to each one of them.

Check

Theprefix,tags,responses, anddependencies parameters are (as in many other cases) just a feature fromFastAPI to help you avoid code duplication.

Import the dependencies

This code lives in the moduleapp.routers.items, the fileapp/routers/items.py.

And we need to get the dependency function from the moduleapp.dependencies, the fileapp/dependencies.py.

So we use a relative import with.. for the dependencies:

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"}

How relative imports work

Tip

If you know perfectly how imports work, continue to the next section below.

A single dot., like in:

from.dependenciesimportget_token_header

would mean:

  • Starting in the same package that this module (the fileapp/routers/items.py) lives in (the directoryapp/routers/)...
  • find the moduledependencies (an imaginary file atapp/routers/dependencies.py)...
  • and from it, import the functionget_token_header.

But that file doesn't exist, our dependencies are in a file atapp/dependencies.py.

Remember how our app/file structure looks like:


The two dots.., like in:

from..dependenciesimportget_token_header

mean:

  • Starting in the same package that this module (the fileapp/routers/items.py) lives in (the directoryapp/routers/)...
  • go to the parent package (the directoryapp/)...
  • and in there, find the moduledependencies (the file atapp/dependencies.py)...
  • and from it, import the functionget_token_header.

That works correctly! 🎉


The same way, if we had used three dots..., like in:

from...dependenciesimportget_token_header

that would mean:

  • Starting in the same package that this module (the fileapp/routers/items.py) lives in (the directoryapp/routers/)...
  • go to the parent package (the directoryapp/)...
  • then go to the parent of that package (there's no parent package,app is the top level 😱)...
  • and in there, find the moduledependencies (the file atapp/dependencies.py)...
  • and from it, import the functionget_token_header.

That would refer to some package aboveapp/, with its own file__init__.py, etc. But we don't have that. So, that would throw an error in our example. 🚨

But now you know how it works, so you can use relative imports in your own apps no matter how complex they are. 🤓

Add some customtags,responses, anddependencies

We are not adding the prefix/items nor thetags=["items"] to eachpath operation because we added them to theAPIRouter.

But we can still addmoretags that will be applied to a specificpath operation, and also some extraresponses specific to thatpath operation:

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"}

Tip

This last path operation will have the combination of tags:["items", "custom"].

And it will also have both responses in the documentation, one for404 and one for403.

The mainFastAPI

Now, let's see the module atapp/main.py.

Here's where you import and use the classFastAPI.

This will be the main file in your application that ties everything together.

And as most of your logic will now live in its own specific module, the main file will be quite simple.

ImportFastAPI

You import and create aFastAPI class as normally.

And we can even declareglobal dependencies that will be combined with the dependencies for eachAPIRouter:

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!"}

Import theAPIRouter

Now we import the other submodules that haveAPIRouters:

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!"}

As the filesapp/routers/users.py andapp/routers/items.py are submodules that are part of the same Python packageapp, we can use a single dot. to import them using "relative imports".

How the importing works

The section:

from.routersimportitems,users

means:

  • Starting in the same package that this module (the fileapp/main.py) lives in (the directoryapp/)...
  • look for the subpackagerouters (the directory atapp/routers/)...
  • and from it, import the submoduleitems (the file atapp/routers/items.py) andusers (the file atapp/routers/users.py)...

The moduleitems will have a variablerouter (items.router). This is the same one we created in the fileapp/routers/items.py, it's anAPIRouter object.

And then we do the same for the moduleusers.

We could also import them like:

fromapp.routersimportitems,users

Info

The first version is a "relative import":

from.routersimportitems,users

The second version is an "absolute import":

fromapp.routersimportitems,users

To learn more about Python Packages and Modules, readthe official Python documentation about Modules.

Avoid name collisions

We are importing the submoduleitems directly, instead of importing just its variablerouter.

This is because we also have another variable namedrouter in the submoduleusers.

If we had imported one after the other, like:

from.routers.itemsimportrouterfrom.routers.usersimportrouter

therouter fromusers would overwrite the one fromitems and we wouldn't be able to use them at the same time.

So, to be able to use both of them in the same file, we import the submodules directly:

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!"}

Include theAPIRouters forusers anditems

Now, let's include therouters from the submodulesusers anditems:

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 contains theAPIRouter inside of the fileapp/routers/users.py.

Anditems.router contains theAPIRouter inside of the fileapp/routers/items.py.

Withapp.include_router() we can add eachAPIRouter to the mainFastAPI application.

It will include all the routes from that router as part of it.

Technical Details

It will actually internally create apath operation for eachpath operation that was declared in theAPIRouter.

So, behind the scenes, it will actually work as if everything was the same single app.

Check

You don't have to worry about performance when including routers.

This will take microseconds and will only happen at startup.

So it won't affect performance. ⚡

Include anAPIRouter with a customprefix,tags,responses, anddependencies

Now, let's imagine your organization gave you theapp/internal/admin.py file.

It contains anAPIRouter with some adminpath operations that your organization shares between several projects.

For this example it will be super simple. But let's say that because it is shared with other projects in the organization, we cannot modify it and add aprefix,dependencies,tags, etc. directly to theAPIRouter:

app/internal/admin.py
fromfastapiimportAPIRouterrouter=APIRouter()@router.post("/")asyncdefupdate_admin():return{"message":"Admin getting schwifty"}

But we still want to set a customprefix when including theAPIRouter so that all itspath operations start with/admin, we want to secure it with thedependencies we already have for this project, and we want to includetags andresponses.

We can declare all that without having to modify the originalAPIRouter by passing those parameters toapp.include_router():

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!"}

That way, the originalAPIRouter will stay unmodified, so we can still share that sameapp/internal/admin.py file with other projects in the organization.

The result is that in our app, each of thepath operations from theadmin module will have:

  • The prefix/admin.
  • The tagadmin.
  • The dependencyget_token_header.
  • The response418. 🍵

But that will only affect thatAPIRouter in our app, not in any other code that uses it.

So, for example, other projects could use the sameAPIRouter with a different authentication method.

Include apath operation

We can also addpath operations directly to theFastAPI app.

Here we do it... just to show that we can 🤷:

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!"}

and it will work correctly, together with all the otherpath operations added withapp.include_router().

Very Technical Details

Note: this is a very technical detail that you probably canjust skip.


TheAPIRouters are not "mounted", they are not isolated from the rest of the application.

This is because we want to include theirpath operations in the OpenAPI schema and the user interfaces.

As we cannot just isolate them and "mount" them independently of the rest, thepath operations are "cloned" (re-created), not included directly.

Check the automatic API docs

Now, run your app:

$fastapidevapp/main.py<span style="color: green;">INFO</span>:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

And open the docs athttp://127.0.0.1:8000/docs.

You will see the automatic API docs, including the paths from all the submodules, using the correct paths (and prefixes) and the correct tags:

Include the same router multiple times with differentprefix

You can also use.include_router() multiple times with thesame router using different prefixes.

This could be useful, for example, to expose the same API under different prefixes, e.g./api/v1 and/api/latest.

This is an advanced usage that you might not really need, but it's there in case you do.

Include anAPIRouter in another

The same way you can include anAPIRouter in aFastAPI application, you can include anAPIRouter in anotherAPIRouter using:

router.include_router(other_router)

Make sure you do it before includingrouter in theFastAPI app, so that thepath operations fromother_router are also included.


[8]ページ先頭

©2009-2026 Movatter.jp