rb-commons 0.2.6__tar.gz → 0.2.7__tar.gz
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.
- {rb_commons-0.2.6 → rb_commons-0.2.7}/PKG-INFO +1 -1
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/orm/managers.py +90 -78
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons.egg-info/PKG-INFO +1 -1
- {rb_commons-0.2.6 → rb_commons-0.2.7}/setup.py +1 -1
- {rb_commons-0.2.6 → rb_commons-0.2.7}/README.md +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/__init__.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/broker/__init__.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/broker/consumer.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/configs/__init__.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/configs/config.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/configs/injections.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/configs/rabbitmq.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/http/__init__.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/http/base_api.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/http/consul.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/http/exceptions.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/orm/__init__.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/orm/exceptions.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/permissions/__init__.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/permissions/role_permissions.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/schemes/__init__.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/schemes/jwt.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/utils/__init__.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons/utils/media.py +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons.egg-info/SOURCES.txt +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons.egg-info/dependency_links.txt +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons.egg-info/requires.txt +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/rb_commons.egg-info/top_level.txt +0 -0
- {rb_commons-0.2.6 → rb_commons-0.2.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
import uuid
|
2
2
|
from typing import TypeVar, Type, Generic, Optional, List, Dict, Literal, Union
|
3
|
-
from sqlalchemy import select, delete, update, and_
|
3
|
+
from sqlalchemy import select, delete, update, and_, func
|
4
4
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError, NoResultFound
|
5
5
|
from sqlalchemy.ext.asyncio import AsyncSession
|
6
6
|
from sqlalchemy.orm import declarative_base, InstrumentedAttribute
|
@@ -10,6 +10,21 @@ from rb_commons.orm.exceptions import DatabaseException, InternalException
|
|
10
10
|
|
11
11
|
ModelType = TypeVar('ModelType', bound=declarative_base())
|
12
12
|
|
13
|
+
def with_transaction_error_handling(func):
|
14
|
+
async def wrapper(self, *args, **kwargs):
|
15
|
+
try:
|
16
|
+
return await func(self, *args, **kwargs)
|
17
|
+
except IntegrityError as e:
|
18
|
+
await self.session.rollback()
|
19
|
+
raise InternalException(f"Constraint violation: {str(e)}") from e
|
20
|
+
except SQLAlchemyError as e:
|
21
|
+
await self.session.rollback()
|
22
|
+
raise DatabaseException(f"Database error: {str(e)}") from e
|
23
|
+
except Exception as e:
|
24
|
+
await self.session.rollback()
|
25
|
+
raise InternalException(f"Unexpected error: {str(e)}") from e
|
26
|
+
return wrapper
|
27
|
+
|
13
28
|
class BaseManager(Generic[ModelType]):
|
14
29
|
model: Type[ModelType]
|
15
30
|
|
@@ -19,6 +34,19 @@ class BaseManager(Generic[ModelType]):
|
|
19
34
|
self.filters = []
|
20
35
|
self._filtered = False
|
21
36
|
|
37
|
+
async def _persist(self, instance: ModelType) -> Optional[ModelType]:
|
38
|
+
self.session.add(instance)
|
39
|
+
await self.session.flush()
|
40
|
+
return await self._smart_commit(instance)
|
41
|
+
|
42
|
+
async def _smart_commit(self, instance: Optional[ModelType] = None) -> Optional[ModelType]:
|
43
|
+
if not self.session.in_transaction():
|
44
|
+
await self.session.commit()
|
45
|
+
|
46
|
+
if instance:
|
47
|
+
await self.session.refresh(instance)
|
48
|
+
return instance
|
49
|
+
|
22
50
|
async def get(self, pk: Union[str, int, uuid.UUID]) -> Optional[ModelType]:
|
23
51
|
"""
|
24
52
|
get object based on conditions
|
@@ -111,35 +139,24 @@ class BaseManager(Generic[ModelType]):
|
|
111
139
|
|
112
140
|
async def count(self) -> int:
|
113
141
|
"""Return the count of matching records."""
|
114
|
-
self._ensure_filtered()
|
115
142
|
|
116
|
-
|
143
|
+
self._ensure_filtered()
|
144
|
+
query = select(func.count()).select_from(self.model).filter(and_(*self.filters))
|
117
145
|
result = await self.session.execute(query)
|
118
|
-
return
|
146
|
+
return result.scalar_one()
|
119
147
|
|
148
|
+
@with_transaction_error_handling
|
120
149
|
async def create(self, **kwargs) -> ModelType:
|
121
150
|
"""
|
122
151
|
Create a new object
|
123
152
|
"""
|
124
153
|
obj = self.model(**kwargs)
|
125
154
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
await self.session.commit()
|
130
|
-
await self.session.refresh(obj)
|
131
|
-
return obj
|
132
|
-
except IntegrityError as e:
|
133
|
-
await self.session.rollback()
|
134
|
-
raise DatabaseException(f"Constraint violation: {str(e)}") from e
|
135
|
-
except SQLAlchemyError as e:
|
136
|
-
await self.session.rollback()
|
137
|
-
raise DatabaseException(f"Database error occurred: {str(e)}") from e
|
138
|
-
except Exception as e:
|
139
|
-
await self.session.rollback()
|
140
|
-
raise InternalException(f"Unexpected error during creation: {str(e)}") from e
|
141
|
-
|
155
|
+
self.session.add(obj)
|
156
|
+
await self.session.flush()
|
157
|
+
return await self._smart_commit(obj)
|
142
158
|
|
159
|
+
@with_transaction_error_handling
|
143
160
|
async def delete(self):
|
144
161
|
"""
|
145
162
|
Delete object(s) with flexible filtering options
|
@@ -155,27 +172,24 @@ class BaseManager(Generic[ModelType]):
|
|
155
172
|
return result.rowcount
|
156
173
|
except NoResultFound:
|
157
174
|
return False
|
158
|
-
except SQLAlchemyError as e:
|
159
|
-
await self.session.rollback()
|
160
|
-
raise DatabaseException(f"Delete operation failed: {str(e)}") from e
|
161
175
|
|
176
|
+
@with_transaction_error_handling
|
162
177
|
async def bulk_delete(self) -> int:
|
163
178
|
"""
|
164
|
-
|
179
|
+
Bulk delete with flexible filtering.
|
180
|
+
|
181
|
+
Automatically commits if not inside a transaction.
|
165
182
|
|
166
|
-
|
183
|
+
:return: Number of deleted records
|
167
184
|
"""
|
168
185
|
self._ensure_filtered()
|
169
186
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
return result.rowcount()
|
175
|
-
except SQLAlchemyError as e:
|
176
|
-
await self.session.rollback()
|
177
|
-
raise DatabaseException(f"Bulk delete failed: {str(e)}") from e
|
187
|
+
delete_stmt = delete(self.model).where(and_(*self.filters))
|
188
|
+
result = await self.session.execute(delete_stmt)
|
189
|
+
await self._smart_commit()
|
190
|
+
return result.rowcount # ignore
|
178
191
|
|
192
|
+
@with_transaction_error_handling
|
179
193
|
async def update_by_filters(self, filters: Dict, **update_fields) -> Optional[ModelType]:
|
180
194
|
"""
|
181
195
|
Update object(s) with flexible filtering options
|
@@ -187,19 +201,13 @@ class BaseManager(Generic[ModelType]):
|
|
187
201
|
if not update_fields:
|
188
202
|
raise InternalException("No fields provided for update")
|
189
203
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
return updated_instance
|
196
|
-
except IntegrityError as e:
|
197
|
-
await self.session.rollback()
|
198
|
-
raise InternalException(f"Constraint violation: {str(e)}") from e
|
199
|
-
except SQLAlchemyError as e:
|
200
|
-
await self.session.rollback()
|
201
|
-
raise DatabaseException(f"Update operation failed: {str(e)}") from e
|
204
|
+
update_stmt = update(self.model).filter_by(**filters).values(**update_fields)
|
205
|
+
await self.session.execute(update_stmt)
|
206
|
+
await self.session.commit()
|
207
|
+
updated_instance = await self.get(**filters)
|
208
|
+
return updated_instance
|
202
209
|
|
210
|
+
@with_transaction_error_handling
|
203
211
|
async def update(self, instance: ModelType, **update_fields) -> Optional[ModelType]:
|
204
212
|
"""
|
205
213
|
Update an existing database instance with new fields
|
@@ -208,32 +216,23 @@ class BaseManager(Generic[ModelType]):
|
|
208
216
|
:param update_fields: Keyword arguments of fields to update
|
209
217
|
:return: The updated instance
|
210
218
|
|
211
|
-
:raises
|
212
|
-
:raises
|
219
|
+
:raises InternalException: If integrity violation
|
220
|
+
:raises DatabaseException: For database-related errors
|
213
221
|
"""
|
214
222
|
# Validate update fields
|
215
223
|
if not update_fields:
|
216
224
|
raise InternalException("No fields provided for update")
|
217
225
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
setattr(instance, key, value)
|
222
|
-
|
223
|
-
self.session.add(instance)
|
224
|
-
await self.session.commit()
|
225
|
-
await self.session.refresh(instance)
|
226
|
+
# Apply updates directly to the instance
|
227
|
+
for key, value in update_fields.items():
|
228
|
+
setattr(instance, key, value)
|
226
229
|
|
227
|
-
|
230
|
+
self.session.add(instance)
|
231
|
+
await self._smart_commit()
|
228
232
|
|
229
|
-
|
230
|
-
await self.session.rollback()
|
231
|
-
raise InternalException(f"Constraint violation: {str(e)}") from e
|
232
|
-
|
233
|
-
except SQLAlchemyError as e:
|
234
|
-
await self.session.rollback()
|
235
|
-
raise DatabaseException(f"Update operation failed: {str(e)}") from e
|
233
|
+
return instance
|
236
234
|
|
235
|
+
@with_transaction_error_handling
|
237
236
|
async def save(self, instance: ModelType) -> Optional[ModelType]:
|
238
237
|
"""
|
239
238
|
Save instance
|
@@ -241,24 +240,37 @@ class BaseManager(Generic[ModelType]):
|
|
241
240
|
:param instance: The database model instance to save
|
242
241
|
:return: The saved instance
|
243
242
|
|
244
|
-
|
245
|
-
|
243
|
+
Automatically commits if not inside a transaction.
|
244
|
+
|
245
|
+
:raises InternalException: If integrity violation
|
246
|
+
:raises DatabaseException: For database-related errors
|
246
247
|
"""
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
await self.session.refresh(instance)
|
251
|
-
return instance
|
248
|
+
self.session.add(instance)
|
249
|
+
await self.session.flush()
|
250
|
+
return await self._smart_commit(instance)
|
252
251
|
|
253
|
-
|
254
|
-
|
255
|
-
|
252
|
+
async def is_exists(self, **kwargs) -> bool:
|
253
|
+
stmt = select(self.model).filter_by(**kwargs)
|
254
|
+
result = await self.session.execute(stmt)
|
255
|
+
return result.scalar_one_or_none() is not None
|
256
256
|
|
257
|
-
|
258
|
-
|
259
|
-
|
257
|
+
@with_transaction_error_handling
|
258
|
+
async def bulk_save(self, instances: List[ModelType]) -> None:
|
259
|
+
"""
|
260
|
+
Bulk save a list of instances into the database.
|
260
261
|
|
262
|
+
If inside a transaction, flushes only.
|
263
|
+
If not in a transaction, commits after flush.
|
261
264
|
|
262
|
-
|
263
|
-
|
265
|
+
:param instances: List of instances
|
266
|
+
:raises DatabaseException: If any database error occurs
|
267
|
+
:raises InternalException: If any unexpected error occurs
|
268
|
+
"""
|
269
|
+
if not instances:
|
270
|
+
return
|
264
271
|
|
272
|
+
self.session.add_all(instances)
|
273
|
+
await self.session.flush()
|
274
|
+
|
275
|
+
if not self.session.in_transaction():
|
276
|
+
await self.session.commit()
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
5
5
|
|
6
6
|
setup(
|
7
7
|
name="rb-commons",
|
8
|
-
version="0.2.
|
8
|
+
version="0.2.7",
|
9
9
|
author="Abdulvoris",
|
10
10
|
author_email="erkinovabdulvoris101@gmail.com",
|
11
11
|
description="Commons of project and simplified orm based on sqlalchemy.",
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|