FastAPI 教程翻译 - 用户指南 30 - 更大的应用程序 - 多个文件
FastAPI Tutorial - User Guide - Bigger Applications - Multiple Files
If you are building an application or a web API, it’s rarely the case that you can put everything on a single file.
如果要构建应用程序或 Web API,则很少将所有内容都放在一个文件中。
FastAPI provides a convenience tool to structure your application while keeping all the flexibility.
FastAPI 提供了一种方便的工具,可在保持所有灵活性的同时构建应用程序。
Info
信息
If you come from Flask, this would be the equivalent of Flask’s Blueprints.
如果您来自 Flask,那将相当于 Flask 的蓝图。
An example file structure
文件结构示例
Let’s say you have a file structure like this:
假设您有一个像这样的文件结构:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ └── routers
│ ├── __init__.py
│ ├── items.py
│ └── users.py
Tip
提示
There are two
__init__.py
files: one in each directory or subdirectory.有两个
__init__.py
文件:每个目录或子目录一个。This is what allows importing code from one file into another.
这就是将代码从一个文件导入另一个文件的方法。
For example, in
app/main.py
you could have a line like:例如,在
app/main.py
中,您可以有如下一行:from app.routers import items
-
The
app
directory contains everything.app
目录包含所有内容。 -
This
app
directory has an empty fileapp/__init__.py
.这个
app
目录有一个空文件app/__init__.py
。-
So, the
app
directory is a “Python package” (a collection of “Python modules”).因此,
app
目录是『Python 包』(『Python 模块』的集合)。
-
-
The
app
directory also has aapp/main.py
file.app
目录中还有一个app/main.py
文件。-
As it is inside a Python package directory (because there’s a file
__init__.py
), it is a “module” of that package:app.main
.由于它位于 Python 软件包目录中(因为存在文件
__init__.py
),因此它是该软件包的『模块』:app.main
。
-
-
There’s a subdirectory
app/routers/
.有一个子目录
app/routers/
。 -
The subdirectory
app/routers
also has an empty file__init__.py
.子目录
app/routers
中还有一个空文件__init__.py
。-
So, it is a “Python subpackage”.
因此,它是一个『Python 子包』。
-
-
The file
app/routers/items.py
is beside theapp/routers/__init__.py
.文件
app/routers/items.py
位于app/routers/__init__.py
旁边。-
So, it’s a submodule:
app.routers.items
.因此,这是一个子模块:
app.routers.items
。
-
-
The file
app/routers/users.py
is beside theapp/routers/__init__.py
.文件
app/routers/users.py
位于app/routers/__init__.py
旁边。-
So, it’s a submodule:
app.routers.users
.因此,它是一个子模块:
app.routers.users
。
-
APIRouter
Let’s say the file dedicated to handling just users is the submodule at /app/routers/users.py
.
假设专门用于处理用户的文件是 /app/routers/users.py
中的子模块。
You want to have the path operations related to your users separated from the rest of the code, to keep it organized.
您希望将与用户相关的路径操作与其余代码分开,以使其井井有条。
But it’s still part of the same FastAPI application/web API (it’s part of the same “Python Package”).
但它仍然是相同的 FastAPI 应用程序 / web API 的一部分(它是同一『Python 包』的一部分)。
You can create the path operations for that module using APIRouter
.
您可以使用 APIRouter
为该模块创建路径操作。
Import APIRouter
导入 APIRouter
You import it and create an “instance” the same way you would with the class FastAPI
:
您可以导入它并以与 FastAPI
类相同的方式创建一个『实例』:
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Foo"}, {"username": "Bar"}]
@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}
Path operations with APIRouter
使用 APIRouter
进行路径操作
And then you use it to declare your path operations.
然后,您可以使用它来声明路径操作。
Use it the same way you would use the FastAPI
class:
以与使用 FastAPI
类相同的方式使用它:
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Foo"}, {"username": "Bar"}]
@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}
You can think of APIRouter
as a "mini FastAPI
" class.
您可以将 APIRouter
视为『小型 FastAPI
』类。
All the same options are supported.
支持所有相同的选项。
All the same parameters, responses, dependencies, tags, etc.
所有相同的参数、响应、依赖性、标签等。
Tip
提示
In this example, the variable is called
router
, but you can name it however you want.在此示例中,该变量称为
router
,但您可以根据需要命名。
We are going to include this APIrouter
in the main FastAPI
app, but first, let’s add another APIRouter
.
我们将在主 FastAPI
应用中包含该 APIrouter
,但首先,让我们添加另一个 APIRouter
。
Another module with APIRouter
另一个带有 APIRouter
的模块
Let’s say you also have the endpoints dedicated to handling “Items” from your application in the module at app/routers/items.py
.
假设您在模块 app/routers/items.py
中还具有专用于处理应用程序中『项目』的端点。
You have path operations for:
您具有路径操作:
/items/
/items/{item_id}
It’s all the same structure as with app/routers/users.py
.
与 app/routers/users.py
的结构相同。
But let’s say that this time we are more lazy.
但是,可以说这一次我们比较懒。
And we don’t want to have to explicitly type /items/
and tags=["items"]
in every path operation (we will be able to do it later):
而且,我们不需要在每个路径操作中显式地键入 /items/
和 tags=["items"]
(稍后我们将可以这样做):
from fastapi import APIRouter, HTTPException
router = APIRouter()
@router.get("/")
async def read_items():
return [{"name": "Item Foo"}, {"name": "item Bar"}]
@router.get("/{item_id}")
async def read_item(item_id: str):
return {"name": "Fake Specific Item", "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 != "foo":
raise HTTPException(status_code=403, detail="You can only update the item: foo")
return {"item_id": item_id, "name": "The Fighters"}
Add some custom tags
, responses
, and dependencies
添加一些自定义的 tags
、responses
和 dependencies
We are not adding the prefix /items/
nor the tags=["items"]
to add them later.
我们没有添加前缀 /items/
或 tags=["items"]
以便以后添加它们。
But we can add custom tags
and responses
that will be applied to a specific path operation:
但是我们可以添加自定义的 tags
和 responses
,这些标签将应用于特定的路径操作:
from fastapi import APIRouter, HTTPException
router = APIRouter()
@router.get("/")
async def read_items():
return [{"name": "Item Foo"}, {"name": "item Bar"}]
@router.get("/{item_id}")
async def read_item(item_id: str):
return {"name": "Fake Specific Item", "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 != "foo":
raise HTTPException(status_code=403, detail="You can only update the item: foo")
return {"item_id": item_id, "name": "The Fighters"}
The main FastAPI
主要的FastAPI
Now, let’s see the module at app/main.py
.
现在,让我们在 app/main.py
处查看该模块。
Here’s where you import and use the class FastAPI
.
在这里导入并使用 FastAPI
类。
This will be the main file in your application that ties everything together.
这将是应用程序中将所有内容捆绑在一起的主文件。
Import FastAPI
导入 FastAPI
You import and create a FastAPI
class as normally:
您可以像往常一样导入并创建一个 FastAPI
类:
from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
Import the APIRouter
导入 APIRouter
But this time we are not adding path operations directly with the FastAPI
app
.
但这一次我们不是直接在 FastAPI
app
中添加路径操作:
We import the other submodules that have APIRouter
s:
我们导入其他具有 APIRouters
的子模块:
from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
As the file app/routers/items.py
is part of the same Python package, we can import it using “dot notation”.
由于文件 app/routers/items.py
是同一 Python 包的一部分,因此我们可以使用『点符号』将其导入。
How the importing works
导入方式
The section:
这部分:
from .routers import items, users
Means:
意味着:
-
Starting in the same package that this module (the file
app/main.py
) lives in (the directoryapp/
)…从与此模块(文件
app/main.py
)所在的相同包(目录app/
)开始…… -
look for the subpackage
routers
(the directory atapp/routers/
)…寻找子包
routers
(目录位于app/routers/
)…… -
and from it, import the submodule
items
(the file atapp/routers/items.py
) andusers
(the file atapp/routers/users.py
)…然后从其中导入子模块
items
(位于app/routers/items.py
的文件)和users
(位于app/routers/users.py
的文件)……
The module items
will have a variable router
(items.router
). This is the same one we created in the file app/routers/items.py
. It’s an APIRouter
. The same for the module users
.
模块 items
将具有变量 router
(items.router
)。这与我们在文件 app/routers/items.py
中创建的相同。这是一个 APIRouter
。用户模块也一样。
We could also import them like:
我们也可以像这样导入它们:
from app.routers import items, users
Info
信息
The first version is a “relative import”.
第一个版本是『相对导入』。
The second version is an “absolute import”.
第二个版本是『绝对导入』。
To learn more about Python Packages and Modules, read the official Python documentation about Modules.
要了解有关 Python 软件包和模块的更多信息,请阅读 有关模块的 Python 官方文档。
Avoid name collisions
避免名称冲突
We are importing the submodule items
directly, instead of importing just its variable router
.
我们将直接导入子模块 items
,而不是仅导入其变量 router
。
This is because we also have another variable named router
in the submodule users
.
这是因为我们在子模块 users
中还有另一个名为 router
的变量。
If we had imported one after the other, like:
如果我们一个接一个地导入,例如:
from .routers.items import router
from .routers.users import router
The router
from users
would overwrite the one from items
and we wouldn’t be able to use them at the same time.
来自 users
的 router
将覆盖 items
中的 router
,我们将无法同时使用它们。
So, to be able to use both of them in the same file, we import the submodules directly:
因此,为了能够在同一个文件中使用它们,我们直接导入子模块:
from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
Include an APIRouter
包含一个 APIRouter
Now, let’s include the router
from the submodule users
:
现在,让我们从子模块 users
中包含 router
:
from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
Info
信息
users.router
contains theAPIRouter
inside of the fileapp/routers/users.py
.
users.router
在文件app/routers/users.py
中包含APIRouter
。
With app.include_router()
we can add an APIRouter
to the main FastAPI
application.
通过 app.include_router()
,我们可以将 APIRouter
添加到主 FastAPI
应用中。
It will include all the routes from that router as part of it.
它将包括来自该路由器的所有路由作为其一部分。
Technical Details
技术细节
It will actually internally create a path operation for each path operation that was declared in the
APIRouter
.实际上,它将为内部在
APIRouter
中声明的每个路径操作创建一个路径操作。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 an APIRouter
with a prefix
, tags
, responses
, and dependencies
包括带有 prefix
、tags
、responses
和 dependencies
的 APIRouter
Now, let’s include the router from the items
submodule.
现在,让我们包括来自 items
子模块的路由器。
But, remember that we were lazy and didn’t add /items/
nor tags
to all the path operations?
但是,请记住我们很懒,没有为所有路径操作添加 /items/
或 tags
吗?
We can add a prefix to all the path operations using the parameter prefix
of app.include_router()
.
我们可以使用 app.include_router()
的参数 prefix
为所有路径操作添加前缀。
As the path of each path operation has to start with /
, like in:
由于每个路径操作的路径都必须以 /
开头,例如:
@router.get("/{item_id}")
async def read_item(item_id: str):
...
…the prefix must not include a final /
.
…… 前缀不得包含最后的 /
。
So, the prefix in this case would be /items
.
因此,这种情况下的前缀为 /items
。
We can also add a list of tags
that will be applied to all the path operations included in this router.
我们还可以添加一个 tags
列表,该列表将应用于此路由器中包括的所有路径操作。
And we can add predefined responses
that will be included in all the path operations too.
我们还可以添加预定义的 responses
,这些响应也将包含在所有路径操作中。
And we can add a list of dependencies
that will be added to all the path operations in the router and will be executed/solved for each request made to them. Note that, much like dependencies in path operation decorators, no value will be passed to your path operation function.
我们可以添加一个 dependencies
列表,该列表将被添加到路由器中的所有路径操作中,并将针对对它们的每个请求执行 / 解决。请注意,就像路径操作装饰器中的依赖项一样,任何值都不会传递给路径操作函数。
from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
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"
.它们将被标记为包含单个字符串
『items』
的标签列表。 -
The path operation that declared a
"custom"
tag will have both tags,items
andcustom
.声明了
『custom』
标签的路径操作将同时具有items
和custom
两个标签。-
These “tags” are especially useful for the automatic interactive documentation systems (using OpenAPI).
这些『标签』对于自动交互式文档系统(使用 OpenAPI)特别有用。
-
-
All of them will include the predefined
responses
.所有这些都将包含预定义的
responses
。 -
The path operation that declared a custom
403
response will have both the predefined responses (404
) and the403
declared in it directly.声明了自定义
403
响应的路径操作将同时具有预定义的响应(404
)和直接声明的403
。 -
All these path operations will have the list of
dependencies
evaluated/executed before them.所有这些路径操作将在其之前评估 / 执行
dependencies
列表。-
If you also declare dependencies in a specific path operation, they will be executed too.
如果您还在特定的路径操作中声明依赖项,则它们也将被执行。
-
The router dependencies are executed first, then the
dependencies
in the decorator, and then the normal parameter dependencies.首先执行路由器相关性,然后执行 装饰器中的
dependencies
,然后执行常规参数依赖项。 -
You can also add
Security
dependencies withscopes
.您还可以通过
scopes
添加Security
依赖项。
-
Tip
提示
Having
dependencies
in a decorator can be used, for example, to require authentication for a whole group of path operations. Even if the dependencies are not added individually to each one of them.例如,可以使用在修饰器中具有
dependencies
来要求对整个路径操作组进行身份验证。即使没有将依赖项单独添加到每个依赖项中。
Check
检查
The
prefix
,tags
,responses
anddependencies
parameters are (as in many other cases) just a feature from FastAPI to help you avoid code duplication.
prefix
、tags
、responses
和dependencies
参数(在许多其他情况下)只是 FastAPI 中的一项功能,可帮助您避免代码重复。
Tip
提示
You could also add path operations directly, for example with:
@app.get(...)
.您也可以直接添加路径操作,例如:
@app.get(...)
。Apart from
app.include_router()
, in the same FastAPI app.在同一 FastAPI 应用中,除了
app.include_router()
。It would still work the same.
仍然可以使用。
Very Technical Details
深度技术细节
Note: this is a very technical detail that you probably can just skip.
注意:这是一个深度技术细节,您可能可以跳过它。
The
APIRouter
s are not “mounted”, they are not isolated from the rest of the application.
APIRouter
没有被『挂载』,它们与应用程序的其余部分没有隔离。This is because we want to include their path operations in the OpenAPI schema and the user interfaces.
这是因为我们要在 OpenAPI 架构和用户界面中包括它们的路径操作。
As we cannot just isolate them and “mount” them independently of the rest, the path operations are “cloned” (re-created), not included directly.
由于我们不能仅将它们隔离并独立于其余部分来『挂载』,因此路径操作是『克隆的』(重新创建),而不是直接包含在内。
Check the automatic API docs
检查自动 API 文档
Now, run uvicorn
, using the module app.main
and the variable app
:
现在,使用模块 app.main
和变量 app
,运行 uvicorn
:
uvicorn app.main:app --reload
And open the docs at http://127.0.0.1:8000/docs.
并在 http://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:
您将看到使用正确的路径(和前缀)和正确的标签的自动 API 文档,包括所有子模块的路径:
Include the same router multiple times with different prefix
多次使用不同的 prefix
添加同一路由器
You can also use .include_router()
multiple times with the same router using different prefixes.
您还可以给同一路由器使用不同的前缀多次使用 .include_router()
。
This could be useful, for example, to expose the same API under different prefixes, e.g. /api/v1
and /api/latest
.
例如,在不同的前缀下公开相同的 API,/api/v1
和 /api/latest
。
This is an advanced usage that you might not really need, but it’s there in case you do.
.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:
您将看到使用正确的路径(和前缀)和正确的标签的自动 API 文档,包括所有子模块的路径:
[外链图片转存中…(img-4F6ZjDe3-1591541294584)]
Include the same router multiple times with different prefix
多次使用不同的 prefix
添加同一路由器
You can also use .include_router()
multiple times with the same router using different prefixes.
您还可以给同一路由器使用不同的前缀多次使用 .include_router()
。
This could be useful, for example, to expose the same API under different prefixes, e.g. /api/v1
and /api/latest
.
例如,在不同的前缀下公开相同的 API,/api/v1
和 /api/latest
。
This is an advanced usage that you might not really need, but it’s there in case you do.
这是您可能真正不需要的高级用法,但是如果您有需要,可以使用。