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.
- dotorm/__init__.py +87 -0
- dotorm/access.py +151 -0
- dotorm/builder/__init__.py +0 -0
- dotorm/builder/builder.py +72 -0
- dotorm/builder/helpers.py +63 -0
- dotorm/builder/mixins/__init__.py +11 -0
- dotorm/builder/mixins/crud.py +246 -0
- dotorm/builder/mixins/m2m.py +110 -0
- dotorm/builder/mixins/relations.py +96 -0
- dotorm/builder/protocol.py +63 -0
- dotorm/builder/request_builder.py +144 -0
- dotorm/components/__init__.py +18 -0
- dotorm/components/dialect.py +99 -0
- dotorm/components/filter_parser.py +195 -0
- dotorm/databases/__init__.py +13 -0
- dotorm/databases/abstract/__init__.py +25 -0
- dotorm/databases/abstract/dialect.py +134 -0
- dotorm/databases/abstract/pool.py +10 -0
- dotorm/databases/abstract/session.py +67 -0
- dotorm/databases/abstract/types.py +36 -0
- dotorm/databases/clickhouse/__init__.py +8 -0
- dotorm/databases/clickhouse/pool.py +60 -0
- dotorm/databases/clickhouse/session.py +100 -0
- dotorm/databases/mysql/__init__.py +13 -0
- dotorm/databases/mysql/pool.py +69 -0
- dotorm/databases/mysql/session.py +128 -0
- dotorm/databases/mysql/transaction.py +39 -0
- dotorm/databases/postgres/__init__.py +23 -0
- dotorm/databases/postgres/pool.py +133 -0
- dotorm/databases/postgres/session.py +174 -0
- dotorm/databases/postgres/transaction.py +82 -0
- dotorm/decorators.py +379 -0
- dotorm/exceptions.py +9 -0
- dotorm/fields.py +604 -0
- dotorm/integrations/__init__.py +0 -0
- dotorm/integrations/pydantic.py +275 -0
- dotorm/model.py +802 -0
- dotorm/orm/__init__.py +15 -0
- dotorm/orm/mixins/__init__.py +13 -0
- dotorm/orm/mixins/access.py +67 -0
- dotorm/orm/mixins/ddl.py +250 -0
- dotorm/orm/mixins/many2many.py +175 -0
- dotorm/orm/mixins/primary.py +218 -0
- dotorm/orm/mixins/relations.py +513 -0
- dotorm/orm/protocol.py +147 -0
- dotorm/orm/utils.py +39 -0
- dotorm-2.0.8.dist-info/METADATA +1240 -0
- dotorm-2.0.8.dist-info/RECORD +50 -0
- dotorm-2.0.8.dist-info/WHEEL +4 -0
- 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
|