qena-shared-lib 0.1.0__tar.gz → 0.1.2__tar.gz
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-0.1.0 → qena_shared_lib-0.1.2}/PKG-INFO +3 -7
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/README.md +2 -6
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/pyproject.toml +1 -1
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/rabbitmq/_base.py +50 -79
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/scheduler.py +20 -19
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/security.py +3 -3
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/tests/test_rabbitmq.py +67 -82
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/tests/test_scheduler.py +8 -9
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/.gitignore +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/.pre-commit-config.yaml +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/requirements.txt +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/__init__.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/application.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/background.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/dependencies/__init__.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/dependencies/http.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/dependencies/miscellaneous.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/exception_handlers.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/exceptions.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/http.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/logging.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/logstash/__init__.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/logstash/_base.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/logstash/_http_sender.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/logstash/_tcp_sender.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/py.typed +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/rabbitmq/__init__.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/rabbitmq/_channel.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/rabbitmq/_exception_handlers.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/rabbitmq/_exceptions.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/rabbitmq/_listener.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/rabbitmq/_pool.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/rabbitmq/_publisher.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/rabbitmq/_rpc_client.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/rabbitmq/_utils.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/src/qena_shared_lib/utils.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/tests/conftest.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/tests/test_application.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/tests/test_background.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/tests/test_dependencies.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/tests/test_logstash.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/tests/test_security.py +0 -0
- {qena_shared_lib-0.1.0 → qena_shared_lib-0.1.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: qena-shared-lib
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.2
|
4
4
|
Summary: A shared tools for other services
|
5
5
|
Requires-Python: >=3.10
|
6
6
|
Requires-Dist: cronsim~=2.0
|
@@ -224,13 +224,11 @@ def main() -> FastAPI:
|
|
224
224
|
...
|
225
225
|
|
226
226
|
rabbitmq = RabbitMqManager(
|
227
|
-
listeners=[
|
228
|
-
UserConsumer
|
229
|
-
],
|
230
227
|
logstash=logstash,
|
231
228
|
container=builder.container,
|
232
229
|
)
|
233
230
|
|
231
|
+
rabbitmq.include_listener(UserConsumer)
|
234
232
|
builder.add_singleton(
|
235
233
|
service=RabbitMqManager,
|
236
234
|
instance=rabbitmq,
|
@@ -314,13 +312,11 @@ def main() -> FastAPI:
|
|
314
312
|
...
|
315
313
|
|
316
314
|
schedule_manager = ScheduleManager(
|
317
|
-
schedulers=[
|
318
|
-
TaskScheduler
|
319
|
-
],
|
320
315
|
logstash=logstash,
|
321
316
|
container=builder.container
|
322
317
|
)
|
323
318
|
|
319
|
+
schedule_manager.include_scheduler(TaskScheduler)
|
324
320
|
builder.with_singleton(
|
325
321
|
service=ScheduleManager,
|
326
322
|
instance=schedule_manager,
|
@@ -207,13 +207,11 @@ def main() -> FastAPI:
|
|
207
207
|
...
|
208
208
|
|
209
209
|
rabbitmq = RabbitMqManager(
|
210
|
-
listeners=[
|
211
|
-
UserConsumer
|
212
|
-
],
|
213
210
|
logstash=logstash,
|
214
211
|
container=builder.container,
|
215
212
|
)
|
216
213
|
|
214
|
+
rabbitmq.include_listener(UserConsumer)
|
217
215
|
builder.add_singleton(
|
218
216
|
service=RabbitMqManager,
|
219
217
|
instance=rabbitmq,
|
@@ -297,13 +295,11 @@ def main() -> FastAPI:
|
|
297
295
|
...
|
298
296
|
|
299
297
|
schedule_manager = ScheduleManager(
|
300
|
-
schedulers=[
|
301
|
-
TaskScheduler
|
302
|
-
],
|
303
298
|
logstash=logstash,
|
304
299
|
container=builder.container
|
305
300
|
)
|
306
301
|
|
302
|
+
schedule_manager.include_scheduler(TaskScheduler)
|
307
303
|
builder.with_singleton(
|
308
304
|
service=ScheduleManager,
|
309
305
|
instance=schedule_manager,
|
@@ -94,7 +94,6 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
94
94
|
|
95
95
|
def __init__(
|
96
96
|
self,
|
97
|
-
listeners: list[Listener | type[ListenerBase]],
|
98
97
|
logstash: BaseLogstashSender,
|
99
98
|
parameters: Parameters | str | None = None,
|
100
99
|
reconnect_delay: float = 5.0,
|
@@ -102,24 +101,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
102
101
|
listener_global_retry_policy: RetryPolicy | None = None,
|
103
102
|
container: Container | None = None,
|
104
103
|
):
|
105
|
-
|
106
|
-
if not isinstance(listener, Listener) and (
|
107
|
-
not isinstance(listener, type)
|
108
|
-
or not issubclass(listener, ListenerBase)
|
109
|
-
):
|
110
|
-
raise TypeError(
|
111
|
-
f"listener {index} is {type(listener)}, expected instance of type or subclass of `Listener` or `type[ListenerBase]`"
|
112
|
-
)
|
113
|
-
|
114
|
-
self._listener_classes = [
|
115
|
-
listener_class
|
116
|
-
for listener_class in listeners
|
117
|
-
if isinstance(listener_class, type)
|
118
|
-
and issubclass(listener_class, ListenerBase)
|
119
|
-
]
|
120
|
-
self._listeners = [
|
121
|
-
listener for listener in listeners if isinstance(listener, Listener)
|
122
|
-
]
|
104
|
+
self._listeners: list[Listener] = []
|
123
105
|
|
124
106
|
if isinstance(parameters, str):
|
125
107
|
self._parameters = URLParameters(parameters)
|
@@ -139,8 +121,6 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
139
121
|
type[Exception], ExceptionHandlerContainer
|
140
122
|
] = {}
|
141
123
|
|
142
|
-
self._register_listener_classes()
|
143
|
-
|
144
124
|
self.exception_handler(RabbitMQException)(handle_rabbitmq_exception)
|
145
125
|
self.exception_handler(ValidationError)(handle_validation_error)
|
146
126
|
self.exception_handler(ServiceException)(handle_microservice_exception)
|
@@ -150,26 +130,6 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
150
130
|
self._logstash = logstash
|
151
131
|
self._logger = LoggerProvider.default().get_logger("rabbitmq")
|
152
132
|
|
153
|
-
def _register_listener_classes(self):
|
154
|
-
for listener_class in self._listener_classes:
|
155
|
-
inner_listener = getattr(listener_class, LISTENER_ATTRIBUTE, None)
|
156
|
-
|
157
|
-
if inner_listener is None:
|
158
|
-
raise AttributeError(
|
159
|
-
"listener is possibly not with `Consumer` or `RpcWorker`"
|
160
|
-
)
|
161
|
-
|
162
|
-
if not isinstance(inner_listener, Listener):
|
163
|
-
raise TypeError(
|
164
|
-
f"listener class {type(listener_class)} is not a type `Listener`, posibilly not decorated with `Consumer` or `RpcWorker`"
|
165
|
-
)
|
166
|
-
|
167
|
-
self._container.register(
|
168
|
-
service=ListenerBase,
|
169
|
-
factory=listener_class,
|
170
|
-
scope=Scope.singleton,
|
171
|
-
)
|
172
|
-
|
173
133
|
@property
|
174
134
|
def container(self) -> Container:
|
175
135
|
return self._container
|
@@ -239,6 +199,40 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
239
199
|
|
240
200
|
return True, None
|
241
201
|
|
202
|
+
def include_listener(self, listener: Listener | type[ListenerBase]):
|
203
|
+
if isinstance(listener, Listener):
|
204
|
+
self._listeners.append(listener)
|
205
|
+
|
206
|
+
return
|
207
|
+
|
208
|
+
if isinstance(listener, type) and issubclass(listener, ListenerBase):
|
209
|
+
self._register_listener_classes(listener)
|
210
|
+
|
211
|
+
return
|
212
|
+
|
213
|
+
raise TypeError(
|
214
|
+
f"listener is {type(listener)}, expected instance of type or subclass of `Listener` or `type[ListenerBase]`"
|
215
|
+
)
|
216
|
+
|
217
|
+
def _register_listener_classes(self, listener_class: type):
|
218
|
+
inner_listener = getattr(listener_class, LISTENER_ATTRIBUTE, None)
|
219
|
+
|
220
|
+
if inner_listener is None:
|
221
|
+
raise AttributeError(
|
222
|
+
"listener is possibly not with `Consumer` or `RpcWorker`"
|
223
|
+
)
|
224
|
+
|
225
|
+
if not isinstance(inner_listener, Listener):
|
226
|
+
raise TypeError(
|
227
|
+
f"listener class {type(listener_class)} is not a type `Listener`, posibilly not decorated with `Consumer` or `RpcWorker`"
|
228
|
+
)
|
229
|
+
|
230
|
+
self._container.register(
|
231
|
+
service=ListenerBase,
|
232
|
+
factory=listener_class,
|
233
|
+
scope=Scope.singleton,
|
234
|
+
)
|
235
|
+
|
242
236
|
def include_service(
|
243
237
|
self,
|
244
238
|
rabbit_mq_service: AbstractRabbitMQService
|
@@ -407,9 +401,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
407
401
|
exception = task.exception()
|
408
402
|
|
409
403
|
if exception is not None:
|
410
|
-
if
|
411
|
-
exception, (ConnectionClosedByClient, ChannelClosedByClient)
|
412
|
-
):
|
404
|
+
if self._can_reconnect(exception):
|
413
405
|
self._logstash.error(
|
414
406
|
message="couldn't drain the channel pool",
|
415
407
|
exception=exception,
|
@@ -462,13 +454,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
462
454
|
if exception is not None:
|
463
455
|
if not self._connected and not self._connected_future.done():
|
464
456
|
self._connected_future.set_exception(exception)
|
465
|
-
elif (
|
466
|
-
self._connected
|
467
|
-
and not self._disconnected
|
468
|
-
and not isinstance(
|
469
|
-
exception, (ConnectionClosedByClient, ChannelClosedByClient)
|
470
|
-
)
|
471
|
-
):
|
457
|
+
elif self._can_reconnect(exception):
|
472
458
|
self._logstash.error(
|
473
459
|
message="couldn't fill the channel pool",
|
474
460
|
exception=exception,
|
@@ -538,13 +524,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
538
524
|
if exception is not None:
|
539
525
|
if not self._connected and not self._connected_future.done():
|
540
526
|
self._connected_future.set_exception(exception)
|
541
|
-
elif (
|
542
|
-
self._connected
|
543
|
-
and not self._disconnected
|
544
|
-
and not isinstance(
|
545
|
-
exception, (ConnectionClosedByClient, ChannelClosedByClient)
|
546
|
-
)
|
547
|
-
):
|
527
|
+
elif self._can_reconnect(exception):
|
548
528
|
self._logstash.error(
|
549
529
|
message="couldn't configure and initialize all listeners and services",
|
550
530
|
exception=exception,
|
@@ -671,13 +651,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
671
651
|
|
672
652
|
return
|
673
653
|
|
674
|
-
if (
|
675
|
-
self._connected
|
676
|
-
and not self._disconnected
|
677
|
-
and not isinstance(
|
678
|
-
exception, (ConnectionClosedByClient, ChannelClosedByClient)
|
679
|
-
)
|
680
|
-
):
|
654
|
+
if self._can_reconnect(exception):
|
681
655
|
self._logstash.error(
|
682
656
|
message="connection to rabbitmq closed unexpectedly, attempting to reconnect",
|
683
657
|
exception=exception,
|
@@ -696,13 +670,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
696
670
|
try:
|
697
671
|
connected_future = self.connect()
|
698
672
|
except Exception as e:
|
699
|
-
if (
|
700
|
-
self._connected
|
701
|
-
and not self._disconnected
|
702
|
-
and not isinstance(
|
703
|
-
e, (ConnectionClosedByClient, ChannelClosedByClient)
|
704
|
-
)
|
705
|
-
):
|
673
|
+
if self._can_reconnect(e):
|
706
674
|
if not self._connected_future.done():
|
707
675
|
self._connected_future.set_result(None)
|
708
676
|
|
@@ -727,15 +695,18 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
727
695
|
if exception is None:
|
728
696
|
return
|
729
697
|
|
730
|
-
if (
|
731
|
-
self._connected
|
732
|
-
and not self._disconnected
|
733
|
-
and not isinstance(
|
734
|
-
exception, (ConnectionClosedByClient, ChannelClosedByClient)
|
735
|
-
)
|
736
|
-
):
|
698
|
+
if self._can_reconnect(exception):
|
737
699
|
self._logstash.error(
|
738
700
|
message="couldn't reconnect to rabbitmq, attempting to reconnect",
|
739
701
|
exception=exception,
|
740
702
|
)
|
741
703
|
self._reconnect()
|
704
|
+
|
705
|
+
def _can_reconnect(self, exception: BaseException) -> bool:
|
706
|
+
return (
|
707
|
+
self._connected
|
708
|
+
and not self._disconnected
|
709
|
+
and not isinstance(
|
710
|
+
exception, (ConnectionClosedByClient, ChannelClosedByClient)
|
711
|
+
)
|
712
|
+
)
|
@@ -181,33 +181,34 @@ class ScheduleManager(AsyncEventLoopMixin):
|
|
181
181
|
|
182
182
|
def __init__(
|
183
183
|
self,
|
184
|
-
schedulers: list[Scheduler | type[SchedulerBase]],
|
185
184
|
logstash: BaseLogstashSender,
|
186
185
|
container: Container | None = None,
|
187
186
|
):
|
188
187
|
self._container = container or Container()
|
189
188
|
self._logstash = logstash
|
190
189
|
self._scheduled_tasks: list[ScheduledTask] = []
|
191
|
-
|
192
|
-
for index, scheduler in enumerate(schedulers):
|
193
|
-
if isinstance(scheduler, Scheduler):
|
194
|
-
self._scheduled_tasks.extend(scheduler.scheduled_tasks)
|
195
|
-
elif isinstance(scheduler, type) and issubclass(
|
196
|
-
scheduler, SchedulerBase
|
197
|
-
):
|
198
|
-
self._container.register(
|
199
|
-
service=SchedulerBase,
|
200
|
-
factory=scheduler,
|
201
|
-
scope=Scope.singleton,
|
202
|
-
)
|
203
|
-
else:
|
204
|
-
raise TypeError(
|
205
|
-
f"scheduler {index} is {type(scheduler)}, expected instance of type or subclass of `Scheduler` or `type[SchedulerBase]`"
|
206
|
-
)
|
207
|
-
|
208
190
|
self._next_run_in = None
|
209
191
|
self._logger = LoggerProvider.default().get_logger("schedule_manager")
|
210
192
|
|
193
|
+
def include_scheduler(self, scheduler: Scheduler | type[SchedulerBase]):
|
194
|
+
if isinstance(scheduler, Scheduler):
|
195
|
+
self._scheduled_tasks.extend(scheduler.scheduled_tasks)
|
196
|
+
|
197
|
+
return
|
198
|
+
|
199
|
+
if isinstance(scheduler, type) and issubclass(scheduler, SchedulerBase):
|
200
|
+
self._container.register(
|
201
|
+
service=SchedulerBase,
|
202
|
+
factory=scheduler,
|
203
|
+
scope=Scope.singleton,
|
204
|
+
)
|
205
|
+
|
206
|
+
return
|
207
|
+
|
208
|
+
raise TypeError(
|
209
|
+
f"scheduler is {type(scheduler)}, expected instance of type or subclass of `Scheduler` or `type[SchedulerBase]`"
|
210
|
+
)
|
211
|
+
|
211
212
|
@property
|
212
213
|
def container(self) -> Container:
|
213
214
|
return self._container
|
@@ -230,7 +231,7 @@ class ScheduleManager(AsyncEventLoopMixin):
|
|
230
231
|
getattr(self, "_scheduler_task", None) is not None
|
231
232
|
and not self._scheduler_task.done()
|
232
233
|
):
|
233
|
-
|
234
|
+
raise RuntimeError("scheduler already running")
|
234
235
|
|
235
236
|
self.use_schedulers()
|
236
237
|
self._logger.info(
|
@@ -26,14 +26,14 @@ __all__ = [
|
|
26
26
|
]
|
27
27
|
|
28
28
|
|
29
|
-
MESSAGE = "you are not authorized to access
|
29
|
+
MESSAGE = "you are not authorized to access requested resouce"
|
30
30
|
RESPONSE_CODE = int(environ.get("UNAUTHORIZED_RESPONSE_CODE") or 0)
|
31
31
|
|
32
32
|
|
33
33
|
class PasswordHasher(AsyncEventLoopMixin):
|
34
|
-
def __init__(self):
|
34
|
+
def __init__(self, schemes=None):
|
35
35
|
self._crypt_context = CryptContext(
|
36
|
-
schemes=["bcrypt"], deprecated="auto"
|
36
|
+
schemes=schemes or ["bcrypt"], deprecated="auto"
|
37
37
|
)
|
38
38
|
|
39
39
|
def hash(self, password: str) -> Future[str]:
|