arpakitlib 1.8.250__py3-none-any.whl → 1.8.251__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,106 @@ def _python_type_from_col(col) -> type | str:
43
44
  return Any
44
45
 
45
46
 
47
+ def _collect_properties_with_types(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
67
  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,
53
74
  ) -> type[BaseModel]:
54
75
  """
55
- Генерирует Pydantic-модель только из колонок SQLAlchemy-модели.
56
- - include_defaults: добавлять ли default/server_default.
76
+ Генерирует Pydantic-модель из колонок SQLAlchemy-модели и (опционально) из @property.
77
+
78
+ - include_column_defaults: добавлять ли default/server_default у колонок.
57
79
  - exclude_column_names: список имён колонок, которые нужно пропустить.
80
+
81
+ - include_properties: включать ли свойства (@property). По умолчанию False.
82
+ - include_property_names: whitelist имён свойств (если задан, берём только их).
83
+ - exclude_property_names: blacklist имён свойств (исключаются после whitelist'а).
58
84
  """
59
85
  mapper = inspect(sqlalchemy_model).mapper
60
86
  model_name = name or f"{sqlalchemy_model.__name__}Schema"
61
87
 
62
88
  annotations: dict[str, Any] = {}
63
89
  attrs: dict[str, Any] = {}
90
+
64
91
  exclude_column_names = set(exclude_column_names or [])
92
+ include_property_names = set(include_property_names or [])
93
+ exclude_property_names = set(exclude_property_names or [])
65
94
 
95
+ # 1) Колонки
66
96
  for prop in mapper.attrs:
67
97
  if not isinstance(prop, ColumnProperty):
68
98
  continue
69
99
  if prop.key in exclude_column_names:
70
100
  continue
71
101
 
72
- col = prop.columns[0]
73
- py_t = _python_type_from_col(col)
102
+ column = prop.columns[0]
103
+ column_type = _python_type_from_col(column)
74
104
 
75
105
  # Аннотация типа
76
- if col.nullable:
77
- annotations[prop.key] = Optional[py_t] # type: ignore[name-defined]
106
+ if column.nullable:
107
+ annotations[prop.key] = Optional[column_type] # type: ignore[name-defined]
78
108
  else:
79
- annotations[prop.key] = py_t
109
+ annotations[prop.key] = column_type
80
110
 
81
- # Если нужно — добавляем дефолт
82
- if include_defaults:
111
+ # Дефолты, если нужно
112
+ if include_column_defaults:
83
113
  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
114
+ if column.default is not None and getattr(column.default, "is_scalar", False):
115
+ default_value = column.default.arg
116
+ elif column.server_default is not None and getattr(column.server_default.arg, "text", None):
117
+ default_value = column.server_default.arg.text
88
118
 
89
119
  if default_value is not None:
90
120
  attrs[prop.key] = Field(default=default_value)
91
121
 
122
+ # 2) Свойства (@property)
123
+ if include_properties:
124
+ property_name_to_type = _collect_properties_with_types(sqlalchemy_model)
125
+
126
+ # whitelist (если задан)
127
+ if include_property_names:
128
+ property_name_to_type = {
129
+ k: v
130
+ for k, v in property_name_to_type.items()
131
+ if k in include_property_names
132
+ }
133
+ else:
134
+ property_name_to_type = dict(property_name_to_type)
135
+
136
+ # blacklist
137
+ if exclude_property_names:
138
+ for property_name in list(property_name_to_type.keys()):
139
+ if property_name in exclude_property_names:
140
+ property_name_to_type.pop(name, None)
141
+
142
+ # Добавляем аннотации свойств (колонки имеют приоритет)
143
+ for property_name, property_type in property_name_to_type.items():
144
+ if property_name in annotations:
145
+ continue
146
+ annotations[property_name] = property_type
147
+
92
148
  attrs["__annotations__"] = annotations
93
149
  return type(model_name, (base_model,), attrs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: arpakitlib
3
- Version: 1.8.250
3
+ Version: 1.8.251
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=NuvydQe7Cih6dW5H53GB6BsG2oFKgf27F3iJxuPO2J0,5364
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=5I7KsKCzJc9hxpn2_0r__RcbqcKpTs_PDw_f5_0P
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.251.dist-info/LICENSE,sha256=GPEDQMam2r7FSTYqM1mm7aKnxLaWcBotH7UvQtea-ec,11355
434
+ arpakitlib-1.8.251.dist-info/METADATA,sha256=qCRaVdN1qqbRnIqYG7bYL2J3i6_sDOIpxqO7SG1BLNA,3919
435
+ arpakitlib-1.8.251.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
436
+ arpakitlib-1.8.251.dist-info/entry_points.txt,sha256=36xqR3PJFT2kuwjkM_EqoIy0qFUDPKSm_mJaI7emewE,87
437
+ arpakitlib-1.8.251.dist-info/RECORD,,