qena-shared-lib 0.1.12__py3-none-any.whl → 0.1.13__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 +2 -2
- qena_shared_lib/application.py +71 -29
- qena_shared_lib/background.py +6 -5
- qena_shared_lib/exception_handlers.py +203 -175
- qena_shared_lib/exceptions.py +10 -10
- qena_shared_lib/http.py +16 -16
- qena_shared_lib/rabbitmq/__init__.py +8 -6
- qena_shared_lib/rabbitmq/_base.py +104 -124
- qena_shared_lib/rabbitmq/_channel.py +4 -1
- qena_shared_lib/rabbitmq/_exception_handlers.py +154 -119
- qena_shared_lib/rabbitmq/_listener.py +94 -38
- qena_shared_lib/rabbitmq/_rpc_client.py +4 -4
- qena_shared_lib/remotelogging/__init__.py +15 -0
- qena_shared_lib/{logstash → remotelogging}/_base.py +47 -67
- qena_shared_lib/remotelogging/logstash/__init__.py +9 -0
- qena_shared_lib/remotelogging/logstash/_base.py +32 -0
- qena_shared_lib/{logstash → remotelogging/logstash}/_http_sender.py +5 -4
- qena_shared_lib/{logstash → remotelogging/logstash}/_tcp_sender.py +7 -5
- qena_shared_lib/scheduler.py +49 -24
- qena_shared_lib/security.py +2 -2
- qena_shared_lib/utils.py +9 -3
- {qena_shared_lib-0.1.12.dist-info → qena_shared_lib-0.1.13.dist-info}/METADATA +23 -20
- qena_shared_lib-0.1.13.dist-info/RECORD +31 -0
- qena_shared_lib/logstash/__init__.py +0 -17
- qena_shared_lib-0.1.12.dist-info/RECORD +0 -29
- {qena_shared_lib-0.1.12.dist-info → qena_shared_lib-0.1.13.dist-info}/WHEEL +0 -0
@@ -17,9 +17,9 @@ from ..logging import LoggerProvider
|
|
17
17
|
from ..utils import AsyncEventLoopMixin
|
18
18
|
|
19
19
|
__all__ = [
|
20
|
-
"
|
20
|
+
"BaseRemoteLogSender",
|
21
21
|
"LogLevel",
|
22
|
-
"
|
22
|
+
"RemoteLogRecord",
|
23
23
|
]
|
24
24
|
|
25
25
|
|
@@ -30,7 +30,7 @@ class LogLevel(Enum):
|
|
30
30
|
ERROR = 3
|
31
31
|
|
32
32
|
|
33
|
-
class
|
33
|
+
class RemoteLogRecord:
|
34
34
|
def __init__(
|
35
35
|
self,
|
36
36
|
message: str,
|
@@ -139,8 +139,9 @@ class LogstashLogRecord:
|
|
139
139
|
|
140
140
|
def __repr__(self) -> str:
|
141
141
|
return (
|
142
|
-
"
|
142
|
+
"%s (\n\tlevel : `%s`,\n\tmessage : `%s`,\n\ttags : %s,\n\tlabel : %s,\n\terror_type : `%s`,\n\terror_message: `%s`\n)%s"
|
143
143
|
% (
|
144
|
+
self.__class__.__name__,
|
144
145
|
self._log_level.name,
|
145
146
|
self._message,
|
146
147
|
self._tags or [],
|
@@ -161,31 +162,6 @@ class LogstashLogRecord:
|
|
161
162
|
def log_retries(self, log_retries: int) -> None:
|
162
163
|
self._log_retries = log_retries
|
163
164
|
|
164
|
-
def to_dict(self) -> dict:
|
165
|
-
log: dict[str, str | list | dict[str, str]] = {
|
166
|
-
"message": self._message,
|
167
|
-
"service.name": self._service_name,
|
168
|
-
"log.level": self._log_level.name.lower(),
|
169
|
-
"log.logger": self._log_logger,
|
170
|
-
}
|
171
|
-
|
172
|
-
if self._tags is not None:
|
173
|
-
log["tags"] = self._tags
|
174
|
-
|
175
|
-
if self._labels is not None:
|
176
|
-
log["labels"] = self._labels
|
177
|
-
|
178
|
-
if self._error_type is not None:
|
179
|
-
log["error.type"] = self._error_type
|
180
|
-
|
181
|
-
if self._error_message is not None:
|
182
|
-
log["error.message"] = self._error_message
|
183
|
-
|
184
|
-
if self._error_stack_trace is not None:
|
185
|
-
log["error.stack_trace"] = self._error_stack_trace
|
186
|
-
|
187
|
-
return log
|
188
|
-
|
189
165
|
|
190
166
|
@dataclass
|
191
167
|
class SenderResponse:
|
@@ -198,20 +174,20 @@ class EndOfLogMarker:
|
|
198
174
|
pass
|
199
175
|
|
200
176
|
|
201
|
-
class
|
202
|
-
|
203
|
-
name="
|
204
|
-
documentation="Successfully sent
|
177
|
+
class BaseRemoteLogSender(AsyncEventLoopMixin):
|
178
|
+
REMOTE_LOGS = Counter(
|
179
|
+
name="successful_remote_logs",
|
180
|
+
documentation="Successfully sent remote log count",
|
205
181
|
labelnames=["log_level"],
|
206
182
|
)
|
207
|
-
|
208
|
-
name="
|
209
|
-
documentation="Failed
|
183
|
+
FAILED_REMOTE_LOGS = Counter(
|
184
|
+
name="failed_remote_logs",
|
185
|
+
documentation="Failed remote log count",
|
210
186
|
labelnames=["log_level", "exception"],
|
211
187
|
)
|
212
|
-
|
213
|
-
name="
|
214
|
-
documentation="
|
188
|
+
REMOTE_SENDER_STATE = PrometheusEnum(
|
189
|
+
name="remote_sender_state",
|
190
|
+
documentation="Remote sender state",
|
215
191
|
states=["running", "stopped"],
|
216
192
|
)
|
217
193
|
|
@@ -222,25 +198,27 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
222
198
|
log_queue_size: int = 1000,
|
223
199
|
failed_log_queue_size: int = 1000,
|
224
200
|
) -> None:
|
225
|
-
self._sender =
|
201
|
+
self._sender = (
|
202
|
+
f"qena_shared_lib.remotelogging.{self.__class__.__name__}"
|
203
|
+
)
|
226
204
|
self._service_name = service_name
|
227
205
|
self._max_log_retry = max_log_retry
|
228
206
|
self._started = False
|
229
207
|
self._closed = False
|
230
|
-
self._log_queue: Queue[
|
208
|
+
self._log_queue: Queue[RemoteLogRecord | EndOfLogMarker] = Queue(
|
231
209
|
log_queue_size
|
232
210
|
)
|
233
|
-
self._dead_letter_log_queue: Queue[
|
234
|
-
|
235
|
-
|
211
|
+
self._dead_letter_log_queue: Queue[RemoteLogRecord | EndOfLogMarker] = (
|
212
|
+
Queue(failed_log_queue_size)
|
213
|
+
)
|
236
214
|
self._level = LogLevel.INFO
|
237
215
|
self._logger = LoggerProvider.default().get_logger(
|
238
|
-
f"
|
216
|
+
f"remotelogging.{self.__class__.__name__.lower()}"
|
239
217
|
)
|
240
218
|
|
241
219
|
async def start(self) -> None:
|
242
220
|
if self._started:
|
243
|
-
raise RuntimeError("
|
221
|
+
raise RuntimeError("remote sender already started")
|
244
222
|
|
245
223
|
self._started = True
|
246
224
|
self._closed = False
|
@@ -254,10 +232,10 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
254
232
|
self._on_log_flusher_closed
|
255
233
|
)
|
256
234
|
self._logger.info(
|
257
|
-
"
|
235
|
+
"remote logger `%s` started accepting logs",
|
258
236
|
self.__class__.__name__,
|
259
237
|
)
|
260
|
-
self.
|
238
|
+
self.REMOTE_SENDER_STATE.state("running")
|
261
239
|
|
262
240
|
def _hook_on_start(self) -> None:
|
263
241
|
pass
|
@@ -265,7 +243,7 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
265
243
|
async def _hook_on_start_async(self) -> None:
|
266
244
|
pass
|
267
245
|
|
268
|
-
def _on_log_flusher_closed(self, task: Task) -> None:
|
246
|
+
def _on_log_flusher_closed(self, task: Task[None]) -> None:
|
269
247
|
if task.cancelled():
|
270
248
|
self._close_future.set_result(None)
|
271
249
|
|
@@ -283,13 +261,13 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
283
261
|
self._hook_on_stop_async(),
|
284
262
|
).add_done_callback(self._on_close_hook_done)
|
285
263
|
|
286
|
-
def _on_close_hook_done(self,
|
287
|
-
if
|
264
|
+
def _on_close_hook_done(self, future: Future[tuple[None, None]]) -> None:
|
265
|
+
if future.cancelled():
|
288
266
|
self._close_future.set_result(None)
|
289
267
|
|
290
268
|
return
|
291
269
|
|
292
|
-
exception =
|
270
|
+
exception = future.exception()
|
293
271
|
|
294
272
|
if exception is not None:
|
295
273
|
self._close_future.set_exception(exception)
|
@@ -298,12 +276,12 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
298
276
|
|
299
277
|
self._close_future.set_result(None)
|
300
278
|
self._logger.debug(
|
301
|
-
"
|
279
|
+
"remote http logger closed, will no longer accept logs"
|
302
280
|
)
|
303
281
|
|
304
|
-
def stop(self) -> Future:
|
282
|
+
def stop(self) -> Future[None]:
|
305
283
|
if self._closed:
|
306
|
-
raise RuntimeError("
|
284
|
+
raise RuntimeError("remote sender already closed")
|
307
285
|
|
308
286
|
self._closed = True
|
309
287
|
self._started = False
|
@@ -318,10 +296,10 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
318
296
|
|
319
297
|
return self._close_future
|
320
298
|
|
321
|
-
def _on_close_future_done(self, future: Future) -> None:
|
299
|
+
def _on_close_future_done(self, future: Future[None]) -> None:
|
322
300
|
del future
|
323
301
|
|
324
|
-
self.
|
302
|
+
self.REMOTE_SENDER_STATE.state("stopped")
|
325
303
|
|
326
304
|
def _hook_on_stop(self) -> None:
|
327
305
|
pass
|
@@ -340,7 +318,7 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
340
318
|
if not self._dead_letter_log_queue.empty():
|
341
319
|
log = await self._dead_letter_log_queue.get()
|
342
320
|
|
343
|
-
if isinstance(log,
|
321
|
+
if isinstance(log, RemoteLogRecord):
|
344
322
|
if log.log_retries >= self._max_log_retry:
|
345
323
|
self._logger.exception(
|
346
324
|
"failed to send log too many times, falling back to stdout or stderr. \n%r",
|
@@ -368,9 +346,9 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
368
346
|
except Exception as e:
|
369
347
|
self._put_to_dead_letter_log_queue(log)
|
370
348
|
self._logger.exception(
|
371
|
-
"error occurred while sending log to
|
349
|
+
"error occurred while sending log to remote logging facility"
|
372
350
|
)
|
373
|
-
self.
|
351
|
+
self.FAILED_REMOTE_LOGS.labels(
|
374
352
|
log_level=log.log_level.name, exception=e.__class__.__name__
|
375
353
|
).inc()
|
376
354
|
|
@@ -393,15 +371,17 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
393
371
|
sender_response.reason or "No reason",
|
394
372
|
)
|
395
373
|
else:
|
396
|
-
self.
|
397
|
-
self._logger.debug(
|
374
|
+
self.REMOTE_LOGS.labels(log_level=log.log_level.name).inc()
|
375
|
+
self._logger.debug(
|
376
|
+
"log sent to remote logging facility.\n%r", log
|
377
|
+
)
|
398
378
|
|
399
|
-
async def _send(self, log:
|
379
|
+
async def _send(self, log: RemoteLogRecord) -> SenderResponse:
|
400
380
|
del log
|
401
381
|
|
402
382
|
raise NotImplementedError()
|
403
383
|
|
404
|
-
def _put_to_dead_letter_log_queue(self, log:
|
384
|
+
def _put_to_dead_letter_log_queue(self, log: RemoteLogRecord) -> None:
|
405
385
|
if self._closed:
|
406
386
|
self._logger.error(
|
407
387
|
"%s logger closed, falling back to stdout or stderr.\n%r",
|
@@ -524,7 +504,7 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
524
504
|
exception: BaseException | None = None,
|
525
505
|
) -> None:
|
526
506
|
if self._closed:
|
527
|
-
self._logger.warning("
|
507
|
+
self._logger.warning("Remote logger is already close")
|
528
508
|
|
529
509
|
return
|
530
510
|
|
@@ -551,8 +531,8 @@ class BaseLogstashSender(AsyncEventLoopMixin):
|
|
551
531
|
tags: list[str] | None = None,
|
552
532
|
extra: dict[str, str] | None = None,
|
553
533
|
exception: BaseException | None = None,
|
554
|
-
) ->
|
555
|
-
log =
|
534
|
+
) -> RemoteLogRecord:
|
535
|
+
log = RemoteLogRecord(
|
556
536
|
message=message,
|
557
537
|
service_name=self._service_name,
|
558
538
|
log_level=level,
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from .._base import BaseRemoteLogSender, RemoteLogRecord
|
4
|
+
|
5
|
+
|
6
|
+
class BaseLogstashSender(BaseRemoteLogSender):
|
7
|
+
def remote_log_record_to_ecs(self, log: RemoteLogRecord) -> dict[str, Any]:
|
8
|
+
log_dict: dict[str, Any] = {
|
9
|
+
"message": log.message,
|
10
|
+
"service.name": log.service_name,
|
11
|
+
"log.level": log.log_level.name.lower(),
|
12
|
+
"log.logger": log.log_logger,
|
13
|
+
}
|
14
|
+
|
15
|
+
if log.tags is not None:
|
16
|
+
log_dict["tags"] = log.tags
|
17
|
+
|
18
|
+
if log.labels is not None:
|
19
|
+
log_dict["labels"] = log.labels
|
20
|
+
|
21
|
+
error_type, error_message, error_stack_trace = log.error
|
22
|
+
|
23
|
+
if error_type is not None:
|
24
|
+
log_dict["error.type"] = error_type
|
25
|
+
|
26
|
+
if error_message is not None:
|
27
|
+
log_dict["error.message"] = error_message
|
28
|
+
|
29
|
+
if error_stack_trace is not None:
|
30
|
+
log_dict["error.stack_trace"] = error_stack_trace
|
31
|
+
|
32
|
+
return log_dict
|
@@ -1,7 +1,8 @@
|
|
1
1
|
from httpx import AsyncClient, Timeout
|
2
2
|
|
3
|
-
from
|
4
|
-
from
|
3
|
+
from ...logging import LoggerProvider
|
4
|
+
from .._base import RemoteLogRecord, SenderResponse
|
5
|
+
from ._base import BaseLogstashSender
|
5
6
|
|
6
7
|
__all__ = ["HTTPSender"]
|
7
8
|
|
@@ -42,10 +43,10 @@ class HTTPSender(BaseLogstashSender):
|
|
42
43
|
"logstash.httpsender"
|
43
44
|
)
|
44
45
|
|
45
|
-
async def _send(self, log:
|
46
|
+
async def _send(self, log: RemoteLogRecord) -> SenderResponse:
|
46
47
|
send_log_response = await self._client.post(
|
47
48
|
url=self._url,
|
48
|
-
json=
|
49
|
+
json=self.remote_log_record_to_ecs(log),
|
49
50
|
)
|
50
51
|
|
51
52
|
if not send_log_response.is_success:
|
@@ -1,9 +1,11 @@
|
|
1
1
|
from asyncio import StreamWriter, open_connection
|
2
|
+
from typing import Any
|
2
3
|
|
3
4
|
from pydantic_core import to_json
|
4
5
|
|
5
|
-
from
|
6
|
-
from
|
6
|
+
from ...logging import LoggerProvider
|
7
|
+
from .._base import RemoteLogRecord, SenderResponse
|
8
|
+
from ._base import BaseLogstashSender
|
7
9
|
|
8
10
|
__all__ = ["TCPSender"]
|
9
11
|
|
@@ -28,8 +30,8 @@ class TCPSender(BaseLogstashSender):
|
|
28
30
|
self._client = AsyncTcpClient(host=host, port=port)
|
29
31
|
self._logger = LoggerProvider.default().get_logger("logstash.tcpsender")
|
30
32
|
|
31
|
-
async def _send(self, log:
|
32
|
-
await self._client.write(
|
33
|
+
async def _send(self, log: RemoteLogRecord) -> SenderResponse:
|
34
|
+
await self._client.write(self.remote_log_record_to_ecs(log))
|
33
35
|
|
34
36
|
return SenderResponse(sent=True)
|
35
37
|
|
@@ -47,7 +49,7 @@ class AsyncTcpClient:
|
|
47
49
|
self._writer: StreamWriter | None = None
|
48
50
|
self._client_closed = False
|
49
51
|
|
50
|
-
async def write(self, json: dict) -> None:
|
52
|
+
async def write(self, json: dict[str, Any]) -> None:
|
51
53
|
if self._client_closed:
|
52
54
|
raise RuntimeError("async tcp client already closed")
|
53
55
|
|
qena_shared_lib/scheduler.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
from asyncio import Future, Task, iscoroutinefunction, sleep
|
1
|
+
from asyncio import Future, Task, gather, iscoroutinefunction, sleep
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from datetime import datetime
|
4
4
|
from functools import partial
|
5
5
|
from importlib import import_module
|
6
6
|
from inspect import signature
|
7
7
|
from os import name as osname
|
8
|
-
from typing import Any, Callable, TypeVar
|
8
|
+
from typing import Any, Callable, TypeVar, cast
|
9
9
|
from zoneinfo import ZoneInfo
|
10
10
|
|
11
11
|
from cronsim import CronSim
|
@@ -14,7 +14,7 @@ from punq import Container, Scope
|
|
14
14
|
|
15
15
|
from .dependencies.miscellaneous import validate_annotation
|
16
16
|
from .logging import LoggerProvider
|
17
|
-
from .
|
17
|
+
from .remotelogging import BaseRemoteLogSender
|
18
18
|
from .utils import AsyncEventLoopMixin
|
19
19
|
|
20
20
|
__all__ = [
|
@@ -32,7 +32,7 @@ SCHEDULED_TASK_ATTRIBUTE = "__scheduled_task__"
|
|
32
32
|
|
33
33
|
@dataclass
|
34
34
|
class ScheduledTask:
|
35
|
-
task: Callable
|
35
|
+
task: Callable[..., None]
|
36
36
|
cron_expression: str
|
37
37
|
zone_info: ZoneInfo | None
|
38
38
|
|
@@ -78,8 +78,8 @@ class Scheduler:
|
|
78
78
|
|
79
79
|
def schedule(
|
80
80
|
self, cron_expression: str, timezone: str | None = None
|
81
|
-
) -> Callable[[Callable], Callable]:
|
82
|
-
def wrapper(task: Callable) -> Callable:
|
81
|
+
) -> Callable[[Callable[..., None]], Callable[..., None]]:
|
82
|
+
def wrapper(task: Callable[..., Any]) -> Callable[..., None]:
|
83
83
|
self.add_task(
|
84
84
|
task=task, cron_expression=cron_expression, timezone=timezone
|
85
85
|
)
|
@@ -89,7 +89,10 @@ class Scheduler:
|
|
89
89
|
return wrapper
|
90
90
|
|
91
91
|
def add_task(
|
92
|
-
self,
|
92
|
+
self,
|
93
|
+
task: Callable[..., Any],
|
94
|
+
cron_expression: str,
|
95
|
+
timezone: str | None,
|
93
96
|
) -> None:
|
94
97
|
self._scheduled_tasks.append(
|
95
98
|
ScheduledTask(
|
@@ -121,8 +124,8 @@ class ScheduledTaskMeta:
|
|
121
124
|
|
122
125
|
def schedule(
|
123
126
|
cron_expression: str, *, timezone: str | None = None
|
124
|
-
) -> Callable[[Callable], Callable]:
|
125
|
-
def wrapper(task: Callable) -> Callable:
|
127
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
128
|
+
def wrapper(task: Callable[..., Any]) -> Callable[..., Any]:
|
126
129
|
setattr(
|
127
130
|
task,
|
128
131
|
SCHEDULED_TASK_ATTRIBUTE,
|
@@ -145,7 +148,7 @@ class SchedulerBase:
|
|
145
148
|
f"{self.__class__.__name__} not a scheduler, possibly no annotated with either `Scheduler`"
|
146
149
|
)
|
147
150
|
|
148
|
-
return scheduler
|
151
|
+
return cast(Scheduler, scheduler)
|
149
152
|
|
150
153
|
def register_scheduled_tasks(self) -> Scheduler:
|
151
154
|
scheduler = self.get_scheduler()
|
@@ -186,14 +189,16 @@ class ScheduleManager(AsyncEventLoopMixin):
|
|
186
189
|
|
187
190
|
def __init__(
|
188
191
|
self,
|
189
|
-
|
192
|
+
remote_logger: BaseRemoteLogSender,
|
190
193
|
container: Container | None = None,
|
191
194
|
) -> None:
|
192
195
|
self._container = container or Container()
|
193
|
-
self.
|
196
|
+
self._remote_logger = remote_logger
|
194
197
|
self._scheduled_tasks: list[ScheduledTask] = []
|
195
198
|
self._next_run_in: int | None = None
|
196
|
-
self._scheduler_task: Task | None = None
|
199
|
+
self._scheduler_task: Task[None] | None = None
|
200
|
+
self._scheduled_tasks_or_futures: list[Task[Any] | Future[Any]] = []
|
201
|
+
self._stopped = False
|
197
202
|
self._logger = LoggerProvider.default().get_logger("schedule_manager")
|
198
203
|
|
199
204
|
def include_scheduler(
|
@@ -260,20 +265,26 @@ class ScheduleManager(AsyncEventLoopMixin):
|
|
260
265
|
]:
|
261
266
|
self._scheduled_tasks.extend(scheduler.scheduled_tasks)
|
262
267
|
|
263
|
-
def stop(self) -> None:
|
268
|
+
async def stop(self) -> None:
|
269
|
+
self._stopped = True
|
270
|
+
|
271
|
+
_ = await gather(
|
272
|
+
*self._scheduled_tasks_or_futures, return_exceptions=True
|
273
|
+
)
|
274
|
+
|
264
275
|
if self._scheduler_task is not None and not self._scheduler_task.done():
|
265
276
|
self._scheduler_task.cancel()
|
266
277
|
|
267
278
|
self.SCHEDULE_MANAGER_STATE.state("stopped")
|
268
279
|
|
269
|
-
def _on_scheduler_done(self, task: Task) -> None:
|
280
|
+
def _on_scheduler_done(self, task: Task[None]) -> None:
|
270
281
|
if task.cancelled():
|
271
282
|
return
|
272
283
|
|
273
284
|
exception = task.exception()
|
274
285
|
|
275
286
|
if exception is not None:
|
276
|
-
self.
|
287
|
+
self._remote_logger.error(
|
277
288
|
message="error occured in schedule manager",
|
278
289
|
exception=exception,
|
279
290
|
)
|
@@ -309,7 +320,7 @@ class ScheduleManager(AsyncEventLoopMixin):
|
|
309
320
|
return True
|
310
321
|
|
311
322
|
async def _run_scheduler(self) -> None:
|
312
|
-
while
|
323
|
+
while not self._stopped:
|
313
324
|
self._calculate_next_schedule()
|
314
325
|
self._logger.debug(
|
315
326
|
"next tasks will be executed after `%d` seconds",
|
@@ -331,15 +342,23 @@ class ScheduleManager(AsyncEventLoopMixin):
|
|
331
342
|
continue
|
332
343
|
|
333
344
|
args = self._resolve_dependencies(scheduled_task)
|
345
|
+
scheduled_task_or_future: Task[Any] | Future[Any] | None = None
|
334
346
|
|
335
347
|
if iscoroutinefunction(scheduled_task.task):
|
336
|
-
self.loop.create_task(
|
348
|
+
scheduled_task_or_future = self.loop.create_task(
|
337
349
|
scheduled_task.task(**args)
|
338
|
-
)
|
350
|
+
)
|
339
351
|
else:
|
340
|
-
self.loop.run_in_executor(
|
341
|
-
executor=None, func=scheduled_task.task
|
342
|
-
)
|
352
|
+
scheduled_task_or_future = self.loop.run_in_executor(
|
353
|
+
executor=None, func=partial(scheduled_task.task, **args)
|
354
|
+
)
|
355
|
+
|
356
|
+
assert scheduled_task_or_future is not None
|
357
|
+
|
358
|
+
scheduled_task_or_future.add_done_callback(self._on_task_done)
|
359
|
+
self._scheduled_tasks_or_futures.append(
|
360
|
+
scheduled_task_or_future
|
361
|
+
)
|
343
362
|
|
344
363
|
scheduled_task.ran = True
|
345
364
|
|
@@ -356,14 +375,20 @@ class ScheduleManager(AsyncEventLoopMixin):
|
|
356
375
|
|
357
376
|
return args
|
358
377
|
|
359
|
-
def _on_task_done(self, task_or_future: Task | Future) -> None:
|
378
|
+
def _on_task_done(self, task_or_future: Task[Any] | Future[Any]) -> None:
|
379
|
+
if (
|
380
|
+
not self._stopped
|
381
|
+
and task_or_future in self._scheduled_tasks_or_futures
|
382
|
+
):
|
383
|
+
self._scheduled_tasks_or_futures.remove(task_or_future)
|
384
|
+
|
360
385
|
if task_or_future.cancelled():
|
361
386
|
return
|
362
387
|
|
363
388
|
exception = task_or_future.exception()
|
364
389
|
|
365
390
|
if exception is not None:
|
366
|
-
self.
|
391
|
+
self._remote_logger.error(
|
367
392
|
message="error occured while executing task",
|
368
393
|
exception=exception,
|
369
394
|
)
|
qena_shared_lib/security.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from asyncio import Future
|
2
2
|
from enum import Enum
|
3
3
|
from os import environ
|
4
|
-
from typing import AbstractSet, Annotated, Any
|
4
|
+
from typing import AbstractSet, Annotated, Any, cast
|
5
5
|
|
6
6
|
from fastapi import Depends, Header
|
7
7
|
from jwt import JWT, AbstractJWKBase, jwk_from_dict
|
@@ -129,7 +129,7 @@ async def extract_user_info(
|
|
129
129
|
extract_exc_info=True,
|
130
130
|
) from e
|
131
131
|
|
132
|
-
return user_info
|
132
|
+
return cast(UserInfo, user_info)
|
133
133
|
|
134
134
|
|
135
135
|
class PermissionMatch(Enum):
|
qena_shared_lib/utils.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
from asyncio import AbstractEventLoop, get_running_loop
|
2
|
-
from functools import lru_cache
|
3
2
|
|
4
3
|
from pydantic import TypeAdapter
|
5
4
|
|
@@ -7,10 +6,17 @@ __all__ = ["AsyncEventLoopMixin", "TypeAdapterCache"]
|
|
7
6
|
|
8
7
|
|
9
8
|
class AsyncEventLoopMixin:
|
9
|
+
_LOOP: AbstractEventLoop | None = None
|
10
|
+
|
10
11
|
@property
|
11
|
-
@lru_cache
|
12
12
|
def loop(self) -> AbstractEventLoop:
|
13
|
-
|
13
|
+
if self._LOOP is None:
|
14
|
+
self._LOOP = get_running_loop()
|
15
|
+
|
16
|
+
return self._LOOP
|
17
|
+
|
18
|
+
def init(self) -> None:
|
19
|
+
self._LOOP = get_running_loop()
|
14
20
|
|
15
21
|
|
16
22
|
class TypeAdapterCache:
|