fastgenerateapi 0.0.23__py2.py3-none-any.whl → 0.0.25__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fastgenerateapi might be problematic. Click here for more details.
- fastgenerateapi/__version__.py +1 -1
- fastgenerateapi/api_view/base_view.py +29 -14
- fastgenerateapi/api_view/delete_tree_view.py +13 -11
- fastgenerateapi/api_view/delete_view.py +12 -11
- fastgenerateapi/api_view/get_tree_view.py +2 -1
- fastgenerateapi/api_view/mixin/dbmodel_mixin.py +5 -3
- fastgenerateapi/example/models.py +19 -11
- fastgenerateapi/example/views.py +38 -26
- fastgenerateapi/my_fields/__init__.py +4 -0
- fastgenerateapi/my_fields/aes_field.py +166 -0
- fastgenerateapi/my_fields/enum_field.py +215 -0
- fastgenerateapi/my_fields/pk_field.py +68 -0
- fastgenerateapi/my_fields/pwd_field.py +81 -0
- fastgenerateapi/my_fields/soft_delete_field.py +54 -0
- fastgenerateapi/schemas_factory/common_function.py +2 -1
- fastgenerateapi/schemas_factory/common_schema_factory.py +1 -1
- fastgenerateapi/schemas_factory/create_schema_factory.py +9 -3
- fastgenerateapi/schemas_factory/get_all_schema_factory.py +7 -0
- fastgenerateapi/schemas_factory/get_one_schema_factory.py +8 -0
- fastgenerateapi/schemas_factory/get_tree_schema_factory.py +8 -0
- fastgenerateapi/schemas_factory/update_schema_factory.py +9 -3
- fastgenerateapi/settings/settings.py +12 -7
- fastgenerateapi/utils/aes.py +93 -0
- fastgenerateapi/utils/snowflake.py +148 -0
- {fastgenerateapi-0.0.23.dist-info → fastgenerateapi-0.0.25.dist-info}/METADATA +1 -1
- {fastgenerateapi-0.0.23.dist-info → fastgenerateapi-0.0.25.dist-info}/RECORD +29 -21
- {fastgenerateapi-0.0.23.dist-info → fastgenerateapi-0.0.25.dist-info}/WHEEL +1 -1
- {fastgenerateapi-0.0.23.dist-info → fastgenerateapi-0.0.25.dist-info}/LICENSE +0 -0
- {fastgenerateapi-0.0.23.dist-info → fastgenerateapi-0.0.25.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
from typing import (
|
|
3
|
+
TYPE_CHECKING,
|
|
4
|
+
Any,
|
|
5
|
+
Callable,
|
|
6
|
+
Dict,
|
|
7
|
+
Generic,
|
|
8
|
+
List,
|
|
9
|
+
Optional,
|
|
10
|
+
Tuple,
|
|
11
|
+
Type,
|
|
12
|
+
TypeVar,
|
|
13
|
+
Union,
|
|
14
|
+
overload,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from fastapi import HTTPException
|
|
18
|
+
from tortoise.fields.data import IntEnumFieldInstance
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING: # pragma: nocoverage
|
|
21
|
+
from tortoise.models import Model
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class IntEnumField(IntEnumFieldInstance):
|
|
25
|
+
"""
|
|
26
|
+
传入参数数组,默认生成枚举类,数字从 1 开始
|
|
27
|
+
例如:
|
|
28
|
+
enum_list = ["A", "B"]
|
|
29
|
+
相当于枚举
|
|
30
|
+
class CategoryEnum(IntEnum):
|
|
31
|
+
one = 1
|
|
32
|
+
two = 2
|
|
33
|
+
通过方法 get_name 获取对应的值, "A"
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, enum_list: List[any], **kwargs: Any) -> None:
|
|
37
|
+
kwargs.setdefault("description", self.get_description(enum_list))
|
|
38
|
+
self.enum_list = enum_list
|
|
39
|
+
self.description = kwargs.get("description")
|
|
40
|
+
super().__init__(enum_type=self.create_enum(enum_list), **kwargs)
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def constraints(self) -> dict:
|
|
44
|
+
return {
|
|
45
|
+
"ge": 0,
|
|
46
|
+
"le": len(self.enum_list),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def to_db_value(self, value: Any, instance: "Union[Type[Model], Model]") -> Any:
|
|
50
|
+
"""
|
|
51
|
+
Converts from the Python type to the DB type.
|
|
52
|
+
"""
|
|
53
|
+
if value is not None:
|
|
54
|
+
value = int(value) # pylint: disable=E1102
|
|
55
|
+
if value > len(self.enum_list) or 0 > value:
|
|
56
|
+
raise HTTPException(detail=f"枚举值:{value} 校验失败。【{self.description}】", status_code=422)
|
|
57
|
+
self.validate(value)
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
def to_python_value(self, value: Any) -> Any:
|
|
61
|
+
"""
|
|
62
|
+
Converts from the DB type to the Python type.
|
|
63
|
+
"""
|
|
64
|
+
if value is not None:
|
|
65
|
+
value = int(value) # pylint: disable=E1102
|
|
66
|
+
self.validate(value)
|
|
67
|
+
return IntEnumClass(value, self.enum_list)
|
|
68
|
+
|
|
69
|
+
def get_description(self, enum_list):
|
|
70
|
+
description = ""
|
|
71
|
+
for index, val in enumerate(enum_list, 1):
|
|
72
|
+
description += f"{index}:{val};"
|
|
73
|
+
return description
|
|
74
|
+
|
|
75
|
+
def create_enum(self, enum_list):
|
|
76
|
+
# 创建枚举类的成员字典,确保值是唯一的
|
|
77
|
+
|
|
78
|
+
members = {self.number_to_words(name): name for name, _ in enumerate(enum_list, 1)}
|
|
79
|
+
|
|
80
|
+
# 使用Enum的元类EnumMeta来动态创建枚举类
|
|
81
|
+
enum_class = IntEnum("CategoryEnum", members)
|
|
82
|
+
|
|
83
|
+
# 返回创建的枚举类
|
|
84
|
+
return enum_class
|
|
85
|
+
|
|
86
|
+
def number_to_words(self, num):
|
|
87
|
+
# 定义数字到单词的映射
|
|
88
|
+
ones = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten',
|
|
89
|
+
'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen']
|
|
90
|
+
tens = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']
|
|
91
|
+
|
|
92
|
+
if num == 0:
|
|
93
|
+
return 'zero'
|
|
94
|
+
|
|
95
|
+
if num < 20:
|
|
96
|
+
return ones[num]
|
|
97
|
+
|
|
98
|
+
if num < 100:
|
|
99
|
+
ten_digit = num // 10
|
|
100
|
+
one_digit = num % 10
|
|
101
|
+
if one_digit == 0:
|
|
102
|
+
return tens[ten_digit]
|
|
103
|
+
else:
|
|
104
|
+
return tens[ten_digit] + '_' + ones[one_digit]
|
|
105
|
+
|
|
106
|
+
if num < 1000:
|
|
107
|
+
hundred_digit = num // 100
|
|
108
|
+
remaining = num % 100
|
|
109
|
+
return ones[hundred_digit] + '_hundred_' + self.number_to_words(remaining)
|
|
110
|
+
|
|
111
|
+
# 对于大于1000的数字,可以进一步扩展这个函数来处理
|
|
112
|
+
# 但请注意,标准的英文数字读法会变得复杂,涉及"thousand", "million", "billion"等词
|
|
113
|
+
|
|
114
|
+
# 这里仅处理到9999,如果需要处理更大的数字,请继续扩展这个函数
|
|
115
|
+
thousand_digit = num // 1000
|
|
116
|
+
remaining = num % 1000
|
|
117
|
+
return self.number_to_words(thousand_digit) + '_thousand_' + self.number_to_words(remaining)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class IntEnumClass:
|
|
121
|
+
def __init__(self, value: int, name_list: list):
|
|
122
|
+
self.value = value
|
|
123
|
+
self.name_list = name_list
|
|
124
|
+
|
|
125
|
+
def __str__(self):
|
|
126
|
+
return f"{self.value}"
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def name(self):
|
|
130
|
+
try:
|
|
131
|
+
return self.name_list[self.value-1]
|
|
132
|
+
except:
|
|
133
|
+
return self.value
|
|
134
|
+
|
|
135
|
+
def __add__(self, other: Union["IntEnumClass", int]):
|
|
136
|
+
if isinstance(other, IntEnumClass):
|
|
137
|
+
return IntEnumClass(self.value + other.value, self.name_list)
|
|
138
|
+
elif isinstance(other, int):
|
|
139
|
+
return IntEnumClass(self.value + other, self.name_list)
|
|
140
|
+
else:
|
|
141
|
+
raise TypeError(f"unsupported operand type(s) for +: 'NamedInt' and '{type(other).__name__}'")
|
|
142
|
+
|
|
143
|
+
def __sub__(self, other: Union["IntEnumClass", int]):
|
|
144
|
+
if isinstance(other, IntEnumClass):
|
|
145
|
+
return IntEnumClass(self.value - other.value, self.name_list)
|
|
146
|
+
elif isinstance(other, int):
|
|
147
|
+
return IntEnumClass(self.value - other, self.name_list)
|
|
148
|
+
else:
|
|
149
|
+
raise TypeError(f"unsupported operand type(s) for -: 'IntEnumClass' and '{type(other).__name__}'")
|
|
150
|
+
|
|
151
|
+
def __mul__(self, other: Union["IntEnumClass", int]):
|
|
152
|
+
if isinstance(other, IntEnumClass):
|
|
153
|
+
return IntEnumClass(self.value * other.value, self.name_list)
|
|
154
|
+
elif isinstance(other, int):
|
|
155
|
+
return IntEnumClass(self.value * other, self.name_list)
|
|
156
|
+
else:
|
|
157
|
+
raise TypeError(f"unsupported operand type(s) for *: 'IntEnumClass' and '{type(other).__name__}'")
|
|
158
|
+
|
|
159
|
+
def __truediv__(self, other: Union["IntEnumClass", int]):
|
|
160
|
+
if isinstance(other, IntEnumClass):
|
|
161
|
+
if other.value == 0:
|
|
162
|
+
raise ZeroDivisionError("division by zero")
|
|
163
|
+
return IntEnumClass(self.value // other.value, self.name_list)
|
|
164
|
+
elif isinstance(other, int):
|
|
165
|
+
if other == 0:
|
|
166
|
+
raise ZeroDivisionError("division by zero")
|
|
167
|
+
return IntEnumClass(self.value // other, self.name_list)
|
|
168
|
+
else:
|
|
169
|
+
raise TypeError(f"unsupported operand type(s) for /: 'IntEnumClass' and '{type(other).__name__}'")
|
|
170
|
+
|
|
171
|
+
def __eq__(self, other: Union["IntEnumClass", int]):
|
|
172
|
+
if isinstance(other, IntEnumClass):
|
|
173
|
+
return self.value == other.value
|
|
174
|
+
elif isinstance(other, int):
|
|
175
|
+
return self.value == other
|
|
176
|
+
return NotImplemented
|
|
177
|
+
|
|
178
|
+
def __ne__(self, other: Union["IntEnumClass", int]):
|
|
179
|
+
result = self.__eq__(other)
|
|
180
|
+
if result is NotImplemented:
|
|
181
|
+
return result
|
|
182
|
+
return not result
|
|
183
|
+
|
|
184
|
+
def __lt__(self, other: Union["IntEnumClass", int]):
|
|
185
|
+
if isinstance(other, IntEnumClass):
|
|
186
|
+
return self.value < other.value
|
|
187
|
+
elif isinstance(other, int):
|
|
188
|
+
return self.value < other
|
|
189
|
+
return NotImplemented
|
|
190
|
+
|
|
191
|
+
def __le__(self, other: Union["IntEnumClass", int]):
|
|
192
|
+
if isinstance(other, IntEnumClass):
|
|
193
|
+
return self.value <= other.value
|
|
194
|
+
elif isinstance(other, int):
|
|
195
|
+
return self.value <= other
|
|
196
|
+
return NotImplemented
|
|
197
|
+
|
|
198
|
+
def __gt__(self, other: Union["IntEnumClass", int]):
|
|
199
|
+
if isinstance(other, IntEnumClass):
|
|
200
|
+
return self.value > other.value
|
|
201
|
+
elif isinstance(other, int):
|
|
202
|
+
return self.value > other
|
|
203
|
+
return NotImplemented
|
|
204
|
+
|
|
205
|
+
def __ge__(self, other: Union["IntEnumClass", int]):
|
|
206
|
+
if isinstance(other, IntEnumClass):
|
|
207
|
+
return self.value >= other.value
|
|
208
|
+
elif isinstance(other, int):
|
|
209
|
+
return self.value >= other
|
|
210
|
+
return NotImplemented
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
TYPE_CHECKING,
|
|
3
|
+
Any,
|
|
4
|
+
Callable,
|
|
5
|
+
Dict,
|
|
6
|
+
Generic,
|
|
7
|
+
List,
|
|
8
|
+
Optional,
|
|
9
|
+
Tuple,
|
|
10
|
+
Type,
|
|
11
|
+
TypeVar,
|
|
12
|
+
Union,
|
|
13
|
+
overload,
|
|
14
|
+
)
|
|
15
|
+
from tortoise.fields import Field
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING: # pragma: nocoverage
|
|
18
|
+
from tortoise.models import Model
|
|
19
|
+
|
|
20
|
+
from fastgenerateapi.utils.snowflake import worker
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PrimaryKeyField(Field[str], str):
|
|
24
|
+
"""
|
|
25
|
+
Big integer field. (64-bit signed)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
SQL_TYPE = "BIGINT"
|
|
29
|
+
allows_generated = True
|
|
30
|
+
|
|
31
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
32
|
+
kwargs["generated"] = False
|
|
33
|
+
kwargs["unique"] = True
|
|
34
|
+
kwargs["description"] = "主键"
|
|
35
|
+
kwargs["default"] = worker.get_id
|
|
36
|
+
super().__init__(pk=True, **kwargs)
|
|
37
|
+
|
|
38
|
+
def to_db_value(self, value: Any, instance: "Union[Type[Model], Model]") -> Any:
|
|
39
|
+
"""
|
|
40
|
+
Converts from the Python type to the DB type.
|
|
41
|
+
"""
|
|
42
|
+
if value is not None:
|
|
43
|
+
value = int(value) # pylint: disable=E1102
|
|
44
|
+
self.validate(value)
|
|
45
|
+
return value
|
|
46
|
+
|
|
47
|
+
def to_python_value(self, value: Any) -> Any:
|
|
48
|
+
"""
|
|
49
|
+
Converts from the DB type to the Python type.
|
|
50
|
+
"""
|
|
51
|
+
if value is not None:
|
|
52
|
+
value = str(value) # pylint: disable=E1102
|
|
53
|
+
self.validate(value)
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
TYPE_CHECKING,
|
|
3
|
+
Any,
|
|
4
|
+
Callable,
|
|
5
|
+
Dict,
|
|
6
|
+
Generic,
|
|
7
|
+
List,
|
|
8
|
+
Optional,
|
|
9
|
+
Tuple,
|
|
10
|
+
Type,
|
|
11
|
+
TypeVar,
|
|
12
|
+
Union,
|
|
13
|
+
overload,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from passlib.handlers.pbkdf2 import pbkdf2_sha256
|
|
17
|
+
from tortoise.fields import CharField
|
|
18
|
+
if TYPE_CHECKING: # pragma: nocoverage
|
|
19
|
+
from tortoise.models import Model
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PasswordField(CharField):
|
|
23
|
+
"""
|
|
24
|
+
Character field.
|
|
25
|
+
|
|
26
|
+
You must provide the following:
|
|
27
|
+
|
|
28
|
+
``salt`` (str):
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, salt: str, rounds=1000, **kwargs: Any) -> None:
|
|
32
|
+
kwargs.setdefault("max_length", 255)
|
|
33
|
+
kwargs.setdefault("description", "密码")
|
|
34
|
+
self.salt = salt
|
|
35
|
+
self.rounds = rounds
|
|
36
|
+
super().__init__(**kwargs)
|
|
37
|
+
|
|
38
|
+
def to_db_value(self, value: Any, instance: "Union[Type[Model], Model]") -> Any:
|
|
39
|
+
"""
|
|
40
|
+
Converts from the Python type to the DB type.
|
|
41
|
+
"""
|
|
42
|
+
if value is not None:
|
|
43
|
+
if not isinstance(value, self.field_type):
|
|
44
|
+
value = self.field_type(value) # pylint: disable=E1102
|
|
45
|
+
custom_pbkdf2 = pbkdf2_sha256.using(salt=self.salt.encode('utf-8'), rounds=self.rounds)
|
|
46
|
+
value = custom_pbkdf2.hash(value)
|
|
47
|
+
|
|
48
|
+
self.validate(value)
|
|
49
|
+
return value
|
|
50
|
+
|
|
51
|
+
def to_python_value(self, value: Any) -> Any:
|
|
52
|
+
"""
|
|
53
|
+
Converts from the DB type to the Python type.
|
|
54
|
+
"""
|
|
55
|
+
if value is not None and not isinstance(value, self.field_type):
|
|
56
|
+
value = PasswordString(self.field_type(value), self.salt, self.rounds) # pylint: disable=E1102
|
|
57
|
+
self.validate(value)
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class PasswordString(str):
|
|
62
|
+
def __init__(self, password, salt, rounds):
|
|
63
|
+
self.value = password
|
|
64
|
+
self.salt = salt
|
|
65
|
+
self.rounds = rounds
|
|
66
|
+
super().__init__()
|
|
67
|
+
|
|
68
|
+
def __str__(self):
|
|
69
|
+
return self.value
|
|
70
|
+
|
|
71
|
+
def check_valid(self, password: str) -> bool:
|
|
72
|
+
"""
|
|
73
|
+
检查密码是否正确
|
|
74
|
+
"""
|
|
75
|
+
custom_pbkdf2 = pbkdf2_sha256.using(salt=self.salt.encode('utf-8'), rounds=self.rounds)
|
|
76
|
+
return self.password == custom_pbkdf2.hash(password)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
TYPE_CHECKING,
|
|
3
|
+
Any,
|
|
4
|
+
Callable,
|
|
5
|
+
Dict,
|
|
6
|
+
Generic,
|
|
7
|
+
List,
|
|
8
|
+
Optional,
|
|
9
|
+
Tuple,
|
|
10
|
+
Type,
|
|
11
|
+
TypeVar,
|
|
12
|
+
Union,
|
|
13
|
+
overload,
|
|
14
|
+
)
|
|
15
|
+
from tortoise.fields import IntField
|
|
16
|
+
if TYPE_CHECKING: # pragma: nocoverage
|
|
17
|
+
from tortoise.models import Model
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SoftDeleteField(IntField):
|
|
21
|
+
"""
|
|
22
|
+
Integer field. (64-bit signed)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
allows_generated = False
|
|
26
|
+
|
|
27
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
28
|
+
kwargs.setdefault("index", True)
|
|
29
|
+
kwargs.setdefault("null", True)
|
|
30
|
+
kwargs.setdefault("default", None)
|
|
31
|
+
kwargs.setdefault("description", "软删除")
|
|
32
|
+
super().__init__(**kwargs)
|
|
33
|
+
|
|
34
|
+
def to_db_value(self, value: Any, instance: "Union[Type[Model], Model]") -> Any:
|
|
35
|
+
"""
|
|
36
|
+
Converts from the Python type to the DB type.
|
|
37
|
+
"""
|
|
38
|
+
if value is not None and not isinstance(value, self.field_type):
|
|
39
|
+
value = self.field_type(value) # pylint: disable=E1102
|
|
40
|
+
if value == 0:
|
|
41
|
+
value = None
|
|
42
|
+
self.validate(value)
|
|
43
|
+
return value
|
|
44
|
+
|
|
45
|
+
def to_python_value(self, value: Any) -> Any:
|
|
46
|
+
"""
|
|
47
|
+
Converts from the DB type to the Python type.
|
|
48
|
+
"""
|
|
49
|
+
if value is not None and not isinstance(value, self.field_type):
|
|
50
|
+
value = self.field_type(value) # pylint: disable=E1102
|
|
51
|
+
self.validate(value)
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
|
|
@@ -63,7 +63,8 @@ def get_field_info_from_model_class(model_class: Type[Model], field: str, descri
|
|
|
63
63
|
return Optional[str], FieldInfo(default=None, description=f"{field}")
|
|
64
64
|
return get_field_info(value, description=description)
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
field_info = model_class._meta.fields_map.get(field.split("__", maxsplit=1)[0])
|
|
67
|
+
if field_info:
|
|
67
68
|
description += field_info.description or ""
|
|
68
69
|
|
|
69
70
|
model_class = DBModelMixin._get_foreign_key_relation_class(model_class, field.split("__", maxsplit=1)[0])
|
|
@@ -26,7 +26,7 @@ def common_schema_factory(
|
|
|
26
26
|
include_fields = set()
|
|
27
27
|
exclude_fields = set()
|
|
28
28
|
if exclude_readonly:
|
|
29
|
-
exclude_fields.update(["id", "is_active", "created_at", "modified_at", "updated_at"])
|
|
29
|
+
exclude_fields.update(["id", "is_active", "deleted_at", "created_at", "modified_at", "updated_at"])
|
|
30
30
|
if hasattr(model_class, "PydanticMeta"):
|
|
31
31
|
if hasattr(model_class.PydanticMeta, "include"):
|
|
32
32
|
include_fields_dict = get_dict_from_pydanticmeta(model_class, model_class.PydanticMeta.include)
|
|
@@ -34,10 +34,16 @@ def create_schema_factory(
|
|
|
34
34
|
include_fields.update(all_fields_info.keys())
|
|
35
35
|
if hasattr(model_class.PydanticMeta, "exclude"):
|
|
36
36
|
exclude_fields.update(model_class.PydanticMeta.exclude)
|
|
37
|
+
if hasattr(model_class.PydanticMeta, "save_include"):
|
|
38
|
+
save_include_fields_dict = get_dict_from_pydanticmeta(model_class, model_class.PydanticMeta.save_include)
|
|
39
|
+
all_fields_info.update(save_include_fields_dict)
|
|
40
|
+
include_fields.update(save_include_fields_dict.keys())
|
|
37
41
|
if hasattr(model_class.PydanticMeta, "create_include"):
|
|
38
|
-
|
|
39
|
-
all_fields_info.update(
|
|
40
|
-
include_fields.update(
|
|
42
|
+
create_include_fields_dict = get_dict_from_pydanticmeta(model_class, model_class.PydanticMeta.create_include)
|
|
43
|
+
all_fields_info.update(create_include_fields_dict)
|
|
44
|
+
include_fields.update(create_include_fields_dict.keys())
|
|
45
|
+
if hasattr(model_class.PydanticMeta, "save_exclude"):
|
|
46
|
+
exclude_fields.update(model_class.PydanticMeta.save_exclude)
|
|
41
47
|
if hasattr(model_class.PydanticMeta, "create_exclude"):
|
|
42
48
|
exclude_fields.update(model_class.PydanticMeta.create_exclude)
|
|
43
49
|
else:
|
|
@@ -40,6 +40,13 @@ def get_all_schema_factory(
|
|
|
40
40
|
if hasattr(model_class.PydanticMeta, "exclude"):
|
|
41
41
|
exclude_fields.update(model_class.PydanticMeta.exclude)
|
|
42
42
|
|
|
43
|
+
if hasattr(model_class.PydanticMeta, "get_include"):
|
|
44
|
+
get_include_fields_dict = get_dict_from_pydanticmeta(model_class, model_class.PydanticMeta.get_include)
|
|
45
|
+
all_fields_info.update(get_include_fields_dict)
|
|
46
|
+
include_fields.update(get_include_fields_dict.keys())
|
|
47
|
+
if hasattr(model_class.PydanticMeta, "get_exclude"):
|
|
48
|
+
exclude_fields.update(model_class.PydanticMeta.get_exclude)
|
|
49
|
+
|
|
43
50
|
# get_all_include
|
|
44
51
|
if hasattr(model_class.PydanticMeta, "get_all_include"):
|
|
45
52
|
get_all_include_fields_dict = get_dict_from_pydanticmeta(model_class, model_class.PydanticMeta.get_all_include)
|
|
@@ -33,6 +33,14 @@ def get_one_schema_factory(
|
|
|
33
33
|
include_fields.update(all_fields_info.keys())
|
|
34
34
|
if hasattr(model_class.PydanticMeta, "exclude"):
|
|
35
35
|
exclude_fields.update(model_class.PydanticMeta.exclude)
|
|
36
|
+
|
|
37
|
+
if hasattr(model_class.PydanticMeta, "get_include"):
|
|
38
|
+
get_include_fields_dict = get_dict_from_pydanticmeta(model_class, model_class.PydanticMeta.get_include)
|
|
39
|
+
all_fields_info.update(get_include_fields_dict)
|
|
40
|
+
include_fields.update(get_include_fields_dict.keys())
|
|
41
|
+
if hasattr(model_class.PydanticMeta, "get_exclude"):
|
|
42
|
+
exclude_fields.update(model_class.PydanticMeta.get_exclude)
|
|
43
|
+
|
|
36
44
|
if hasattr(model_class.PydanticMeta, "get_one_include"):
|
|
37
45
|
get_one_include_fields_dict = get_dict_from_pydanticmeta(model_class, model_class.PydanticMeta.get_one_include)
|
|
38
46
|
all_fields_info.update(get_one_include_fields_dict)
|
|
@@ -38,6 +38,14 @@ def get_tree_schema_factory(
|
|
|
38
38
|
include_fields.update(include_fields_dict.keys())
|
|
39
39
|
else:
|
|
40
40
|
include_fields.update(all_fields_info.keys())
|
|
41
|
+
|
|
42
|
+
if hasattr(model_class.PydanticMeta, "get_include"):
|
|
43
|
+
get_include_fields_dict = get_dict_from_pydanticmeta(model_class, model_class.PydanticMeta.get_include)
|
|
44
|
+
all_fields_info.update(get_include_fields_dict)
|
|
45
|
+
include_fields.update(get_include_fields_dict.keys())
|
|
46
|
+
if hasattr(model_class.PydanticMeta, "get_exclude"):
|
|
47
|
+
exclude_fields.update(model_class.PydanticMeta.get_exclude)
|
|
48
|
+
|
|
41
49
|
if hasattr(model_class.PydanticMeta, "get_tree_exclude"):
|
|
42
50
|
exclude_fields.update(model_class.PydanticMeta.get_tree_exclude)
|
|
43
51
|
if hasattr(model_class.PydanticMeta, "exclude"):
|
|
@@ -34,10 +34,16 @@ def update_schema_factory(
|
|
|
34
34
|
include_fields.update(all_fields_info.keys())
|
|
35
35
|
if hasattr(model_class.PydanticMeta, "exclude"):
|
|
36
36
|
exclude_fields.update(model_class.PydanticMeta.exclude)
|
|
37
|
+
if hasattr(model_class.PydanticMeta, "save_include"):
|
|
38
|
+
save_include_fields_dict = get_dict_from_pydanticmeta(model_class, model_class.PydanticMeta.save_include)
|
|
39
|
+
all_fields_info.update(save_include_fields_dict)
|
|
40
|
+
include_fields.update(save_include_fields_dict.keys())
|
|
37
41
|
if hasattr(model_class.PydanticMeta, "update_include"):
|
|
38
|
-
|
|
39
|
-
all_fields_info.update(
|
|
40
|
-
include_fields.update(
|
|
42
|
+
update_include_fields_dict = get_dict_from_pydanticmeta(model_class, model_class.PydanticMeta.update_include)
|
|
43
|
+
all_fields_info.update(update_include_fields_dict)
|
|
44
|
+
include_fields.update(update_include_fields_dict.keys())
|
|
45
|
+
if hasattr(model_class.PydanticMeta, "save_exclude"):
|
|
46
|
+
exclude_fields.update(model_class.PydanticMeta.update_exclude)
|
|
41
47
|
if hasattr(model_class.PydanticMeta, "update_exclude"):
|
|
42
48
|
exclude_fields.update(model_class.PydanticMeta.update_exclude)
|
|
43
49
|
else:
|
|
@@ -29,12 +29,12 @@ class AppSettings(BaseSettings):
|
|
|
29
29
|
ROUTER_WHETHER_UNDERLINE_TO_STRIKE: Optional[bool] = Field(default=False, description="路由是否下划线转中划线")
|
|
30
30
|
ROUTER_WHETHER_ADD_SUFFIX: Optional[bool] = Field(default=True, description="增删改查路由是否添加后缀")
|
|
31
31
|
ROUTER_CREATE_SUFFIX_FIELD: Optional[str] = Field(default='create', description="创建路由后缀字段")
|
|
32
|
-
ROUTER_GET_ONE_SUFFIX_FIELD: Optional[str] = Field(default='
|
|
33
|
-
ROUTER_GET_ALL_SUFFIX_FIELD: Optional[str] = Field(default='
|
|
34
|
-
ROUTER_GET_TREE_SUFFIX_FIELD: Optional[str] = Field(default='
|
|
32
|
+
ROUTER_GET_ONE_SUFFIX_FIELD: Optional[str] = Field(default='get_one', description="获取一个路由后缀字段")
|
|
33
|
+
ROUTER_GET_ALL_SUFFIX_FIELD: Optional[str] = Field(default='get_all', description="获取列表路由后缀字段")
|
|
34
|
+
ROUTER_GET_TREE_SUFFIX_FIELD: Optional[str] = Field(default='get_tree', description="获取树状数据路由后缀字段")
|
|
35
35
|
ROUTER_UPDATE_SUFFIX_FIELD: Optional[str] = Field(default='update', description="修改路由后缀字段")
|
|
36
36
|
ROUTER_DELETE_SUFFIX_FIELD: Optional[str] = Field(default='delete', description="删除路由后缀字段")
|
|
37
|
-
ROUTER_RECURSION_DELETE_SUFFIX_FIELD: Optional[str] = Field(default='
|
|
37
|
+
ROUTER_RECURSION_DELETE_SUFFIX_FIELD: Optional[str] = Field(default='delete_tree', description="递归删除路由后缀字段")
|
|
38
38
|
|
|
39
39
|
# 函数转换路由时,默认添加字段,(遵循restful规范时,get路由处理方案)
|
|
40
40
|
RESTFUL_GET_ROUTER_ADD_PREFIX: Optional[str] = Field(default='', description="函数转换路由时:前缀添加字段")
|
|
@@ -46,9 +46,14 @@ class AppSettings(BaseSettings):
|
|
|
46
46
|
RESTFUL_DELETE_ROUTER_ADD_PREFIX: Optional[str] = Field(default='', description="函数转换路由时:前缀添加字段")
|
|
47
47
|
RESTFUL_DELETE_ROUTER_ADD_SUFFIX: Optional[str] = Field(default='', description="函数转换路由时:后缀pk前添加字段")
|
|
48
48
|
|
|
49
|
+
# 分布式id
|
|
50
|
+
WORKER_ID: Optional[int] = Field(default=1, description="数据中心(机器区域)ID")
|
|
51
|
+
DATACENTER_ID: Optional[int] = Field(default=1, description="机器ID")
|
|
52
|
+
|
|
49
53
|
# 数据库字段默认值
|
|
50
|
-
WHETHER_DELETE_FIELD: Optional[str] = Field(default="
|
|
51
|
-
|
|
54
|
+
WHETHER_DELETE_FIELD: Optional[str] = Field(default="deleted_at", description="是否删除字段;推荐命名 >> deleted_at;is_active")
|
|
55
|
+
DELETE_FIELD_TYPE: Optional[str] = Field(default="time", description="删除字段类型;推荐命名 >> time;bool")
|
|
56
|
+
# ACTIVE_DEFAULT_VALUE: Optional[bool] = Field(default=True, description="有效的默认值")
|
|
52
57
|
GET_EXCLUDE_ACTIVE_VALUE: Optional[bool] = Field(default=True, description="查询结果是否排除有效字段")
|
|
53
58
|
CREATE_EXCLUDE_ACTIVE_VALUE: Optional[bool] = Field(default=True, description="创建是否排除有效字段")
|
|
54
59
|
UPDATE_EXCLUDE_ACTIVE_VALUE: Optional[bool] = Field(default=True, description="修改是否排除有效字段")
|
|
@@ -70,7 +75,7 @@ class AppSettings(BaseSettings):
|
|
|
70
75
|
SCHEMAS_UNDERLINE_WHETHER_DOUBLE_TO_SINGLE: Optional[bool] = Field(default=True, description="序列化字段是否双下划线转单下划线")
|
|
71
76
|
|
|
72
77
|
class Config:
|
|
73
|
-
|
|
78
|
+
env_prefix = 'APP_'
|
|
74
79
|
env_file = "./.env"
|
|
75
80
|
case_sensitive = True
|
|
76
81
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from Crypto import Random
|
|
4
|
+
from Crypto.Cipher import AES
|
|
5
|
+
import base64
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AESCipher(object):
|
|
9
|
+
"""
|
|
10
|
+
可用于二进制(文件)和字符串加密
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, salt):
|
|
13
|
+
'''
|
|
14
|
+
CBC加密需要一个十六位的key(密钥)和一个十六位iv(偏移量)
|
|
15
|
+
'''
|
|
16
|
+
self.salt = self.check_key(salt)
|
|
17
|
+
# 这里使用随机偏移量
|
|
18
|
+
# self.iv = self.check_key(iv) if iv else self.salt
|
|
19
|
+
# 数据块的大小 16位
|
|
20
|
+
self.BS = AES.block_size
|
|
21
|
+
# CBC模式 相对安全 因为有偏移向量 iv 也是16位字节的
|
|
22
|
+
self.mode = AES.MODE_CBC
|
|
23
|
+
# 填充函数 因为AES加密是一段一段加密的 每段都是BS位字节,不够的话是需要自己填充的
|
|
24
|
+
self.pad = lambda s: s + (self.BS - len(s) % self.BS) * chr(self.BS - len(s) % self.BS).encode('utf-8')
|
|
25
|
+
# 将填充的数据剔除
|
|
26
|
+
self.unpad = lambda s: s[:-ord(s[len(s) - 1:])]
|
|
27
|
+
|
|
28
|
+
def encrypt(self, text: Union[bytes, str]) -> bytes:
|
|
29
|
+
text = self.check_data(text)
|
|
30
|
+
text = self.pad(text)
|
|
31
|
+
# 随机获取iv
|
|
32
|
+
iv = Random.new().read(AES.block_size)
|
|
33
|
+
# 初始化自定义
|
|
34
|
+
cipher = AES.new(self.salt, self.mode, iv)
|
|
35
|
+
# 此处是将密文和iv一起 base64 解密的时候就可以根据这个iv来解密
|
|
36
|
+
return base64.b64encode(iv + cipher.encrypt(text))
|
|
37
|
+
# return base64.b64encode(iv + cipher.encrypt(text)).decode("utf-8")
|
|
38
|
+
|
|
39
|
+
def decrypt(self, text: Union[bytes, str, int]) -> bytes:
|
|
40
|
+
text = self.check_data(text)
|
|
41
|
+
# 先将密文进行base64解码
|
|
42
|
+
text = base64.b64decode(text)
|
|
43
|
+
# 取出iv值
|
|
44
|
+
iv = text[:self.BS]
|
|
45
|
+
# 初始化自定义
|
|
46
|
+
cipher = AES.new(self.salt, self.mode, iv)
|
|
47
|
+
return self.unpad(cipher.decrypt(text[self.BS:]))
|
|
48
|
+
# return self.unpad(cipher.decrypt(text[self.BS:])).decode("utf-8")
|
|
49
|
+
|
|
50
|
+
def check_key(self, key: Union[bytes, str]) -> bytes:
|
|
51
|
+
'''
|
|
52
|
+
检测key的长度是否为16,24或者32bytes的长度
|
|
53
|
+
'''
|
|
54
|
+
if isinstance(key, bytes):
|
|
55
|
+
assert len(key) in [16, 24, 32]
|
|
56
|
+
return key
|
|
57
|
+
elif isinstance(key, str):
|
|
58
|
+
assert len(key.encode("utf-8")) in [16, 24, 32]
|
|
59
|
+
return key.encode("utf-8")
|
|
60
|
+
else:
|
|
61
|
+
raise Exception(f'密钥必须为str或bytes,不能为{type(key)}')
|
|
62
|
+
|
|
63
|
+
def check_data(self, data: Union[bytes, str, int]) -> bytes:
|
|
64
|
+
'''
|
|
65
|
+
检测加密的数据类型
|
|
66
|
+
'''
|
|
67
|
+
if isinstance(data, int):
|
|
68
|
+
data = str(data).encode('utf-8')
|
|
69
|
+
elif isinstance(data, str):
|
|
70
|
+
data = data.encode('utf-8')
|
|
71
|
+
elif isinstance(data, bytes):
|
|
72
|
+
pass
|
|
73
|
+
else:
|
|
74
|
+
raise Exception(f'加密的数据必须为str或bytes,不能为{type(data)}')
|
|
75
|
+
return data
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# if __name__ == '__main__':
|
|
79
|
+
# # 加密字符串
|
|
80
|
+
# import json
|
|
81
|
+
# # aes_encrypt = AESCipher('ningbozhihuirend') # 自己设定的密钥
|
|
82
|
+
# aes_encrypt = AESCipher('ningbozhihuirend') # 自己设定的密钥
|
|
83
|
+
# data = json.dumps({"mobile": "1312345646", "content": "xczczczc"})
|
|
84
|
+
# print(type(data))
|
|
85
|
+
# print(data)
|
|
86
|
+
# e = aes_encrypt.encrypt(data).decode("utf-8") # 加密内容
|
|
87
|
+
# print(type(e))
|
|
88
|
+
# print(e)
|
|
89
|
+
# d = aes_encrypt.decrypt(e).decode("utf-8")
|
|
90
|
+
# print(type(d))
|
|
91
|
+
# print(json.loads(d))
|
|
92
|
+
# print(len(e))
|
|
93
|
+
# print("加密后%s,解密后%s" % (e, d))
|