rb-commons 0.5.17__py3-none-any.whl → 0.5.19__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 +49 -7
- rb_commons/permissions/role_permissions.py +2 -1
- rb_commons/schemes/jwt.py +8 -0
- {rb_commons-0.5.17.dist-info → rb_commons-0.5.19.dist-info}/METADATA +1 -1
- {rb_commons-0.5.17.dist-info → rb_commons-0.5.19.dist-info}/RECORD +7 -7
- {rb_commons-0.5.17.dist-info → rb_commons-0.5.19.dist-info}/WHEEL +1 -1
- {rb_commons-0.5.17.dist-info → rb_commons-0.5.19.dist-info}/top_level.txt +0 -0
rb_commons/orm/managers.py
CHANGED
@@ -12,6 +12,16 @@ from rb_commons.orm.exceptions import DatabaseException, InternalException
|
|
12
12
|
|
13
13
|
ModelType = TypeVar('ModelType', bound=declarative_base())
|
14
14
|
|
15
|
+
class QJSON:
|
16
|
+
def __init__(self, field: str, key: str, operator: str, value: Any):
|
17
|
+
self.field = field
|
18
|
+
self.key = key
|
19
|
+
self.operator = operator
|
20
|
+
self.value = value
|
21
|
+
|
22
|
+
def __repr__(self):
|
23
|
+
return f"QJSON(field={self.field}, key={self.key}, op={self.operator}, value={self.value})"
|
24
|
+
|
15
25
|
class Q:
|
16
26
|
"""Boolean logic container that can be combined with `&`, `|`, and `~`."""
|
17
27
|
|
@@ -124,7 +134,10 @@ class BaseManager(Generic[ModelType]):
|
|
124
134
|
return current.is_(None) if value else current.isnot(None)
|
125
135
|
raise ValueError(f"Unsupported operator in lookup: {lookup}")
|
126
136
|
|
127
|
-
def _q_to_expr(self, q: Q):
|
137
|
+
def _q_to_expr(self, q: Union[Q, QJSON]):
|
138
|
+
if isinstance(q, QJSON):
|
139
|
+
return self._parse_qjson(q)
|
140
|
+
|
128
141
|
clauses: List[Any] = [self._parse_lookup(k, v) for k, v in q.lookups.items()]
|
129
142
|
for child in q.children:
|
130
143
|
clauses.append(self._q_to_expr(child))
|
@@ -135,6 +148,29 @@ class BaseManager(Generic[ModelType]):
|
|
135
148
|
)
|
136
149
|
return ~combined if q.negated else combined
|
137
150
|
|
151
|
+
def _parse_qjson(self, qjson: QJSON):
|
152
|
+
col = getattr(self.model, qjson.field, None)
|
153
|
+
if col is None:
|
154
|
+
raise ValueError(f"Invalid JSON field: {qjson.field}")
|
155
|
+
|
156
|
+
json_expr = col[qjson.key].astext
|
157
|
+
|
158
|
+
if qjson.operator == "eq":
|
159
|
+
return json_expr == str(qjson.value)
|
160
|
+
if qjson.operator == "ne":
|
161
|
+
return json_expr != str(qjson.value)
|
162
|
+
if qjson.operator == "contains":
|
163
|
+
return json_expr.ilike(f"%{qjson.value}%")
|
164
|
+
if qjson.operator == "startswith":
|
165
|
+
return json_expr.ilike(f"{qjson.value}%")
|
166
|
+
if qjson.operator == "endswith":
|
167
|
+
return json_expr.ilike(f"%{qjson.value}")
|
168
|
+
if qjson.operator == "in":
|
169
|
+
if not isinstance(qjson.value, (list, tuple, set)):
|
170
|
+
raise ValueError(f"{qjson.field}[{qjson.key}]__in requires an iterable")
|
171
|
+
return json_expr.in_(qjson.value)
|
172
|
+
raise ValueError(f"Unsupported QJSON operator: {qjson.operator}")
|
173
|
+
|
138
174
|
def _loader_from_path(self, path: str) -> Load:
|
139
175
|
"""
|
140
176
|
Turn 'attributes.attribute.attribute_group' into
|
@@ -173,25 +209,31 @@ class BaseManager(Generic[ModelType]):
|
|
173
209
|
return self
|
174
210
|
|
175
211
|
def filter(self, *expressions: Any, **lookups: Any) -> "BaseManager[ModelType]":
|
176
|
-
"""Add **AND** constraints (default behaviour).
|
212
|
+
"""Add **AND** constraints (default behaviour)."""
|
177
213
|
|
178
|
-
* `expressions` can be raw SQLAlchemy clauses **or** `Q` objects.
|
179
|
-
* `lookups` are Django‑style keyword filters.
|
180
|
-
"""
|
181
214
|
self._filtered = True
|
182
215
|
for expr in expressions:
|
183
|
-
|
216
|
+
if isinstance(expr, Q) or isinstance(expr, QJSON):
|
217
|
+
self.filters.append(self._q_to_expr(expr))
|
218
|
+
else:
|
219
|
+
self.filters.append(expr)
|
184
220
|
for k, v in lookups.items():
|
185
221
|
self.filters.append(self._parse_lookup(k, v))
|
186
222
|
return self
|
187
223
|
|
188
224
|
def or_filter(self, *expressions: Any, **lookups: Any) -> "BaseManager[ModelType]":
|
189
225
|
"""Add one OR group (shortcut for `filter(Q() | Q())`)."""
|
226
|
+
|
190
227
|
or_clauses: List[Any] = []
|
191
228
|
for expr in expressions:
|
192
|
-
|
229
|
+
if isinstance(expr, Q) or isinstance(expr, QJSON):
|
230
|
+
or_clauses.append(self._q_to_expr(expr))
|
231
|
+
else:
|
232
|
+
or_clauses.append(expr)
|
233
|
+
|
193
234
|
for k, v in lookups.items():
|
194
235
|
or_clauses.append(self._parse_lookup(k, v))
|
236
|
+
|
195
237
|
if or_clauses:
|
196
238
|
self._filtered = True
|
197
239
|
self.filters.append(or_(*or_clauses))
|
@@ -23,7 +23,8 @@ class IsAdmin(BasePermission):
|
|
23
23
|
|
24
24
|
class IsCustomer(BasePermission):
|
25
25
|
def has_permission(self, claims: Claims) -> bool:
|
26
|
-
return claims.user_role == UserRole.CUSTOMER and claims.user_id is not None and claims.shop_id is not None
|
26
|
+
return claims.user_role == UserRole.CUSTOMER and claims.user_id is not None and claims.shop_id is not None \
|
27
|
+
and claims.customer_id is not None
|
27
28
|
|
28
29
|
|
29
30
|
IsAdminDep = Annotated[Claims, Depends(IsAdmin())]
|
rb_commons/schemes/jwt.py
CHANGED
@@ -14,6 +14,7 @@ class Claims(BaseModel):
|
|
14
14
|
model_config = ConfigDict(extra="ignore")
|
15
15
|
|
16
16
|
user_id: Optional[int] = Field(None, alias="x-user-id")
|
17
|
+
customer_id: Optional[int] = Field(None, alias="x-customer-id")
|
17
18
|
user_role: UserRole = Field(UserRole.GUEST, alias="x-user-role")
|
18
19
|
shop_id: Optional[uuid.UUID] = Field(None, alias="x-shop-id")
|
19
20
|
jwt_token: Optional[str] = Field(None, alias="x-jwt-token")
|
@@ -22,6 +23,7 @@ class Claims(BaseModel):
|
|
22
23
|
def from_headers(cls, headers: dict) -> 'Claims':
|
23
24
|
raw_claims = {
|
24
25
|
"x-user-id": headers.get("x-user-id"),
|
26
|
+
"x-customer-id": headers.get("x-customer-id"),
|
25
27
|
"x-user-role": headers.get("x-user-role", "admin"),
|
26
28
|
"x-shop-id": headers.get("x-shop-id"),
|
27
29
|
"x-jwt-token": headers.get("x-jwt-token")
|
@@ -34,6 +36,12 @@ class Claims(BaseModel):
|
|
34
36
|
except ValueError as e:
|
35
37
|
raise ValueError(f"Invalid user_id format: {e}")
|
36
38
|
|
39
|
+
if raw_claims["x-customer-id"]:
|
40
|
+
try:
|
41
|
+
raw_claims["x-customer-id"] = int(raw_claims["x-customer-id"])
|
42
|
+
except ValueError as e:
|
43
|
+
raise ValueError(f"Invalid customer_id format: {e}")
|
44
|
+
|
37
45
|
if raw_claims["x-shop-id"]:
|
38
46
|
try:
|
39
47
|
raw_claims["x-shop-id"] = uuid.UUID(raw_claims["x-shop-id"])
|
@@ -13,16 +13,16 @@ rb_commons/http/consul.py,sha256=Ioq72VD1jGwoC96set7n2SgxN40olzI-myA2lwKkYi4,186
|
|
13
13
|
rb_commons/http/exceptions.py,sha256=EGRMr1cRgiJ9Q2tkfANbf0c6-zzXf1CD6J3cmCaT_FA,1885
|
14
14
|
rb_commons/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
rb_commons/orm/exceptions.py,sha256=1aMctiEwrPjyehoXVX1l6ML5ZOhmDkmBISzlTD5ey1Y,509
|
16
|
-
rb_commons/orm/managers.py,sha256=
|
16
|
+
rb_commons/orm/managers.py,sha256=czc2czBswfVjm51vYvMEupDGxniinAYMqkTXbiMEC7Y,18660
|
17
17
|
rb_commons/orm/services.py,sha256=71eRcJ4TxZvzNz-hLXo12X4U7PGK54ZfbLAb27AjZi8,1589
|
18
18
|
rb_commons/permissions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
-
rb_commons/permissions/role_permissions.py,sha256=
|
19
|
+
rb_commons/permissions/role_permissions.py,sha256=4dV89z6ggzLqCCiFYlMp7kQVJRESu6MHpkT5ZNjLo6A,1096
|
20
20
|
rb_commons/schemes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
rb_commons/schemes/jwt.py,sha256=
|
21
|
+
rb_commons/schemes/jwt.py,sha256=5J-VTAgRWnRrOU6cuumc8eDJQDyTm5y8cn-kLSLan_g,2453
|
22
22
|
rb_commons/schemes/pagination.py,sha256=8VZW1wZGJIPR9jEBUgppZUoB4uqP8ORudHkMwvEJSxg,1866
|
23
23
|
rb_commons/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
24
|
rb_commons/utils/media.py,sha256=J2Zi0J28DhcVQVzt-myNNVuzj9Msaetul53VjZtdDdc,820
|
25
|
-
rb_commons-0.5.
|
26
|
-
rb_commons-0.5.
|
27
|
-
rb_commons-0.5.
|
28
|
-
rb_commons-0.5.
|
25
|
+
rb_commons-0.5.19.dist-info/METADATA,sha256=BjA7S-dZXfP3gExEwuqyNqmZbYuGx1cb_d1Ww3MjhRA,6571
|
26
|
+
rb_commons-0.5.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
27
|
+
rb_commons-0.5.19.dist-info/top_level.txt,sha256=HPx_WAYo3_fbg1WCeGHsz3wPGio1ucbnrlm2lmqlJog,11
|
28
|
+
rb_commons-0.5.19.dist-info/RECORD,,
|
File without changes
|