qena-shared-lib 0.1.1__tar.gz → 0.1.3__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.1 → qena_shared_lib-0.1.3}/PKG-INFO +43 -7
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/README.md +42 -6
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/pyproject.toml +1 -1
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/rabbitmq/_base.py +50 -79
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/rabbitmq/_exception_handlers.py +1 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/scheduler.py +23 -23
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/security.py +11 -5
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/tests/test_rabbitmq.py +67 -82
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/tests/test_scheduler.py +8 -9
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/tests/test_security.py +45 -45
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/uv.lock +1 -1
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/.gitignore +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/.pre-commit-config.yaml +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/requirements.txt +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/__init__.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/application.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/background.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/dependencies/__init__.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/dependencies/http.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/dependencies/miscellaneous.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/exception_handlers.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/exceptions.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/http.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/logging.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/logstash/__init__.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/logstash/_base.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/logstash/_http_sender.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/logstash/_tcp_sender.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/py.typed +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/rabbitmq/__init__.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/rabbitmq/_channel.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/rabbitmq/_exceptions.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/rabbitmq/_listener.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/rabbitmq/_pool.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/rabbitmq/_publisher.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/rabbitmq/_rpc_client.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/rabbitmq/_utils.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/src/qena_shared_lib/utils.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/tests/conftest.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/tests/test_application.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/tests/test_background.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/tests/test_dependencies.py +0 -0
- {qena_shared_lib-0.1.1 → qena_shared_lib-0.1.3}/tests/test_logstash.py +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.3
|
4
4
|
Summary: A shared tools for other services
|
5
5
|
Requires-Python: >=3.10
|
6
6
|
Requires-Dist: cronsim~=2.0
|
@@ -30,6 +30,12 @@ A shared tools for other services. It includes.
|
|
30
30
|
|
31
31
|
# Usage
|
32
32
|
|
33
|
+
## Environment variables
|
34
|
+
|
35
|
+
- `LOGGER_NAME` root logger name.
|
36
|
+
- `UNAUTHORIZED_RESPONSE_CODE` an integer response on an authorized access of resource.
|
37
|
+
- `TOKEN_HEADER` to header key for jwt token.
|
38
|
+
|
33
39
|
## Http
|
34
40
|
|
35
41
|
To create fastapi app.
|
@@ -224,13 +230,11 @@ def main() -> FastAPI:
|
|
224
230
|
...
|
225
231
|
|
226
232
|
rabbitmq = RabbitMqManager(
|
227
|
-
listeners=[
|
228
|
-
UserConsumer
|
229
|
-
],
|
230
233
|
logstash=logstash,
|
231
234
|
container=builder.container,
|
232
235
|
)
|
233
236
|
|
237
|
+
rabbitmq.include_listener(UserConsumer)
|
234
238
|
builder.add_singleton(
|
235
239
|
service=RabbitMqManager,
|
236
240
|
instance=rabbitmq,
|
@@ -275,6 +279,40 @@ async def get_user(
|
|
275
279
|
return user
|
276
280
|
```
|
277
281
|
|
282
|
+
### Flow control
|
283
|
+
|
284
|
+
``` py
|
285
|
+
@Consumer("UserQueue")
|
286
|
+
class UserConsumer(ListenerBase):
|
287
|
+
|
288
|
+
@consume()
|
289
|
+
async def store_user(self, ctx: ListenerContext, user: User):
|
290
|
+
...
|
291
|
+
|
292
|
+
await ctx.flow_control.request(10)
|
293
|
+
|
294
|
+
...
|
295
|
+
|
296
|
+
```
|
297
|
+
|
298
|
+
### Rpc reply
|
299
|
+
|
300
|
+
Optionally it is possible to reply to rpc calls, through.
|
301
|
+
|
302
|
+
``` py
|
303
|
+
@RpcWorker("UserQueue")
|
304
|
+
class UserWorker(ListenerBase):
|
305
|
+
|
306
|
+
@execute()
|
307
|
+
async def store_user(self, ctx: ListenerContext, user: User):
|
308
|
+
...
|
309
|
+
|
310
|
+
await ctx.rpc_reply.reply("Done")
|
311
|
+
|
312
|
+
...
|
313
|
+
```
|
314
|
+
|
315
|
+
|
278
316
|
## Scheduler
|
279
317
|
|
280
318
|
``` py
|
@@ -314,13 +352,11 @@ def main() -> FastAPI:
|
|
314
352
|
...
|
315
353
|
|
316
354
|
schedule_manager = ScheduleManager(
|
317
|
-
schedulers=[
|
318
|
-
TaskScheduler
|
319
|
-
],
|
320
355
|
logstash=logstash,
|
321
356
|
container=builder.container
|
322
357
|
)
|
323
358
|
|
359
|
+
schedule_manager.include_scheduler(TaskScheduler)
|
324
360
|
builder.with_singleton(
|
325
361
|
service=ScheduleManager,
|
326
362
|
instance=schedule_manager,
|
@@ -13,6 +13,12 @@ A shared tools for other services. It includes.
|
|
13
13
|
|
14
14
|
# Usage
|
15
15
|
|
16
|
+
## Environment variables
|
17
|
+
|
18
|
+
- `LOGGER_NAME` root logger name.
|
19
|
+
- `UNAUTHORIZED_RESPONSE_CODE` an integer response on an authorized access of resource.
|
20
|
+
- `TOKEN_HEADER` to header key for jwt token.
|
21
|
+
|
16
22
|
## Http
|
17
23
|
|
18
24
|
To create fastapi app.
|
@@ -207,13 +213,11 @@ def main() -> FastAPI:
|
|
207
213
|
...
|
208
214
|
|
209
215
|
rabbitmq = RabbitMqManager(
|
210
|
-
listeners=[
|
211
|
-
UserConsumer
|
212
|
-
],
|
213
216
|
logstash=logstash,
|
214
217
|
container=builder.container,
|
215
218
|
)
|
216
219
|
|
220
|
+
rabbitmq.include_listener(UserConsumer)
|
217
221
|
builder.add_singleton(
|
218
222
|
service=RabbitMqManager,
|
219
223
|
instance=rabbitmq,
|
@@ -258,6 +262,40 @@ async def get_user(
|
|
258
262
|
return user
|
259
263
|
```
|
260
264
|
|
265
|
+
### Flow control
|
266
|
+
|
267
|
+
``` py
|
268
|
+
@Consumer("UserQueue")
|
269
|
+
class UserConsumer(ListenerBase):
|
270
|
+
|
271
|
+
@consume()
|
272
|
+
async def store_user(self, ctx: ListenerContext, user: User):
|
273
|
+
...
|
274
|
+
|
275
|
+
await ctx.flow_control.request(10)
|
276
|
+
|
277
|
+
...
|
278
|
+
|
279
|
+
```
|
280
|
+
|
281
|
+
### Rpc reply
|
282
|
+
|
283
|
+
Optionally it is possible to reply to rpc calls, through.
|
284
|
+
|
285
|
+
``` py
|
286
|
+
@RpcWorker("UserQueue")
|
287
|
+
class UserWorker(ListenerBase):
|
288
|
+
|
289
|
+
@execute()
|
290
|
+
async def store_user(self, ctx: ListenerContext, user: User):
|
291
|
+
...
|
292
|
+
|
293
|
+
await ctx.rpc_reply.reply("Done")
|
294
|
+
|
295
|
+
...
|
296
|
+
```
|
297
|
+
|
298
|
+
|
261
299
|
## Scheduler
|
262
300
|
|
263
301
|
``` py
|
@@ -297,13 +335,11 @@ def main() -> FastAPI:
|
|
297
335
|
...
|
298
336
|
|
299
337
|
schedule_manager = ScheduleManager(
|
300
|
-
schedulers=[
|
301
|
-
TaskScheduler
|
302
|
-
],
|
303
338
|
logstash=logstash,
|
304
339
|
container=builder.container
|
305
340
|
)
|
306
341
|
|
342
|
+
schedule_manager.include_scheduler(TaskScheduler)
|
307
343
|
builder.with_singleton(
|
308
344
|
service=ScheduleManager,
|
309
345
|
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
|
@@ -226,10 +227,7 @@ class ScheduleManager(AsyncEventLoopMixin):
|
|
226
227
|
if not self._aquired_lock():
|
227
228
|
return
|
228
229
|
|
229
|
-
if (
|
230
|
-
getattr(self, "_scheduler_task", None) is not None
|
231
|
-
and not self._scheduler_task.done()
|
232
|
-
):
|
230
|
+
if self._scheduler_task is not None and not self._scheduler_task.done():
|
233
231
|
raise RuntimeError("scheduler already running")
|
234
232
|
|
235
233
|
self.use_schedulers()
|
@@ -239,6 +237,8 @@ class ScheduleManager(AsyncEventLoopMixin):
|
|
239
237
|
"tasks" if self.scheduled_task_count > 1 else "task",
|
240
238
|
)
|
241
239
|
|
240
|
+
self._scheduler_task = None
|
241
|
+
|
242
242
|
if self.scheduled_task_count == 0:
|
243
243
|
return
|
244
244
|
|
@@ -255,7 +255,7 @@ class ScheduleManager(AsyncEventLoopMixin):
|
|
255
255
|
self._scheduled_tasks.extend(scheduler.scheduled_tasks)
|
256
256
|
|
257
257
|
def stop(self):
|
258
|
-
if not self._scheduler_task.done():
|
258
|
+
if self._scheduler_task is not None and not self._scheduler_task.done():
|
259
259
|
self._scheduler_task.cancel()
|
260
260
|
|
261
261
|
self.SCHEDULE_MANAGER_STATE.state("stopped")
|
@@ -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 resource"
|
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]:
|
@@ -92,9 +92,15 @@ class UserInfo(BaseModel):
|
|
92
92
|
async def extract_user_info(
|
93
93
|
jwt_adapter: Annotated[JwtAdapter, DependsOn(JwtAdapter)],
|
94
94
|
token: Annotated[
|
95
|
-
str | None,
|
95
|
+
str | None,
|
96
|
+
Header(
|
97
|
+
alias=environ.get("TOKEN_HEADER") or "authorization",
|
98
|
+
include_in_schema=False,
|
99
|
+
),
|
100
|
+
] = None,
|
101
|
+
user_agent: Annotated[
|
102
|
+
str | None, Header(alias="user-agent", include_in_schema=False)
|
96
103
|
] = None,
|
97
|
-
user_agent: Annotated[str | None, Header(alias="user-agent")] = None,
|
98
104
|
) -> UserInfo:
|
99
105
|
extra = {"userAgent": user_agent} if user_agent is not None else None
|
100
106
|
|