rb-commons 0.7.17__py3-none-any.whl → 0.7.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/broker/consumer.py +28 -28
- rb_commons/configs/config.py +51 -51
- rb_commons/configs/injections.py +8 -8
- rb_commons/configs/rabbitmq.py +25 -25
- rb_commons/configs/v2/config.py +51 -51
- rb_commons/http/base_api.py +96 -96
- rb_commons/http/consul.py +43 -43
- rb_commons/http/exceptions.py +41 -41
- rb_commons/orm/enum.py +23 -19
- rb_commons/orm/exceptions.py +10 -10
- rb_commons/orm/managers.py +733 -733
- rb_commons/orm/querysets.py +56 -56
- rb_commons/orm/services.py +44 -44
- rb_commons/permissions/role_permissions.py +31 -31
- rb_commons/schemes/jwt.py +66 -66
- rb_commons/schemes/pagination.py +46 -46
- rb_commons/utils/media.py +33 -33
- {rb_commons-0.7.17.dist-info → rb_commons-0.7.19.dist-info}/METADATA +1 -1
- rb_commons-0.7.19.dist-info/RECORD +30 -0
- rb_commons-0.7.17.dist-info/RECORD +0 -30
- {rb_commons-0.7.17.dist-info → rb_commons-0.7.19.dist-info}/WHEEL +0 -0
- {rb_commons-0.7.17.dist-info → rb_commons-0.7.19.dist-info}/top_level.txt +0 -0
rb_commons/orm/querysets.py
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
from functools import wraps
|
|
4
|
-
from typing import Callable, TypeVar, Any
|
|
5
|
-
|
|
6
|
-
class QJSON:
|
|
7
|
-
def __init__(self, field: str, key: str, operator: str, value: Any):
|
|
8
|
-
self.field = field
|
|
9
|
-
self.key = key
|
|
10
|
-
self.operator = operator
|
|
11
|
-
self.value = value
|
|
12
|
-
|
|
13
|
-
def __repr__(self):
|
|
14
|
-
return f"QJSON(field={self.field}, key={self.key}, op={self.operator}, value={self.value})"
|
|
15
|
-
|
|
16
|
-
class Q:
|
|
17
|
-
"""Boolean logic container that can be combined with `&`, `|`, and `~`."""
|
|
18
|
-
|
|
19
|
-
def __init__(self, **lookups: Any) -> None:
|
|
20
|
-
self.lookups: Dict[str, Any] = lookups
|
|
21
|
-
self.children: List[Q] = []
|
|
22
|
-
self._operator: str = "AND"
|
|
23
|
-
self.negated: bool = False
|
|
24
|
-
|
|
25
|
-
def _combine(self, other: "Q", operator: str) -> "Q":
|
|
26
|
-
combined = Q()
|
|
27
|
-
combined.children = [self, other]
|
|
28
|
-
combined._operator = operator
|
|
29
|
-
return combined
|
|
30
|
-
|
|
31
|
-
def __or__(self, other: "Q") -> "Q":
|
|
32
|
-
return self._combine(other, "OR")
|
|
33
|
-
|
|
34
|
-
def __and__(self, other: "Q") -> "Q":
|
|
35
|
-
return self._combine(other, "AND")
|
|
36
|
-
|
|
37
|
-
def __invert__(self) -> "Q":
|
|
38
|
-
clone = Q()
|
|
39
|
-
clone.lookups = self.lookups.copy()
|
|
40
|
-
clone.children = list(self.children)
|
|
41
|
-
clone._operator = self._operator
|
|
42
|
-
clone.negated = not self.negated
|
|
43
|
-
return clone
|
|
44
|
-
|
|
45
|
-
def __repr__(self) -> str:
|
|
46
|
-
if self.lookups:
|
|
47
|
-
base = f"Q({self.lookups})"
|
|
48
|
-
else:
|
|
49
|
-
base = "Q()"
|
|
50
|
-
if self.children:
|
|
51
|
-
base += f" {self._operator} {self.children}"
|
|
52
|
-
if self.negated:
|
|
53
|
-
base = f"NOT({base})"
|
|
54
|
-
return base
|
|
55
|
-
|
|
56
|
-
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Callable, TypeVar, Any
|
|
5
|
+
|
|
6
|
+
class QJSON:
|
|
7
|
+
def __init__(self, field: str, key: str, operator: str, value: Any):
|
|
8
|
+
self.field = field
|
|
9
|
+
self.key = key
|
|
10
|
+
self.operator = operator
|
|
11
|
+
self.value = value
|
|
12
|
+
|
|
13
|
+
def __repr__(self):
|
|
14
|
+
return f"QJSON(field={self.field}, key={self.key}, op={self.operator}, value={self.value})"
|
|
15
|
+
|
|
16
|
+
class Q:
|
|
17
|
+
"""Boolean logic container that can be combined with `&`, `|`, and `~`."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, **lookups: Any) -> None:
|
|
20
|
+
self.lookups: Dict[str, Any] = lookups
|
|
21
|
+
self.children: List[Q] = []
|
|
22
|
+
self._operator: str = "AND"
|
|
23
|
+
self.negated: bool = False
|
|
24
|
+
|
|
25
|
+
def _combine(self, other: "Q", operator: str) -> "Q":
|
|
26
|
+
combined = Q()
|
|
27
|
+
combined.children = [self, other]
|
|
28
|
+
combined._operator = operator
|
|
29
|
+
return combined
|
|
30
|
+
|
|
31
|
+
def __or__(self, other: "Q") -> "Q":
|
|
32
|
+
return self._combine(other, "OR")
|
|
33
|
+
|
|
34
|
+
def __and__(self, other: "Q") -> "Q":
|
|
35
|
+
return self._combine(other, "AND")
|
|
36
|
+
|
|
37
|
+
def __invert__(self) -> "Q":
|
|
38
|
+
clone = Q()
|
|
39
|
+
clone.lookups = self.lookups.copy()
|
|
40
|
+
clone.children = list(self.children)
|
|
41
|
+
clone._operator = self._operator
|
|
42
|
+
clone.negated = not self.negated
|
|
43
|
+
return clone
|
|
44
|
+
|
|
45
|
+
def __repr__(self) -> str:
|
|
46
|
+
if self.lookups:
|
|
47
|
+
base = f"Q({self.lookups})"
|
|
48
|
+
else:
|
|
49
|
+
base = "Q()"
|
|
50
|
+
if self.children:
|
|
51
|
+
base += f" {self._operator} {self.children}"
|
|
52
|
+
if self.negated:
|
|
53
|
+
base = f"NOT({base})"
|
|
54
|
+
return base
|
|
55
|
+
|
|
56
|
+
|
rb_commons/orm/services.py
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
from abc import ABCMeta
|
|
2
|
-
from typing import Any, Callable, Awaitable, Dict
|
|
3
|
-
|
|
4
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
5
|
-
|
|
6
|
-
from rb_commons.http.exceptions import ForbiddenException
|
|
7
|
-
from rb_commons.schemes.jwt import Claims
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
32
|
-
def __init__(self, claims: Claims, session: AsyncSession):
|
|
33
|
-
self.claims = claims
|
|
34
|
-
self.session = session
|
|
35
|
-
|
|
36
|
-
for name in self.feign_clients:
|
|
37
|
-
setattr(self, f'_{name}', None)
|
|
38
|
-
|
|
39
|
-
def _verify_shop_permission(self, target: Any, raise_exception: bool = True) -> bool:
|
|
40
|
-
if self.claims.shop_id != getattr(target, "shop_id", None):
|
|
41
|
-
if raise_exception:
|
|
42
|
-
raise ForbiddenException("You are not allowed to access this resource")
|
|
43
|
-
return False
|
|
44
|
-
return True
|
|
1
|
+
from abc import ABCMeta
|
|
2
|
+
from typing import Any, Callable, Awaitable, Dict
|
|
3
|
+
|
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
5
|
+
|
|
6
|
+
from rb_commons.http.exceptions import ForbiddenException
|
|
7
|
+
from rb_commons.schemes.jwt import Claims
|
|
8
|
+
|
|
9
|
+
|
|
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
|
+
|
|
32
|
+
def __init__(self, claims: Claims, session: AsyncSession):
|
|
33
|
+
self.claims = claims
|
|
34
|
+
self.session = session
|
|
35
|
+
|
|
36
|
+
for name in self.feign_clients:
|
|
37
|
+
setattr(self, f'_{name}', None)
|
|
38
|
+
|
|
39
|
+
def _verify_shop_permission(self, target: Any, raise_exception: bool = True) -> bool:
|
|
40
|
+
if self.claims.shop_id != getattr(target, "shop_id", None):
|
|
41
|
+
if raise_exception:
|
|
42
|
+
raise ForbiddenException("You are not allowed to access this resource")
|
|
43
|
+
return False
|
|
44
|
+
return True
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
from typing import Annotated
|
|
2
|
-
from fastapi import Depends
|
|
3
|
-
from rb_commons.configs.injections import get_claims
|
|
4
|
-
from rb_commons.http.exceptions import ForbiddenException
|
|
5
|
-
from rb_commons.schemes.jwt import Claims, UserRole
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class BasePermission:
|
|
9
|
-
def __call__(self, claims: Claims = Depends(get_claims)):
|
|
10
|
-
if not self.has_permission(claims):
|
|
11
|
-
raise ForbiddenException(message=f"Access denied", status=401, code="0000")
|
|
12
|
-
|
|
13
|
-
return claims
|
|
14
|
-
|
|
15
|
-
def has_permission(self, claims: Claims) -> bool:
|
|
16
|
-
return False
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class IsAdmin(BasePermission):
|
|
20
|
-
def has_permission(self, claims: Claims) -> bool:
|
|
21
|
-
return claims.user_role == UserRole.ADMIN and claims.shop_id is not None
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class IsCustomer(BasePermission):
|
|
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 \
|
|
27
|
-
and claims.customer_id is not None
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
IsAdminDep = Annotated[Claims, Depends(IsAdmin())]
|
|
31
|
-
IsCustomerDep = Annotated[Claims, Depends(IsCustomer())]
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
from fastapi import Depends
|
|
3
|
+
from rb_commons.configs.injections import get_claims
|
|
4
|
+
from rb_commons.http.exceptions import ForbiddenException
|
|
5
|
+
from rb_commons.schemes.jwt import Claims, UserRole
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BasePermission:
|
|
9
|
+
def __call__(self, claims: Claims = Depends(get_claims)):
|
|
10
|
+
if not self.has_permission(claims):
|
|
11
|
+
raise ForbiddenException(message=f"Access denied", status=401, code="0000")
|
|
12
|
+
|
|
13
|
+
return claims
|
|
14
|
+
|
|
15
|
+
def has_permission(self, claims: Claims) -> bool:
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IsAdmin(BasePermission):
|
|
20
|
+
def has_permission(self, claims: Claims) -> bool:
|
|
21
|
+
return claims.user_role == UserRole.ADMIN and claims.shop_id is not None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class IsCustomer(BasePermission):
|
|
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 \
|
|
27
|
+
and claims.customer_id is not None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
IsAdminDep = Annotated[Claims, Depends(IsAdmin())]
|
|
31
|
+
IsCustomerDep = Annotated[Claims, Depends(IsCustomer())]
|
rb_commons/schemes/jwt.py
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import uuid
|
|
2
|
-
from enum import Enum
|
|
3
|
-
from typing import Optional
|
|
4
|
-
|
|
5
|
-
from pydantic import BaseModel, Field, ConfigDict
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class UserRole(str, Enum):
|
|
9
|
-
ADMIN = "admin"
|
|
10
|
-
CUSTOMER = "customer"
|
|
11
|
-
GUEST = "guest"
|
|
12
|
-
|
|
13
|
-
class Claims(BaseModel):
|
|
14
|
-
model_config = ConfigDict(extra="ignore")
|
|
15
|
-
|
|
16
|
-
user_id: Optional[int] = Field(None, alias="x-user-id")
|
|
17
|
-
customer_id: Optional[int] = Field(None, alias="x-customer-id")
|
|
18
|
-
user_role: UserRole = Field(UserRole.GUEST, alias="x-user-role")
|
|
19
|
-
shop_id: Optional[uuid.UUID] = Field(None, alias="x-shop-id")
|
|
20
|
-
jwt_token: Optional[str] = Field(None, alias="x-jwt-token")
|
|
21
|
-
|
|
22
|
-
@classmethod
|
|
23
|
-
def from_headers(cls, headers: dict) -> 'Claims':
|
|
24
|
-
raw_claims = {
|
|
25
|
-
"x-user-id": headers.get("x-user-id"),
|
|
26
|
-
"x-customer-id": headers.get("x-customer-id"),
|
|
27
|
-
"x-user-role": headers.get("x-user-role", "admin"),
|
|
28
|
-
"x-shop-id": headers.get("x-shop-id"),
|
|
29
|
-
"x-jwt-token": headers.get("x-jwt-token")
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try:
|
|
33
|
-
if raw_claims["x-user-id"]:
|
|
34
|
-
try:
|
|
35
|
-
raw_claims["x-user-id"] = int(raw_claims["x-user-id"])
|
|
36
|
-
except ValueError as e:
|
|
37
|
-
raise ValueError(f"Invalid user_id format: {e}")
|
|
38
|
-
|
|
39
|
-
if raw_claims["x-customer-id"] and raw_claims["x-customer-id"] != 'null':
|
|
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
|
-
|
|
45
|
-
if raw_claims["x-shop-id"]:
|
|
46
|
-
try:
|
|
47
|
-
raw_claims["x-shop-id"] = uuid.UUID(raw_claims["x-shop-id"])
|
|
48
|
-
except ValueError as e:
|
|
49
|
-
raise ValueError(f"Invalid shop_id format: {e}")
|
|
50
|
-
|
|
51
|
-
if raw_claims["x-user-role"]:
|
|
52
|
-
try:
|
|
53
|
-
UserRole(raw_claims["x-user-role"].lower())
|
|
54
|
-
except ValueError as e:
|
|
55
|
-
raise ValueError(f"Invalid user_role: {e}")
|
|
56
|
-
|
|
57
|
-
if raw_claims["x-jwt-token"]:
|
|
58
|
-
try:
|
|
59
|
-
raw_claims["x-jwt-token"] = str(raw_claims["x-jwt-token"])
|
|
60
|
-
except ValueError as e:
|
|
61
|
-
raise ValueError(f"Invalid jwt token format: {e}")
|
|
62
|
-
|
|
63
|
-
return cls(**raw_claims)
|
|
64
|
-
|
|
65
|
-
except Exception as e:
|
|
66
|
-
raise ValueError(f"Failed to parse claims: {str(e)}")
|
|
1
|
+
import uuid
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UserRole(str, Enum):
|
|
9
|
+
ADMIN = "admin"
|
|
10
|
+
CUSTOMER = "customer"
|
|
11
|
+
GUEST = "guest"
|
|
12
|
+
|
|
13
|
+
class Claims(BaseModel):
|
|
14
|
+
model_config = ConfigDict(extra="ignore")
|
|
15
|
+
|
|
16
|
+
user_id: Optional[int] = Field(None, alias="x-user-id")
|
|
17
|
+
customer_id: Optional[int] = Field(None, alias="x-customer-id")
|
|
18
|
+
user_role: UserRole = Field(UserRole.GUEST, alias="x-user-role")
|
|
19
|
+
shop_id: Optional[uuid.UUID] = Field(None, alias="x-shop-id")
|
|
20
|
+
jwt_token: Optional[str] = Field(None, alias="x-jwt-token")
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_headers(cls, headers: dict) -> 'Claims':
|
|
24
|
+
raw_claims = {
|
|
25
|
+
"x-user-id": headers.get("x-user-id"),
|
|
26
|
+
"x-customer-id": headers.get("x-customer-id"),
|
|
27
|
+
"x-user-role": headers.get("x-user-role", "admin"),
|
|
28
|
+
"x-shop-id": headers.get("x-shop-id"),
|
|
29
|
+
"x-jwt-token": headers.get("x-jwt-token")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
if raw_claims["x-user-id"]:
|
|
34
|
+
try:
|
|
35
|
+
raw_claims["x-user-id"] = int(raw_claims["x-user-id"])
|
|
36
|
+
except ValueError as e:
|
|
37
|
+
raise ValueError(f"Invalid user_id format: {e}")
|
|
38
|
+
|
|
39
|
+
if raw_claims["x-customer-id"] and raw_claims["x-customer-id"] != 'null':
|
|
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
|
+
|
|
45
|
+
if raw_claims["x-shop-id"]:
|
|
46
|
+
try:
|
|
47
|
+
raw_claims["x-shop-id"] = uuid.UUID(raw_claims["x-shop-id"])
|
|
48
|
+
except ValueError as e:
|
|
49
|
+
raise ValueError(f"Invalid shop_id format: {e}")
|
|
50
|
+
|
|
51
|
+
if raw_claims["x-user-role"]:
|
|
52
|
+
try:
|
|
53
|
+
UserRole(raw_claims["x-user-role"].lower())
|
|
54
|
+
except ValueError as e:
|
|
55
|
+
raise ValueError(f"Invalid user_role: {e}")
|
|
56
|
+
|
|
57
|
+
if raw_claims["x-jwt-token"]:
|
|
58
|
+
try:
|
|
59
|
+
raw_claims["x-jwt-token"] = str(raw_claims["x-jwt-token"])
|
|
60
|
+
except ValueError as e:
|
|
61
|
+
raise ValueError(f"Invalid jwt token format: {e}")
|
|
62
|
+
|
|
63
|
+
return cls(**raw_claims)
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
raise ValueError(f"Failed to parse claims: {str(e)}")
|
rb_commons/schemes/pagination.py
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
from typing import Generic, TypeVar, Optional
|
|
2
|
-
from pydantic import BaseModel, Field
|
|
3
|
-
|
|
4
|
-
T = TypeVar('T')
|
|
5
|
-
|
|
6
|
-
class PaginationParams(BaseModel):
|
|
7
|
-
"""Pagination parameters for querying."""
|
|
8
|
-
page: int = Field(default=1, ge=1, description="Page number")
|
|
9
|
-
per_page: int = Field(default=10, ge=1, le=100, description="Items per page")
|
|
10
|
-
sort_by: Optional[str] = Field(default=None, description="Field to sort by")
|
|
11
|
-
sort_order: Optional[str] = Field(default="desc", description="Sort order (asc/desc)")
|
|
12
|
-
|
|
13
|
-
class PaginationMeta(BaseModel):
|
|
14
|
-
"""Metadata for paginated responses."""
|
|
15
|
-
total: int = Field(..., description="Total number of items")
|
|
16
|
-
page: int = Field(..., description="Current page number")
|
|
17
|
-
per_page: int = Field(..., description="Items per page")
|
|
18
|
-
total_pages: int = Field(..., description="Total number of pages")
|
|
19
|
-
has_next: bool = Field(..., description="Whether there is a next page")
|
|
20
|
-
has_prev: bool = Field(..., description="Whether there is a previous page")
|
|
21
|
-
|
|
22
|
-
class PaginatedResponse(BaseModel, Generic[T]):
|
|
23
|
-
"""Generic paginated response."""
|
|
24
|
-
items: list[T] = Field(..., description="List of items")
|
|
25
|
-
meta: PaginationMeta = Field(..., description="Pagination metadata")
|
|
26
|
-
|
|
27
|
-
@classmethod
|
|
28
|
-
def create(
|
|
29
|
-
cls,
|
|
30
|
-
items: list[T],
|
|
31
|
-
total: int,
|
|
32
|
-
page: int,
|
|
33
|
-
per_page: int
|
|
34
|
-
) -> "PaginatedResponse[T]":
|
|
35
|
-
"""Create a paginated response with calculated metadata."""
|
|
36
|
-
total_pages = (total + per_page - 1) // per_page
|
|
37
|
-
return cls(
|
|
38
|
-
items=items,
|
|
39
|
-
meta=PaginationMeta(
|
|
40
|
-
total=total,
|
|
41
|
-
page=page,
|
|
42
|
-
per_page=per_page,
|
|
43
|
-
total_pages=total_pages,
|
|
44
|
-
has_next=page < total_pages,
|
|
45
|
-
has_prev=page > 1
|
|
46
|
-
)
|
|
1
|
+
from typing import Generic, TypeVar, Optional
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
T = TypeVar('T')
|
|
5
|
+
|
|
6
|
+
class PaginationParams(BaseModel):
|
|
7
|
+
"""Pagination parameters for querying."""
|
|
8
|
+
page: int = Field(default=1, ge=1, description="Page number")
|
|
9
|
+
per_page: int = Field(default=10, ge=1, le=100, description="Items per page")
|
|
10
|
+
sort_by: Optional[str] = Field(default=None, description="Field to sort by")
|
|
11
|
+
sort_order: Optional[str] = Field(default="desc", description="Sort order (asc/desc)")
|
|
12
|
+
|
|
13
|
+
class PaginationMeta(BaseModel):
|
|
14
|
+
"""Metadata for paginated responses."""
|
|
15
|
+
total: int = Field(..., description="Total number of items")
|
|
16
|
+
page: int = Field(..., description="Current page number")
|
|
17
|
+
per_page: int = Field(..., description="Items per page")
|
|
18
|
+
total_pages: int = Field(..., description="Total number of pages")
|
|
19
|
+
has_next: bool = Field(..., description="Whether there is a next page")
|
|
20
|
+
has_prev: bool = Field(..., description="Whether there is a previous page")
|
|
21
|
+
|
|
22
|
+
class PaginatedResponse(BaseModel, Generic[T]):
|
|
23
|
+
"""Generic paginated response."""
|
|
24
|
+
items: list[T] = Field(..., description="List of items")
|
|
25
|
+
meta: PaginationMeta = Field(..., description="Pagination metadata")
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def create(
|
|
29
|
+
cls,
|
|
30
|
+
items: list[T],
|
|
31
|
+
total: int,
|
|
32
|
+
page: int,
|
|
33
|
+
per_page: int
|
|
34
|
+
) -> "PaginatedResponse[T]":
|
|
35
|
+
"""Create a paginated response with calculated metadata."""
|
|
36
|
+
total_pages = (total + per_page - 1) // per_page
|
|
37
|
+
return cls(
|
|
38
|
+
items=items,
|
|
39
|
+
meta=PaginationMeta(
|
|
40
|
+
total=total,
|
|
41
|
+
page=page,
|
|
42
|
+
per_page=per_page,
|
|
43
|
+
total_pages=total_pages,
|
|
44
|
+
has_next=page < total_pages,
|
|
45
|
+
has_prev=page > 1
|
|
46
|
+
)
|
|
47
47
|
)
|
rb_commons/utils/media.py
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
from pydantic import ValidationError
|
|
4
|
-
from rb_commons.orm.enum import MediaSource
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class MediaUtils:
|
|
8
|
-
BILLZ_S3_ENDPOINT_URL = "https://cdn-grocery.billz.ai/billz"
|
|
9
|
-
BITTO_S3_ENDPOINT_URL = "https://api.bito.uz/upload-api/public/uploads"
|
|
10
|
-
EURO_PHARM_S3_ENDPOINT_URL = "https://api.europharm.uz/images/click_webp"
|
|
11
|
-
|
|
12
|
-
@classmethod
|
|
13
|
-
def url_builder(cls, key: str, source: Optional[MediaSource] = MediaSource.ROBO):
|
|
14
|
-
source = MediaSource.ROBO if source is None else source
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
from rb_commons.configs.config import configs
|
|
18
|
-
DIGITALOCEAN_S3_ENDPOINT_URL = configs.DIGITALOCEAN_S3_ENDPOINT_URL
|
|
19
|
-
DIGITALOCEAN_STORAGE_BUCKET_NAME = configs.DIGITALOCEAN_STORAGE_BUCKET_NAME
|
|
20
|
-
except ValidationError as e:
|
|
21
|
-
from rb_commons.configs.v2.config import configs
|
|
22
|
-
DIGITALOCEAN_S3_ENDPOINT_URL = configs.DIGITALOCEAN_S3_ENDPOINT_URL
|
|
23
|
-
DIGITALOCEAN_STORAGE_BUCKET_NAME = configs.DIGITALOCEAN_STORAGE_BUCKET_NAME
|
|
24
|
-
|
|
25
|
-
media_url = f"{DIGITALOCEAN_S3_ENDPOINT_URL}/{DIGITALOCEAN_STORAGE_BUCKET_NAME}/{key}"
|
|
26
|
-
|
|
27
|
-
if source == MediaSource.BILLZ:
|
|
28
|
-
media_url = f"{cls.BILLZ_S3_ENDPOINT_URL}/{key}"
|
|
29
|
-
elif source == MediaSource.BITO:
|
|
30
|
-
media_url = f"{cls.BITTO_S3_ENDPOINT_URL}/{key}"
|
|
31
|
-
elif source == MediaSource.EUROPHARM:
|
|
32
|
-
media_url = f"{cls.EURO_PHARM_S3_ENDPOINT_URL}/{key}"
|
|
33
|
-
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import ValidationError
|
|
4
|
+
from rb_commons.orm.enum import MediaSource
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MediaUtils:
|
|
8
|
+
BILLZ_S3_ENDPOINT_URL = "https://cdn-grocery.billz.ai/billz"
|
|
9
|
+
BITTO_S3_ENDPOINT_URL = "https://api.bito.uz/upload-api/public/uploads"
|
|
10
|
+
EURO_PHARM_S3_ENDPOINT_URL = "https://api.europharm.uz/images/click_webp"
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def url_builder(cls, key: str, source: Optional[MediaSource] = MediaSource.ROBO):
|
|
14
|
+
source = MediaSource.ROBO if source is None else source
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from rb_commons.configs.config import configs
|
|
18
|
+
DIGITALOCEAN_S3_ENDPOINT_URL = configs.DIGITALOCEAN_S3_ENDPOINT_URL
|
|
19
|
+
DIGITALOCEAN_STORAGE_BUCKET_NAME = configs.DIGITALOCEAN_STORAGE_BUCKET_NAME
|
|
20
|
+
except ValidationError as e:
|
|
21
|
+
from rb_commons.configs.v2.config import configs
|
|
22
|
+
DIGITALOCEAN_S3_ENDPOINT_URL = configs.DIGITALOCEAN_S3_ENDPOINT_URL
|
|
23
|
+
DIGITALOCEAN_STORAGE_BUCKET_NAME = configs.DIGITALOCEAN_STORAGE_BUCKET_NAME
|
|
24
|
+
|
|
25
|
+
media_url = f"{DIGITALOCEAN_S3_ENDPOINT_URL}/{DIGITALOCEAN_STORAGE_BUCKET_NAME}/{key}"
|
|
26
|
+
|
|
27
|
+
if source == MediaSource.BILLZ:
|
|
28
|
+
media_url = f"{cls.BILLZ_S3_ENDPOINT_URL}/{key}"
|
|
29
|
+
elif source == MediaSource.BITO:
|
|
30
|
+
media_url = f"{cls.BITTO_S3_ENDPOINT_URL}/{key}"
|
|
31
|
+
elif source == MediaSource.EUROPHARM:
|
|
32
|
+
media_url = f"{cls.EURO_PHARM_S3_ENDPOINT_URL}/{key}"
|
|
33
|
+
|
|
34
34
|
return media_url
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
rb_commons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
rb_commons/broker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
rb_commons/broker/consumer.py,sha256=JenQgH5Wyt2T-tVK23JCLo-WInIMZBPHnxdS0I6WMF4,1236
|
|
4
|
+
rb_commons/configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
rb_commons/configs/config.py,sha256=EAHgZ1X0tbxNNSFHTCI1vNmQPuyIVPcEoAFO5-KRTQ0,1520
|
|
6
|
+
rb_commons/configs/injections.py,sha256=2tR8ZP-YJ4QJ4Cv7pcukyhJMuhqsqI_TLxnhXpJyC6A,265
|
|
7
|
+
rb_commons/configs/rabbitmq.py,sha256=n35MXmCEzWgy2Rs9gxDu1cN-6J4VukEDS5ZUhsfrXD4,716
|
|
8
|
+
rb_commons/configs/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
rb_commons/configs/v2/config.py,sha256=GPaZPbwJznTs5qH-3Y1ca0cXAMSyChYtX03isY8Bwb4,1530
|
|
10
|
+
rb_commons/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
rb_commons/http/base_api.py,sha256=vDWfFCFwEWqKqFlVKKGjTDlSPsqOdAPhe-LEGMCkImM,5183
|
|
12
|
+
rb_commons/http/consul.py,sha256=xDJX7zS56nt-sH5x4p-7AB9TbtPmPFPtRSjqSn17ehY,1821
|
|
13
|
+
rb_commons/http/exceptions.py,sha256=NC4pXVT7osYvwqfB1SVMWI5R0sTT0FQY8NZXTvZjWMc,1844
|
|
14
|
+
rb_commons/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
rb_commons/orm/enum.py,sha256=A5op83A_PM9GELsI8putigLcYHJy_eeg8rGboKkMqh4,454
|
|
16
|
+
rb_commons/orm/exceptions.py,sha256=x23TmtmW8263E74uB2RffCenZPdxIkMAdmyd0V7suok,499
|
|
17
|
+
rb_commons/orm/managers.py,sha256=ZOpREqqdbcMxbL3BOlJW707OJvsDlAOTZYXhO0dCmXM,22439
|
|
18
|
+
rb_commons/orm/querysets.py,sha256=VVsLXmwZmzSSelCesVskYHGU_dy_8o60sY_N-FFLjA8,1610
|
|
19
|
+
rb_commons/orm/services.py,sha256=zfk0DA9zVIpogQx_p0s1lZvT9zF_yfYazf4Ln2gnG00,1545
|
|
20
|
+
rb_commons/permissions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
rb_commons/permissions/role_permissions.py,sha256=DCPtGbmracF-vMhtdtvArj8l_mbFsRAJzVd6I5YfMvI,1065
|
|
22
|
+
rb_commons/schemes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
rb_commons/schemes/jwt.py,sha256=pQ9muOuLXBcJlIHJLEErC_6KqAAZtq_FsipjGNoMQXk,2429
|
|
24
|
+
rb_commons/schemes/pagination.py,sha256=InUhUW4HDS-g0Fzq3OLsUeZv2-dvuK166Piykh1R-3I,1820
|
|
25
|
+
rb_commons/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
rb_commons/utils/media.py,sha256=3lg5lXSEckdU-eLnDA95usd7LpKRPpeaxwMq51Y0Vqc,1460
|
|
27
|
+
rb_commons-0.7.19.dist-info/METADATA,sha256=B2mHzdFw827KjyB1yadwTq39EZ39iHNqHLZIdLOXMeY,6571
|
|
28
|
+
rb_commons-0.7.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
rb_commons-0.7.19.dist-info/top_level.txt,sha256=HPx_WAYo3_fbg1WCeGHsz3wPGio1ucbnrlm2lmqlJog,11
|
|
30
|
+
rb_commons-0.7.19.dist-info/RECORD,,
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
rb_commons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
rb_commons/broker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
rb_commons/broker/consumer.py,sha256=84yLq8kJIGNYAH9_ySSTDpOMNL54NSs3cA8lNG8CTGY,1264
|
|
4
|
-
rb_commons/configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
rb_commons/configs/config.py,sha256=1XVqxb9QY0E3P3n7vRJF_w238WgRpArfPVcpeT3geUE,1571
|
|
6
|
-
rb_commons/configs/injections.py,sha256=6B1EOgIGnkWv3UrFaV9PRgG0-CJAbLu1UZ3kq-SjPVU,273
|
|
7
|
-
rb_commons/configs/rabbitmq.py,sha256=vUqa_PcZkPp1lX0B6HLSAXVMUcPR2_8-8cN-C7xFxRI,741
|
|
8
|
-
rb_commons/configs/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
rb_commons/configs/v2/config.py,sha256=kZ02y_HIMD-SXqA990yR1r9qrkpvzwn4Nda6fhVMcmo,1581
|
|
10
|
-
rb_commons/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
rb_commons/http/base_api.py,sha256=ZiSJX5z-1QbL5RuKm30wf-3kbOu4s2plb8q3v0DQUKo,5279
|
|
12
|
-
rb_commons/http/consul.py,sha256=Ioq72VD1jGwoC96set7n2SgxN40olzI-myA2lwKkYi4,1864
|
|
13
|
-
rb_commons/http/exceptions.py,sha256=EGRMr1cRgiJ9Q2tkfANbf0c6-zzXf1CD6J3cmCaT_FA,1885
|
|
14
|
-
rb_commons/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
rb_commons/orm/enum.py,sha256=PRNSuP1X7ADJW1snnxRGcHj9mxE3JaO-a2AyaHmB6es,380
|
|
16
|
-
rb_commons/orm/exceptions.py,sha256=1aMctiEwrPjyehoXVX1l6ML5ZOhmDkmBISzlTD5ey1Y,509
|
|
17
|
-
rb_commons/orm/managers.py,sha256=6tqSIu11QEFEbElgS_QlXa1EDeAhMXp75YJ0JJnYwBU,23172
|
|
18
|
-
rb_commons/orm/querysets.py,sha256=Q4iY_TKZItpNnrbCAvlSwyCxJikhaX5qVF3Y2rVgAYQ,1666
|
|
19
|
-
rb_commons/orm/services.py,sha256=71eRcJ4TxZvzNz-hLXo12X4U7PGK54ZfbLAb27AjZi8,1589
|
|
20
|
-
rb_commons/permissions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
rb_commons/permissions/role_permissions.py,sha256=4dV89z6ggzLqCCiFYlMp7kQVJRESu6MHpkT5ZNjLo6A,1096
|
|
22
|
-
rb_commons/schemes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
rb_commons/schemes/jwt.py,sha256=ZKLJ5D3fcEmEKySjzbxEgUcza4K-oPoHr14_Z0r9Yic,2495
|
|
24
|
-
rb_commons/schemes/pagination.py,sha256=8VZW1wZGJIPR9jEBUgppZUoB4uqP8ORudHkMwvEJSxg,1866
|
|
25
|
-
rb_commons/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
rb_commons/utils/media.py,sha256=pwwGyImI5BK-NCJkX0Q6w2Nm-QL9_CCQC7B7O7wz38I,1493
|
|
27
|
-
rb_commons-0.7.17.dist-info/METADATA,sha256=JnqHtcWML24KyQjmlN7t43DHk5U69XSloyDyHN1kvRM,6571
|
|
28
|
-
rb_commons-0.7.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
-
rb_commons-0.7.17.dist-info/top_level.txt,sha256=HPx_WAYo3_fbg1WCeGHsz3wPGio1ucbnrlm2lmqlJog,11
|
|
30
|
-
rb_commons-0.7.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|