rb-commons 0.1.22__py3-none-any.whl → 0.1.24__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.
- rb_commons/orm/managers.py +75 -20
- {rb_commons-0.1.22.dist-info → rb_commons-0.1.24.dist-info}/METADATA +3 -3
- {rb_commons-0.1.22.dist-info → rb_commons-0.1.24.dist-info}/RECORD +5 -5
- {rb_commons-0.1.22.dist-info → rb_commons-0.1.24.dist-info}/WHEEL +1 -1
- {rb_commons-0.1.22.dist-info → rb_commons-0.1.24.dist-info}/top_level.txt +0 -0
rb_commons/orm/managers.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
import uuid
|
2
2
|
from typing import TypeVar, Type, Generic, Optional, List, Dict, Literal, Union
|
3
|
-
from sqlalchemy import select, delete, update
|
3
|
+
from sqlalchemy import select, delete, update, and_
|
4
4
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError, NoResultFound
|
5
5
|
from sqlalchemy.ext.asyncio import AsyncSession
|
6
|
-
from sqlalchemy.orm import declarative_base
|
6
|
+
from sqlalchemy.orm import declarative_base, InstrumentedAttribute
|
7
7
|
|
8
8
|
from rb_commons.http.exceptions import NotFoundException
|
9
9
|
from rb_commons.orm.exceptions import DatabaseException, InternalException
|
@@ -16,7 +16,8 @@ class BaseManager(Generic[ModelType]):
|
|
16
16
|
def __init__(self, session: AsyncSession) -> None:
|
17
17
|
self.session = session
|
18
18
|
self.data = None
|
19
|
-
self.filters =
|
19
|
+
self.filters = []
|
20
|
+
self._filtered = False
|
20
21
|
|
21
22
|
async def get(self, pk: Union[str, int, uuid.UUID]) -> Optional[ModelType]:
|
22
23
|
"""
|
@@ -37,26 +38,82 @@ class BaseManager(Generic[ModelType]):
|
|
37
38
|
|
38
39
|
def filter(self, **kwargs) -> 'BaseManager[ModelType]':
|
39
40
|
"""
|
40
|
-
|
41
|
+
Apply filtering conditions dynamically.
|
42
|
+
|
43
|
+
Supports:
|
44
|
+
- `field__eq`: Equal (`=`)
|
45
|
+
- `field__ne`: Not Equal (`!=`)
|
46
|
+
- `field__gt`: Greater Than (`>`)
|
47
|
+
- `field__lt`: Less Than (`<`)
|
48
|
+
- `field__gte`: Greater Than or Equal (`>=`)
|
49
|
+
- `field__lte`: Less Than or Equal (`<=`)
|
50
|
+
- `field__in`: IN Query
|
51
|
+
- `field__contains`: LIKE Query
|
41
52
|
"""
|
42
|
-
self.
|
53
|
+
self._filtered = True
|
54
|
+
self.filters = []
|
55
|
+
|
56
|
+
for key, value in kwargs.items():
|
57
|
+
if '__' in key:
|
58
|
+
field_name, operator = key.split('__', 1)
|
59
|
+
else:
|
60
|
+
field_name, operator = key, 'eq'
|
61
|
+
|
62
|
+
column = getattr(self.model, field_name, None)
|
63
|
+
if column is None or not isinstance(column, InstrumentedAttribute):
|
64
|
+
raise ValueError(f"Invalid filter field: {field_name}")
|
65
|
+
|
66
|
+
if operator == "eq":
|
67
|
+
self.filters.append(column == value)
|
68
|
+
elif operator == "ne":
|
69
|
+
self.filters.append(column != value)
|
70
|
+
elif operator == "gt":
|
71
|
+
self.filters.append(column > value)
|
72
|
+
elif operator == "lt":
|
73
|
+
self.filters.append(column < value)
|
74
|
+
elif operator == "gte":
|
75
|
+
self.filters.append(column >= value)
|
76
|
+
elif operator == "lte":
|
77
|
+
self.filters.append(column <= value)
|
78
|
+
elif operator == "in" and isinstance(value, list):
|
79
|
+
if not isinstance(value, list):
|
80
|
+
raise ValueError(f"`{field_name}__in` requires a list, got {type(value)}")
|
81
|
+
|
82
|
+
self.filters.append(column.in_(value))
|
83
|
+
elif operator == "contains":
|
84
|
+
self.filters.append(column.ilike(f"%{value}%"))
|
85
|
+
|
43
86
|
return self
|
44
87
|
|
88
|
+
def _ensure_filtered(self):
|
89
|
+
"""Ensure that `filter()` has been called before using certain methods."""
|
90
|
+
if not self._filtered:
|
91
|
+
raise RuntimeError("You must call `filter()` before using this method.")
|
92
|
+
|
45
93
|
async def all(self) -> List[ModelType]:
|
46
|
-
"""Return all
|
47
|
-
|
94
|
+
"""Return all results based on applied filters."""
|
95
|
+
self._ensure_filtered()
|
96
|
+
|
97
|
+
query = select(self.model)
|
98
|
+
if self.filters:
|
99
|
+
query = query.filter(and_(*self.filters))
|
100
|
+
|
48
101
|
result = await self.session.execute(query)
|
49
102
|
return list(result.scalars().all())
|
50
103
|
|
51
104
|
async def first(self) -> Optional[ModelType]:
|
52
105
|
"""Return the first matching object, or None."""
|
53
|
-
|
106
|
+
self._ensure_filtered()
|
107
|
+
|
108
|
+
query = select(self.model).filter(and_(*self.filters))
|
54
109
|
result = await self.session.execute(query)
|
55
110
|
return result.scalars().first()
|
56
111
|
|
57
112
|
async def count(self) -> int:
|
58
113
|
"""Return the count of matching records."""
|
59
|
-
|
114
|
+
self._ensure_filtered()
|
115
|
+
|
116
|
+
query = select(self.model).filter(and_(*self.filters))
|
60
117
|
result = await self.session.execute(query)
|
61
118
|
return len(result.scalars().all())
|
62
119
|
|
@@ -83,37 +140,35 @@ class BaseManager(Generic[ModelType]):
|
|
83
140
|
raise InternalException(f"Unexpected error during creation: {str(e)}") from e
|
84
141
|
|
85
142
|
|
86
|
-
async def delete(self
|
143
|
+
async def delete(self):
|
87
144
|
"""
|
88
145
|
Delete object(s) with flexible filtering options
|
89
146
|
|
90
|
-
:param id: Specific ID to delete
|
91
|
-
:param filters: Additional filter conditions
|
92
147
|
:return: Number of deleted records or None
|
93
148
|
"""
|
94
|
-
|
95
|
-
if id is not None:
|
96
|
-
filters['id'] = id
|
149
|
+
self._ensure_filtered()
|
97
150
|
|
98
|
-
|
151
|
+
try:
|
152
|
+
delete_stmt = delete(self.model).where(and_(*self.filters))
|
99
153
|
result = await self.session.execute(delete_stmt)
|
100
154
|
await self.session.commit()
|
101
|
-
return result
|
155
|
+
return result.rowcount
|
102
156
|
except NoResultFound:
|
103
157
|
return False
|
104
158
|
except SQLAlchemyError as e:
|
105
159
|
await self.session.rollback()
|
106
160
|
raise DatabaseException(f"Delete operation failed: {str(e)}") from e
|
107
161
|
|
108
|
-
async def bulk_delete(self
|
162
|
+
async def bulk_delete(self) -> int:
|
109
163
|
"""
|
110
164
|
Bulk delete with flexible filtering
|
111
165
|
|
112
|
-
:param filters: Conditions for bulk deletion
|
113
166
|
:return: Number of deleted records
|
114
167
|
"""
|
168
|
+
self._ensure_filtered()
|
169
|
+
|
115
170
|
try:
|
116
|
-
delete_stmt = delete(self.model).
|
171
|
+
delete_stmt = delete(self.model).where(and_(*self.filters))
|
117
172
|
result = await self.session.execute(delete_stmt)
|
118
173
|
await self.session.commit()
|
119
174
|
return result.rowcount()
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: rb-commons
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.24
|
4
4
|
Summary: Commons of project and simplified orm based on sqlalchemy.
|
5
5
|
Home-page: https://github.com/RoboSell-organization/rb-commons
|
6
6
|
Author: Abdulvoris
|
@@ -12,7 +12,7 @@ Requires-Python: >=3.11
|
|
12
12
|
Description-Content-Type: text/markdown
|
13
13
|
Requires-Dist: annotated-types==0.7.0
|
14
14
|
Requires-Dist: greenlet==3.1.1
|
15
|
-
Requires-Dist: pydantic
|
15
|
+
Requires-Dist: pydantic<3.0.0,>=1.7.4
|
16
16
|
Requires-Dist: PyJWT==2.10.1
|
17
17
|
Requires-Dist: python-dotenv==1.0.1
|
18
18
|
Requires-Dist: SQLAlchemy==2.0.36
|
@@ -6,14 +6,14 @@ rb_commons/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
rb_commons/http/exceptions.py,sha256=EGRMr1cRgiJ9Q2tkfANbf0c6-zzXf1CD6J3cmCaT_FA,1885
|
7
7
|
rb_commons/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
rb_commons/orm/exceptions.py,sha256=1aMctiEwrPjyehoXVX1l6ML5ZOhmDkmBISzlTD5ey1Y,509
|
9
|
-
rb_commons/orm/managers.py,sha256=
|
9
|
+
rb_commons/orm/managers.py,sha256=aYwBAkewrw2tEEHKO_kBBnkkKR1lk8UKblr6YOMWr94,9805
|
10
10
|
rb_commons/permissions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
rb_commons/permissions/role_permissions.py,sha256=3ZTwKclavAKSheO58fybo2uLDeTkd-iluTY7hUEjRPk,957
|
12
12
|
rb_commons/schemes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
rb_commons/schemes/jwt.py,sha256=F66JJDhholuOPPzlKeoC6f1TL4gXg4oRUrV5yheNpyo,1675
|
14
14
|
rb_commons/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
rb_commons/utils/media.py,sha256=KNY_9SdRa3Rp7d3B1tZaXkhmzVa65RcS62BYwZP1bVM,332
|
16
|
-
rb_commons-0.1.
|
17
|
-
rb_commons-0.1.
|
18
|
-
rb_commons-0.1.
|
19
|
-
rb_commons-0.1.
|
16
|
+
rb_commons-0.1.24.dist-info/METADATA,sha256=4LV30IZhpybCS8S5KXALPnQ7YqJVyIcjVKlPDkGh-Q8,6539
|
17
|
+
rb_commons-0.1.24.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
18
|
+
rb_commons-0.1.24.dist-info/top_level.txt,sha256=HPx_WAYo3_fbg1WCeGHsz3wPGio1ucbnrlm2lmqlJog,11
|
19
|
+
rb_commons-0.1.24.dist-info/RECORD,,
|
File without changes
|