rb-commons 0.3.7__py3-none-any.whl → 0.4.0__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.
@@ -64,52 +64,94 @@ 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
 
106
+ allowed_operators = {"eq", "ne", "gt", "lt", "gte", "lte", "in", "contains"}
107
+
84
108
  for key, value in kwargs.items():
85
- if '__' in key:
86
- field_name, operator = key.split('__', 1)
109
+ parts = key.split("__")
110
+
111
+ if parts[-1] in allowed_operators:
112
+ operator = parts[-1]
113
+ field_path = parts[:-1]
87
114
  else:
88
- field_name, operator = key, 'eq'
115
+ operator = "eq"
116
+ field_path = parts
117
+
118
+ current_attr = self.model
119
+ for idx, field_name in enumerate(field_path):
120
+ current_attr = getattr(current_attr, field_name, None)
121
+ if current_attr is None:
122
+ raise ValueError(f"Invalid filter field: {'.'.join(field_path)}")
89
123
 
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}")
124
+ if not isinstance(current_attr, InstrumentedAttribute):
125
+ raise ValueError(f"Invalid filter field: {'.'.join(field_path)}")
93
126
 
94
127
  if operator == "eq":
95
- self.filters.append(column == value)
128
+ # e.g., column == value
129
+ self.filters.append(current_attr == value)
130
+
96
131
  elif operator == "ne":
97
- self.filters.append(column != value)
132
+ # e.g., column != value
133
+ self.filters.append(current_attr != value)
134
+
98
135
  elif operator == "gt":
99
- self.filters.append(column > value)
136
+ self.filters.append(current_attr > value)
137
+
100
138
  elif operator == "lt":
101
- self.filters.append(column < value)
139
+ self.filters.append(current_attr < value)
140
+
102
141
  elif operator == "gte":
103
- self.filters.append(column >= value)
142
+ self.filters.append(current_attr >= value)
143
+
104
144
  elif operator == "lte":
105
- self.filters.append(column <= value)
106
- elif operator == "in" and isinstance(value, list):
145
+ self.filters.append(current_attr <= value)
146
+
147
+ elif operator == "in":
107
148
  if not isinstance(value, list):
108
- raise ValueError(f"`{field_name}__in` requires a list, got {type(value)}")
149
+ raise ValueError(f"{'.'.join(field_path)}__in requires a list, got {type(value)}")
150
+ self.filters.append(current_attr.in_(value))
109
151
 
110
- self.filters.append(column.in_(value))
111
152
  elif operator == "contains":
112
- self.filters.append(column.ilike(f"%{value}%"))
153
+ # e.g., column ILIKE %value%
154
+ self.filters.append(current_attr.ilike(f"%{value}%"))
113
155
 
114
156
  return self
115
157
 
@@ -118,13 +160,11 @@ class BaseManager(Generic[ModelType]):
118
160
  if not self._filtered:
119
161
  raise RuntimeError("You must call `filter()` before using this method.")
120
162
 
121
- async def all(self) -> List[ModelType]:
122
- """Return all results based on applied filters."""
163
+ async def all(self, load_all_relations: bool = False) -> List[ModelType]:
123
164
  self._ensure_filtered()
124
165
 
125
- query = select(self.model)
126
- if self.filters:
127
- query = query.filter(and_(*self.filters))
166
+ query = select(self.model).filter(and_(*self.filters))
167
+ query = self._apply_eager_loading(query, load_all_relations)
128
168
 
129
169
  result = await self.session.execute(query)
130
170
  return list(result.scalars().all())
@@ -1,4 +1,5 @@
1
- from typing import Any, Callable, Awaitable
1
+ from abc import ABCMeta
2
+ from typing import Any, Callable, Awaitable, Dict
2
3
 
3
4
  from sqlalchemy.ext.asyncio import AsyncSession
4
5
 
@@ -6,27 +7,34 @@ from rb_commons.http.exceptions import ForbiddenException
6
7
  from rb_commons.schemes.jwt import Claims
7
8
 
8
9
 
9
- class BaseService:
10
+ class FeignClientMeta(ABCMeta):
11
+ def __new__(cls, name, bases, namespace):
12
+ feign_clients = namespace.get('feign_clients', {})
13
+
14
+ for client_name, init_func in feign_clients.items():
15
+ method_name = f'get_{client_name}'
16
+
17
+ async def getter(self, name=client_name, init_func=init_func):
18
+ attr_name = f'_{name}'
19
+ if getattr(self, attr_name, None) is None:
20
+ client = await init_func()
21
+ setattr(self, attr_name, client)
22
+ return getattr(self, attr_name)
23
+
24
+ namespace[method_name] = getter
25
+
26
+ return super().__new__(cls, name, bases, namespace)
27
+
28
+
29
+ class BaseService(metaclass=FeignClientMeta):
30
+ feign_clients: Dict[str, Callable[[], Awaitable[Any]]] = {}
31
+
10
32
  def __init__(self, claims: Claims, session: AsyncSession):
11
33
  self.claims = claims
12
34
  self.session = session
13
- self._feign_clients = {}
14
-
15
- def register_feign_client(self, name: str, init_func: Callable[[], Awaitable[Any]]):
16
- """
17
- Dynamically adds a get_<name>() method that lazy-loads the client.
18
- """
19
- attr_name = f"_{name}"
20
-
21
- async def getter(self):
22
- if getattr(self, attr_name, None) is None:
23
- client = await init_func()
24
- setattr(self, attr_name, client)
25
- return getattr(self, attr_name)
26
-
27
- method_name = f"get_{name}"
28
- bound_method = getter.__get__(self, self.__class__)
29
- setattr(self, method_name, bound_method)
35
+
36
+ for name in self.feign_clients:
37
+ setattr(self, f'_{name}', None)
30
38
 
31
39
  def _verify_shop_permission(self, target: Any, raise_exception: bool = True) -> bool:
32
40
  if self.claims.shop_id != getattr(target, "shop_id", None):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rb-commons
3
- Version: 0.3.7
3
+ Version: 0.4.0
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,15 +11,15 @@ 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
15
- rb_commons/orm/services.py,sha256=41AcjOmi-GUdm-J38Rk_1OOAO_QTscMpGZ29Q2hmq2M,1325
14
+ rb_commons/orm/managers.py,sha256=IktMS_oq0drkt_MwBZTYPE179YgjkQB1HfaDcvnr5Zw,13928
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
18
18
  rb_commons/schemes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
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.7.dist-info/METADATA,sha256=NQXtdUfCsyrUaa5NjGO94qvvGckzOLf2e_vWmVknjg0,6570
23
- rb_commons-0.3.7.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
24
- rb_commons-0.3.7.dist-info/top_level.txt,sha256=HPx_WAYo3_fbg1WCeGHsz3wPGio1ucbnrlm2lmqlJog,11
25
- rb_commons-0.3.7.dist-info/RECORD,,
22
+ rb_commons-0.4.0.dist-info/METADATA,sha256=MrBcay1HyOdGBeKwC8yohoDXLFi0tsjsMdk_S39Lcag,6570
23
+ rb_commons-0.4.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
24
+ rb_commons-0.4.0.dist-info/top_level.txt,sha256=HPx_WAYo3_fbg1WCeGHsz3wPGio1ucbnrlm2lmqlJog,11
25
+ rb_commons-0.4.0.dist-info/RECORD,,