dotorm 2.0.8__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.
Files changed (50) hide show
  1. dotorm/__init__.py +87 -0
  2. dotorm/access.py +151 -0
  3. dotorm/builder/__init__.py +0 -0
  4. dotorm/builder/builder.py +72 -0
  5. dotorm/builder/helpers.py +63 -0
  6. dotorm/builder/mixins/__init__.py +11 -0
  7. dotorm/builder/mixins/crud.py +246 -0
  8. dotorm/builder/mixins/m2m.py +110 -0
  9. dotorm/builder/mixins/relations.py +96 -0
  10. dotorm/builder/protocol.py +63 -0
  11. dotorm/builder/request_builder.py +144 -0
  12. dotorm/components/__init__.py +18 -0
  13. dotorm/components/dialect.py +99 -0
  14. dotorm/components/filter_parser.py +195 -0
  15. dotorm/databases/__init__.py +13 -0
  16. dotorm/databases/abstract/__init__.py +25 -0
  17. dotorm/databases/abstract/dialect.py +134 -0
  18. dotorm/databases/abstract/pool.py +10 -0
  19. dotorm/databases/abstract/session.py +67 -0
  20. dotorm/databases/abstract/types.py +36 -0
  21. dotorm/databases/clickhouse/__init__.py +8 -0
  22. dotorm/databases/clickhouse/pool.py +60 -0
  23. dotorm/databases/clickhouse/session.py +100 -0
  24. dotorm/databases/mysql/__init__.py +13 -0
  25. dotorm/databases/mysql/pool.py +69 -0
  26. dotorm/databases/mysql/session.py +128 -0
  27. dotorm/databases/mysql/transaction.py +39 -0
  28. dotorm/databases/postgres/__init__.py +23 -0
  29. dotorm/databases/postgres/pool.py +133 -0
  30. dotorm/databases/postgres/session.py +174 -0
  31. dotorm/databases/postgres/transaction.py +82 -0
  32. dotorm/decorators.py +379 -0
  33. dotorm/exceptions.py +9 -0
  34. dotorm/fields.py +604 -0
  35. dotorm/integrations/__init__.py +0 -0
  36. dotorm/integrations/pydantic.py +275 -0
  37. dotorm/model.py +802 -0
  38. dotorm/orm/__init__.py +15 -0
  39. dotorm/orm/mixins/__init__.py +13 -0
  40. dotorm/orm/mixins/access.py +67 -0
  41. dotorm/orm/mixins/ddl.py +250 -0
  42. dotorm/orm/mixins/many2many.py +175 -0
  43. dotorm/orm/mixins/primary.py +218 -0
  44. dotorm/orm/mixins/relations.py +513 -0
  45. dotorm/orm/protocol.py +147 -0
  46. dotorm/orm/utils.py +39 -0
  47. dotorm-2.0.8.dist-info/METADATA +1240 -0
  48. dotorm-2.0.8.dist-info/RECORD +50 -0
  49. dotorm-2.0.8.dist-info/WHEEL +4 -0
  50. dotorm-2.0.8.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,275 @@
1
+ try:
2
+ from pydantic import create_model, ConfigDict
3
+ from pydantic.fields import Field
4
+ except:
5
+ print("pydantic lib not installed")
6
+ from types import UnionType
7
+ from typing import Any, List, Literal, Optional, Type, TypeAlias, Union
8
+
9
+
10
+ from ..fields import (
11
+ Many2many,
12
+ One2many,
13
+ Field as DotField,
14
+ )
15
+
16
+
17
+ def dotorm_to_pydantic_nested_one(cls):
18
+ """Работает с моделями DotOrm.
19
+ Которая возвращает все поля модели.
20
+ Используется на вход get и create_default
21
+ Прерывается на первом уровне вложенности"""
22
+ fields_store = []
23
+ fields_relation = []
24
+ # fields = []
25
+ for field_name, field in cls.__dict__.items():
26
+ if isinstance(field, DotField):
27
+ if not isinstance(field, (Many2many, One2many)):
28
+ fields_store.append(field_name)
29
+
30
+ else:
31
+ # если это поле множественной связи m2m или o2m
32
+ # то это поле будет содержать просто список своих полей
33
+ allowed_fields = list(field.relation_table.get_fields())
34
+ params = {field_name: (list[Literal[*allowed_fields]], ...)}
35
+ SchemaGetFieldRelationInput = create_model(
36
+ "SchemaGetFieldRelationInput",
37
+ **params,
38
+ # field_name=(list[Literal[*allowed_fields]], ...),
39
+ )
40
+ fields_relation.append(SchemaGetFieldRelationInput)
41
+
42
+ return create_model(
43
+ "SchemaGetInput",
44
+ fields=(list[Union[Literal[*fields_store], *fields_relation]], ...),
45
+ )
46
+
47
+
48
+ # from pydantic import BaseModel, create_model
49
+ # from typing import Any, Dict, Type
50
+
51
+
52
+ # def create_pydantic_model_from_class(cls: Type) -> Type[BaseModel]:
53
+ # annotations: dict[str, TypeAlias] = getattr(cls, '__annotations__', {})
54
+ # fields: dict[str, tuple] = {}
55
+
56
+ # for field_name, field_type in annotations.items():
57
+ # # default = getattr(cls, field_name, ...)
58
+ # # fields[field_name] = (field_type, default)
59
+ # fields[field_name] = (field_type, None)
60
+
61
+ # model = create_model(cls.__name__ + 'Schema', **fields)
62
+ # return model
63
+
64
+ from typing import (
65
+ Annotated,
66
+ get_type_hints,
67
+ ForwardRef,
68
+ Union,
69
+ get_args,
70
+ get_origin,
71
+ )
72
+ from pydantic import BaseModel, Field, create_model
73
+
74
+
75
+ # def parse_string_union_type(text: str):
76
+ # """
77
+ # Парсит строку вида 'Uom | None' или 'Role | None | Other'
78
+ # Возвращает (is_union: bool, list of parts)
79
+ # """
80
+ # if "|" not in text:
81
+ # return False, [text.strip()]
82
+
83
+ # parts = [p.strip() for p in text.split("|")]
84
+ # return True, parts
85
+
86
+
87
+ def replace_custom_types(py_type, class_map: dict[str, Type[BaseModel]]):
88
+ """
89
+ Рекурсивно заменяет кастомные типы на SchemaXXX,
90
+ поддерживает строковые аннотации:
91
+ - "Uom"
92
+ - "Uom | None"
93
+ - "Role | None | Attachment"
94
+ """
95
+ # --- строковый тип ---
96
+ if isinstance(py_type, str):
97
+
98
+ # # 1) проверяем на union-строку
99
+ # is_union, parts = parse_string_union_type(py_type)
100
+
101
+ # if is_union:
102
+ # converted = []
103
+ # for part in parts:
104
+ # if part == "None":
105
+ # converted.append(type(None))
106
+ # else:
107
+ # converted.append(ForwardRef(f"Schema{part}"))
108
+ # return Union[tuple(converted)]
109
+
110
+ # 2) обычная строка: "Uom"
111
+ return ForwardRef(f"Schema{py_type}")
112
+
113
+ # --- обычный Union ---
114
+ origin = get_origin(py_type)
115
+ args = get_args(py_type)
116
+
117
+ if origin is UnionType and args:
118
+ return Union[
119
+ tuple(replace_custom_types(arg, class_map) for arg in args)
120
+ ]
121
+
122
+ # --- список ---
123
+ if origin in (list, List) and args:
124
+ return list[replace_custom_types(args[0], class_map)]
125
+
126
+ # --- кастомный класс ---
127
+ if hasattr(py_type, "__name__"):
128
+ name = py_type.__name__
129
+ if name in class_map:
130
+ return ForwardRef(f"Schema{name}")
131
+
132
+ return py_type
133
+
134
+
135
+ def convert_field_type(
136
+ py_type, field_value, class_map: dict[str, Type[BaseModel]]
137
+ ):
138
+ """
139
+ Оборачиваем тип в Annotated и корректно обрабатываем None.
140
+ """
141
+ final_type = replace_custom_types(py_type, class_map)
142
+
143
+ # --- определяем, допускает ли поле None ---
144
+ allows_none = False
145
+
146
+ # 1) строковая аннотация: "Uom | None"
147
+ if isinstance(py_type, str) and "None" in py_type:
148
+ allows_none = True
149
+
150
+ # 2) обычный Python union: Role | None
151
+ elif get_origin(py_type) is UnionType and type(None) in get_args(py_type):
152
+ allows_none = True
153
+
154
+ # --- обёртка Annotated ---
155
+ if field_value is not None:
156
+ annotated = Annotated[final_type, field_value.__class__]
157
+
158
+ # optional?
159
+ if allows_none:
160
+ return Optional[annotated]
161
+
162
+ return annotated
163
+
164
+ return final_type
165
+
166
+
167
+ # def convert_field_type(
168
+ # py_type, field_value, class_map: dict[str, Type[BaseModel]]
169
+ # ):
170
+ # """
171
+ # Оборачиваем тип в Annotated, если поле кастомное.
172
+
173
+ # - Сохраняет None для одиночных моделей (Role | None)
174
+ # - Сохраняет поведение для списков (list[Rule])
175
+ # - Минимальные изменения
176
+ # """
177
+ # final_type = replace_custom_types(py_type, class_map)
178
+
179
+ # if field_value is not None:
180
+ # # Определяем origin, чтобы проверить, что это не список
181
+ # origin = get_origin(final_type)
182
+
183
+ # # Если это одиночная модель (не список) и поле допускает None — добавляем Optional
184
+ # if (
185
+ # origin not in (list, List)
186
+ # and get_origin(py_type) is UnionType
187
+ # and type(None) in get_args(py_type)
188
+ # ):
189
+ # # role_id: Role | None
190
+ # # return Annotated[final_type | None, field_value.__class__]
191
+ # return Optional[Annotated[final_type, field_value.__class__]]
192
+
193
+ # # Иначе обычное поведение (списки и одиночные обязательные поля)
194
+ # return Annotated[final_type, field_value.__class__]
195
+
196
+ # return final_type
197
+
198
+
199
+ def generate_pydantic_models(
200
+ classes: list[type], prefix="Schema"
201
+ ) -> dict[str, type[BaseModel]]:
202
+ known_models: dict[str, type[BaseModel]] = {}
203
+ pending_fields: dict[str, dict] = {}
204
+
205
+ # Сначала создаем "пустые оболочки" моделей, чтобы можно было ссылаться на них
206
+ for cls in classes:
207
+ model_name = f"{prefix}{cls.__name__}"
208
+ model = create_model(
209
+ model_name,
210
+ # __base__=BaseModel,
211
+ __config__=ConfigDict(protected_namespaces=()),
212
+ )
213
+ known_models[cls.__name__] = model
214
+ pending_fields[cls.__name__] = {}
215
+
216
+ # Теперь наполняем поля
217
+ for cls in classes:
218
+ cls_name = cls.__name__
219
+ annotations = getattr(cls, "__annotations__", {})
220
+ model_fields = {}
221
+
222
+ for name, py_type in annotations.items():
223
+ field_value = getattr(cls, name, None)
224
+
225
+ # Разрешаем ForwardRef или вложенные типы
226
+ final_type = convert_field_type(py_type, field_value, known_models)
227
+
228
+ if isinstance(field_value, DotField):
229
+ # Используем метод модели для определения обязательности
230
+ is_required = cls._is_field_required(name, field_value)
231
+ required = Ellipsis if is_required else None
232
+
233
+ # если есть значение по умолчанию
234
+ if field_value.default is not None:
235
+ # если default callable — вызываем
236
+ if callable(field_value.default):
237
+ default: Any = field_value.default()
238
+ else:
239
+ default = field_value.default
240
+ # необходимо проставить через json_schema_extra чтобы поле осталось
241
+ # обязательным, но с default по умолчанию любое default делает поле
242
+ # не обязательным прихожится обходить это для более интуитивной работы
243
+ # отделить значение по умолчанию и обязательность поля
244
+ default = Field(
245
+ required, json_schema_extra={"default": default}
246
+ )
247
+ else:
248
+ # нет default значит поле обязательное
249
+ default = required
250
+
251
+ model_fields[name] = (final_type, default)
252
+ else:
253
+ raise ValueError("Found not DotField object")
254
+
255
+ # Обновляем модель
256
+ model_name = f"{prefix}{cls_name}"
257
+ known_models[cls_name] = create_model(
258
+ model_name,
259
+ # __base__=known_models[cls_name],
260
+ **model_fields,
261
+ __config__=ConfigDict(protected_namespaces=(), frozen=False),
262
+ )
263
+
264
+ # known_models[cls_name].model_fields.update(model_fields)
265
+ # known_models[cls_name].model_rebuild(force=True)
266
+
267
+ # Обновляем forward refs
268
+ _types_namespace = {
269
+ f"Schema{name}": model for name, model in known_models.items()
270
+ }
271
+ for model in known_models.values():
272
+ model.model_rebuild(force=True, _types_namespace=_types_namespace)
273
+ # model.update_forward_refs(**known_models)
274
+
275
+ return known_models