arpakitlib 1.8.250__py3-none-any.whl → 1.8.252__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.
@@ -1,6 +1,6 @@
1
1
  # arpakit
2
2
  import datetime as dt
3
- from typing import Any, Optional
3
+ from typing import Any, Optional, get_type_hints
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
  from sqlalchemy import inspect
@@ -11,6 +11,7 @@ from sqlalchemy.sql.sqltypes import (
11
11
  DateTime, Date, Time,
12
12
  Float, Numeric, DECIMAL, LargeBinary, JSON
13
13
  )
14
+
14
15
  _ARPAKIT_LIB_MODULE_VERSION = "3.0"
15
16
 
16
17
  _SQLA_TYPE_MAP = {
@@ -43,51 +44,143 @@ def _python_type_from_col(col) -> type | str:
43
44
  return Any
44
45
 
45
46
 
47
+ def _get_property_name_to_type_from_model_class(model_class: type) -> dict[str, Any]:
48
+ """
49
+ Находит все @property в классе и вытаскивает их возвращаемый тип.
50
+ Если тип не удаётся получить — подставляем Any.
51
+ """
52
+ props: dict[str, Any] = {}
53
+ for name, attr in vars(model_class).items():
54
+ if isinstance(attr, property):
55
+ try:
56
+ hints = get_type_hints(attr.fget) if attr.fget else {}
57
+ ret_type = hints.get("return", Any)
58
+ except Exception:
59
+ ret_type = Any
60
+ props[name] = ret_type
61
+ return props
62
+
63
+
46
64
  def pydantic_schema_from_sqlalchemy_model(
47
65
  sqlalchemy_model: type,
48
66
  *,
49
- name: str | None = None,
67
+ model_name: str | None = None,
50
68
  base_model: type[BaseModel] = BaseModel,
51
- include_defaults: bool = False,
69
+ include_column_defaults: bool = False,
52
70
  exclude_column_names: list[str] | None = None,
71
+ include_properties: bool = False,
72
+ include_property_names: list[str] | None = None,
73
+ exclude_property_names: list[str] | None = None,
74
+ filter_property_prefixes: list[str] | None = None,
75
+ remove_prefixes: list[str] | None = None,
53
76
  ) -> type[BaseModel]:
54
77
  """
55
- Генерирует Pydantic-модель только из колонок SQLAlchemy-модели.
56
- - include_defaults: добавлять ли default/server_default.
78
+ Генерирует Pydantic-модель из колонок SQLAlchemy-модели и (опционально) из @property.
79
+
80
+ - include_column_defaults: добавлять ли default/server_default у колонок.
57
81
  - exclude_column_names: список имён колонок, которые нужно пропустить.
82
+
83
+ - include_properties: включать ли свойства (@property). По умолчанию False.
84
+ - include_property_names: whitelist имён свойств (если задан, берём только их).
85
+ - exclude_property_names: blacklist имён свойств (исключаются после whitelist'а).
58
86
  """
59
87
  mapper = inspect(sqlalchemy_model).mapper
60
- model_name = name or f"{sqlalchemy_model.__name__}Schema"
88
+ model_name = model_name or f"{sqlalchemy_model.__name__}Schema"
61
89
 
62
90
  annotations: dict[str, Any] = {}
63
91
  attrs: dict[str, Any] = {}
64
- exclude_column_names = set(exclude_column_names or [])
65
92
 
93
+ exclude_column_names = set(set(exclude_column_names) or [])
94
+ include_property_names = set(set(include_property_names) or [])
95
+ exclude_property_names = set(set(exclude_property_names) or [])
96
+ filter_property_prefixes = list(set(filter_property_prefixes) or [])
97
+ remove_prefixes = list(set(remove_prefixes) or [])
98
+
99
+ # 1) Колонки
66
100
  for prop in mapper.attrs:
67
101
  if not isinstance(prop, ColumnProperty):
68
102
  continue
69
103
  if prop.key in exclude_column_names:
70
104
  continue
71
105
 
72
- col = prop.columns[0]
73
- py_t = _python_type_from_col(col)
106
+ column = prop.columns[0]
107
+ column_type = _python_type_from_col(column)
74
108
 
75
109
  # Аннотация типа
76
- if col.nullable:
77
- annotations[prop.key] = Optional[py_t] # type: ignore[name-defined]
110
+ if column.nullable:
111
+ annotations[prop.key] = Optional[column_type] # type: ignore[name-defined]
78
112
  else:
79
- annotations[prop.key] = py_t
113
+ annotations[prop.key] = column_type
80
114
 
81
- # Если нужно — добавляем дефолт
82
- if include_defaults:
115
+ # Дефолты, если нужно
116
+ if include_column_defaults:
83
117
  default_value = None
84
- if col.default is not None and col.default.is_scalar:
85
- default_value = col.default.arg
86
- elif col.server_default is not None and getattr(col.server_default.arg, "text", None):
87
- default_value = col.server_default.arg.text
118
+ if column.default is not None and getattr(column.default, "is_scalar", False):
119
+ default_value = column.default.arg
120
+ elif column.server_default is not None and getattr(column.server_default.arg, "text", None):
121
+ default_value = column.server_default.arg.text
88
122
 
89
123
  if default_value is not None:
90
124
  attrs[prop.key] = Field(default=default_value)
91
125
 
126
+ # 2) Свойства (@property)
127
+ if include_properties:
128
+ property_name_to_type = _get_property_name_to_type_from_model_class(sqlalchemy_model)
129
+
130
+ # фильтр по префиксам (если задан и не пуст)
131
+ if filter_property_prefixes:
132
+ property_name_to_type = {
133
+ property_name: property_type
134
+ for property_name, property_type in property_name_to_type.items()
135
+ if any(property_name.startswith(v) for v in filter_property_prefixes)
136
+ }
137
+
138
+ # whitelist по именам
139
+ if include_property_names:
140
+ property_name_to_type = {
141
+ k: v
142
+ for k, v in property_name_to_type.items()
143
+ if k in include_property_names
144
+ }
145
+ else:
146
+ property_name_to_type = dict(property_name_to_type)
147
+
148
+ # blacklist по именам
149
+ if exclude_property_names:
150
+ for property_name in list(property_name_to_type.keys()):
151
+ if property_name in exclude_property_names:
152
+ property_name_to_type.pop(property_name, None)
153
+
154
+ # удаляем префиксы
155
+ if remove_prefixes:
156
+ renamed_property_name_to_type = {}
157
+ for property_name, property_type in property_name_to_type.items():
158
+ new_property_name = property_name
159
+ for prefix in remove_prefixes:
160
+ if new_property_name.startswith(prefix):
161
+ new_property_name = new_property_name[len(prefix):].strip()
162
+ break
163
+ if new_property_name != property_name:
164
+ property_name_to_type.pop(property_name)
165
+ # избегаем коллизий
166
+ if (
167
+ new_property_name not in annotations
168
+ and new_property_name not in property_name_to_type
169
+ and new_property_name not in renamed_property_name_to_type
170
+ ):
171
+ renamed_property_name_to_type[new_property_name] = property_type
172
+ else:
173
+ raise ValueError(
174
+ f"Property name '{property_name}' after removing prefix "
175
+ f"conflicts with existing name '{new_property_name}'"
176
+ )
177
+ property_name_to_type.update(renamed_property_name_to_type)
178
+
179
+ # добавляем (колонки в приоритете)
180
+ for property_name, property_type in property_name_to_type.items():
181
+ if property_name in annotations:
182
+ continue
183
+ annotations[property_name] = property_type
184
+
92
185
  attrs["__annotations__"] = annotations
93
186
  return type(model_name, (base_model,), attrs)
@@ -174,6 +174,7 @@ class BaseDBM(DeclarativeBase):
174
174
  exclude_sd_properties: Collection[str] | None = None,
175
175
  include_columns_and_sd_properties: Collection[str] | None = None,
176
176
  prefix: str = "sdp_",
177
+ remove_prefix: bool = True,
177
178
  kwargs: dict[str, Any] | None = None
178
179
  ) -> dict[str, Any]:
179
180
  if exclude_columns is None:
@@ -199,7 +200,9 @@ class BaseDBM(DeclarativeBase):
199
200
  if not attr_name.startswith(prefix) or not isinstance(getattr(type(self), attr_name, None), property):
200
201
  continue
201
202
 
202
- sd_property_name = attr_name.removeprefix(prefix)
203
+ sd_property_name = attr_name
204
+ if remove_prefix:
205
+ sd_property_name = sd_property_name.removeprefix(prefix)
203
206
 
204
207
  if (
205
208
  include_columns_and_sd_properties is not None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: arpakitlib
3
- Version: 1.8.250
3
+ Version: 1.8.252
4
4
  Summary: arpakitlib
5
5
  License: Apache-2.0
6
6
  Keywords: arpakitlib,arpakit,arpakit-company,arpakitcompany,arpakit_company
@@ -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=_5Y79kQ4lLIOL6_afIFVwxY1EXzTMpi-veRR-WkPFOs,2879
417
+ arpakitlib/ar_pydantic_schema_from_sqlalchemy_model.py,sha256=Dy53opHf_WjAMUC_PpvLkYXnrKOlqhbuNeEqlwIWJ8E,7369
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
@@ -426,12 +426,12 @@ arpakitlib/ar_schedule_uust_api_client_util.py,sha256=rXI2_3OTaIBgR-GixM1Ti-Ue1f
426
426
  arpakitlib/ar_settings_util.py,sha256=Y5wi_cmsjDjfJpM0VJHjbo0NoVPKfypKaD1USowwDtQ,1327
427
427
  arpakitlib/ar_sleep_util.py,sha256=ggaj7ML6QK_ADsHMcyu6GUmUpQ_9B9n-SKYH17h-9lM,1045
428
428
  arpakitlib/ar_sqladmin_util.py,sha256=SEoaowAPF3lhxPsNjwmOymNJ55Ty9rmzvsDm7gD5Ceo,861
429
- arpakitlib/ar_sqlalchemy_util.py,sha256=5I7KsKCzJc9hxpn2_0r__RcbqcKpTs_PDw_f5_0P7Xw,16050
429
+ arpakitlib/ar_sqlalchemy_util.py,sha256=FDva9onjtCPrYZYIHHb93NMwD1WlmaORjiWgPRJQKoU,16180
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.250.dist-info/LICENSE,sha256=GPEDQMam2r7FSTYqM1mm7aKnxLaWcBotH7UvQtea-ec,11355
434
- arpakitlib-1.8.250.dist-info/METADATA,sha256=T4V7KUip-9Bg5nzPDEWl9peAgaitrQnWrlNqYpIY194,3919
435
- arpakitlib-1.8.250.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
436
- arpakitlib-1.8.250.dist-info/entry_points.txt,sha256=36xqR3PJFT2kuwjkM_EqoIy0qFUDPKSm_mJaI7emewE,87
437
- arpakitlib-1.8.250.dist-info/RECORD,,
433
+ arpakitlib-1.8.252.dist-info/LICENSE,sha256=GPEDQMam2r7FSTYqM1mm7aKnxLaWcBotH7UvQtea-ec,11355
434
+ arpakitlib-1.8.252.dist-info/METADATA,sha256=y2bDBx7FFGVOBgDvKBp20DIzFNfjhHBMqJJ5G9HUXf4,3919
435
+ arpakitlib-1.8.252.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
436
+ arpakitlib-1.8.252.dist-info/entry_points.txt,sha256=36xqR3PJFT2kuwjkM_EqoIy0qFUDPKSm_mJaI7emewE,87
437
+ arpakitlib-1.8.252.dist-info/RECORD,,