更大的应用程序 - 多个文件¶
如果您正在构建一个应用程序或 Web API,通常很难将所有代码都放在一个文件中。
FastAPI 提供了一个便捷的工具来组织您的应用程序,同时保持其所有的灵活性。
信息
如果您之前使用过 Flask,这相当于 Flask 的蓝图 (Blueprints)。
示例文件结构¶
假设您有这样的文件结构
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
提示
这里有几个 __init__.py 文件:每个目录或子目录中都有一个。
这使得可以将代码从一个文件导入到另一个文件中。
例如,在 app/main.py 中,您可以有这样一行代码
from app.routers import items
app目录包含了所有内容。它有一个空的app/__init__.py文件,因此它是一个“Python 包”(“Python 模块”的集合):app。- 它包含一个
app/main.py文件。由于它位于一个 Python 包中(即包含__init__.py文件的目录),所以它是该包的一个“模块”:app.main。 - 还有一个
app/dependencies.py文件,就像app/main.py一样,它也是一个“模块”:app.dependencies。 - 有一个子目录
app/routers/以及另一个__init__.py文件,因此它是一个“Python 子包”:app.routers。 - 文件
app/routers/items.py位于包app/routers/内,因此,它是一个子模块:app.routers.items。 app/routers/users.py也是一样,它是另一个子模块:app.routers.users。- 还有一个子目录
app/internal/以及另一个__init__.py文件,因此它是另一个“Python 子包”:app.internal。 - 文件
app/internal/admin.py是另一个子模块:app.internal.admin。
带有注释的相同文件结构
.
├── 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¶
假设专门用于处理用户的文件是 /app/routers/users.py 这个子模块。
为了保持代码整洁,您希望将与用户相关的路径操作与其余代码分离开来。
但这仍然是同一个 FastAPI 应用程序/Web API 的一部分(它是同一个“Python 包”的一部分)。
您可以使用 APIRouter 为该模块创建路径操作。
导入 APIRouter¶
您可以像使用 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}
使用 APIRouter 的路径操作¶
然后您可以使用它来声明您的路径操作。
使用方式与使用 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}
您可以将 APIRouter 视为一个“小型 FastAPI”类。
支持所有相同的选项。
所有相同的 parameters、responses、dependencies、tags 等。
提示
在此示例中,变量名为 router,但您可以随意命名它。
我们将把这个 APIRouter 包含在主要的 FastAPI 应用程序中,但首先,让我们看看依赖项和另一个 APIRouter。
依赖项¶
我们看到我们需要在应用程序的多个地方使用一些依赖项。
因此,我们将它们放在自己的 dependencies 模块 (app/dependencies.py) 中。
现在我们将使用一个简单的依赖项来读取自定义的 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")
另一个带有 APIRouter 的模块¶
假设您还有专门用于处理应用程序中“项目 (items)”的端点,位于模块 app/routers/items.py 中。
您拥有以下内容的路径操作
/items//items/{item_id}
结构与 app/routers/users.py 完全相同。
但我们想更聪明一点,稍微简化代码。
我们知道此模块中的所有路径操作都具有相同的
- 路径
prefix:/items。 tags:(只有一个标签:items)。- 额外的
responses。 dependencies:它们都需要我们创建的X-Token依赖项。
因此,与其将所有这些添加到每个路径操作中,我们可以将其添加到 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"}
由于每个路径操作的路径必须以 / 开头,例如
@router.get("/{item_id}")
async def read_item(item_id: str):
...
...前缀不得包含末尾的 /。
因此,在这种情况下,前缀是 /items。
我们还可以添加一个 tags 列表和额外的 responses,这些将被应用于此路由器中包含的所有路径操作。
我们还可以添加一个 dependencies 列表,它们将被添加到路由器中的所有路径操作中,并为每个请求执行/解析。
提示
请注意,与 路径操作装饰器中的依赖项 一样,您的路径操作函数不会接收任何值。
最终结果是,项目路径现在为
/items//items/{item_id}
...正如我们预期的那样。
- 它们将被标记为包含单个字符串
"items"的标签列表。- 这些“标签”对于自动交互式文档系统(使用 OpenAPI)特别有用。
- 所有这些都将包含预定义的
responses。 - 所有这些路径操作在执行前,都会对其
dependencies列表进行评估/执行。- 如果您还在特定的路径操作中声明了依赖项,它们也会被执行。
- 路由器依赖项最先执行,然后是 装饰器中的
dependencies,最后是正常的参数依赖项。 - 您还可以添加 带有
scopes的Security依赖项。
提示
在 APIRouter 中拥有 dependencies 可以用于例如要求对整组路径操作进行身份验证。即使这些依赖项没有单独添加到它们中的每一个。
检查
prefix、tags、responses 和 dependencies 参数(正如许多其他情况一样)只是 FastAPI 的一个特性,旨在帮助您避免代码重复。
导入依赖项¶
此代码位于模块 app.routers.items 即文件 app/routers/items.py 中。
我们需要从模块 app.dependencies 即文件 app/dependencies.py 中获取依赖函数。
因此,我们使用相对导入,用 .. 表示依赖项
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"}
相对导入是如何工作的¶
提示
如果您非常了解导入是如何工作的,请继续阅读下面的下一节。
一个单点 .,例如在
from .dependencies import get_token_header
中,意味着
- 从该模块(文件
app/routers/items.py)所在的同一包(目录app/routers/)开始... - 找到模块
dependencies(一个位于app/routers/dependencies.py的虚构文件)... - 并从中导入函数
get_token_header。
但该文件不存在,我们的依赖项位于 app/dependencies.py 文件中。
请记住我们的应用程序/文件结构是什么样的
两个点 ..,例如在
from ..dependencies import get_token_header
中,意味着
- 从该模块(文件
app/routers/items.py)所在的同一包(目录app/routers/)开始... - 转到父包(目录
app/)... - 并在其中找到模块
dependencies(位于app/dependencies.py的文件)... - 并从中导入函数
get_token_header。
这样工作正常!🎉
同样,如果我们使用了三个点 ...,例如在
from ...dependencies import get_token_header
中,那将意味着
- 从该模块(文件
app/routers/items.py)所在的同一包(目录app/routers/)开始... - 转到父包(目录
app/)... - 然后转到该包的父级(没有父包,
app是顶级 😱)... - 并在其中找到模块
dependencies(位于app/dependencies.py的文件)... - 并从中导入函数
get_token_header。
那将引用 app/ 之上的某个包,带有它自己的 __init__.py 文件等。但我们没有。所以,这在我们的示例中会抛出一个错误。🚨
但现在您知道它是如何工作的了,所以无论您的应用程序多么复杂,您都可以在自己的应用程序中使用相对导入。🤓
添加一些自定义的 tags、responses 和 dependencies¶
我们没有将前缀 /items 或 tags=["items"] 添加到每个路径操作中,因为我们已经将它们添加到了 APIRouter 中。
但我们仍然可以添加将应用于特定路径操作的更多 tags,以及该路径操作特有的一些额外 responses
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"}
提示
这最后一个路径操作将结合这些标签:["items", "custom"]。
并且它还将在文档中包含两个响应,一个用于 404,另一个用于 403。
主要的 FastAPI¶
现在,让我们看看 app/main.py 中的模块。
这是您导入并使用 FastAPI 类的地方。
这将是您应用程序中的主文件,它将所有内容联系在一起。
而且由于您的大部分逻辑现在都将驻留在其自己的特定模块中,因此主文件将非常简单。
导入 FastAPI¶
您像平常一样导入并创建 FastAPI 类。
我们甚至可以声明 全局依赖项,它们将与每个 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!"}
导入 APIRouter¶
现在我们导入具有 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!"}
由于文件 app/routers/users.py 和 app/routers/items.py 是属于同一个 Python 包 app 的子模块,我们可以使用单点 . 来使用“相对导入”导入它们。
导入是如何工作的¶
这一节
from .routers import items, users
意味着
- 从该模块(文件
app/main.py)所在的同一包(目录app/)开始... - 查找子包
routers(位于app/routers/的目录)... - 并从中导入子模块
items(文件app/routers/items.py)和users(文件app/routers/users.py)...
模块 items 将具有变量 router (items.router)。这与我们在 app/routers/items.py 文件中创建的是同一个,它是一个 APIRouter 对象。
然后我们对模块 users 执行同样的操作。
我们也可以这样导入它们
from app.routers import items, users
信息
第一个版本是“相对导入”
from .routers import items, users
第二个版本是“绝对导入”
from app.routers import items, users
要了解有关 Python 包和模块的更多信息,请阅读 关于模块的 Python 官方文档。
避免命名冲突¶
我们直接导入子模块 items,而不是只导入其变量 router。
这是因为我们在子模块 users 中也有另一个名为 router 的变量。
如果我们一个接一个地导入它们,例如
from .routers.items import router
from .routers.users import router
来自 users 的 router 将覆盖来自 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!"}
包含 users 和 items 的 APIRouter¶
现在,让我们包含来自子模块 users 和 items 的 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!"}
信息
users.router 包含文件 app/routers/users.py 内的 APIRouter。
items.router 包含文件 app/routers/items.py 内的 APIRouter。
使用 app.include_router(),我们可以将每个 APIRouter 添加到主要的 FastAPI 应用程序中。
它将把来自该路由器的所有路由作为其一部分包含进来。
技术细节
它实际上会在内部为在 APIRouter 中声明的每个路径操作创建一个路径操作。
所以,在后台,它实际上就像一切都在同一个单一应用程序中一样工作。
检查
您不必担心包含路由器时的性能问题。
这只需要微秒级的时间,并且只会在启动时发生。
所以它不会影响性能。⚡
包含一个带有自定义 prefix、tags、responses 和 dependencies 的 APIRouter¶
现在,让我们想象您的组织给了您 app/internal/admin.py 文件。
它包含一个 APIRouter,其中包含一些您的组织在多个项目之间共享的管理路径操作。
对于这个示例,它将非常简单。但假设由于它与其他项目共享,我们不能修改它并直接将 prefix、dependencies、tags 等添加到 APIRouter 中
from fastapi import APIRouter
router = APIRouter()
@router.post("/")
async def update_admin():
return {"message": "Admin getting schwifty"}
但我们仍然想在包含 APIRouter 时设置一个自定义的 prefix,以便其所有路径操作都以 /admin 开头,我们想使用我们已经为该项目拥有的 dependencies 来保护它,并且我们想包含 tags 和 responses。
我们可以通过将这些参数传递给 app.include_router() 来声明所有这些,而不必修改原始的 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!"}
这样,原始的 APIRouter 将保持不变,因此我们仍然可以与组织中的其他项目共享同一个 app/internal/admin.py 文件。
结果是,在我们的应用程序中,来自 admin 模块的每个路径操作都将拥有
- 前缀
/admin。 - 标签
admin。 - 依赖项
get_token_header。 - 响应
418。🍵
但这只会影响我们应用程序中的那个 APIRouter,而不会影响任何其他使用它的代码。
所以,例如,其他项目可以使用相同的 APIRouter 但使用不同的身份验证方法。
包含一个路径操作¶
我们还可以直接将路径操作添加到 FastAPI 应用程序中。
这里我们这样做……只是为了证明我们可以 🤷
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!"}
它将正常工作,并与通过 app.include_router() 添加的所有其他路径操作一起工作。
非常技术性的细节
注意:这是一个非常技术性的细节,您可能可以直接跳过。
APIRouter 没有被“挂载 (mounted)”,它们没有与应用程序的其余部分隔离。
这是因为我们想将它们的路径操作包含在 OpenAPI 模式和用户界面中。
由于我们不能仅仅将它们隔离并独立于其余部分进行“挂载”,因此路径操作是“克隆”的(重新创建),而不是直接包含的。
在 pyproject.toml 中配置 entrypoint¶
由于您的 FastAPI app 对象位于 app/main.py 中,您可以像这样在 pyproject.toml 文件中配置 entrypoint
[tool.fastapi]
entrypoint = "app.main:app"
这相当于这样的导入
from app.main import app
这样 fastapi 命令就知道在哪里可以找到您的应用程序。
注意
您也可以将路径传递给该命令,例如
$ fastapi dev app/main.py
但你必须记得在每次调用 fastapi 命令时都传递正确的路径。
此外,其他工具可能无法找到它,例如 VS Code 扩展 或 FastAPI Cloud,因此建议在 pyproject.toml 中使用 entrypoint。
检查自动生成的 API 文档¶
现在,运行您的应用程序
$ fastapi dev
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
并在 http://127.0.0.1:8000/docs 打开文档。
您将看到自动生成的 API 文档,包括来自所有子模块的路径,使用正确的路径(和前缀)以及正确的标签

使用不同的 prefix 多次包含同一个路由器¶
您还可以使用 .include_router() 多次使用同一个路由器,使用不同的前缀。
例如,这对于在不同的前缀下暴露相同的 API 非常有用,例如 /api/v1 和 /api/latest。
这是一个您可能并不真正需要的高级用法,但如果需要,它就在那里。
在一个 APIRouter 中包含另一个 APIRouter¶
您可以像在 FastAPI 应用程序中包含 APIRouter 一样,使用以下方式在另一个 APIRouter 中包含一个 APIRouter
router.include_router(other_router)
请确保在将 router 包含到 FastAPI 应用程序之前执行此操作,以便 other_router 中的路径操作也被包含在内。