FAST API 多次包含同一路由器,不同 prefix

 

如果要构建应用程序或Web API,则很少将所有内容都放在一个文件中。

FastAPI提供了一种方便的工具,可在保持所有灵活性的同时构建应用程序。

信息

如果您来自Flask,那将相当于Flask的蓝图。

示例文件结构

假设您的文件结构如下:

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

小费

有两个__init__.py文件:每个目录或子目录一个。

这就是允许将代码从一个文件导入另一个文件的原因。

例如,app/main.py您可以输入以下内容:

from app.routers import items
  • app目录包含所有内容。
  • app目录有一个空文件app/__init__.py
    • 因此,该app目录是“ Python包”(“ Python模块”的集合)。
  • app目录还有一个app/main.py文件。
    • 由于它位于Python软件包目录中(因为存在一个文件__init__.py),因此它是该软件包的“模块”:app.main
  • 有一个子目录app/routers/
  • 子目录app/routers也有一个空文件__init__.py
    • 因此,它是一个“ Python子包”。
  • 该文件app/routers/items.py在旁边app/routers/__init__.py
    • 因此,这是一个子模块:app.routers.items
  • 该文件app/routers/users.py在旁边app/routers/__init__.py
    • 因此,这是一个子模块:app.routers.users

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

路径操作APIRouter

然后使用它声明路径操作

以与使用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}

您可以将其APIRouter视为“迷你FastAPI”类。

支持所有相同的选项。

所有相同的参数,响应,依赖性,标签等。

小费

在此示例中,变量称为router,但是您可以根据需要命名。

我们将把它包含APIrouter在主FastAPI应用程序中,但首先,让我们添加另一个APIRouter

另一个模块 APIRouter

假设您在的模块中还具有专用于处理应用程序中“项目”的端点app/routers/items.py

您具有以下路径操作

  • /items/
  • /items/{item_id}

与的结构相同app/routers/users.py

但是,可以说这一次我们比较懒。

而且我们不需要在每个路径操作中都显式地键入/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"}

添加一些自定义的tagsresponsesdependencies

我们既不添加前缀/items/也不在tags=["items"]以后添加它们。

但是,我们可以添加自定义tagsresponses将被应用到特定的路径操作

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

主要的 FastAPI

现在,让我们在中查看模块app/main.py

在这里导入和使用类FastAPI

这将是应用程序中将所有内容捆绑在一起的主文件。

进口 FastAPI

您可以像平常一样导入和创建一个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"}},
)

导入 APIRouter

但是这次我们没有直接在中添加路径操作FastAPI app

我们导入其他具有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"}},
)

由于该文件app/routers/items.py是同一Python包的一部分,因此我们可以使用“点符号”将其导入。

汇入的运作方式

这部分:

from .routers import items, users

手段:

  • 从此模块(文件app/main.py)所在的相同包(目录app/)开始...
  • 寻找子包routers(目录位于app/routers/)...
  • 然后从中导入子模块items(位于的文件app/routers/items.py)和users(位于的文件app/routers/users.py)...

该模块items将具有一个变量routeritems.router)。这与我们在文件中创建的相同app/routers/items.py这是一个APIRouter模块也一样users

我们也可以像这样导入它们:

from app.routers import items, users

信息

第一个版本是“相对导入”。

第二个版本是“绝对导入”。

要了解有关Python包和模块的更多信息,请阅读有关Modules的官方Python文档

避免名称冲突

我们将items直接导入子模块,而不是仅导入其变量router

这是因为我们router在子模块中还有另一个变量users

如果我们一个接一个地导入,例如:

from .routers.items import router
from .routers.users import router

routerusers将覆盖从一个items,我们将无法在同一时间使用它们。

因此,为了能够在同一个文件中使用它们,我们直接导入子模块:

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

包括一个 APIRouter

现在,包括router子模块中的users

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

信息

users.router包含APIRouter文件内部app/routers/users.py

随着app.include_router()我们可以添加APIRouter到主FastAPI应用程序。

它将包括来自该路由器的所有路由作为其一部分。

技术细节

实际上,它将为内部声明的每个路径操作内部创建一个路径操作APIRouter

因此,在幕后,它实际上将像一切都是同一单个应用程序一样工作。

校验

包括路由器时,您不必担心性能。

这将需要几微秒,并且只会在启动时发生。

因此,它不会影响性能。

包括APIRouterprefixtagsresponses,和dependencies

现在,让我们包括items子模块中的路由器

但是,请记住,我们很懒,没有对所有路径操作添加/items/也不添加吗?tags

我们可以添加一个前缀的所有路径操作使用参数prefixapp.include_router()

由于每个路径操作的路径都必须以开头/,例如:

@router.get("/{item_id}")
async def read_item(item_id: str):
    ...

...前缀不得包含final /

因此,在这种情况下,前缀为/items

我们还可以添加一个列表,tags列表将应用于此路由器中包括的所有路径操作

并且我们可以添加预定义responses,该预定义也将包含在所有路径操作中

我们可以添加一个列表,dependencies列表将被添加到路由器中的所有路径操作中,并将针对对它们的每个请求执行/解决。请注意,就像路径操作修饰符中的依赖项一样,任何值都不会传递给路径操作function

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

最终结果是项目路径现在为:

  • /items/
  • /items/{item_id}

...按照我们的意图。

  • 它们将被标记为包含单个字符串的标签列表"items"
  • 声明了标签路径操作"custom"将同时包含标签itemscustom
    • 这些“标签”对于自动交互式文档系统(使用OpenAPI)特别有用。
  • 所有这些都将包括预定义的responses
  • 声明自定义响应路径操作403将同时具有预定义的响应(404)和403声明中声明。
  • 所有这些路径操作将在其dependencies之前具有评估/执行的列表
    • 如果您还在特定的路径操作中声明依赖项它们也将被执行
    • 首先执行路由器相关性,然后dependencies在装饰器中执行,然后执行常规参数相关性。
    • 您还可以使用添加Security依赖项scopes

小费

dependencies一个装饰都可以使用,例如,要求身份验证整组的路径操作即使没有将依赖项单独添加到每个依赖项中。

校验

prefixtagsresponsesdependencies参数(如在许多其他情况下),从仅仅是一种功能FastAPI来帮助你避免代码重复。

小费

您也可以直接添加路径操作,例如使用:@app.get(...)

除了app.include_router(),在同一个FastAPI应用中。

它仍然可以正常工作。

非常技术细节

注意:这是一个非常技术性的细节,您可能可以跳过


所述APIRouters的不是“安装”,它们不从应用程序的其余部分隔离。

这是因为我们希望将它们的路径操作包括在OpenAPI架构和用户界面中。

由于我们不能仅仅隔离它们并独立于其余部分来“装载”它们,因此路径操作被“克隆”(重新创建),而不是直接包含在内。

检查自动API文档

现在,运行uvicorn,使用模块app.main和变量app

uvicorn app.main:app --reload
信息:在http://127.0.0.1:8000上运行的Uvicorn(按CTRL + C退出)

restart↻

然后在http://127.0.0.1:8000/docs中打开文档

您将看到使用正确的路径(和前缀)和正确的标记的自动API文档,包括来自所有子模块的路径:

多次包含同一路由器,不同 prefix

您也可以.include_router()同一路由器上使用不同的前缀多次使用。

例如,这可以用于以不同的前缀(例如/api/v1和)公开相同的API /api/latest

这是您可能真正不需要的高级用法,但是如果您有需要,可以使用。

posted @ 2020-06-27 22:28  DaisyLinux  阅读(7307)  评论(0编辑  收藏  举报