You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 lines
5.2 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

from collections.abc import Mapping
from datetime import date, datetime, time
from typing import Any
from fastapi import status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
from pydantic import BaseModel, Field
from starlette.background import BackgroundTask
from app.common.constant import DATE_DISPLAY_FMT, DATETIME_DISPLAY_FMT, RET, TIME_DISPLAY_FMT
# 裸 datetime/date/time未走 Pydantic 的 dict 等JSON 输出与 constant 中展示格式一致
_JSON_DATETIME_CUSTOM_ENCODER: dict[type[Any], Any] = {
datetime: lambda d: d.strftime(DATETIME_DISPLAY_FMT),
date: lambda d: d.strftime(DATE_DISPLAY_FMT),
time: lambda t: t.strftime(TIME_DISPLAY_FMT),
}
def jsonable_response_content(content: Any) -> Any:
return jsonable_encoder(content, custom_encoder=_JSON_DATETIME_CUSTOM_ENCODER)
class ResponseSchema[T](BaseModel):
"""响应模型"""
code: int = Field(default=RET.OK.code, description="业务状态码")
msg: str = Field(default=RET.OK.msg, description="响应消息")
data: T | None = Field(default=None, description="响应数据")
status_code: int = Field(default=status.HTTP_200_OK, description="HTTP状态码")
success: bool = Field(default=True, description="操作是否成功")
class SuccessResponse(JSONResponse):
"""成功响应类"""
def __init__(
self,
data: Any | None = None,
msg: str = RET.OK.msg,
code: int = RET.OK.code,
status_code: int = status.HTTP_200_OK,
success: bool = True,
) -> None:
"""
初始化成功响应类
参数:
- data (Any | None): 响应数据。
- msg (str): 响应消息。
- code (int): 业务状态码。
- status_code (int): HTTP 状态码。
- success (bool): 操作是否成功。
返回:
- None
"""
content = ResponseSchema(
code=code,
msg=msg,
data=data,
status_code=status_code,
success=success,
).model_dump()
super().__init__(content=jsonable_response_content(content), status_code=status_code)
self.headers["Content-Type"] = "application/json; charset=utf-8"
class ErrorResponse(JSONResponse):
"""错误响应类"""
def __init__(
self,
data: Any = None,
msg: str = RET.ERROR.msg,
code: int = RET.ERROR.code,
status_code: int = status.HTTP_400_BAD_REQUEST,
success: bool = False,
) -> None:
"""
初始化错误响应类
参数:
- data (Any): 响应数据。
- msg (str): 响应消息。
- code (int): 业务状态码。
- status_code (int): HTTP 状态码。
- success (bool): 操作是否成功。
返回:
- None
"""
content = ResponseSchema(
code=code,
msg=msg,
data=data,
status_code=status_code,
success=success,
).model_dump()
super().__init__(content=jsonable_response_content(content), status_code=status_code)
self.headers["Content-Type"] = "application/json; charset=utf-8"
class StreamResponse(StreamingResponse):
"""流式响应类"""
def __init__(
self,
data: Any = None,
status_code: int = status.HTTP_200_OK,
headers: Mapping[str, str] | None = None,
media_type: str | None = None,
background: BackgroundTask | None = None,
) -> None:
"""
初始化流式响应类
参数:
- data (Any): 响应数据。
- status_code (int): HTTP 状态码。
- headers (Mapping[str, str] | None): 响应头。
- media_type (str | None): 媒体类型。
- background (BackgroundTask | None): 后台任务。
返回:
- None
"""
super().__init__(
content=data,
status_code=status_code,
media_type=media_type, # 文件类型
headers=headers, # 文件名
background=background, # 文件大小
)
class UploadFileResponse(FileResponse):
"""
文件响应
"""
def __init__(
self,
file_path: str,
filename: str,
media_type: str = "application/octet-stream",
headers: Mapping[str, str] | None = None,
background: BackgroundTask | None = None,
status_code: int = 200,
) -> None:
"""
初始化文件响应类
参数:
- file_path (str): 文件路径。
- filename (str): 文件名。
- media_type (str): 文件类型。
- headers (Mapping[str, str] | None): 响应头。
- background (BackgroundTask | None): 后台任务。
- status_code (int): HTTP 状态码。
返回:
- None
"""
super().__init__(
path=file_path,
status_code=status_code,
headers=headers,
media_type=media_type,
background=background,
filename=filename,
stat_result=None,
method=None,
content_disposition_type="attachment",
)