arpakitlib 1.8.254__py3-none-any.whl → 1.8.256__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.
- arpakitlib/ar_pydantic_schema_from_sqlalchemy_model.py +83 -9
- {arpakitlib-1.8.254.dist-info → arpakitlib-1.8.256.dist-info}/METADATA +1 -1
- {arpakitlib-1.8.254.dist-info → arpakitlib-1.8.256.dist-info}/RECORD +6 -6
- {arpakitlib-1.8.254.dist-info → arpakitlib-1.8.256.dist-info}/LICENSE +0 -0
- {arpakitlib-1.8.254.dist-info → arpakitlib-1.8.256.dist-info}/WHEEL +0 -0
- {arpakitlib-1.8.254.dist-info → arpakitlib-1.8.256.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,8 @@
|
|
1
1
|
# arpakit
|
2
2
|
import datetime as dt
|
3
|
-
from typing import Any, Optional, get_type_hints
|
3
|
+
from typing import Any, Optional, get_type_hints, get_origin, Union, Annotated, get_args
|
4
4
|
|
5
|
-
from pydantic import BaseModel, Field
|
5
|
+
from pydantic import BaseModel, Field, ConfigDict
|
6
6
|
from sqlalchemy import inspect
|
7
7
|
from sqlalchemy.orm import ColumnProperty
|
8
8
|
from sqlalchemy.sql.sqltypes import (
|
@@ -27,7 +27,7 @@ _SQLA_TYPE_MAP = {
|
|
27
27
|
Text: str,
|
28
28
|
UnicodeText: str,
|
29
29
|
LargeBinary: bytes,
|
30
|
-
JSON: dict,
|
30
|
+
JSON: dict | list,
|
31
31
|
DateTime: dt.datetime,
|
32
32
|
Date: dt.date,
|
33
33
|
Time: dt.time,
|
@@ -44,23 +44,70 @@ def _python_type_from_col(col) -> type | str:
|
|
44
44
|
return Any
|
45
45
|
|
46
46
|
|
47
|
-
def _get_property_name_to_type_from_model_class(
|
47
|
+
def _get_property_name_to_type_from_model_class(
|
48
|
+
*,
|
49
|
+
model_class: type,
|
50
|
+
skip_property_if_cannot_define_type: bool = True,
|
51
|
+
exclude_property_names: list[str] | None = None,
|
52
|
+
exclude_property_types: list[type] | None = None,
|
53
|
+
) -> dict[str, Any]:
|
48
54
|
"""
|
49
55
|
Находит все @property в классе и вытаскивает их возвращаемый тип.
|
50
56
|
Если тип не удаётся получить — подставляем Any.
|
51
57
|
"""
|
58
|
+
exclude_property_names = set(exclude_property_names or [])
|
52
59
|
props: dict[str, Any] = {}
|
53
|
-
for
|
60
|
+
for property_name, attr in vars(model_class).items():
|
54
61
|
if isinstance(attr, property):
|
55
62
|
try:
|
56
63
|
hints = get_type_hints(attr.fget) if attr.fget else {}
|
57
64
|
ret_type = hints.get("return", Any)
|
58
65
|
except Exception:
|
66
|
+
if skip_property_if_cannot_define_type:
|
67
|
+
continue
|
59
68
|
ret_type = Any
|
60
|
-
|
69
|
+
if exclude_property_names:
|
70
|
+
if property_name in exclude_property_names:
|
71
|
+
continue
|
72
|
+
if exclude_property_types:
|
73
|
+
if not _type_matches(type_=ret_type, allowed_types=exclude_property_types):
|
74
|
+
continue
|
75
|
+
props[property_name] = ret_type
|
61
76
|
return props
|
62
77
|
|
63
78
|
|
79
|
+
def _type_matches(*, type_: Any, allowed_types: list[type]) -> bool:
|
80
|
+
"""
|
81
|
+
Возвращает True, если тип `tp` соответствует хоть одному из типов в `allowed`.
|
82
|
+
- поддерживает Union/Optional (перебирает аргументы),
|
83
|
+
- Annotated (смотрит на базовый тип),
|
84
|
+
- generics (List[int], Dict[str, Any]) — сравнивает по origin (list, dict, и т.п.).
|
85
|
+
"""
|
86
|
+
if type_ is Any:
|
87
|
+
return True
|
88
|
+
|
89
|
+
origin = get_origin(type_)
|
90
|
+
if origin is Union: # Optional/Union
|
91
|
+
return any(_type_matches(type_=arg, allowed_types=allowed_types) for arg in get_args(type_))
|
92
|
+
if origin is Annotated:
|
93
|
+
return _type_matches(type_=get_args(type_)[0], allowed_types=allowed_types)
|
94
|
+
|
95
|
+
# если это generic, сравним по origin (например, list/dict)
|
96
|
+
type_check = origin or type_
|
97
|
+
|
98
|
+
for allowed_type in allowed_types:
|
99
|
+
# точное совпадение по объекту типа
|
100
|
+
if type_check is allowed_type:
|
101
|
+
return True
|
102
|
+
# безопасная проверка наследования
|
103
|
+
try:
|
104
|
+
if isinstance(type_check, type) and isinstance(allowed_type, type) and issubclass(type_check, allowed_type):
|
105
|
+
return True
|
106
|
+
except Exception:
|
107
|
+
pass
|
108
|
+
return False
|
109
|
+
|
110
|
+
|
64
111
|
def pydantic_schema_from_sqlalchemy_model(
|
65
112
|
sqlalchemy_model: type,
|
66
113
|
*,
|
@@ -70,9 +117,12 @@ def pydantic_schema_from_sqlalchemy_model(
|
|
70
117
|
exclude_column_names: list[str] | None = None,
|
71
118
|
include_properties: bool = False,
|
72
119
|
include_property_names: list[str] | None = None,
|
120
|
+
include_property_types: list[type] | None = None,
|
73
121
|
exclude_property_names: list[str] | None = None,
|
122
|
+
exclude_property_types: list[type] | None = None,
|
74
123
|
filter_property_prefixes: list[str] | None = None,
|
75
124
|
remove_property_prefixes: list[str] | None = None,
|
125
|
+
skip_property_if_cannot_define_type: bool = True
|
76
126
|
) -> type[BaseModel]:
|
77
127
|
"""
|
78
128
|
Генерирует Pydantic-модель из колонок SQLAlchemy-модели и (опционально) из @property.
|
@@ -125,9 +175,29 @@ def pydantic_schema_from_sqlalchemy_model(
|
|
125
175
|
|
126
176
|
# 2) Свойства (@property)
|
127
177
|
if include_properties:
|
128
|
-
property_name_to_type = _get_property_name_to_type_from_model_class(
|
178
|
+
property_name_to_type = _get_property_name_to_type_from_model_class(
|
179
|
+
model_class=sqlalchemy_model,
|
180
|
+
skip_property_if_cannot_define_type=skip_property_if_cannot_define_type,
|
181
|
+
exclude_property_names=list(exclude_property_names),
|
182
|
+
exclude_property_types=exclude_property_types
|
183
|
+
)
|
129
184
|
|
130
|
-
# фильтр по
|
185
|
+
# (НОВОЕ) фильтр по типам, если задан
|
186
|
+
if include_property_types:
|
187
|
+
property_name_to_type = {
|
188
|
+
k: v for k, v in property_name_to_type.items()
|
189
|
+
if _type_matches(type_=v, allowed_types=include_property_types)
|
190
|
+
}
|
191
|
+
|
192
|
+
# Затем исключающие типы (EXCLUDE)
|
193
|
+
if exclude_property_types:
|
194
|
+
property_name_to_type = {
|
195
|
+
k: v
|
196
|
+
for k, v in property_name_to_type.items()
|
197
|
+
if not _type_matches(type_=v, allowed_types=exclude_property_types)
|
198
|
+
}
|
199
|
+
|
200
|
+
# фильтр по префиксам
|
131
201
|
if filter_property_prefixes:
|
132
202
|
property_name_to_type = {
|
133
203
|
property_name: property_type
|
@@ -151,7 +221,7 @@ def pydantic_schema_from_sqlalchemy_model(
|
|
151
221
|
if property_name in exclude_property_names:
|
152
222
|
property_name_to_type.pop(property_name, None)
|
153
223
|
|
154
|
-
#
|
224
|
+
# удаление префиксов (без изменений)
|
155
225
|
if remove_property_prefixes:
|
156
226
|
renamed_property_name_to_type = {}
|
157
227
|
for property_name, property_type in list(property_name_to_type.items()):
|
@@ -183,4 +253,8 @@ def pydantic_schema_from_sqlalchemy_model(
|
|
183
253
|
annotations[property_name] = property_type
|
184
254
|
|
185
255
|
attrs["__annotations__"] = annotations
|
256
|
+
|
257
|
+
if "model_config" not in attrs:
|
258
|
+
attrs["model_config"] = ConfigDict(extra="ignore", arbitrary_types_allowed=True, from_attributes=True)
|
259
|
+
|
186
260
|
return type(model_name, (base_model,), attrs)
|
@@ -414,7 +414,7 @@ arpakitlib/ar_need_type_util.py,sha256=XmY1kswz8j9oo5f9CxRu0_zgfvxWrXPYKOj6MM04s
|
|
414
414
|
arpakitlib/ar_openai_api_client_util.py,sha256=dWgsSPXtxNBxS5VRi_NharGQrUXF_YjIfhU3Bj5cW9M,5651
|
415
415
|
arpakitlib/ar_parse_command.py,sha256=1WTdQoWVshoDZ1jDaKeTzajfqaYHP3FNO0-REyo1aMY,3003
|
416
416
|
arpakitlib/ar_postgresql_util.py,sha256=1AuLjEaa1Lg4pzn-ukCVnDi35Eg1k91APRTqZhIJAdo,945
|
417
|
-
arpakitlib/ar_pydantic_schema_from_sqlalchemy_model.py,sha256
|
417
|
+
arpakitlib/ar_pydantic_schema_from_sqlalchemy_model.py,sha256=qk439Ii9WFo8vqESt6w93HkRe5XvjOiy-PGa0wXazVg,10677
|
418
418
|
arpakitlib/ar_raise_own_exception_if_exception.py,sha256=A6TuNSBk1pHaQ_qxnUmE2LgsNGA1IGqX26b1_HEA4Nc,5978
|
419
419
|
arpakitlib/ar_rat_func_util.py,sha256=Ca10o3RJwyx_DJLxjTxgHDO6NU3M6CWgUR4bif67OE4,2006
|
420
420
|
arpakitlib/ar_really_validate_email.py,sha256=HBfhyiDB3INI6Iq6hR2WOMKA5wVWWRl0Qun-x__OZ9o,1201
|
@@ -430,8 +430,8 @@ arpakitlib/ar_sqlalchemy_util.py,sha256=FDva9onjtCPrYZYIHHb93NMwD1WlmaORjiWgPRJQ
|
|
430
430
|
arpakitlib/ar_str_util.py,sha256=2lGpnXDf2h1cBZpVf5i1tX_HCv5iBd6IGnrCw4QWWlY,4350
|
431
431
|
arpakitlib/ar_type_util.py,sha256=Cs_tef-Fc5xeyAF54KgISCsP11NHyzIsglm4S3Xx7iM,4049
|
432
432
|
arpakitlib/ar_yookassa_api_client_util.py,sha256=VozuZeCJjmLd1zj2BdC9WfiAQ3XYOrIMsdpNK-AUlm0,5347
|
433
|
-
arpakitlib-1.8.
|
434
|
-
arpakitlib-1.8.
|
435
|
-
arpakitlib-1.8.
|
436
|
-
arpakitlib-1.8.
|
437
|
-
arpakitlib-1.8.
|
433
|
+
arpakitlib-1.8.256.dist-info/LICENSE,sha256=GPEDQMam2r7FSTYqM1mm7aKnxLaWcBotH7UvQtea-ec,11355
|
434
|
+
arpakitlib-1.8.256.dist-info/METADATA,sha256=9PSap0kkeDfMLn2Zv9uffEhxTtP5Y68sT5-eoerWx7M,3919
|
435
|
+
arpakitlib-1.8.256.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
436
|
+
arpakitlib-1.8.256.dist-info/entry_points.txt,sha256=36xqR3PJFT2kuwjkM_EqoIy0qFUDPKSm_mJaI7emewE,87
|
437
|
+
arpakitlib-1.8.256.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|