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,218 @@
1
+ """Primary ORM operations mixin."""
2
+
3
+ from typing import TYPE_CHECKING, Self, TypeVar
4
+
5
+ from ...access import Operation
6
+ from ...components.dialect import POSTGRES
7
+ from ...model import JsonMode
8
+ from ...decorators import hybridmethod
9
+
10
+ if TYPE_CHECKING:
11
+ from ..protocol import DotModelProtocol
12
+ from ...model import DotModel
13
+
14
+ _Base = DotModelProtocol
15
+ else:
16
+ _Base = object
17
+
18
+
19
+ # TypeVar for generic payload - accepts any DotModel subclass
20
+ _M = TypeVar("_M", bound="DotModel")
21
+
22
+
23
+ class OrmPrimaryMixin(_Base):
24
+ """
25
+ Mixin providing primary CRUD ORM operations.
26
+
27
+ Provides:
28
+ - create, create_bulk
29
+ - get, table_len
30
+ - update, update_bulk
31
+ - delete, delete_bulk
32
+
33
+ Expects DotModel to provide:
34
+ - _get_db_session()
35
+ - _builder
36
+ - _dialect
37
+ - __table__
38
+ - prepare_form_id()
39
+ """
40
+
41
+ async def delete(self, session=None):
42
+ await self._check_access(Operation.DELETE, record_ids=[self.id])
43
+
44
+ session = self._get_db_session(session)
45
+ stmt = self._builder.build_delete()
46
+ return await session.execute(stmt, [self.id])
47
+
48
+ @hybridmethod
49
+ async def delete_bulk(self, ids: list[int], session=None):
50
+ cls = self.__class__
51
+
52
+ # Одна проверка для всех ID
53
+ await cls._check_access(Operation.DELETE, record_ids=ids)
54
+
55
+ session = cls._get_db_session(session)
56
+ stmt = cls._builder.build_delete_bulk(len(ids))
57
+ return await session.execute(stmt, ids)
58
+
59
+ async def update(
60
+ self,
61
+ payload: "_M | None" = None,
62
+ fields=None,
63
+ session=None,
64
+ ):
65
+ await self._check_access(Operation.UPDATE, record_ids=[self.id])
66
+
67
+ session = self._get_db_session(session)
68
+ if payload is None:
69
+ payload = self
70
+ if not fields:
71
+ fields = []
72
+
73
+ if fields:
74
+ payload_dict = payload.json(
75
+ include=set(fields),
76
+ exclude_none=True,
77
+ only_store=True,
78
+ mode=JsonMode.UPDATE,
79
+ )
80
+ else:
81
+ payload_dict = payload.json(
82
+ exclude=payload.get_none_update_fields_set(),
83
+ exclude_none=True,
84
+ exclude_unset=True,
85
+ only_store=True,
86
+ mode=JsonMode.UPDATE,
87
+ )
88
+
89
+ stmt, values = self._builder.build_update(payload_dict, self.id)
90
+ return await session.execute(stmt, values)
91
+
92
+ @hybridmethod
93
+ async def update_bulk(
94
+ self,
95
+ ids: list[int],
96
+ payload: _M,
97
+ session=None,
98
+ ):
99
+ cls = self.__class__
100
+
101
+ # Одна проверка для всех ID
102
+ await cls._check_access(Operation.UPDATE, record_ids=ids)
103
+
104
+ session = cls._get_db_session(session)
105
+
106
+ payload_dict = payload.json(
107
+ exclude=payload.get_none_update_fields_set(),
108
+ exclude_none=True,
109
+ exclude_unset=True,
110
+ only_store=True,
111
+ )
112
+
113
+ stmt, values = cls._builder.build_update_bulk(payload_dict, ids)
114
+ return await session.execute(stmt, values)
115
+
116
+ @hybridmethod
117
+ async def create(self, payload: _M, session=None) -> int:
118
+ cls = self.__class__
119
+
120
+ # Проверяем table access до создания
121
+ await cls._check_access(Operation.CREATE)
122
+
123
+ session = cls._get_db_session(session)
124
+
125
+ payload_dict = payload.json(
126
+ exclude=payload.get_none_update_fields_set(),
127
+ exclude_none=True,
128
+ only_store=True,
129
+ mode=JsonMode.CREATE,
130
+ )
131
+
132
+ stmt, values = cls._builder.build_create(payload_dict)
133
+
134
+ if cls._dialect.supports_returning:
135
+ stmt += " RETURNING id"
136
+ record = await session.execute(stmt, values, cursor="fetch")
137
+ assert record is not None
138
+ record_id = record[0]["id"]
139
+ else:
140
+ record = await session.execute(stmt, values, cursor="lastrowid")
141
+ assert record is not None
142
+ record_id = record
143
+
144
+ # Проверяем row access после создания (для Rules типа "только свои записи")
145
+ await cls._check_access(Operation.CREATE, record_ids=[record_id])
146
+
147
+ return record_id
148
+
149
+ @hybridmethod
150
+ async def create_bulk(self, payload: list[_M], session=None):
151
+ cls = self.__class__
152
+
153
+ # Проверяем table access до создания
154
+ await cls._check_access(Operation.CREATE)
155
+
156
+ session = cls._get_db_session(session)
157
+
158
+ exclude_fields = {
159
+ name
160
+ for name, field in cls.get_fields().items()
161
+ if field.primary_key
162
+ }
163
+
164
+ payloads_dicts = [
165
+ p.json(
166
+ exclude=exclude_fields, only_store=True, mode=JsonMode.CREATE
167
+ )
168
+ for p in payload
169
+ ]
170
+
171
+ stmt, values = cls._builder.build_create_bulk(payloads_dicts)
172
+
173
+ if cls._dialect.supports_returning:
174
+ stmt += " RETURNING id"
175
+
176
+ records = await session.execute(stmt, values, cursor="fetch")
177
+
178
+ # Проверяем row access после создания
179
+ if records:
180
+ created_ids = [r["id"] for r in records]
181
+ await cls._check_access(Operation.CREATE, record_ids=created_ids)
182
+
183
+ return records
184
+
185
+ @hybridmethod
186
+ async def get(self, id, fields: list[str] = [], session=None) -> Self:
187
+ cls = self.__class__
188
+
189
+ await cls._check_access(Operation.READ, record_ids=[id])
190
+
191
+ session = cls._get_db_session(session)
192
+
193
+ stmt, values = cls._builder.build_get(id, fields)
194
+ record = await session.execute(
195
+ stmt, values, prepare=cls.prepare_form_id
196
+ )
197
+
198
+ if not record:
199
+ raise ValueError("Record not found")
200
+ assert isinstance(record, cls)
201
+ return record
202
+
203
+ @hybridmethod
204
+ async def table_len(self, session=None) -> int:
205
+ cls = self.__class__
206
+ session = cls._get_db_session(session)
207
+ stmt, values = cls._builder.build_table_len()
208
+
209
+ if cls._dialect == POSTGRES:
210
+ prepare = lambda rows: [r["count"] for r in rows]
211
+ else:
212
+ prepare = lambda rows: [r["COUNT(*)"] for r in rows]
213
+
214
+ records = await session.execute(stmt, values, prepare=prepare)
215
+ assert records is not None
216
+ if len(records):
217
+ return records[0]
218
+ return 0