From 01c28dace4da9717493abe28dd3df69788b4fa18 Mon Sep 17 00:00:00 2001 From: ktianc Date: Mon, 17 Jul 2023 15:53:19 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=E4=BE=9D=E8=B5=96=E5=8C=85=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=EF=BC=8Cfastapi=E5=8D=87=E7=BA=A7=E5=88=B0=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E7=89=88=E6=9C=AC0.100,=20pydantic=E5=8D=87=E7=BA=A7?= =?UTF-8?q?=E5=88=B02.0=E7=89=88=E6=9C=AC=EF=BC=8C=E5=A4=9A=E5=A4=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=9C=89=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kinit-api/application/settings.py | 2 +- kinit-api/apps/vadmin/auth/crud.py | 34 ++-- kinit-api/apps/vadmin/auth/params/role.py | 9 +- kinit-api/apps/vadmin/auth/params/user.py | 10 +- kinit-api/apps/vadmin/auth/schemas/menu.py | 68 ++++---- kinit-api/apps/vadmin/auth/schemas/role.py | 25 ++- kinit-api/apps/vadmin/auth/schemas/user.py | 72 ++++---- kinit-api/apps/vadmin/auth/utils/current.py | 3 +- .../apps/vadmin/auth/utils/validation/auth.py | 2 +- .../vadmin/auth/utils/validation/login.py | 16 +- kinit-api/apps/vadmin/auth/views.py | 2 +- kinit-api/apps/vadmin/help/schemas/issue.py | 24 ++- .../vadmin/help/schemas/issue_category.py | 24 ++- .../apps/vadmin/help/schemas/issue_m2m.py | 18 +- kinit-api/apps/vadmin/record/crud.py | 3 +- kinit-api/apps/vadmin/record/params/login.py | 11 +- .../apps/vadmin/record/params/operation.py | 9 +- kinit-api/apps/vadmin/record/schemas/login.py | 41 ++--- .../apps/vadmin/record/schemas/operation.py | 42 +++-- kinit-api/apps/vadmin/record/schemas/sms.py | 14 +- kinit-api/apps/vadmin/system/crud.py | 19 +-- kinit-api/apps/vadmin/system/schemas/dict.py | 31 ++-- .../apps/vadmin/system/schemas/settings.py | 17 +- .../vadmin/system/schemas/settings_tab.py | 8 +- kinit-api/apps/vadmin/system/schemas/task.py | 19 +-- kinit-api/apps/vadmin/system/views.py | 4 +- kinit-api/core/crud.py | 23 +-- kinit-api/core/data_types.py | 154 +++++++++++------- kinit-api/core/dependencies.py | 7 +- kinit-api/requirements.txt | 12 +- kinit-api/utils/excel/write_xlsx.py | 3 +- kinit-api/utils/ip_manage.py | 20 +-- 32 files changed, 375 insertions(+), 371 deletions(-) diff --git a/kinit-api/application/settings.py b/kinit-api/application/settings.py index 49447a9..c53a12b 100644 --- a/kinit-api/application/settings.py +++ b/kinit-api/application/settings.py @@ -11,7 +11,7 @@ from fastapi.security import OAuth2PasswordBearer """ 系统版本 """ -VERSION = "1.9.3" +VERSION = "1.10.0" """安全警告: 不要在生产中打开调试运行!""" DEBUG = True diff --git a/kinit-api/apps/vadmin/auth/crud.py b/kinit-api/apps/vadmin/auth/crud.py index 58c64a2..f3695b2 100644 --- a/kinit-api/apps/vadmin/auth/crud.py +++ b/kinit-api/apps/vadmin/auth/crud.py @@ -6,7 +6,7 @@ # @IDE : PyCharm # @desc : 增删改查 -from typing import List, Any +from typing import Any from aioredis import Redis from fastapi import UploadFile from sqlalchemy.orm import joinedload @@ -61,7 +61,7 @@ class UserDal(DalBase): password = data.telephone[5:12] if settings.DEFAULT_PASSWORD == "0" else settings.DEFAULT_PASSWORD data.password = self.model.get_password_hash(password) data.avatar = data.avatar if data.avatar else settings.DEFAULT_AVATAR - obj = self.model(**data.dict(exclude={'role_ids'})) + obj = self.model(**data.model_dump(exclude={'role_ids'})) if data.role_ids: roles = await RoleDal(self.db).get_datas(limit=0, id=("in", data.role_ids), v_return_objs=True) for role in roles: @@ -206,7 +206,7 @@ class UserDal(DalBase): "error_url": im.generate_error_url() } - async def init_password(self, ids: List[int]): + async def init_password(self, ids: list[int]): """ 初始化所选用户密码 将用户密码改为系统默认密码,并将初始化密码状态改为false @@ -226,7 +226,7 @@ class UserDal(DalBase): await self.db.flush() return result - async def init_password_send_sms(self, ids: List[int], rd: Redis): + async def init_password_send_sms(self, ids: list[int], rd: Redis): """ 初始化所选用户密码并发送通知短信 将用户密码改为系统默认密码,并将初始化密码状态改为false @@ -248,7 +248,7 @@ class UserDal(DalBase): user["send_sms_msg"] = e.msg return result - async def init_password_send_email(self, ids: List[int], rd: Redis): + async def init_password_send_email(self, ids: list[int], rd: Redis): """ 初始化所选用户密码并发送通知邮件 将用户密码改为系统默认密码,并将初始化密码状态改为false @@ -301,7 +301,7 @@ class UserDal(DalBase): await self.flush(user) return True - async def delete_datas(self, ids: List[int], v_soft: bool = False, **kwargs): + async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs): """ 删除多个用户,软删除 删除后清空所关联的角色 @@ -330,7 +330,7 @@ class RoleDal(DalBase): v_schema: Any = None ): """创建数据""" - obj = self.model(**data.dict(exclude={'menu_ids'})) + obj = self.model(**data.model_dump(exclude={'menu_ids'})) menus = await MenuDal(db=self.db).get_datas(limit=0, id=("in", data.menu_ids), v_return_objs=True) if data.menu_ids: for menu in menus: @@ -370,9 +370,9 @@ class RoleDal(DalBase): """获取选择数据,全部数据""" sql = select(self.model) queryset = await self.db.execute(sql) - return [schemas.RoleSelectOut.from_orm(i).dict() for i in queryset.scalars().all()] + return [schemas.RoleSelectOut.model_validate(i).model_dump() for i in queryset.scalars().all()] - async def delete_datas(self, ids: List[int], v_soft: bool = False, **kwargs): + async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs): """ 删除多个角色,硬删除 如果存在用户关联则无法删除 @@ -442,7 +442,7 @@ class MenuDal(DalBase): menus = self.generate_router_tree(datas, roots) return self.menus_order(menus) - def generate_router_tree(self, menus: List[models.VadminMenu], nodes: filter, name: str = "") -> list: + def generate_router_tree(self, menus: list[models.VadminMenu], nodes: filter, name: str = "") -> list: """ 生成路由树 @@ -452,16 +452,16 @@ class MenuDal(DalBase): """ data = [] for root in nodes: - router = schemas.RouterOut.from_orm(root) + router = schemas.RouterOut.model_validate(root) router.name = name + "".join(name.capitalize() for name in router.path.split("/")) router.meta = schemas.Meta(title=root.title, icon=root.icon, hidden=root.hidden, alwaysShow=root.alwaysShow) if root.menu_type == "0": sons = filter(lambda i: i.parent_id == root.id, menus) router.children = self.generate_router_tree(menus, sons, router.name) - data.append(router.dict()) + data.append(router.model_dump()) return data - def generate_tree_list(self, menus: List[models.VadminMenu], nodes: filter) -> list: + def generate_tree_list(self, menus: list[models.VadminMenu], nodes: filter) -> list: """ 生成菜单树列表 @@ -470,14 +470,14 @@ class MenuDal(DalBase): """ data = [] for root in nodes: - router = schemas.TreeListOut.from_orm(root) + router = schemas.TreeListOut.model_validate(root) if root.menu_type == "0" or root.menu_type == "1": sons = filter(lambda i: i.parent_id == root.id, menus) router.children = self.generate_tree_list(menus, sons) - data.append(router.dict()) + data.append(router.model_dump()) return data - def generate_tree_options(self, menus: List[models.VadminMenu], nodes: filter) -> list: + def generate_tree_options(self, menus: list[models.VadminMenu], nodes: filter) -> list: """ 生成菜单树选择项 @@ -504,7 +504,7 @@ class MenuDal(DalBase): item[children] = sorted(item[children], key=lambda menu: menu[order]) return result - async def delete_datas(self, ids: List[int], v_soft: bool = False, **kwargs): + async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs): """ 删除多个菜单 如果存在角色关联则无法删除 diff --git a/kinit-api/apps/vadmin/auth/params/role.py b/kinit-api/apps/vadmin/auth/params/role.py index 0d1b00e..6dc2a43 100644 --- a/kinit-api/apps/vadmin/auth/params/role.py +++ b/kinit-api/apps/vadmin/auth/params/role.py @@ -17,7 +17,14 @@ class RoleParams(QueryParams): """ 列表分页 """ - def __init__(self, name: str = None, role_key: str = None, disabled: bool = None, params: Paging = Depends()): + + def __init__( + self, + name: str | None = None, + role_key: str | None = None, + disabled: bool | None = None, + params: Paging = Depends() + ): super().__init__(params) self.name = ("like", name) self.role_key = ("like", role_key) diff --git a/kinit-api/apps/vadmin/auth/params/user.py b/kinit-api/apps/vadmin/auth/params/user.py index b3ca398..0bb9a3b 100644 --- a/kinit-api/apps/vadmin/auth/params/user.py +++ b/kinit-api/apps/vadmin/auth/params/user.py @@ -20,11 +20,11 @@ class UserParams(QueryParams): def __init__( self, - name: str = None, - telephone: str = None, - email: str = None, - is_active: bool | str = None, - is_staff: bool | str = None, + name: str | None = None, + telephone: str | None = None, + email: str | None = None, + is_active: bool | None = None, + is_staff: bool | None = None, params: Paging = Depends() ): super().__init__(params) diff --git a/kinit-api/apps/vadmin/auth/schemas/menu.py b/kinit-api/apps/vadmin/auth/schemas/menu.py index 8b63793..4f2801e 100644 --- a/kinit-api/apps/vadmin/auth/schemas/menu.py +++ b/kinit-api/apps/vadmin/auth/schemas/menu.py @@ -6,72 +6,60 @@ # @IDE : PyCharm # @desc : pydantic 模型,用于数据库序列化操作 -# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 - -from typing import Optional, List -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from core.data_types import DatetimeStr class Menu(BaseModel): title: str - icon: Optional[str] = None - component: Optional[str] = None - redirect: Optional[str] = None - path: Optional[str] = None + icon: str | None = None + component: str | None = None + redirect: str | None = None + path: str | None = None disabled: bool = False hidden: bool = False - order: Optional[int] = None - perms: Optional[str] = None - parent_id: Optional[int] = None + order: int | None = None + perms: str | None = None + parent_id: int | None = None menu_type: str - alwaysShow: Optional[bool] = True + alwaysShow: bool | None = True class MenuSimpleOut(Menu): + model_config = ConfigDict(from_attributes=True) + id: int create_datetime: DatetimeStr update_datetime: DatetimeStr - class Config: - orm_mode = True - class Meta(BaseModel): title: str - icon: Optional[str] = None + icon: str | None = None hidden: bool = False - noCache: Optional[bool] = False - breadcrumb: Optional[bool] = True - affix: Optional[bool] = False - noTagsView: Optional[bool] = False - canTo: Optional[bool] = False - alwaysShow: Optional[bool] = True + noCache: bool | None = False + breadcrumb: bool | None = True + affix: bool | None = False + noTagsView: bool | None = False + canTo: bool | None = False + alwaysShow: bool | None = True # 路由展示 class RouterOut(BaseModel): - name: Optional[str] = None - component: Optional[str] = None + model_config = ConfigDict(from_attributes=True) + + name: str | None = None + component: str | None = None path: str - redirect: Optional[str] = None - meta: Optional[Meta] = None - order: Optional[int] = None - children: List['RouterOut'] = [] - - class Config: - orm_mode = True - - -RouterOut.update_forward_refs() + redirect: str | None = None + meta: Meta | None = None + order: int | None = None + children: list[dict] = [] class TreeListOut(MenuSimpleOut): - children: List['TreeListOut'] = [] + model_config = ConfigDict(from_attributes=True) - class Config: - orm_mode = True - - -RouterOut.update_forward_refs() + children: list[dict] = [] diff --git a/kinit-api/apps/vadmin/auth/schemas/role.py b/kinit-api/apps/vadmin/auth/schemas/role.py index b261058..48efb1c 100644 --- a/kinit-api/apps/vadmin/auth/schemas/role.py +++ b/kinit-api/apps/vadmin/auth/schemas/role.py @@ -6,11 +6,8 @@ # @IDE : PyCharm # @desc : pydantic 模型,用于数据库序列化操作 -# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 - -from typing import Optional, List -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from core.data_types import DatetimeStr from .menu import MenuSimpleOut @@ -18,36 +15,34 @@ from .menu import MenuSimpleOut class Role(BaseModel): name: str disabled: bool = False - order: Optional[int] = None - desc: Optional[str] = None + order: int | None = None + desc: str | None = None role_key: str is_admin: bool = False class RoleSimpleOut(Role): + model_config = ConfigDict(from_attributes=True) + id: int create_datetime: DatetimeStr update_datetime: DatetimeStr - class Config: - orm_mode = True - class RoleOut(RoleSimpleOut): - menus: Optional[List[MenuSimpleOut]] = [] + model_config = ConfigDict(from_attributes=True) - class Config: - orm_mode = True + menus: list[MenuSimpleOut] = [] class RoleIn(Role): - menu_ids: Optional[List[int]] = [] + menu_ids: list[int] = [] class RoleSelectOut(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: int name: str disabled: bool - class Config: - orm_mode = True diff --git a/kinit-api/apps/vadmin/auth/schemas/user.py b/kinit-api/apps/vadmin/auth/schemas/user.py index 59c6306..3988128 100644 --- a/kinit-api/apps/vadmin/auth/schemas/user.py +++ b/kinit-api/apps/vadmin/auth/schemas/user.py @@ -6,33 +6,32 @@ # @IDE : PyCharm # @desc : pydantic 模型,用于数据库序列化操作 -# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 +from pydantic import BaseModel, ConfigDict, field_validator +from pydantic_core.core_schema import FieldValidationInfo -from typing import List, Optional -from pydantic import BaseModel, root_validator from core.data_types import Telephone, DatetimeStr, Email from .role import RoleSimpleOut class User(BaseModel): - name: Optional[str] = None + name: str | None = None telephone: Telephone - email: Optional[Email] = None - nickname: Optional[str] = None - avatar: Optional[str] = None - is_active: Optional[bool] = True - is_staff: Optional[bool] = True - gender: Optional[str] = "0" - is_wx_server_openid: Optional[bool] = False + email: Email | None = None + nickname: str | None = None + avatar: str | None = None + is_active: bool | None = True + is_staff: bool | None = True + gender: str | None = "0" + is_wx_server_openid: bool | None = False class UserIn(User): """ 创建用户 """ - role_ids: Optional[List[int]] = [] - password: Optional[str] = "" + role_ids: list[int] = [] + password: str | None = "" class UserUpdateBaseInfo(BaseModel): @@ -41,53 +40,50 @@ class UserUpdateBaseInfo(BaseModel): """ name: str telephone: Telephone - email: Optional[Email] = None - nickname: Optional[str] = None - gender: Optional[str] = "0" + email: Email | None = None + nickname: str | None = None + gender: str | None = "0" class UserUpdate(User): """ 更新用户详细信息 """ - name: Optional[str] = None + name: str | None = None telephone: Telephone - email: Optional[Email] = None - nickname: Optional[str] = None - avatar: Optional[str] = None - is_active: Optional[bool] = True - is_staff: Optional[bool] = False - gender: Optional[str] = "0" - role_ids: Optional[List[int]] = [] + email: Email | None = None + nickname: str | None = None + avatar: str | None = None + is_active: bool | None = True + is_staff: bool | None = False + gender: str | None = "0" + role_ids: list[int] = [] class UserSimpleOut(User): + model_config = ConfigDict(from_attributes=True) + id: int update_datetime: DatetimeStr create_datetime: DatetimeStr - is_reset_password: Optional[bool] = None - last_login: Optional[DatetimeStr] = None - last_ip: Optional[str] = None - - class Config: - orm_mode = True + is_reset_password: bool | None = None + last_login: DatetimeStr | None = None + last_ip: str | None = None class UserOut(UserSimpleOut): - roles: Optional[List[RoleSimpleOut]] = [] + model_config = ConfigDict(from_attributes=True) - class Config: - orm_mode = True + roles: list[RoleSimpleOut] = [] class ResetPwd(BaseModel): password: str password_two: str - @root_validator - def check_passwords_match(cls, values): - pw1, pw2 = values.get('password'), values.get('password_two') - if pw1 is not None and pw2 is not None and pw1 != pw2: + @field_validator('password_two') + def check_passwords_match(cls, v, info: FieldValidationInfo): + if 'password' in info.data and v != info.data['password']: raise ValueError('两次密码不一致!') - return values + return v diff --git a/kinit-api/apps/vadmin/auth/utils/current.py b/kinit-api/apps/vadmin/auth/utils/current.py index a29dc18..4f65f93 100644 --- a/kinit-api/apps/vadmin/auth/utils/current.py +++ b/kinit-api/apps/vadmin/auth/utils/current.py @@ -5,7 +5,6 @@ # @IDE : PyCharm # @desc : 获取认证后的信息工具 -from typing import List, Optional from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import joinedload from apps.vadmin.auth.crud import UserDal @@ -76,7 +75,7 @@ class FullAdminAuth(AuthValidation): 如果有权限,那么会验证该用户是否包括权限列表中的其中一个权限 """ - def __init__(self, permissions: Optional[List[str]] = None): + def __init__(self, permissions: list[str] | None = None): if permissions: self.permissions = set(permissions) else: diff --git a/kinit-api/apps/vadmin/auth/utils/validation/auth.py b/kinit-api/apps/vadmin/auth/utils/validation/auth.py index c775b0b..cbac1b4 100644 --- a/kinit-api/apps/vadmin/auth/utils/validation/auth.py +++ b/kinit-api/apps/vadmin/auth/utils/validation/auth.py @@ -21,6 +21,7 @@ class Auth(BaseModel): db: AsyncSession class Config: + # 接收任意类型 arbitrary_types_allowed = True @@ -49,7 +50,6 @@ class AuthValidation: status_code=status.HTTP_403_FORBIDDEN ) try: - # TODO token解析失败问题解决 payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) telephone: str = payload.get("sub") exp: int = payload.get("exp") diff --git a/kinit-api/apps/vadmin/auth/utils/validation/login.py b/kinit-api/apps/vadmin/auth/utils/validation/login.py index 9bc2975..195cc81 100644 --- a/kinit-api/apps/vadmin/auth/utils/validation/login.py +++ b/kinit-api/apps/vadmin/auth/utils/validation/login.py @@ -7,7 +7,7 @@ # @desc : 登录验证装饰器 from fastapi import Request -from pydantic import BaseModel, validator +from pydantic import BaseModel, validator, field_validator from sqlalchemy.ext.asyncio import AsyncSession from application.settings import DEFAULT_AUTH_ERROR_MAX_NUMBER, DEMO from apps.vadmin.auth import crud, schemas @@ -23,21 +23,21 @@ class LoginForm(BaseModel): method: str = '0' # 认证方式,0:密码登录,1:短信登录,2:微信一键登录 platform: str = '0' # 登录平台,0:PC端管理系统,1:移动端管理系统 - # validators - _normalize_telephone = validator('telephone', allow_reuse=True)(vali_telephone) + # 重用验证器:https://docs.pydantic.dev/dev-v2/usage/validators/#reuse-validators + normalize_telephone = field_validator('telephone')(vali_telephone) class WXLoginForm(BaseModel): - telephone: Optional[str] = None + telephone: str | None = None code: str method: str = '2' # 认证方式,0:密码登录,1:短信登录,2:微信一键登录 platform: str = '1' # 登录平台,0:PC端管理系统,1:移动端管理系统 class LoginResult(BaseModel): - status: Optional[bool] = False - user: Optional[schemas.UserOut] = None - msg: Optional[str] = None + status: bool | None = False + user: schemas.UserOut | None = None + msg: str | None = None class Config: arbitrary_types_allowed = True @@ -85,6 +85,6 @@ class LoginValidation: await count.delete() self.result.msg = "OK" self.result.status = True - self.result.user = schemas.UserSimpleOut.from_orm(user) + self.result.user = schemas.UserSimpleOut.model_validate(user) await user.update_login_info(db, request.client.host) return self.result diff --git a/kinit-api/apps/vadmin/auth/views.py b/kinit-api/apps/vadmin/auth/views.py index 45ab83f..6725eb8 100644 --- a/kinit-api/apps/vadmin/auth/views.py +++ b/kinit-api/apps/vadmin/auth/views.py @@ -87,7 +87,7 @@ async def post_user_current_update_avatar(file: UploadFile, auth: Auth = Depends @app.get("/user/admin/current/info", summary="获取当前管理员信息") async def get_user_admin_current_info(auth: Auth = Depends(FullAdminAuth())): - result = schemas.UserOut.from_orm(auth.user).dict() + result = schemas.UserOut.model_validate(auth.user).model_dump() result["permissions"] = list(FullAdminAuth.get_user_permissions(auth.user)) return SuccessResponse(result) diff --git a/kinit-api/apps/vadmin/help/schemas/issue.py b/kinit-api/apps/vadmin/help/schemas/issue.py index 6f80a59..976d663 100644 --- a/kinit-api/apps/vadmin/help/schemas/issue.py +++ b/kinit-api/apps/vadmin/help/schemas/issue.py @@ -7,34 +7,32 @@ # @desc : 常见问题 from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from core.data_types import DatetimeStr from apps.vadmin.auth.schemas import UserSimpleOut from .issue_category import IssueCategorySimpleOut class Issue(BaseModel): - category_id: Optional[int] = None - create_user_id: Optional[int] = None + category_id: int | None = None + create_user_id: int | None = None - title: Optional[str] = None - content: Optional[str] = None - view_number: Optional[int] = None - is_active: Optional[bool] = None + title: str | None = None + content: str | None = None + view_number: int | None = None + is_active: bool | None = None class IssueSimpleOut(Issue): + model_config = ConfigDict(from_attributes=True) + id: int update_datetime: DatetimeStr create_datetime: DatetimeStr - class Config: - orm_mode = True - class IssueListOut(IssueSimpleOut): + model_config = ConfigDict(from_attributes=True) + create_user: UserSimpleOut category: IssueCategorySimpleOut - - class Config: - orm_mode = True diff --git a/kinit-api/apps/vadmin/help/schemas/issue_category.py b/kinit-api/apps/vadmin/help/schemas/issue_category.py index ae4047b..0c8df06 100644 --- a/kinit-api/apps/vadmin/help/schemas/issue_category.py +++ b/kinit-api/apps/vadmin/help/schemas/issue_category.py @@ -8,38 +8,36 @@ from typing import Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict from core.data_types import DatetimeStr from apps.vadmin.auth.schemas import UserSimpleOut class IssueCategory(BaseModel): - name: Optional[str] = None - platform: Optional[str] = None - is_active: Optional[bool] = None + name: str | None = None + platform: str | None = None + is_active: bool | None = None - create_user_id: Optional[int] = None + create_user_id: int | None = None class IssueCategorySimpleOut(IssueCategory): + model_config = ConfigDict(from_attributes=True) + id: int update_datetime: DatetimeStr create_datetime: DatetimeStr - class Config: - orm_mode = True - class IssueCategoryListOut(IssueCategorySimpleOut): - create_user: UserSimpleOut + model_config = ConfigDict(from_attributes=True) - class Config: - orm_mode = True + create_user: UserSimpleOut class IssueCategoryOptionsOut(BaseModel): + model_config = ConfigDict(from_attributes=True) + label: str = Field(alias='name') value: int = Field(alias='id') - class Config: - orm_mode = True diff --git a/kinit-api/apps/vadmin/help/schemas/issue_m2m.py b/kinit-api/apps/vadmin/help/schemas/issue_m2m.py index 601a6c3..ff901a5 100644 --- a/kinit-api/apps/vadmin/help/schemas/issue_m2m.py +++ b/kinit-api/apps/vadmin/help/schemas/issue_m2m.py @@ -6,24 +6,22 @@ # @IDE : PyCharm # @desc : 简要说明 -from typing import Optional, List -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict from core.data_types import DatetimeStr from .issue import IssueSimpleOut class IssueCategoryPlatformOut(BaseModel): - name: Optional[str] = None - platform: Optional[str] = None - is_active: Optional[bool] = None - create_user_id: Optional[int] = None + model_config = ConfigDict(from_attributes=True) + + name: str | None = None + platform: str | None = None + is_active: bool | None = None + create_user_id: int | None = None id: int update_datetime: DatetimeStr create_datetime: DatetimeStr - issues: Optional[List[IssueSimpleOut]] = None - - class Config: - orm_mode = True + issues: list[IssueSimpleOut] = None diff --git a/kinit-api/apps/vadmin/record/crud.py b/kinit-api/apps/vadmin/record/crud.py index c3783a9..0955d62 100644 --- a/kinit-api/apps/vadmin/record/crud.py +++ b/kinit-api/apps/vadmin/record/crud.py @@ -6,7 +6,6 @@ # @IDE : PyCharm # @desc : 数据库 增删改查操作 import random -from typing import List # sqlalchemy 查询操作:https://segmentfault.com/a/1190000016767008 # sqlalchemy 关联查询:https://www.jianshu.com/p/dfad7c08c57a # sqlalchemy 关联查询详细:https://blog.csdn.net/u012324798/article/details/103940527 @@ -22,7 +21,7 @@ class LoginRecordDal(DalBase): def __init__(self, db: AsyncSession): super(LoginRecordDal, self).__init__(db, models.VadminLoginRecord, schemas.LoginRecordSimpleOut) - async def get_user_distribute(self) -> List[dict]: + async def get_user_distribute(self) -> list[dict]: """ 获取用户登录分布情况 高德经纬度查询:https://lbs.amap.com/tools/picker diff --git a/kinit-api/apps/vadmin/record/params/login.py b/kinit-api/apps/vadmin/record/params/login.py index 020cd27..be84da2 100644 --- a/kinit-api/apps/vadmin/record/params/login.py +++ b/kinit-api/apps/vadmin/record/params/login.py @@ -17,8 +17,15 @@ class LoginParams(QueryParams): """ 列表分页 """ - def __init__(self, ip: str = None, address: str = None, telephone: str = None, status: bool = None, - platform: str = None, params: Paging = Depends()): + def __init__( + self, + ip: str = None, + address: str = None, + telephone: str = None, + status: bool = None, + platform: str = None, + params: Paging = Depends() + ): super().__init__(params) self.ip = ("like", ip) self.telephone = ("like", telephone) diff --git a/kinit-api/apps/vadmin/record/params/operation.py b/kinit-api/apps/vadmin/record/params/operation.py index 375f1ef..9e3cb99 100644 --- a/kinit-api/apps/vadmin/record/params/operation.py +++ b/kinit-api/apps/vadmin/record/params/operation.py @@ -17,8 +17,13 @@ class OperationParams(QueryParams): """ 列表分页 """ - def __init__(self, summary: str = None, telephone: str = None, request_method: str = None, - params: Paging = Depends()): + def __init__( + self, + summary: str = None, + telephone: str = None, + request_method: str = None, + params: Paging = Depends() + ): super().__init__(params) self.summary = ("like", summary) self.telephone = ("like", telephone) diff --git a/kinit-api/apps/vadmin/record/schemas/login.py b/kinit-api/apps/vadmin/record/schemas/login.py index cdf409f..f200180 100644 --- a/kinit-api/apps/vadmin/record/schemas/login.py +++ b/kinit-api/apps/vadmin/record/schemas/login.py @@ -6,38 +6,33 @@ # @IDE : PyCharm # @desc : pydantic 模型,用于数据库序列化操作 -# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 - - -from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from core.data_types import DatetimeStr class LoginRecord(BaseModel): telephone: str status: bool - ip: Optional[str] = None - address: Optional[str] = None - browser: Optional[str] = None - system: Optional[str] = None - response: Optional[str] = None - request: Optional[str] = None - postal_code: Optional[str] = None - area_code: Optional[str] = None - country: Optional[str] = None - province: Optional[str] = None - city: Optional[str] = None - county: Optional[str] = None - operator: Optional[str] = None - platform: Optional[str] = None - login_method: Optional[str] = None + ip: str | None = None + address: str | None = None + browser: str | None = None + system: str | None = None + response: str | None = None + request: str | None = None + postal_code: str | None = None + area_code: str | None = None + country: str | None = None + province: str | None = None + city: str | None = None + county: str | None = None + operator: str | None = None + platform: str | None = None + login_method: str | None = None class LoginRecordSimpleOut(LoginRecord): + model_config = ConfigDict(from_attributes=True) + id: int create_datetime: DatetimeStr update_datetime: DatetimeStr - - class Config: - orm_mode = True diff --git a/kinit-api/apps/vadmin/record/schemas/operation.py b/kinit-api/apps/vadmin/record/schemas/operation.py index 1cf4ce0..4766e07 100644 --- a/kinit-api/apps/vadmin/record/schemas/operation.py +++ b/kinit-api/apps/vadmin/record/schemas/operation.py @@ -6,34 +6,30 @@ # @IDE : PyCharm # @desc : pydantic 模型,用于数据库序列化操作 -# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 - -from typing import Optional, List -from pydantic import BaseModel -from core.data_types import DatetimeStr, ObjectIdStr +from pydantic import BaseModel, ConfigDict +from core.data_types import DatetimeStr class OperationRecord(BaseModel): - telephone: Optional[str] = None - user_id: Optional[str] = None - user_name: Optional[str] = None - status_code: Optional[int] = None - client_ip: Optional[str] = None - request_method: Optional[str] = None - api_path: Optional[str] = None - system: Optional[str] = None - browser: Optional[str] = None - summary: Optional[str] = None - route_name: Optional[str] = None - description: Optional[str] = None - tags: Optional[List[str]] = None - process_time: Optional[str] = None - params: Optional[str] = None + telephone: str | None = None + user_id: int | None = None + user_name: str | None = None + status_code: int | None = None + client_ip: str | None = None + request_method: str | None = None + api_path: str | None = None + system: str | None = None + browser: str | None = None + summary: str | None = None + route_name: str | None = None + description: str | None = None + tags: list[str] | None = None + process_time: float | None = None + params: str | None = None class OperationRecordSimpleOut(OperationRecord): - create_datetime: DatetimeStr + model_config = ConfigDict(from_attributes=True) - class Config: - orm_mode = True + create_datetime: DatetimeStr diff --git a/kinit-api/apps/vadmin/record/schemas/sms.py b/kinit-api/apps/vadmin/record/schemas/sms.py index 4ea03f4..6f4db13 100644 --- a/kinit-api/apps/vadmin/record/schemas/sms.py +++ b/kinit-api/apps/vadmin/record/schemas/sms.py @@ -7,18 +7,17 @@ # @desc : 简要说明 -from typing import Optional, List -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from core.data_types import DatetimeStr class SMSSendRecord(BaseModel): telephone: str status: bool = True - user_id: Optional[int] = None - content: Optional[str] = None - desc: Optional[str] = None - scene: Optional[str] = None + user_id: int | None = None + content: str | None = None + desc: str | None = None + scene: str | None = None class SMSSendRecordSimpleOut(SMSSendRecord): @@ -26,5 +25,4 @@ class SMSSendRecordSimpleOut(SMSSendRecord): create_datetime: DatetimeStr update_datetime: DatetimeStr - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/kinit-api/apps/vadmin/system/crud.py b/kinit-api/apps/vadmin/system/crud.py index 69b6296..903c31d 100644 --- a/kinit-api/apps/vadmin/system/crud.py +++ b/kinit-api/apps/vadmin/system/crud.py @@ -12,11 +12,10 @@ import json import os from enum import Enum -from typing import List, Union, Any +from typing import Any from aioredis import Redis from fastapi.encoders import jsonable_encoder from motor.motor_asyncio import AsyncIOMotorDatabase -from pymongo.results import InsertOneResult, UpdateResult from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import joinedload @@ -34,7 +33,7 @@ class DictTypeDal(DalBase): def __init__(self, db: AsyncSession): super(DictTypeDal, self).__init__(db, models.VadminDictType, schemas.DictTypeSimpleOut) - async def get_dicts_details(self, dict_types: List[str]) -> dict: + async def get_dicts_details(self, dict_types: list[str]) -> dict: """ 获取多个字典类型下的字典元素列表 """ @@ -51,14 +50,14 @@ class DictTypeDal(DalBase): data[obj.dict_type] = [] continue else: - data[obj.dict_type] = [schemas.DictDetailsSimpleOut.from_orm(i).dict() for i in obj.details] + data[obj.dict_type] = [schemas.DictDetailsSimpleOut.model_validate(i).model_dump() for i in obj.details] return data async def get_select_datas(self): """获取选择数据,全部数据""" sql = select(self.model) queryset = await self.db.execute(sql) - return [schemas.DictTypeSelectOut.from_orm(i).dict() for i in queryset.scalars().all()] + return [schemas.DictTypeSelectOut.model_validate(i).model_dump() for i in queryset.scalars().all()] class DictDetailsDal(DalBase): @@ -128,7 +127,7 @@ class SettingsTabDal(DalBase): def __init__(self, db: AsyncSession): super(SettingsTabDal, self).__init__(db, models.VadminSystemSettingsTab, schemas.SettingsTabSimpleOut) - async def get_classify_tab_values(self, classify: List[str], hidden: bool | None = False): + async def get_classify_tab_values(self, classify: list[str], hidden: bool | None = False): """ 获取系统配置分类下的标签信息 """ @@ -144,7 +143,7 @@ class SettingsTabDal(DalBase): ) return self.__generate_values(datas) - async def get_tab_name_values(self, tab_names: List[str], hidden: bool | None = False): + async def get_tab_name_values(self, tab_names: list[str], hidden: bool | None = False): """ 获取系统配置标签下的标签信息 """ @@ -161,7 +160,7 @@ class SettingsTabDal(DalBase): return self.__generate_values(datas) @classmethod - def __generate_values(cls, datas: List[models.VadminSystemSettingsTab]): + def __generate_values(cls, datas: list[models.VadminSystemSettingsTab]): """ 生成字典值 """ @@ -398,7 +397,7 @@ class TaskDal(MongoManage): """ 创建任务 """ - data_dict = data.dict() + data_dict = data.model_dump() is_active = data_dict.pop('is_active') insert_result = await super().create_data(data_dict) obj = await self.get_task(insert_result.inserted_id, v_schema=schemas.TaskSimpleOut) @@ -422,7 +421,7 @@ class TaskDal(MongoManage): """ 更新任务 """ - data_dict = data.dict() + data_dict = data.model_dump() is_active = data_dict.pop('is_active') await super(TaskDal, self).put_data(_id, data) obj: dict = await self.get_task(_id, v_schema=schemas.TaskSimpleOut) diff --git a/kinit-api/apps/vadmin/system/schemas/dict.py b/kinit-api/apps/vadmin/system/schemas/dict.py index 10c5f9f..557933c 100644 --- a/kinit-api/apps/vadmin/system/schemas/dict.py +++ b/kinit-api/apps/vadmin/system/schemas/dict.py @@ -6,53 +6,48 @@ # @IDE : PyCharm # @desc : pydantic 模型,用于数据库序列化操作 -# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 - -from typing import Optional, List -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from core.data_types import DatetimeStr class DictType(BaseModel): dict_name: str dict_type: str - disabled: Optional[bool] = False - remark: Optional[str] = None + disabled: bool | None = False + remark: str | None = None class DictTypeSimpleOut(DictType): + model_config = ConfigDict(from_attributes=True) + id: int create_datetime: DatetimeStr update_datetime: DatetimeStr - class Config: - orm_mode = True - class DictTypeSelectOut(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: int dict_name: str disabled: bool - class Config: - orm_mode = True - class DictDetails(BaseModel): label: str value: str - disabled: Optional[bool] = False - is_default: Optional[bool] = False - remark: Optional[str] = None - order: Optional[int] = None + disabled: bool | None = False + is_default: bool | None = False + remark: str | None = None + order: int | None = None dict_type_id: int class DictDetailsSimpleOut(DictDetails): + model_config = ConfigDict(from_attributes=True) + id: int create_datetime: DatetimeStr update_datetime: DatetimeStr - class Config: - orm_mode = True diff --git a/kinit-api/apps/vadmin/system/schemas/settings.py b/kinit-api/apps/vadmin/system/schemas/settings.py index e94d55e..775d59d 100644 --- a/kinit-api/apps/vadmin/system/schemas/settings.py +++ b/kinit-api/apps/vadmin/system/schemas/settings.py @@ -6,27 +6,24 @@ # @IDE : PyCharm # @desc : pydantic 模型,用于数据库序列化操作 -# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 - -from typing import Optional, List -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from core.data_types import DatetimeStr class Settings(BaseModel): - config_label: Optional[str] = None + config_label: str | None = None config_key: str - config_value: Optional[str] = None - remark: Optional[str] = None - disabled: Optional[bool] = None + config_value: str | None = None + remark: str | None = None + disabled: bool | None = None tab_id: int class SettingsSimpleOut(Settings): + model_config = ConfigDict(from_attributes=True) + id: int create_datetime: DatetimeStr update_datetime: DatetimeStr - class Config: - orm_mode = True diff --git a/kinit-api/apps/vadmin/system/schemas/settings_tab.py b/kinit-api/apps/vadmin/system/schemas/settings_tab.py index 2c15d87..fd8a9f2 100644 --- a/kinit-api/apps/vadmin/system/schemas/settings_tab.py +++ b/kinit-api/apps/vadmin/system/schemas/settings_tab.py @@ -6,10 +6,8 @@ # @IDE : PyCharm # @desc : pydantic 模型,用于数据库序列化操作 -# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 - -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from core.data_types import DatetimeStr @@ -22,9 +20,9 @@ class SettingsTab(BaseModel): class SettingsTabSimpleOut(SettingsTab): + model_config = ConfigDict(from_attributes=True) + id: int create_datetime: DatetimeStr update_datetime: DatetimeStr - class Config: - orm_mode = True diff --git a/kinit-api/apps/vadmin/system/schemas/task.py b/kinit-api/apps/vadmin/system/schemas/task.py index 15d1572..774085c 100644 --- a/kinit-api/apps/vadmin/system/schemas/task.py +++ b/kinit-api/apps/vadmin/system/schemas/task.py @@ -6,28 +6,27 @@ # @IDE : PyCharm # @desc : 简要说明 -from typing import Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict from core.data_types import DatetimeStr, ObjectIdStr class Task(BaseModel): name: str - group: Optional[str] = None + group: str | None = None job_class: str exec_strategy: str expression: str - is_active: Optional[bool] = True # 临时字段,不在表中创建 - remark: Optional[str] = None - start_date: Optional[DatetimeStr] = None - end_date: Optional[DatetimeStr] = None + is_active: bool | None = True # 临时字段,不在表中创建 + remark: str | None = None + start_date: DatetimeStr | None = None + end_date: DatetimeStr | None = None class TaskSimpleOut(Task): + model_config = ConfigDict(from_attributes=True) + id: ObjectIdStr = Field(..., alias='_id') create_datetime: DatetimeStr update_datetime: DatetimeStr - last_run_datetime: Optional[DatetimeStr] = None # 临时字段,不在表中创建 + last_run_datetime: DatetimeStr | None = None # 临时字段,不在表中创建 - class Config: - orm_mode = True diff --git a/kinit-api/apps/vadmin/system/views.py b/kinit-api/apps/vadmin/system/views.py index b3c9f5c..8df3986 100644 --- a/kinit-api/apps/vadmin/system/views.py +++ b/kinit-api/apps/vadmin/system/views.py @@ -6,7 +6,6 @@ # @desc : 主要接口文件 # UploadFile 库依赖:pip install python-multipart -from typing import List from aioredis import Redis from fastapi import APIRouter, Depends, Body, UploadFile, Form from motor.motor_asyncio import AsyncIOMotorDatabase @@ -17,7 +16,6 @@ from utils.file.aliyun_oss import AliyunOSS, BucketConf from utils.file.file_manage import FileManage from utils.response import SuccessResponse, ErrorResponse from utils.sms.code import CodeSMS -from utils.tools import generate_string from . import schemas, crud from core.dependencies import IdList from apps.vadmin.auth.utils.current import AllUserAuth, FullAdminAuth, OpenAuth @@ -53,7 +51,7 @@ async def delete_dict_types(ids: IdList = Depends(), auth: Auth = Depends(AllUse @app.post("/dict/types/details", summary="获取多个字典类型下的字典元素列表") async def post_dicts_details( auth: Auth = Depends(AllUserAuth()), - dict_types: List[str] = Body(None, title="字典元素列表", description="查询字典元素列表") + dict_types: list[str] = Body(None, title="字典元素列表", description="查询字典元素列表") ): datas = await crud.DictTypeDal(auth.db).get_dicts_details(dict_types) return SuccessResponse(datas) diff --git a/kinit-api/core/crud.py b/kinit-api/core/crud.py index c0d7ecc..71fcb99 100644 --- a/kinit-api/core/crud.py +++ b/kinit-api/core/crud.py @@ -19,7 +19,7 @@ # https://www.osgeo.cn/sqlalchemy/orm/loading_relationships.html?highlight=selectinload#sqlalchemy.orm.joinedload import datetime -from typing import List, Set +from typing import Set from fastapi import HTTPException from fastapi.encoders import jsonable_encoder from sqlalchemy import func, delete, update, or_ @@ -47,7 +47,7 @@ class DalBase: data_id: int = None, v_options: list = None, v_join_query: dict = None, - v_or: List[tuple] = None, + v_or: list[tuple] = None, v_order: str = None, v_order_field: str = None, v_return_none: bool = False, @@ -82,7 +82,7 @@ class DalBase: if not data and v_return_none: return None if data and v_schema: - return v_schema.from_orm(data).dict() + return v_schema.model_validate(data).model_dump() if data: return data raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="未找到此数据") @@ -93,7 +93,7 @@ class DalBase: limit: int = 10, v_options: list = None, v_join_query: dict = None, - v_or: List[tuple] = None, + v_or: list[tuple] = None, v_order: str = None, v_order_field: str = None, v_return_objs: bool = False, @@ -131,7 +131,7 @@ class DalBase: return queryset.scalars().unique().all() return [await self.out_dict(i, v_schema=v_schema) for i in queryset.scalars().unique().all()] - async def get_count(self, v_options: list = None, v_join_query: dict = None, v_or: List[tuple] = None, **kwargs): + async def get_count(self, v_options: list = None, v_join_query: dict = None, v_or: list[tuple] = None, **kwargs): """ 获取数据总数 @@ -156,7 +156,7 @@ class DalBase: if isinstance(data, dict): obj = self.model(**data) else: - obj = self.model(**data.dict()) + obj = self.model(**data.model_dump()) await self.flush(obj) return await self.out_dict(obj, v_options, v_return_obj, v_schema) @@ -183,7 +183,7 @@ class DalBase: await self.flush(obj) return await self.out_dict(obj, None, v_return_obj, v_schema) - async def delete_datas(self, ids: List[int], v_soft: bool = False, **kwargs): + async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs): """ 删除多条数据 :param ids: 数据集 @@ -200,13 +200,14 @@ class DalBase: ) else: await self.db.execute(delete(self.model).where(self.model.id.in_(ids))) + await self.flush() def add_filter_condition( self, sql: select, v_options: list = None, v_join_query: dict = None, - v_or: List[tuple] = None, + v_or: list[tuple] = None, **kwargs ) -> select: """ @@ -245,7 +246,7 @@ class DalBase: sql = sql.options(*[load for load in v_options]) return sql - def __or_filter(self, sql: select, v_or: List[tuple], v_join_left: Set[str], v_join: Set[str]): + def __or_filter(self, sql: select, v_or: list[tuple], v_join_left: Set[str], v_join: Set[str]): """ 或逻辑操作 :param sql: @@ -337,5 +338,5 @@ class DalBase: if v_return_obj: return obj if v_schema: - return v_schema.from_orm(obj).dict() - return self.schema.from_orm(obj).dict() + return v_schema.model_validate(obj).model_dump() + return self.schema.model_validate(obj).model_dump() diff --git a/kinit-api/core/data_types.py b/kinit-api/core/data_types.py index 1a51273..2b26762 100644 --- a/kinit-api/core/data_types.py +++ b/kinit-api/core/data_types.py @@ -1,84 +1,122 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # @version : 1.0 -# @Create Time : 2022/11/14 12:42 +# @Create Time : 2023/7/16 12:42 # @File : data_types.py # @IDE : PyCharm # @desc : 自定义数据类型 """ -自定义数据类型 - 官方文档:https://pydantic-docs.helpmanual.io/usage/types/#custom-data-types +自定义数据类型 - 官方文档:https://docs.pydantic.dev/dev-v2/usage/types/custom/#adding-validation-and-serialization """ import datetime +from typing import Annotated, Any from bson import ObjectId - +from pydantic import AfterValidator, PlainSerializer, WithJsonSchema from .validator import * -class DatetimeStr(str): +def DatetimeStrVali(value: str | datetime.datetime | int | float | dict): + """ + 日期时间字符串验证 + 如果我传入的是字符串,那么直接返回,如果我传入的是一个日期类型,那么会转为字符串格式后返回 + 因为在 pydantic 2.0 中是支持 int 或 float 自动转换类型的,所以我这里添加进去,但是在处理时会使这两种类型报错 - @classmethod - def __get_validators__(cls): - yield cls.validate - - @classmethod - def validate(cls, v): - if isinstance(v, str): - return v - elif isinstance(v, dict): - # 转换为datetime对象 - v = datetime.datetime.strptime(v.get("$date"), "%Y-%m-%dT%H:%M:%S.%fZ") - return v.strftime("%Y-%m-%d %H:%M:%S") + 官方文档:https://docs.pydantic.dev/dev-v2/usage/types/datetime/ + """ + if isinstance(value, str): + pattern = "%Y-%m-%d %H:%M:%S" + try: + datetime.datetime.strptime(value, pattern) + return value + except ValueError: + pass + elif isinstance(value, datetime.datetime): + return value.strftime("%Y-%m-%d %H:%M:%S") + elif isinstance(value, dict): + # 用于处理 mongodb 日期时间数据类型 + date_str = value.get("$date") + date_format = '%Y-%m-%dT%H:%M:%S.%fZ' + # 将字符串转换为datetime.datetime类型 + datetime_obj = datetime.datetime.strptime(date_str, date_format) + # 将datetime.datetime对象转换为指定的字符串格式 + return datetime_obj.strftime('%Y-%m-%d %H:%M:%S') + raise ValueError("无效的日期时间或字符串数据") -class Telephone(str): - - @classmethod - def __get_validators__(cls): - yield cls.validate - - @classmethod - def validate(cls, v): - return vali_telephone(v) +# 实现自定义一个日期时间字符串的数据类型 +DatetimeStr = Annotated[ + str | datetime.datetime | int | float | dict, + AfterValidator(DatetimeStrVali), + PlainSerializer(lambda x: x, return_type=str), + WithJsonSchema({'type': 'string'}, mode='serialization') +] -class Email(str): - - @classmethod - def __get_validators__(cls): - yield cls.validate - - @classmethod - def validate(cls, v): - return vali_email(v) +# 实现自定义一个手机号类型 +Telephone = Annotated[ + str, + AfterValidator(lambda x: vali_telephone(x)), + PlainSerializer(lambda x: x, return_type=str), + WithJsonSchema({'type': 'string'}, mode='serialization') +] -class DateStr(str): - - @classmethod - def __get_validators__(cls): - yield cls.validate - - @classmethod - def validate(cls, v): - if isinstance(v, str): - return v - return v.strftime("%Y-%m-%d") +# 实现自定义一个邮箱类型 +Email = Annotated[ + str, + AfterValidator(lambda x: vali_email(x)), + PlainSerializer(lambda x: x, return_type=str), + WithJsonSchema({'type': 'string'}, mode='serialization') +] -class ObjectIdStr(str): +def DateStrVali(value: str | datetime.date | int | float): + """ + 日期字符串验证 + 如果我传入的是字符串,那么直接返回,如果我传入的是一个日期类型,那么会转为字符串格式后返回 + 因为在 pydantic 2.0 中是支持 int 或 float 自动转换类型的,所以我这里添加进去,但是在处理时会使这两种类型报错 - @classmethod - def __get_validators__(cls): - yield cls.validate + 官方文档:https://docs.pydantic.dev/dev-v2/usage/types/datetime/ + """ + if isinstance(value, str): + pattern = "%Y-%m-%d" + try: + datetime.datetime.strptime(value, pattern) + return value + except ValueError: + pass + elif isinstance(value, datetime.date): + return value.strftime("%Y-%m-%d") + raise ValueError("无效的日期时间或字符串数据") - @classmethod - def validate(cls, v): - if isinstance(v, str): - return v - elif isinstance(v, dict): - return v.get("$oid") - elif isinstance(v, ObjectId): - return str(v) - return v + +# 实现自定义一个日期字符串的数据类型 +DateStr = Annotated[ + str | datetime.date | int | float, + AfterValidator(DateStrVali), + PlainSerializer(lambda x: x, return_type=str), + WithJsonSchema({'type': 'string'}, mode='serialization') +] + + +def ObjectIdStrVali(value: str | dict | ObjectId): + """ + 官方文档:https://docs.pydantic.dev/dev-v2/usage/types/datetime/ + """ + if isinstance(value, str): + return value + elif isinstance(value, dict): + return value.get("$oid") + elif isinstance(value, ObjectId): + return str(value) + raise ValueError("无效的 ObjectId 数据类型") + + +ObjectIdStr = Annotated[ + Any, # 这里不能直接使用 any,需要使用 typing.Any + AfterValidator(ObjectIdStrVali), + PlainSerializer(lambda x: x, return_type=str), + WithJsonSchema({'type': 'string'}, mode='serialization') +] diff --git a/kinit-api/core/dependencies.py b/kinit-api/core/dependencies.py index 3259d02..33b7e5b 100644 --- a/kinit-api/core/dependencies.py +++ b/kinit-api/core/dependencies.py @@ -10,7 +10,6 @@ 类依赖项-官方文档:https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/ """ -from typing import List from fastapi import Body import copy @@ -24,7 +23,7 @@ class QueryParams: self.v_order = params.v_order self.v_order_field = params.v_order_field - def dict(self, exclude: List[str] = None) -> dict: + def dict(self, exclude: list[str] = None) -> dict: result = copy.deepcopy(self.__dict__) if exclude: for item in exclude: @@ -34,7 +33,7 @@ class QueryParams: pass return result - def to_count(self, exclude: List[str] = None) -> dict: + def to_count(self, exclude: list[str] = None) -> dict: params = self.dict(exclude=exclude) del params["page"] del params["limit"] @@ -59,5 +58,5 @@ class IdList: """ id 列表 """ - def __init__(self, ids: List[int] = Body(..., title="ID 列表")): + def __init__(self, ids: list[int] = Body(..., title="ID 列表")): self.ids = ids diff --git a/kinit-api/requirements.txt b/kinit-api/requirements.txt index 711d92a..f3358e0 100644 --- a/kinit-api/requirements.txt +++ b/kinit-api/requirements.txt @@ -16,10 +16,11 @@ alibabacloud-tea-util==0.3.8 alibabacloud-tea-xml==0.0.2 aliyun-python-sdk-core==2.13.36 aliyun-python-sdk-kms==2.16.0 +annotated-types==0.5.0 anyio==3.6.2 asgiref==3.5.2 async-timeout==4.0.2 -asyncmy==0.2.5 +asyncmy==0.2.8 attrs==22.1.0 bcrypt==4.0.1 certifi==2022.9.24 @@ -32,7 +33,7 @@ cryptography==38.0.3 dnspython==2.2.1 ecdsa==0.18.0 et-xmlfile==1.1.0 -fastapi==0.97.0 +fastapi==0.100.0 frozenlist==1.3.3 greenlet==2.0.1 gunicorn==20.1.0 @@ -47,6 +48,7 @@ Mako==1.2.4 MarkupSafe==2.1.1 motor==3.1.1 multidict==6.0.2 +openai==0.27.4 openpyxl==3.0.10 orjson==3.8.2 oss2==2.16.0 @@ -55,7 +57,8 @@ Pillow==9.3.0 pyasn1==0.4.8 pycparser==2.21 pycryptodome==3.15.0 -pydantic==1.10.2 +pydantic==2.0.3 +pydantic-core==2.3.0 PyJWT==2.6.0 pymongo==4.3.3 PyMySQL==1.0.2 @@ -70,8 +73,9 @@ SQLAlchemy==1.4.44 SQLAlchemy-Utils==0.38.3 SSIM-PIL==1.0.14 starlette==0.27.0 +tqdm==4.65.0 typer==0.7.0 -typing-extensions==4.4.0 +typing-extensions==4.7.1 ua-parser==0.16.1 urllib3==1.26.12 user-agents==2.2.0 diff --git a/kinit-api/utils/excel/write_xlsx.py b/kinit-api/utils/excel/write_xlsx.py index 6a561f6..4ef7f35 100644 --- a/kinit-api/utils/excel/write_xlsx.py +++ b/kinit-api/utils/excel/write_xlsx.py @@ -14,7 +14,6 @@ import datetime import hashlib import os.path import random -from typing import List import xlsxwriter from application.settings import TEMP_DIR, TEMP_URL @@ -45,7 +44,7 @@ class WriteXlsx: self.wb = xlsxwriter.Workbook(self.filename) self.sheet = self.wb.add_worksheet(self.sheet_name) - def generate_template(self, headers: List[dict] = None, max_row: int = 101) -> None: + def generate_template(self, headers: list[dict] = None, max_row: int = 101) -> None: """ 生成模板 :param headers: 表头 diff --git a/kinit-api/utils/ip_manage.py b/kinit-api/utils/ip_manage.py index 3dc80a3..5db6e06 100644 --- a/kinit-api/utils/ip_manage.py +++ b/kinit-api/utils/ip_manage.py @@ -16,24 +16,22 @@ https://api.ip138.com/ip/?ip=58.16.180.3&datatype=jsonp&token=cc87f3c77747bccbaa aiohttp 异步请求文档:https://docs.aiohttp.org/en/stable/client_quickstart.html """ from aiohttp import TCPConnector - from application.settings import IP_PARSE_TOKEN, IP_PARSE_ENABLE import aiohttp from core.logger import logger from pydantic import BaseModel -from typing import Optional class IPLocationOut(BaseModel): - ip: Optional[str] = None - address: Optional[str] = None - country: Optional[str] = None - province: Optional[str] = None - city: Optional[str] = None - county: Optional[str] = None - operator: Optional[str] = None - postal_code: Optional[str] = None - area_code: Optional[str] = None + ip: str | None = None + address: str | None = None + country: str | None = None + province: str | None = None + city: str | None = None + county: str | None = None + operator: str | None = None + postal_code: str | None = None + area_code: str | None = None class IPManage: