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.
@@ -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
- Apply filtering conditions dynamically.
70
-
71
- Supports:
72
- - `field__eq`: Equal (`=`)
73
- - `field__ne`: Not Equal (`!=`)
74
- - `field__gt`: Greater Than (`>`)
75
- - `field__lt`: Less Than (`<`)
76
- - `field__gte`: Greater Than or Equal (`>=`)
77
- - `field__lte`: Less Than or Equal (`<=`)
78
- - `field__in`: IN Query
79
- - `field__contains`: LIKE Query
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
- if '__' in key:
86
- field_name, operator = key.split('__', 1)
87
- else:
88
- field_name, operator = key, 'eq'
107
+ parts = key.split("__")
89
108
 
90
- column = getattr(self.model, field_name, None)
91
- if column is None or not isinstance(column, InstrumentedAttribute):
92
- raise ValueError(f"Invalid filter field: {field_name}")
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
- self.filters.append(column == value)
127
+ # e.g., column == value
128
+ self.filters.append(current_attr == value)
129
+
96
130
  elif operator == "ne":
97
- self.filters.append(column != value)
131
+ # e.g., column != value
132
+ self.filters.append(current_attr != value)
133
+
98
134
  elif operator == "gt":
99
- self.filters.append(column > value)
135
+ self.filters.append(current_attr > value)
136
+
100
137
  elif operator == "lt":
101
- self.filters.append(column < value)
138
+ self.filters.append(current_attr < value)
139
+
102
140
  elif operator == "gte":
103
- self.filters.append(column >= value)
141
+ self.filters.append(current_attr >= value)
142
+
104
143
  elif operator == "lte":
105
- self.filters.append(column <= value)
106
- elif operator == "in" and isinstance(value, list):
144
+ self.filters.append(current_attr <= value)
145
+
146
+ elif operator == "in":
107
147
  if not isinstance(value, list):
108
- raise ValueError(f"`{field_name}__in` requires a list, got {type(value)}")
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
- self.filters.append(column.ilike(f"%{value}%"))
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
- if self.filters:
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())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rb-commons
3
- Version: 0.3.8
3
+ Version: 0.4.1
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
@@ -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=iAlsBfcxVt_GUfbJckTiFgTCIDYzWcXqyyat8qC4ThQ,12475
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.3.8.dist-info/METADATA,sha256=QroXJUdprFJlGnWXdg2jfdOxC88PqY5cM36HDq57cFM,6570
23
- rb_commons-0.3.8.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
24
- rb_commons-0.3.8.dist-info/top_level.txt,sha256=HPx_WAYo3_fbg1WCeGHsz3wPGio1ucbnrlm2lmqlJog,11
25
- rb_commons-0.3.8.dist-info/RECORD,,
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,,