qena-shared-lib 0.1.17__py3-none-any.whl → 0.1.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.
- qena_shared_lib/__init__.py +20 -2
- qena_shared_lib/alias.py +27 -0
- qena_shared_lib/application.py +4 -4
- qena_shared_lib/background.py +9 -7
- qena_shared_lib/cache.py +61 -0
- qena_shared_lib/enums.py +8 -0
- qena_shared_lib/eventbus.py +373 -0
- qena_shared_lib/exception_handling.py +409 -0
- qena_shared_lib/exceptions.py +167 -57
- qena_shared_lib/http/__init__.py +110 -0
- qena_shared_lib/{http.py → http/_base.py} +36 -36
- qena_shared_lib/http/_exception_handlers.py +202 -0
- qena_shared_lib/http/_request.py +24 -0
- qena_shared_lib/http/_response.py +24 -0
- qena_shared_lib/kafka/__init__.py +21 -0
- qena_shared_lib/kafka/_base.py +233 -0
- qena_shared_lib/kafka/_consumer.py +597 -0
- qena_shared_lib/kafka/_exception_handlers.py +124 -0
- qena_shared_lib/kafka/_producer.py +133 -0
- qena_shared_lib/logging.py +17 -13
- qena_shared_lib/mongodb.py +575 -0
- qena_shared_lib/rabbitmq/__init__.py +6 -6
- qena_shared_lib/rabbitmq/_base.py +68 -132
- qena_shared_lib/rabbitmq/_channel.py +2 -4
- qena_shared_lib/rabbitmq/_exception_handlers.py +69 -142
- qena_shared_lib/rabbitmq/_listener.py +245 -180
- qena_shared_lib/rabbitmq/_publisher.py +5 -5
- qena_shared_lib/rabbitmq/_rpc_client.py +21 -22
- qena_shared_lib/rabbitmq/message/__init__.py +19 -0
- qena_shared_lib/rabbitmq/message/_inbound.py +13 -0
- qena_shared_lib/rabbitmq/message/_outbound.py +13 -0
- qena_shared_lib/redis.py +47 -0
- qena_shared_lib/remotelogging/_base.py +34 -28
- qena_shared_lib/remotelogging/logstash/_base.py +3 -2
- qena_shared_lib/remotelogging/logstash/_http_sender.py +2 -4
- qena_shared_lib/remotelogging/logstash/_tcp_sender.py +2 -2
- qena_shared_lib/scheduler.py +24 -15
- qena_shared_lib/security.py +39 -32
- qena_shared_lib/sync.py +91 -0
- qena_shared_lib/utils.py +13 -11
- {qena_shared_lib-0.1.17.dist-info → qena_shared_lib-0.1.19.dist-info}/METADATA +395 -32
- qena_shared_lib-0.1.19.dist-info/RECORD +50 -0
- qena_shared_lib-0.1.19.dist-info/WHEEL +4 -0
- qena_shared_lib/exception_handlers.py +0 -235
- qena_shared_lib-0.1.17.dist-info/RECORD +0 -31
- qena_shared_lib-0.1.17.dist-info/WHEEL +0 -4
qena_shared_lib/security.py
CHANGED
|
@@ -173,44 +173,51 @@ class EndpointAclValidator:
|
|
|
173
173
|
permission_match_strategy or PermissionMatch.SOME
|
|
174
174
|
)
|
|
175
175
|
|
|
176
|
+
if self._permissions is not None:
|
|
177
|
+
self._permissions = sorted(self._permissions)
|
|
178
|
+
|
|
176
179
|
def __call__(
|
|
177
180
|
self, user_info: Annotated[UserInfo, Depends(extract_user_info)]
|
|
178
181
|
) -> UserInfo:
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
and (
|
|
182
|
-
user_info.user_type is None
|
|
183
|
-
or user_info.user_type != self._user_type
|
|
184
|
-
)
|
|
185
|
-
) or (
|
|
186
|
-
self._permissions is not None
|
|
187
|
-
and (
|
|
188
|
-
user_info.user_permissions is None
|
|
189
|
-
or not self._permissions_match(user_info.user_permissions)
|
|
190
|
-
)
|
|
182
|
+
if self._user_type_match(user_info) and self._permissions_match(
|
|
183
|
+
user_info
|
|
191
184
|
):
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def
|
|
209
|
-
|
|
185
|
+
return user_info
|
|
186
|
+
|
|
187
|
+
raise Unauthorized(
|
|
188
|
+
message=MESSAGE,
|
|
189
|
+
response_code=RESPONSE_CODE,
|
|
190
|
+
tags=[user_info.user_id],
|
|
191
|
+
extra={
|
|
192
|
+
"userId": user_info.user_id,
|
|
193
|
+
"userType": user_info.user_type,
|
|
194
|
+
"userPermissions": str(user_info.user_permissions or []),
|
|
195
|
+
"requiredUserType": self._user_type or "None",
|
|
196
|
+
"requiredPermissions": str(self._permissions or []),
|
|
197
|
+
"permissionMatchStrategy": self._permission_match_strategy.name,
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def _user_type_match(self, user_info: UserInfo) -> bool:
|
|
202
|
+
if self._user_type is None:
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
if user_info.user_type is None:
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
return user_info.user_type == self._user_type
|
|
209
|
+
|
|
210
|
+
def _permissions_match(self, user_info: UserInfo) -> bool:
|
|
211
|
+
if self._permissions is None:
|
|
212
|
+
return True
|
|
213
|
+
|
|
214
|
+
if user_info.user_permissions is None:
|
|
215
|
+
return False
|
|
210
216
|
|
|
211
217
|
if self._permission_match_strategy == PermissionMatch.ALL:
|
|
212
|
-
return
|
|
218
|
+
return self._permissions == sorted(user_info.user_permissions)
|
|
213
219
|
|
|
214
220
|
return any(
|
|
215
|
-
permission in self._permissions
|
|
221
|
+
permission in self._permissions
|
|
222
|
+
for permission in user_info.user_permissions
|
|
216
223
|
)
|
qena_shared_lib/sync.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from inspect import Traceback
|
|
2
|
+
from typing import cast
|
|
3
|
+
|
|
4
|
+
from redis.asyncio import Redis
|
|
5
|
+
from redis.asyncio.lock import Lock
|
|
6
|
+
from typing_extensions import Self
|
|
7
|
+
|
|
8
|
+
from .redis import RedisDependent
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"DistributedLockGuard",
|
|
12
|
+
"DistributedLockManager",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DistributedLockGuard:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
distributed_lock_manager: "DistributedLockManager",
|
|
20
|
+
key: str,
|
|
21
|
+
blocking: bool = True,
|
|
22
|
+
):
|
|
23
|
+
self._distributed_lock_manager = distributed_lock_manager
|
|
24
|
+
self._key = key
|
|
25
|
+
self._blocking = blocking
|
|
26
|
+
self._is_acquired = False
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def is_aquired(self) -> bool:
|
|
30
|
+
return self._is_acquired
|
|
31
|
+
|
|
32
|
+
async def __aenter__(self) -> Self:
|
|
33
|
+
if self._blocking:
|
|
34
|
+
await self._distributed_lock_manager.acquire(self._key)
|
|
35
|
+
else:
|
|
36
|
+
self._is_acquired = (
|
|
37
|
+
await self._distributed_lock_manager.try_acquire(self._key)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
async def __aexit__(
|
|
43
|
+
self,
|
|
44
|
+
exception_type: type[Exception],
|
|
45
|
+
exception: Exception,
|
|
46
|
+
traceback: Traceback,
|
|
47
|
+
) -> None:
|
|
48
|
+
del exception_type, exception, traceback
|
|
49
|
+
|
|
50
|
+
await self._distributed_lock_manager.release(self._key)
|
|
51
|
+
|
|
52
|
+
self._is_acquired = False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class DistributedLockManager(RedisDependent):
|
|
56
|
+
def __init__(self, lock_timeout: int = 30):
|
|
57
|
+
self._lock_timeout = lock_timeout
|
|
58
|
+
self._lockes: dict[str, Lock] = {}
|
|
59
|
+
|
|
60
|
+
def attach(self, redis_client: Redis) -> None:
|
|
61
|
+
self._redis_client = redis_client
|
|
62
|
+
|
|
63
|
+
async def acquire(self, key: str) -> None:
|
|
64
|
+
lock = self._register_lock(key)
|
|
65
|
+
|
|
66
|
+
await lock.acquire()
|
|
67
|
+
|
|
68
|
+
async def try_acquire(self, key: str) -> bool:
|
|
69
|
+
lock = self._register_lock(key=key, blocking=False)
|
|
70
|
+
is_acquired = await lock.acquire()
|
|
71
|
+
|
|
72
|
+
return cast(bool, is_acquired)
|
|
73
|
+
|
|
74
|
+
def _register_lock(self, key: str, blocking: bool = True) -> Lock:
|
|
75
|
+
if key not in self._lockes:
|
|
76
|
+
self._lockes[key] = self._redis_client.lock(
|
|
77
|
+
name=key, blocking=blocking, timeout=self._lock_timeout
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return self._lockes[key]
|
|
81
|
+
|
|
82
|
+
async def release(self, key: str) -> None:
|
|
83
|
+
if key not in self._lockes or not await self._lockes[key].owned():
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
await self._lockes[key].release()
|
|
87
|
+
|
|
88
|
+
def __call__(self, key: str, blocking: bool = True) -> DistributedLockGuard:
|
|
89
|
+
return DistributedLockGuard(
|
|
90
|
+
distributed_lock_manager=self, key=key, blocking=blocking
|
|
91
|
+
)
|
qena_shared_lib/utils.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from asyncio import AbstractEventLoop, get_running_loop
|
|
2
|
+
from types import UnionType
|
|
2
3
|
from typing import Generator
|
|
3
4
|
|
|
4
5
|
from pydantic import TypeAdapter
|
|
@@ -11,28 +12,29 @@ class AsyncEventLoopMixin:
|
|
|
11
12
|
|
|
12
13
|
@property
|
|
13
14
|
def loop(self) -> AbstractEventLoop:
|
|
14
|
-
if
|
|
15
|
-
|
|
15
|
+
if AsyncEventLoopMixin._LOOP is None:
|
|
16
|
+
AsyncEventLoopMixin._LOOP = get_running_loop()
|
|
16
17
|
|
|
17
|
-
return
|
|
18
|
+
return AsyncEventLoopMixin._LOOP
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
@staticmethod
|
|
21
|
+
def reset_running_loop() -> None:
|
|
22
|
+
AsyncEventLoopMixin._LOOP = get_running_loop()
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
class TypeAdapterCache:
|
|
24
|
-
|
|
26
|
+
_CACHE: dict[type | UnionType, TypeAdapter] = {}
|
|
25
27
|
|
|
26
28
|
@classmethod
|
|
27
|
-
def cache_annotation(cls, annotation: type) -> None:
|
|
28
|
-
if annotation not in cls.
|
|
29
|
-
cls.
|
|
29
|
+
def cache_annotation(cls, annotation: type | UnionType) -> None:
|
|
30
|
+
if annotation not in cls._CACHE:
|
|
31
|
+
cls._CACHE[annotation] = TypeAdapter(annotation)
|
|
30
32
|
|
|
31
33
|
@classmethod
|
|
32
|
-
def get_type_adapter(cls, annotation: type) -> TypeAdapter:
|
|
34
|
+
def get_type_adapter(cls, annotation: type | UnionType) -> TypeAdapter:
|
|
33
35
|
cls.cache_annotation(annotation)
|
|
34
36
|
|
|
35
|
-
return cls.
|
|
37
|
+
return cls._CACHE[annotation]
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
class YieldOnce:
|