初始提交
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
import glob
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Callable, Sequence
|
||||
from enum import Enum
|
||||
from typing import Annotated, Any, Literal
|
||||
|
||||
from annotated_doc import Doc
|
||||
from fastapi import FastAPI, params
|
||||
from fastapi.datastructures import Default
|
||||
from fastapi.routing import APIRoute, APIRouter
|
||||
from fastapi.utils import generate_unique_id
|
||||
from starlette.responses import JSONResponse, Response
|
||||
from starlette.routing import BaseRoute
|
||||
from starlette.types import ASGIApp, Lifespan
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
class APIRouterPro(APIRouter):
|
||||
"""
|
||||
`APIRouterPro` class, inherited from the `APIRouter` class, it has all the functions of `APIRouter` and provides some additional parameter settings.
|
||||
`APIRouter` class, used to group *path operations*, for example to structure
|
||||
an app in multiple files. It would then be included in the `FastAPI` app, or
|
||||
in another `APIRouter` (ultimately included in the app).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/).
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from common.router import APIRouterPro, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
router = APIRouterPro()
|
||||
|
||||
|
||||
@router.get('/users/', tags=['users'])
|
||||
async def read_users():
|
||||
return [{'username': 'Rick'}, {'username': 'Morty'}]
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__( # noqa: PLR0913
|
||||
self,
|
||||
*,
|
||||
prefix: Annotated[str, Doc('An optional path prefix for the router.')] = '',
|
||||
order_num: Annotated[int, Doc('An optional order number for the router.')] = 100,
|
||||
auto_register: Annotated[bool, Doc('An optional auto register flag for the router.')] = True,
|
||||
tags: Annotated[
|
||||
list[str | Enum] | None,
|
||||
Doc(
|
||||
"""
|
||||
A list of tags to be applied to all the *path operations* in this
|
||||
router.
|
||||
|
||||
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
dependencies: Annotated[
|
||||
Sequence[params.Depends] | None,
|
||||
Doc(
|
||||
"""
|
||||
A list of dependencies (using `Depends()`) to be applied to all the
|
||||
*path operations* in this router.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
default_response_class: Annotated[
|
||||
type[Response],
|
||||
Doc(
|
||||
"""
|
||||
The default response class to be used.
|
||||
|
||||
Read more in the
|
||||
[FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class).
|
||||
"""
|
||||
),
|
||||
] = Default(JSONResponse),
|
||||
responses: Annotated[
|
||||
dict[int | str, dict[str, Any]] | None,
|
||||
Doc(
|
||||
"""
|
||||
Additional responses to be shown in OpenAPI.
|
||||
|
||||
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/).
|
||||
|
||||
And in the
|
||||
[FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
callbacks: Annotated[
|
||||
list[BaseRoute] | None,
|
||||
Doc(
|
||||
"""
|
||||
OpenAPI callbacks that should apply to all *path operations* in this
|
||||
router.
|
||||
|
||||
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
routes: Annotated[
|
||||
list[BaseRoute] | None,
|
||||
Doc(
|
||||
"""
|
||||
**Note**: you probably shouldn't use this parameter, it is inherited
|
||||
from Starlette and supported for compatibility.
|
||||
|
||||
---
|
||||
|
||||
A list of routes to serve incoming HTTP and WebSocket requests.
|
||||
"""
|
||||
),
|
||||
deprecated(
|
||||
"""
|
||||
You normally wouldn't use this parameter with FastAPI, it is inherited
|
||||
from Starlette and supported for compatibility.
|
||||
|
||||
In FastAPI, you normally would use the *path operation methods*,
|
||||
like `router.get()`, `router.post()`, etc.
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
redirect_slashes: Annotated[
|
||||
bool,
|
||||
Doc(
|
||||
"""
|
||||
Whether to detect and redirect slashes in URLs when the client doesn't
|
||||
use the same format.
|
||||
"""
|
||||
),
|
||||
] = True,
|
||||
default: Annotated[
|
||||
ASGIApp | None,
|
||||
Doc(
|
||||
"""
|
||||
Default function handler for this router. Used to handle
|
||||
404 Not Found errors.
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
dependency_overrides_provider: Annotated[
|
||||
Any | None,
|
||||
Doc(
|
||||
"""
|
||||
Only used internally by FastAPI to handle dependency overrides.
|
||||
|
||||
You shouldn't need to use it. It normally points to the `FastAPI` app
|
||||
object.
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
route_class: Annotated[
|
||||
type[APIRoute],
|
||||
Doc(
|
||||
"""
|
||||
Custom route (*path operation*) class to be used by this router.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Custom Request and APIRoute class](https://fastapi.tiangolo.com/how-to/custom-request-and-route/#custom-apiroute-class-in-a-router).
|
||||
"""
|
||||
),
|
||||
] = APIRoute,
|
||||
on_startup: Annotated[
|
||||
Sequence[Callable[[], Any]] | None,
|
||||
Doc(
|
||||
"""
|
||||
A list of startup event handler functions.
|
||||
|
||||
You should instead use the `lifespan` handlers.
|
||||
|
||||
Read more in the [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
on_shutdown: Annotated[
|
||||
Sequence[Callable[[], Any]] | None,
|
||||
Doc(
|
||||
"""
|
||||
A list of shutdown event handler functions.
|
||||
|
||||
You should instead use the `lifespan` handlers.
|
||||
|
||||
Read more in the
|
||||
[FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
# the generic to Lifespan[AppType] is the type of the top level application
|
||||
# which the router cannot know statically, so we use typing.Any
|
||||
lifespan: Annotated[
|
||||
Lifespan[Any] | None,
|
||||
Doc(
|
||||
"""
|
||||
A `Lifespan` context manager handler. This replaces `startup` and
|
||||
`shutdown` functions with a single context manager.
|
||||
|
||||
Read more in the
|
||||
[FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
deprecated: Annotated[
|
||||
bool | None,
|
||||
Doc(
|
||||
"""
|
||||
Mark all *path operations* in this router as deprecated.
|
||||
|
||||
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
include_in_schema: Annotated[
|
||||
bool,
|
||||
Doc(
|
||||
"""
|
||||
To include (or not) all the *path operations* in this router in the
|
||||
generated OpenAPI.
|
||||
|
||||
This affects the generated OpenAPI (e.g. visible at `/docs`).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
|
||||
"""
|
||||
),
|
||||
] = True,
|
||||
generate_unique_id_function: Annotated[
|
||||
Callable[[APIRoute], str],
|
||||
Doc(
|
||||
"""
|
||||
Customize the function used to generate unique IDs for the *path
|
||||
operations* shown in the generated OpenAPI.
|
||||
|
||||
This is particularly useful when automatically generating clients or
|
||||
SDKs for your API.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
|
||||
"""
|
||||
),
|
||||
] = Default(generate_unique_id),
|
||||
) -> None:
|
||||
self.order_num = order_num
|
||||
self.auto_register = auto_register
|
||||
super().__init__(
|
||||
prefix=prefix,
|
||||
tags=tags,
|
||||
dependencies=dependencies,
|
||||
default_response_class=default_response_class,
|
||||
responses=responses,
|
||||
callbacks=callbacks,
|
||||
routes=routes,
|
||||
redirect_slashes=redirect_slashes,
|
||||
default=default,
|
||||
dependency_overrides_provider=dependency_overrides_provider,
|
||||
route_class=route_class,
|
||||
on_startup=on_startup,
|
||||
on_shutdown=on_shutdown,
|
||||
lifespan=lifespan,
|
||||
deprecated=deprecated,
|
||||
include_in_schema=include_in_schema,
|
||||
generate_unique_id_function=generate_unique_id_function,
|
||||
)
|
||||
|
||||
|
||||
class RouterRegister:
|
||||
"""
|
||||
路由注册器,用于自动注册所有controller目录下的路由
|
||||
"""
|
||||
|
||||
def __init__(self, app: FastAPI) -> None:
|
||||
"""
|
||||
初始化路由注册器
|
||||
|
||||
:param app: FastAPI对象
|
||||
"""
|
||||
self.app = app
|
||||
# 获取项目根目录
|
||||
self.project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
sys.path.insert(0, self.project_root)
|
||||
|
||||
def _find_controller_files(self) -> list[str]:
|
||||
"""
|
||||
查找所有controller目录下的py文件
|
||||
|
||||
:return: py文件路径列表
|
||||
"""
|
||||
pattern = os.path.join(self.project_root, '*', 'controller', '[!_]*.py')
|
||||
return sorted(glob.glob(pattern))
|
||||
|
||||
def _import_module_and_get_routers(self, controller_files: list[str]) -> list[tuple[str, APIRouter]]:
|
||||
"""
|
||||
导入模块并获取路由实例
|
||||
|
||||
:param controller_files: controller目录下的py文件路径列表
|
||||
:return: 路由实例列表
|
||||
"""
|
||||
routers = []
|
||||
for file_path in controller_files:
|
||||
# 计算模块路径
|
||||
relative_path = os.path.relpath(file_path, self.project_root)
|
||||
module_name = relative_path.replace(os.sep, '.')[:-3]
|
||||
|
||||
# 动态导入模块
|
||||
module = importlib.import_module(module_name)
|
||||
# 直接遍历模块__dict__,只检查模块自身定义的属性
|
||||
for attr_name, attr in module.__dict__.items():
|
||||
# 对于APIRouterPro实例,只有当auto_register=True时才添加
|
||||
if isinstance(attr, APIRouterPro):
|
||||
if attr.auto_register:
|
||||
routers.append((attr_name, attr))
|
||||
# 对于APIRouter实例,直接添加
|
||||
elif isinstance(attr, APIRouter):
|
||||
routers.append((attr_name, attr))
|
||||
return routers
|
||||
|
||||
def _sort_routers(self, routers: list[tuple[str, APIRouter]]) -> list[tuple[str, APIRouter]]:
|
||||
"""
|
||||
按规则排序路由
|
||||
|
||||
:param routers: 路由实例列表
|
||||
:return: 排序后的路由实例列表
|
||||
"""
|
||||
|
||||
# 按规则排序路由
|
||||
def sort_key(item: tuple[str, APIRouter]) -> tuple[Literal[0], int, str] | tuple[Literal[1], str]:
|
||||
attr_name, router = item
|
||||
# APIRouterPro实例按order_num排序,序号越小越靠前
|
||||
if isinstance(router, APIRouterPro):
|
||||
return (0, router.order_num, attr_name)
|
||||
# APIRouter实例按变量名首字母排序
|
||||
return (1, attr_name)
|
||||
|
||||
return sorted(routers, key=sort_key)
|
||||
|
||||
def _register_routers_to_app(self, routers: list[tuple[str, APIRouter]]) -> None:
|
||||
"""
|
||||
将路由注册到FastAPI应用
|
||||
|
||||
:param routers: 排序后的路由实例列表
|
||||
:return: None
|
||||
"""
|
||||
for _attr_name, router in routers:
|
||||
self.app.include_router(router=router)
|
||||
|
||||
def register_routers(self) -> None:
|
||||
"""
|
||||
自动注册所有controller目录下的路由
|
||||
|
||||
:return: None
|
||||
"""
|
||||
# 查找所有controller目录下的py文件
|
||||
controller_files = self._find_controller_files()
|
||||
# 导入模块并获取路由实例
|
||||
routers = self._import_module_and_get_routers(controller_files)
|
||||
# 按规则排序路由
|
||||
sorted_routers = self._sort_routers(routers)
|
||||
# 注册路由到FastAPI应用
|
||||
self._register_routers_to_app(sorted_routers)
|
||||
|
||||
|
||||
def auto_register_routers(app: FastAPI) -> None:
|
||||
"""
|
||||
自动注册所有controller目录下的路由
|
||||
|
||||
:param app: FastAPI对象
|
||||
:return: None
|
||||
"""
|
||||
# 使用路由注册器进行注册
|
||||
router_register = RouterRegister(app)
|
||||
router_register.register_routers()
|
||||
Reference in New Issue
Block a user