arpakitlib 1.8.249__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,7 +1,7 @@
1
1
  from typing import Any
2
2
 
3
3
  import fastapi
4
- from arpakitlib.raise_own_exception_if_exception import raise_own_exception_if_exception
4
+ from arpakitlib.ar_raise_own_exception_if_exception import raise_own_exception_if_exception
5
5
 
6
6
  from project.api.api_exception import APIException
7
7
  from project.api.schema.out.common.error import ErrorCommonSO
@@ -1,6 +1,6 @@
1
1
  import asyncio
2
2
 
3
- from arpakitlib.pydantic_schema_from_sqlalchemy_model import pydantic_schema_from_sqlalchemy_model
3
+ from arpakitlib.ar_pydantic_schema_from_sqlalchemy_model import pydantic_schema_from_sqlalchemy_model
4
4
  from project.sqlalchemy_db_.sqlalchemy_model import UserDBM
5
5
 
6
6
 
@@ -0,0 +1,149 @@
1
+ # arpakit
2
+ import datetime as dt
3
+ from typing import Any, Optional, get_type_hints
4
+
5
+ from pydantic import BaseModel, Field
6
+ from sqlalchemy import inspect
7
+ from sqlalchemy.orm import ColumnProperty
8
+ from sqlalchemy.sql.sqltypes import (
9
+ Boolean, Integer, BigInteger, SmallInteger,
10
+ String, Text, Unicode, UnicodeText,
11
+ DateTime, Date, Time,
12
+ Float, Numeric, DECIMAL, LargeBinary, JSON
13
+ )
14
+
15
+ _ARPAKIT_LIB_MODULE_VERSION = "3.0"
16
+
17
+ _SQLA_TYPE_MAP = {
18
+ Boolean: bool,
19
+ Integer: int,
20
+ BigInteger: int,
21
+ SmallInteger: int,
22
+ Float: float,
23
+ Numeric: float,
24
+ DECIMAL: float,
25
+ String: str,
26
+ Unicode: str,
27
+ Text: str,
28
+ UnicodeText: str,
29
+ LargeBinary: bytes,
30
+ JSON: dict,
31
+ DateTime: dt.datetime,
32
+ Date: dt.date,
33
+ Time: dt.time,
34
+ }
35
+
36
+
37
+ def _python_type_from_col(col) -> type | str:
38
+ try:
39
+ return col.type.python_type
40
+ except Exception:
41
+ for sa_t, py_t in _SQLA_TYPE_MAP.items():
42
+ if isinstance(col.type, sa_t):
43
+ return py_t
44
+ return Any
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
+
64
+ def pydantic_schema_from_sqlalchemy_model(
65
+ sqlalchemy_model: type,
66
+ *,
67
+ name: str | None = None,
68
+ base_model: type[BaseModel] = BaseModel,
69
+ include_column_defaults: bool = False,
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
+ ) -> type[BaseModel]:
75
+ """
76
+ Генерирует Pydantic-модель из колонок SQLAlchemy-модели и (опционально) из @property.
77
+
78
+ - include_column_defaults: добавлять ли default/server_default у колонок.
79
+ - exclude_column_names: список имён колонок, которые нужно пропустить.
80
+
81
+ - include_properties: включать ли свойства (@property). По умолчанию False.
82
+ - include_property_names: whitelist имён свойств (если задан, берём только их).
83
+ - exclude_property_names: blacklist имён свойств (исключаются после whitelist'а).
84
+ """
85
+ mapper = inspect(sqlalchemy_model).mapper
86
+ model_name = name or f"{sqlalchemy_model.__name__}Schema"
87
+
88
+ annotations: dict[str, Any] = {}
89
+ attrs: dict[str, Any] = {}
90
+
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 [])
94
+
95
+ # 1) Колонки
96
+ for prop in mapper.attrs:
97
+ if not isinstance(prop, ColumnProperty):
98
+ continue
99
+ if prop.key in exclude_column_names:
100
+ continue
101
+
102
+ column = prop.columns[0]
103
+ column_type = _python_type_from_col(column)
104
+
105
+ # Аннотация типа
106
+ if column.nullable:
107
+ annotations[prop.key] = Optional[column_type] # type: ignore[name-defined]
108
+ else:
109
+ annotations[prop.key] = column_type
110
+
111
+ # Дефолты, если нужно
112
+ if include_column_defaults:
113
+ default_value = None
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
118
+
119
+ if default_value is not None:
120
+ attrs[prop.key] = Field(default=default_value)
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
+
148
+ attrs["__annotations__"] = annotations
149
+ return type(model_name, (base_model,), attrs)
@@ -0,0 +1,47 @@
1
+ from urllib.parse import urlparse
2
+
3
+
4
+ class ReallyValidateUrlException(Exception):
5
+ def __init__(self, url: str, error_message: str):
6
+ self.url = url
7
+ self.error_message = error_message
8
+
9
+ def __str__(self) -> str:
10
+ return f"{self.__class__.__name__}, {self.url=}, {self.error_message}"
11
+
12
+ def __repr__(self) -> str:
13
+ return f"{self.__class__.__name__}, {self.url=}, {self.error_message}"
14
+
15
+
16
+ def really_validate_url(url: str) -> None:
17
+ result = urlparse(url)
18
+
19
+ if not result.scheme:
20
+ raise ReallyValidateUrlException(
21
+ url=url,
22
+ error_message="URL must include a scheme (e.g., http/https)."
23
+ )
24
+
25
+ if not result.netloc:
26
+ raise ReallyValidateUrlException(
27
+ url=url,
28
+ error_message="URL must include a domain or host."
29
+ )
30
+
31
+ # Optional: restrict allowed schemes
32
+ if result.scheme not in ("http", "https", "ftp"):
33
+ raise ReallyValidateUrlException(
34
+ url=url,
35
+ error_message=f"Unsupported scheme: {result.scheme}"
36
+ )
37
+
38
+ # If everything is fine — return None (no errors)
39
+ return None
40
+
41
+
42
+ def is_really_url_valid(email: str) -> bool:
43
+ try:
44
+ really_validate_url(url=email)
45
+ except ReallyValidateUrlException:
46
+ return False
47
+ return True
@@ -299,7 +299,7 @@ class SQLAlchemyDb:
299
299
  self._logger.info("alembic tables data were removed")
300
300
 
301
301
  def ensure_check_constraints(self):
302
- from arpakitlib.ensure_sqlalchemy_check_constraints import ensure_sqlalchemy_check_constraints
302
+ from arpakitlib.ar_ensure_sqlalchemy_check_constraints import ensure_sqlalchemy_check_constraints
303
303
  ensure_sqlalchemy_check_constraints(base_=self.base_dbm, engine=self.engine)
304
304
 
305
305
  def init(self, ensure_check_constraints: bool = True):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: arpakitlib
3
- Version: 1.8.249
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
@@ -101,7 +101,7 @@ arpakitlib/_arpakit_project_template_v_5/project/api/middleware/__init__.py,sha2
101
101
  arpakitlib/_arpakit_project_template_v_5/project/api/middleware/add_api_middlewares.py,sha256=TrWn0yPDCn0yTlHqLf19ypGtRumNjDxm0sM60yVsqXE,468
102
102
  arpakitlib/_arpakit_project_template_v_5/project/api/middleware/limit_content_length.py,sha256=cEtGcJSh3e2EyuQCFqKzRcrl3qS6oIvpSv5UEbYsvR4,1622
103
103
  arpakitlib/_arpakit_project_template_v_5/project/api/openapi_ui.py,sha256=7yg9ZNu6BjTAregnfbRFpwvqa-gTIY6o6NCz5pmsbeo,976
104
- arpakitlib/_arpakit_project_template_v_5/project/api/raise_own_exception_if_exception_in_api_router.py,sha256=EtkoPfsGPz4Qma4ZvYlPtPlxaRPH93cX4D0YpEL9ql0,1410
104
+ arpakitlib/_arpakit_project_template_v_5/project/api/raise_own_exception_if_exception_in_api_router.py,sha256=7zssLmFEfZ5WvC-ZW1bPyn_HrYNPjvK_Q9Ln1-xH3O4,1413
105
105
  arpakitlib/_arpakit_project_template_v_5/project/api/response.py,sha256=xZMymP2BuQaRNVWLeIp3UgUUo-MFN8MJnsn9Al4vOb8,1028
106
106
  arpakitlib/_arpakit_project_template_v_5/project/api/router/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
107
  arpakitlib/_arpakit_project_template_v_5/project/api/router/admin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -237,7 +237,7 @@ arpakitlib/_arpakit_project_template_v_5/project/resource/templates/simple_email
237
237
  arpakitlib/_arpakit_project_template_v_5/project/sandbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
238
238
  arpakitlib/_arpakit_project_template_v_5/project/sandbox/sandbox_1.py,sha256=xKSp7tIBu3Ffp_kgJkwVtdam3BcoFZ44JPVHoRRaP0E,163
239
239
  arpakitlib/_arpakit_project_template_v_5/project/sandbox/sandbox_2.py,sha256=xKSp7tIBu3Ffp_kgJkwVtdam3BcoFZ44JPVHoRRaP0E,163
240
- arpakitlib/_arpakit_project_template_v_5/project/sandbox/sandbox_3.py,sha256=tcva9-NB0irswjg6RirZ9-pmckuZwoAr8-DUiPB2ASI,424
240
+ arpakitlib/_arpakit_project_template_v_5/project/sandbox/sandbox_3.py,sha256=CIv4vQfBLhlIh92fiLhmyTXSZXjDovxTFZtUBDnWyFY,427
241
241
  arpakitlib/_arpakit_project_template_v_5/project/sandbox/sandbox_4.py,sha256=xKSp7tIBu3Ffp_kgJkwVtdam3BcoFZ44JPVHoRRaP0E,163
242
242
  arpakitlib/_arpakit_project_template_v_5/project/sandbox/sandbox_5.py,sha256=xKSp7tIBu3Ffp_kgJkwVtdam3BcoFZ44JPVHoRRaP0E,163
243
243
  arpakitlib/_arpakit_project_template_v_5/project/sandbox/sandbox_6.py,sha256=xKSp7tIBu3Ffp_kgJkwVtdam3BcoFZ44JPVHoRRaP0E,163
@@ -388,15 +388,18 @@ arpakitlib/ar_base_worker_util.py,sha256=e-Vj9w1D-59KN3Zz7TQlOB2lW4fZphJlsPpFkmX
388
388
  arpakitlib/ar_blank_util.py,sha256=qFUdY8usL_pRYamz8Rw1fW3fzNIgrLmpdYP8q-_PQvw,2281
389
389
  arpakitlib/ar_cache_file_util.py,sha256=Fo2pH-Zqm966KWFBHG_pbiySGZvhIFCYqy7k1weRfJ0,3476
390
390
  arpakitlib/ar_class_util.py,sha256=i76pQW_7k_S2m_DlQh6xNjtggv9Col3WSx9W_bwk98E,722
391
+ arpakitlib/ar_clone_pydantic_model_fields.py,sha256=xxLwtvJzDf8EWMvBE4psWIj8c-cyeCxLRX76oCY_4zk,1214
391
392
  arpakitlib/ar_datetime_util.py,sha256=Xe1NiT9oPQzNSG7RVRkhukhbg4i-hhS5ImmV7sPUc8o,971
392
393
  arpakitlib/ar_dict_util.py,sha256=oet-9AJEjQZfG_EI82BuYW0jdW2NQxKjPXol_nfTXjw,447
393
394
  arpakitlib/ar_dream_ai_api_client_util.py,sha256=Z1oii_XSsgunYx17SdcHl54S4KPQti7-MJs0xXZ8vL0,2830
394
395
  arpakitlib/ar_encrypt_decrypt_util.py,sha256=GhWnp7HHkbhwFVVCzO1H07m-5gryr4yjWsXjOaNQm1Y,520
396
+ arpakitlib/ar_ensure_sqlalchemy_check_constraints.py,sha256=JJbgaUiLT8D8oDVevP25wlMz1BTtx0JMasJ_ILyIJ3o,5328
395
397
  arpakitlib/ar_enumeration_util.py,sha256=XoFInWtGzbIr6fJq0un5nettaDfOLAyY84ovwj7n_7g,3030
396
398
  arpakitlib/ar_exception_util.py,sha256=3hZKsj34TZVdmd4JAQz7w515smWqB8o3gTwAEjuMdnI,408
397
399
  arpakitlib/ar_file_storage_in_dir_util.py,sha256=Zh922S6-aIy0p_Fen8GTTrGpixpPQ6c-wFLukiSK4Ck,4091
398
400
  arpakitlib/ar_file_util.py,sha256=GUdJYm1tUZnYpY-SIPRHAZBHGra8NKy1eYEI0D5AfhY,489
399
401
  arpakitlib/ar_func_util.py,sha256=lG0bx_DtxWN4skbUim0liRZ6WUyLVV8Qfk6iZNtCZOs,1042
402
+ arpakitlib/ar_generate_simple_code.py,sha256=EkrebrTi7sArSRAuxvN5BPm_A0-dFSCZgdoJhx5kPhk,344
400
403
  arpakitlib/ar_hash_util.py,sha256=Iqy6KBAOLBQMFLWv676boI5sV7atT2B-fb7aCdHOmIQ,340
401
404
  arpakitlib/ar_http_request_util.py,sha256=PCUtGOQIvNScrLqD_9Z8LqT-7a-lP2y-Y-CH5vGdn7Q,7663
402
405
  arpakitlib/ar_ip_util.py,sha256=aEAa1Hvobh9DWX7cmBAPLqnXSTiKe2hRk-WJaiKMaI8,1009
@@ -411,7 +414,11 @@ arpakitlib/ar_need_type_util.py,sha256=XmY1kswz8j9oo5f9CxRu0_zgfvxWrXPYKOj6MM04s
411
414
  arpakitlib/ar_openai_api_client_util.py,sha256=dWgsSPXtxNBxS5VRi_NharGQrUXF_YjIfhU3Bj5cW9M,5651
412
415
  arpakitlib/ar_parse_command.py,sha256=1WTdQoWVshoDZ1jDaKeTzajfqaYHP3FNO0-REyo1aMY,3003
413
416
  arpakitlib/ar_postgresql_util.py,sha256=1AuLjEaa1Lg4pzn-ukCVnDi35Eg1k91APRTqZhIJAdo,945
417
+ arpakitlib/ar_pydantic_schema_from_sqlalchemy_model.py,sha256=NuvydQe7Cih6dW5H53GB6BsG2oFKgf27F3iJxuPO2J0,5364
418
+ arpakitlib/ar_raise_own_exception_if_exception.py,sha256=A6TuNSBk1pHaQ_qxnUmE2LgsNGA1IGqX26b1_HEA4Nc,5978
414
419
  arpakitlib/ar_rat_func_util.py,sha256=Ca10o3RJwyx_DJLxjTxgHDO6NU3M6CWgUR4bif67OE4,2006
420
+ arpakitlib/ar_really_validate_email.py,sha256=HBfhyiDB3INI6Iq6hR2WOMKA5wVWWRl0Qun-x__OZ9o,1201
421
+ arpakitlib/ar_really_validate_url.py,sha256=aaSPVMbz2DSqlC2yk2g44-kTIiHlITfJwIG97L-Y93U,1309
415
422
  arpakitlib/ar_retry_func_util.py,sha256=LB4FJRsu2cssnPw6X8bCEcaGpQsXhkLkgeU37w1t9fU,2250
416
423
  arpakitlib/ar_run_cmd_util.py,sha256=D_rPavKMmWkQtwvZFz-Io5Ak8eSODHkcFeLPzNVC68g,1072
417
424
  arpakitlib/ar_safe_func.py,sha256=c2P1hzB8uQn3l8cAr2nttfHbrM-4QrxgDejk_jM1OVE,2321
@@ -419,18 +426,12 @@ arpakitlib/ar_schedule_uust_api_client_util.py,sha256=rXI2_3OTaIBgR-GixM1Ti-Ue1f
419
426
  arpakitlib/ar_settings_util.py,sha256=Y5wi_cmsjDjfJpM0VJHjbo0NoVPKfypKaD1USowwDtQ,1327
420
427
  arpakitlib/ar_sleep_util.py,sha256=ggaj7ML6QK_ADsHMcyu6GUmUpQ_9B9n-SKYH17h-9lM,1045
421
428
  arpakitlib/ar_sqladmin_util.py,sha256=SEoaowAPF3lhxPsNjwmOymNJ55Ty9rmzvsDm7gD5Ceo,861
422
- arpakitlib/ar_sqlalchemy_util.py,sha256=U_pb6oKKaLH_JcCh_6Zvm3DRpv79EOUxhr5kD3h-G3Y,16047
429
+ arpakitlib/ar_sqlalchemy_util.py,sha256=5I7KsKCzJc9hxpn2_0r__RcbqcKpTs_PDw_f5_0P7Xw,16050
423
430
  arpakitlib/ar_str_util.py,sha256=2lGpnXDf2h1cBZpVf5i1tX_HCv5iBd6IGnrCw4QWWlY,4350
424
431
  arpakitlib/ar_type_util.py,sha256=Cs_tef-Fc5xeyAF54KgISCsP11NHyzIsglm4S3Xx7iM,4049
425
432
  arpakitlib/ar_yookassa_api_client_util.py,sha256=VozuZeCJjmLd1zj2BdC9WfiAQ3XYOrIMsdpNK-AUlm0,5347
426
- arpakitlib/clone_pydantic_model_fields.py,sha256=xxLwtvJzDf8EWMvBE4psWIj8c-cyeCxLRX76oCY_4zk,1214
427
- arpakitlib/ensure_sqlalchemy_check_constraints.py,sha256=JJbgaUiLT8D8oDVevP25wlMz1BTtx0JMasJ_ILyIJ3o,5328
428
- arpakitlib/generate_simple_code.py,sha256=EkrebrTi7sArSRAuxvN5BPm_A0-dFSCZgdoJhx5kPhk,344
429
- arpakitlib/pydantic_schema_from_sqlalchemy_model.py,sha256=_5Y79kQ4lLIOL6_afIFVwxY1EXzTMpi-veRR-WkPFOs,2879
430
- arpakitlib/raise_own_exception_if_exception.py,sha256=A6TuNSBk1pHaQ_qxnUmE2LgsNGA1IGqX26b1_HEA4Nc,5978
431
- arpakitlib/really_validate_email.py,sha256=HBfhyiDB3INI6Iq6hR2WOMKA5wVWWRl0Qun-x__OZ9o,1201
432
- arpakitlib-1.8.249.dist-info/LICENSE,sha256=GPEDQMam2r7FSTYqM1mm7aKnxLaWcBotH7UvQtea-ec,11355
433
- arpakitlib-1.8.249.dist-info/METADATA,sha256=Cg-pyrGVi2RHXny_fIxg3kprdoJHO2OBqYa7Dggujn0,3919
434
- arpakitlib-1.8.249.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
435
- arpakitlib-1.8.249.dist-info/entry_points.txt,sha256=36xqR3PJFT2kuwjkM_EqoIy0qFUDPKSm_mJaI7emewE,87
436
- arpakitlib-1.8.249.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,,
@@ -1,93 +0,0 @@
1
- # arpakit
2
- import datetime as dt
3
- from typing import Any, Optional
4
-
5
- from pydantic import BaseModel, Field
6
- from sqlalchemy import inspect
7
- from sqlalchemy.orm import ColumnProperty
8
- from sqlalchemy.sql.sqltypes import (
9
- Boolean, Integer, BigInteger, SmallInteger,
10
- String, Text, Unicode, UnicodeText,
11
- DateTime, Date, Time,
12
- Float, Numeric, DECIMAL, LargeBinary, JSON
13
- )
14
- _ARPAKIT_LIB_MODULE_VERSION = "3.0"
15
-
16
- _SQLA_TYPE_MAP = {
17
- Boolean: bool,
18
- Integer: int,
19
- BigInteger: int,
20
- SmallInteger: int,
21
- Float: float,
22
- Numeric: float,
23
- DECIMAL: float,
24
- String: str,
25
- Unicode: str,
26
- Text: str,
27
- UnicodeText: str,
28
- LargeBinary: bytes,
29
- JSON: dict,
30
- DateTime: dt.datetime,
31
- Date: dt.date,
32
- Time: dt.time,
33
- }
34
-
35
-
36
- def _python_type_from_col(col) -> type | str:
37
- try:
38
- return col.type.python_type
39
- except Exception:
40
- for sa_t, py_t in _SQLA_TYPE_MAP.items():
41
- if isinstance(col.type, sa_t):
42
- return py_t
43
- return Any
44
-
45
-
46
- def pydantic_schema_from_sqlalchemy_model(
47
- sqlalchemy_model: type,
48
- *,
49
- name: str | None = None,
50
- base_model: type[BaseModel] = BaseModel,
51
- include_defaults: bool = False,
52
- exclude_column_names: list[str] | None = None,
53
- ) -> type[BaseModel]:
54
- """
55
- Генерирует Pydantic-модель только из колонок SQLAlchemy-модели.
56
- - include_defaults: добавлять ли default/server_default.
57
- - exclude_column_names: список имён колонок, которые нужно пропустить.
58
- """
59
- mapper = inspect(sqlalchemy_model).mapper
60
- model_name = name or f"{sqlalchemy_model.__name__}Schema"
61
-
62
- annotations: dict[str, Any] = {}
63
- attrs: dict[str, Any] = {}
64
- exclude_column_names = set(exclude_column_names or [])
65
-
66
- for prop in mapper.attrs:
67
- if not isinstance(prop, ColumnProperty):
68
- continue
69
- if prop.key in exclude_column_names:
70
- continue
71
-
72
- col = prop.columns[0]
73
- py_t = _python_type_from_col(col)
74
-
75
- # Аннотация типа
76
- if col.nullable:
77
- annotations[prop.key] = Optional[py_t] # type: ignore[name-defined]
78
- else:
79
- annotations[prop.key] = py_t
80
-
81
- # Если нужно — добавляем дефолт
82
- if include_defaults:
83
- 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
88
-
89
- if default_value is not None:
90
- attrs[prop.key] = Field(default=default_value)
91
-
92
- attrs["__annotations__"] = annotations
93
- return type(model_name, (base_model,), attrs)