Skip to content

Commit fcdb44f

Browse files
committed
优化pydantic v2兼容并测试
1 parent f653b99 commit fcdb44f

File tree

9 files changed

+81
-36
lines changed

9 files changed

+81
-36
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,10 @@ flowchart LR
282282
from datetime import date
283283

284284
from fastapi_amis_admin.models.fields import Field
285-
from fastapi_user_auth.auth.models import User
285+
from fastapi_user_auth.auth.models import BaseUser
286286

287-
# 自定义`User`模型,继承`User`
288-
class MyUser(User, table = True):
287+
# 自定义`User`模型,继承`BaseUser`
288+
class MyUser(BaseUser, table = True):
289289
point: float = Field(default = 0, title = '积分', description = '用户积分')
290290
phone: str = Field(None, title = '手机号', max_length = 15)
291291
parent_id: int = Field(None, title = "上级", foreign_key = "auth_user.id")

fastapi_user_auth/admin/actions.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class UpdateSubRolesAction(BaseSubAction):
124124

125125
class schema(BaseModel):
126126
role_keys: str = Field(
127-
None,
127+
"",
128128
title="角色列表",
129129
amis_form_item=amis.Transfer(
130130
selectMode="table",
@@ -198,7 +198,7 @@ class BaseSubPermAction(BaseSubAction):
198198
# 创建动作表单数据模型
199199
class schema(BaseModel):
200200
permissions: str = Field(
201-
None,
201+
"",
202202
title="权限列表",
203203
amis_form_item=amis.InputTree(
204204
multiple=True,
@@ -279,7 +279,7 @@ class UpdateSubDataPermAction(BaseSubPermAction):
279279
# 创建动作表单数据模型
280280
class schema(BaseSubPermAction.schema):
281281
effect_matrix: list = Field(
282-
None,
282+
[],
283283
title="当前权限",
284284
amis_form_item=amis.MatrixCheckboxes(
285285
rowLabel="权限名称",
@@ -290,7 +290,7 @@ class schema(BaseSubPermAction.schema):
290290
),
291291
)
292292
policy_matrix: list = Field(
293-
None,
293+
[],
294294
title="权限配置",
295295
amis_form_item=amis.MatrixCheckboxes(
296296
rowLabel="名称",

fastapi_user_auth/admin/admin.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from fastapi_amis_admin.amis.constants import DisplayModeEnum, LevelEnum
2828
from fastapi_amis_admin.crud.base import SchemaUpdateT
2929
from fastapi_amis_admin.crud.schema import BaseApiOut
30+
from fastapi_amis_admin.utils.pydantic import model_fields
3031
from fastapi_amis_admin.utils.translation import i18n as _
3132
from pydantic import BaseModel
3233
from sqlalchemy import select
@@ -238,8 +239,8 @@ async def get_form(self, request: Request) -> Form:
238239
form = await super().get_form(request)
239240
formitems = [
240241
await self.get_form_item(request, modelfield)
241-
for k, modelfield in self.user_model.__fields__.items()
242-
if k not in self.schema.__fields__.keys() | {"delete_time"}
242+
for k, modelfield in model_fields(self.user_model).items()
243+
if k not in model_fields(self.schema).keys() | {"delete_time"}
243244
]
244245
form.body.extend(formitem.update_from_kwargs(disabled=True) for formitem in formitems if formitem)
245246
return form

fastapi_user_auth/auth/models.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fastapi_amis_admin.crud.parser import LabelField
55
from fastapi_amis_admin.models import Field
66
from fastapi_amis_admin.utils.translation import i18n as _
7-
from sqlalchemy import ForeignKey, func, select
7+
from sqlalchemy import func, select
88

99
from fastapi_user_auth.mixins.models import ( # noqa F401
1010
CreateTimeMixin,
@@ -49,16 +49,20 @@ class User(BaseUser, table=True):
4949
pass
5050

5151

52-
class Role(PkMixin, CUDTimeMixin, table=True):
53-
"""角色"""
54-
52+
class BaseRole(PkMixin, CUDTimeMixin):
5553
__tablename__ = "auth_role"
5654

5755
key: str = Field(title="角色标识", max_length=40, unique=True, index=True, nullable=False)
5856
name: str = Field(default="", title="角色名称", max_length=40)
5957
desc: str = Field(default="", title="角色描述", max_length=400, amis_form_item="textarea")
6058

6159

60+
class Role(BaseRole, table=True):
61+
"""角色"""
62+
63+
pass
64+
65+
6266
class CasbinRule(PkMixin, table=True):
6367
__tablename__ = "auth_casbin_rule"
6468

@@ -115,7 +119,7 @@ class LoginHistory(PkMixin, CreateTimeMixin, table=True):
115119

116120
__tablename__ = "auth_login_history"
117121

118-
user_id: int = Field(None, title="用户ID", sa_column_args=(ForeignKey("auth_user.id", ondelete="CASCADE"),))
122+
user_id: Optional[int] = Field(None, title="用户ID")
119123
login_name: str = Field("", title="登录名", max_length=20)
120124
ip: str = Field("", title="登录IP", max_length=20)
121125
ip_info: str = Field("", title="IP信息", max_length=255)

fastapi_user_auth/auth/schemas.py

+22-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from enum import Enum
2+
from typing import Optional
23

4+
from fastapi_amis_admin.utils.pydantic import PYDANTIC_V2
35
from fastapi_amis_admin.utils.translation import i18n as _
4-
from pydantic import BaseModel, SecretStr, validator
6+
from pydantic import BaseModel, SecretStr
57
from sqlmodel import Field
68

79
from .models import BaseUser, EmailMixin, PasswordMixin, UsernameMixin
@@ -16,20 +18,32 @@ class UserLoginOut(BaseUser):
1618
"""用户登录返回信息"""
1719

1820
token_type: str = "bearer"
19-
access_token: str = None
20-
password: SecretStr = None
21+
access_token: Optional[str] = None
22+
password: Optional[SecretStr] = None
2123

2224

2325
class UserRegIn(UsernameMixin, PasswordMixin, EmailMixin):
2426
"""用户注册"""
2527

2628
password2: str = Field(title=_("Confirm Password"), max_length=128)
2729

28-
@validator("password2")
29-
def passwords_match(cls, v, values, **kwargs):
30-
if "password" in values and v != values["password"]:
31-
raise ValueError("passwords do not match!")
32-
return v
30+
if PYDANTIC_V2:
31+
from pydantic import model_validator
32+
33+
@model_validator(mode="after")
34+
def check_passwords_match(self):
35+
if self.password is not None and self.password.get_secret_value() != self.password2:
36+
raise ValueError("passwords do not match!")
37+
return self
38+
39+
else:
40+
from pydantic import validator
41+
42+
@validator("password2")
43+
def passwords_match_(cls, v, values, **kwargs):
44+
if "password" in values and v != values["password"]:
45+
raise ValueError("passwords do not match!")
46+
return v
3347

3448

3549
# 默认保留的用户

fastapi_user_auth/mixins/models.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from datetime import datetime
22
from typing import Optional
33

4-
from fastapi_amis_admin.models import Field, SQLModel
4+
from fastapi_amis_admin.models.fields import Field
55
from fastapi_amis_admin.utils.translation import i18n as _
66
from pydantic import EmailStr, SecretStr
77
from sqlalchemy import func
88
from sqlmodel import AutoString
9+
from sqlmodelx import SQLModel
10+
11+
from fastapi_user_auth.utils.sqltypes import SecretStrType
912

1013

1114
class PkMixin(SQLModel):
@@ -17,7 +20,7 @@ class CreateTimeMixin(SQLModel):
1720

1821

1922
class UpdateTimeMixin(SQLModel):
20-
update_time: datetime = Field(
23+
update_time: Optional[datetime] = Field(
2124
default_factory=datetime.now,
2225
title=_("Update Time"),
2326
sa_column_kwargs={"onupdate": func.now(), "server_default": func.now()},
@@ -38,13 +41,9 @@ class UsernameMixin(SQLModel):
3841
username: str = Field(title=_("Username"), max_length=32, unique=True, index=True, nullable=False)
3942

4043

41-
# class PasswordStr(SecretStr, str):
42-
# pass
43-
44-
4544
class PasswordMixin(SQLModel):
4645
password: SecretStr = Field(
47-
title=_("Password"), sa_type=AutoString, max_length=128, nullable=False, amis_form_item="input-password"
46+
title=_("Password"), max_length=128, sa_type=SecretStrType, nullable=False, amis_form_item="input-password"
4847
)
4948

5049

fastapi_user_auth/utils/sqltypes.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import cast
2+
3+
from pydantic import SecretStr
4+
from sqlalchemy import String, types
5+
from sqlalchemy.engine import Dialect
6+
7+
8+
class SecretStrType(types.TypeDecorator):
9+
impl = String
10+
cache_ok = True
11+
mysql_default_length = 255
12+
13+
@property
14+
def python_type(self):
15+
return self.impl.python_type
16+
17+
def load_dialect_impl(self, dialect: Dialect):
18+
impl = cast(types.String, self.impl)
19+
if impl.length is None and dialect.name == "mysql":
20+
return dialect.type_descriptor(types.String(self.mysql_default_length))
21+
return super().load_dialect_impl(dialect)
22+
23+
def process_bind_param(self, value, dialect):
24+
if value and isinstance(value, SecretStr):
25+
return value.get_secret_value()
26+
return value
27+
28+
def process_result_value(self, value, dialect):
29+
if value is not None:
30+
return SecretStr(value)
31+
return value

pyproject.toml

+3-4
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ classifiers = [
3636
"Programming Language :: Python :: 3.11",
3737
]
3838
dependencies = [
39-
"pydantic>=1.10.0,<2.0.0",
40-
"fastapi-amis-admin[sqlmodel]>=0.7.0,<0.8.0",
39+
"fastapi-amis-admin>=0.6.0,<0.8.0",
4140
"email-validator>=1.3.1,<3.0.0",
4241
"passlib>=1.7.4",
4342
"bcrypt>=4.0.0",
@@ -51,15 +50,15 @@ FastAPI-Amis-Admin = "https://github.com/amisadmin/fastapi_amis_admin"
5150

5251
[project.optional-dependencies]
5352
jwt = [
54-
"python-jose==3.3.0",
53+
"python-jose>=3.3.0",
5554
]
5655
redis = ["redis>=4.2.0"]
5756
test = [
5857
"uvicorn[standard] >=0.19.0,<1.0",
5958
"pytest >=6.2.4",
6059
"pytest-asyncio>=0.17",
6160
"aiosqlite>=0.15.0",
62-
"python-jose==3.3.0",
61+
"python-jose>=3.3.0",
6362
"jinja2 >=2.11.2,<4.0.0",
6463
"ujson>=5.5.0",
6564
"requests>=2.28.1",

tests/test_auth/test_auth_user_model.py

-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ class MyUser(BaseUser, table=True):
1717
location: str = Field("", title="位置")
1818

1919

20-
print(123)
21-
22-
2320
@pytest.mark.parametrize("logins", ["admin"], indirect=True)
2421
async def test_custom_user_model(fake_auth, logins):
2522
# 使用自定义的`User`模型,创建auth对象

0 commit comments

Comments
 (0)