rb-commons 0.3.8__py3-none-any.whl → 0.4.1__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 +74 -35
- {rb_commons-0.3.8.dist-info → rb_commons-0.4.1.dist-info}/METADATA +1 -1
- {rb_commons-0.3.8.dist-info → rb_commons-0.4.1.dist-info}/RECORD +5 -5
- {rb_commons-0.3.8.dist-info → rb_commons-0.4.1.dist-info}/WHEEL +0 -0
- {rb_commons-0.3.8.dist-info → rb_commons-0.4.1.dist-info}/top_level.txt +0 -0
rb_commons/orm/managers.py
CHANGED
@@ -3,7 +3,7 @@ from typing import TypeVar, Type, Generic, Optional, List, Dict, Literal, Union,
|
|
3
3
|
from sqlalchemy import select, delete, update, and_, func, desc, inspect
|
4
4
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError, NoResultFound
|
5
5
|
from sqlalchemy.ext.asyncio import AsyncSession
|
6
|
-
from sqlalchemy.orm import declarative_base, InstrumentedAttribute, selectinload
|
6
|
+
from sqlalchemy.orm import declarative_base, InstrumentedAttribute, selectinload, RelationshipProperty
|
7
7
|
|
8
8
|
from rb_commons.http.exceptions import NotFoundException
|
9
9
|
from rb_commons.orm.exceptions import DatabaseException, InternalException
|
@@ -64,52 +64,93 @@ class BaseManager(Generic[ModelType]):
|
|
64
64
|
|
65
65
|
return instance
|
66
66
|
|
67
|
+
def _apply_eager_loading(self, stmt, load_all_relations: bool = False):
|
68
|
+
"""
|
69
|
+
Apply selectinload for eager loading relationships.
|
70
|
+
|
71
|
+
:param stmt: SQLAlchemy select statement
|
72
|
+
:param load_all_relations: If True, load all relationships
|
73
|
+
:return: Modified statement with eager loading applied
|
74
|
+
"""
|
75
|
+
if load_all_relations:
|
76
|
+
mapper = inspect(self.model)
|
77
|
+
load_relations = [rel.key for rel in mapper.relationships]
|
78
|
+
|
79
|
+
if load_relations:
|
80
|
+
for rel in load_relations:
|
81
|
+
stmt = stmt.options(selectinload(getattr(self.model, rel)))
|
82
|
+
|
83
|
+
return stmt
|
84
|
+
|
67
85
|
def filter(self, **kwargs) -> 'BaseManager[ModelType]':
|
68
86
|
"""
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
87
|
+
Dynamically apply filters to the query.
|
88
|
+
|
89
|
+
Supported operators:
|
90
|
+
- __eq (e.g., field__eq=value) or just field=value
|
91
|
+
- __ne (field__ne=value)
|
92
|
+
- __gt (field__gt=value)
|
93
|
+
- __lt (field__lt=value)
|
94
|
+
- __gte (field__gte=value)
|
95
|
+
- __lte (field__lte=value)
|
96
|
+
- __in (field__in=[val1, val2, ...])
|
97
|
+
- __contains (field__contains='text')
|
98
|
+
|
99
|
+
Additionally supports nested paths, e.g.,
|
100
|
+
product__shop_id=None
|
101
|
+
product__shop__country='US'
|
102
|
+
"""
|
81
103
|
self._filtered = True
|
82
104
|
self.filters = []
|
83
105
|
|
84
106
|
for key, value in kwargs.items():
|
85
|
-
|
86
|
-
field_name, operator = key.split('__', 1)
|
87
|
-
else:
|
88
|
-
field_name, operator = key, 'eq'
|
107
|
+
parts = key.split("__")
|
89
108
|
|
90
|
-
|
91
|
-
|
92
|
-
|
109
|
+
operator = "eq"
|
110
|
+
|
111
|
+
if parts[-1] in {"eq", "ne", "gt", "lt", "gte", "lte", "in", "contains"}:
|
112
|
+
operator = parts.pop()
|
113
|
+
|
114
|
+
current_attr = self.model
|
115
|
+
|
116
|
+
for field_name in parts:
|
117
|
+
attr_candidate = getattr(current_attr, field_name, None)
|
118
|
+
if attr_candidate is None:
|
119
|
+
raise ValueError(f"Invalid filter field: {'.'.join(parts)}")
|
120
|
+
|
121
|
+
if hasattr(attr_candidate, "property") and isinstance(attr_candidate.property, RelationshipProperty):
|
122
|
+
current_attr = attr_candidate.property.mapper.class_
|
123
|
+
else:
|
124
|
+
current_attr = attr_candidate
|
93
125
|
|
94
126
|
if operator == "eq":
|
95
|
-
|
127
|
+
# e.g., column == value
|
128
|
+
self.filters.append(current_attr == value)
|
129
|
+
|
96
130
|
elif operator == "ne":
|
97
|
-
|
131
|
+
# e.g., column != value
|
132
|
+
self.filters.append(current_attr != value)
|
133
|
+
|
98
134
|
elif operator == "gt":
|
99
|
-
self.filters.append(
|
135
|
+
self.filters.append(current_attr > value)
|
136
|
+
|
100
137
|
elif operator == "lt":
|
101
|
-
self.filters.append(
|
138
|
+
self.filters.append(current_attr < value)
|
139
|
+
|
102
140
|
elif operator == "gte":
|
103
|
-
self.filters.append(
|
141
|
+
self.filters.append(current_attr >= value)
|
142
|
+
|
104
143
|
elif operator == "lte":
|
105
|
-
self.filters.append(
|
106
|
-
|
144
|
+
self.filters.append(current_attr <= value)
|
145
|
+
|
146
|
+
elif operator == "in":
|
107
147
|
if not isinstance(value, list):
|
108
|
-
raise ValueError(f"
|
148
|
+
raise ValueError(f"{'.'.join(parts)}__in requires a list, got {type(value)}")
|
149
|
+
self.filters.append(current_attr.in_(value))
|
109
150
|
|
110
|
-
self.filters.append(column.in_(value))
|
111
151
|
elif operator == "contains":
|
112
|
-
|
152
|
+
# e.g., column ILIKE %value%
|
153
|
+
self.filters.append(current_attr.ilike(f"%{value}%"))
|
113
154
|
|
114
155
|
return self
|
115
156
|
|
@@ -118,13 +159,11 @@ class BaseManager(Generic[ModelType]):
|
|
118
159
|
if not self._filtered:
|
119
160
|
raise RuntimeError("You must call `filter()` before using this method.")
|
120
161
|
|
121
|
-
async def all(self) -> List[ModelType]:
|
122
|
-
"""Return all results based on applied filters."""
|
162
|
+
async def all(self, load_all_relations: bool = False) -> List[ModelType]:
|
123
163
|
self._ensure_filtered()
|
124
164
|
|
125
|
-
query = select(self.model)
|
126
|
-
|
127
|
-
query = query.filter(and_(*self.filters))
|
165
|
+
query = select(self.model).filter(and_(*self.filters))
|
166
|
+
query = self._apply_eager_loading(query, load_all_relations)
|
128
167
|
|
129
168
|
result = await self.session.execute(query)
|
130
169
|
return list(result.scalars().all())
|
@@ -11,7 +11,7 @@ rb_commons/http/consul.py,sha256=Ioq72VD1jGwoC96set7n2SgxN40olzI-myA2lwKkYi4,186
|
|
11
11
|
rb_commons/http/exceptions.py,sha256=EGRMr1cRgiJ9Q2tkfANbf0c6-zzXf1CD6J3cmCaT_FA,1885
|
12
12
|
rb_commons/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
rb_commons/orm/exceptions.py,sha256=1aMctiEwrPjyehoXVX1l6ML5ZOhmDkmBISzlTD5ey1Y,509
|
14
|
-
rb_commons/orm/managers.py,sha256=
|
14
|
+
rb_commons/orm/managers.py,sha256=PedAa8zEIrbeW0d9V0_2rrPvbYsJuB05_aVNnLewsI8,13893
|
15
15
|
rb_commons/orm/services.py,sha256=71eRcJ4TxZvzNz-hLXo12X4U7PGK54ZfbLAb27AjZi8,1589
|
16
16
|
rb_commons/permissions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
17
|
rb_commons/permissions/role_permissions.py,sha256=3ZTwKclavAKSheO58fybo2uLDeTkd-iluTY7hUEjRPk,957
|
@@ -19,7 +19,7 @@ rb_commons/schemes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
19
19
|
rb_commons/schemes/jwt.py,sha256=F66JJDhholuOPPzlKeoC6f1TL4gXg4oRUrV5yheNpyo,1675
|
20
20
|
rb_commons/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
21
|
rb_commons/utils/media.py,sha256=KNY_9SdRa3Rp7d3B1tZaXkhmzVa65RcS62BYwZP1bVM,332
|
22
|
-
rb_commons-0.
|
23
|
-
rb_commons-0.
|
24
|
-
rb_commons-0.
|
25
|
-
rb_commons-0.
|
22
|
+
rb_commons-0.4.1.dist-info/METADATA,sha256=lmDjCIO0rK2XvyaMM3lvE7T9YZi-BXBPXr_KJulRL5E,6570
|
23
|
+
rb_commons-0.4.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
24
|
+
rb_commons-0.4.1.dist-info/top_level.txt,sha256=HPx_WAYo3_fbg1WCeGHsz3wPGio1ucbnrlm2lmqlJog,11
|
25
|
+
rb_commons-0.4.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|