qena-shared-lib 0.1.12__tar.gz → 0.1.14__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.12 → qena_shared_lib-0.1.14}/.pre-commit-config.yaml +8 -0
- qena_shared_lib-0.1.14/CHANGELOG.md +35 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/PKG-INFO +23 -20
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/README.md +22 -19
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/pyproject.toml +5 -10
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/__init__.py +2 -2
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/application.py +71 -29
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/background.py +6 -5
- qena_shared_lib-0.1.14/src/qena_shared_lib/exception_handlers.py +235 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/exceptions.py +10 -10
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/http.py +16 -16
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/rabbitmq/__init__.py +8 -6
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/rabbitmq/_base.py +96 -127
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/rabbitmq/_channel.py +4 -1
- qena_shared_lib-0.1.14/src/qena_shared_lib/rabbitmq/_exception_handlers.py +182 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/rabbitmq/_listener.py +94 -38
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/rabbitmq/_rpc_client.py +4 -4
- qena_shared_lib-0.1.14/src/qena_shared_lib/remotelogging/__init__.py +15 -0
- {qena_shared_lib-0.1.12/src/qena_shared_lib/logstash → qena_shared_lib-0.1.14/src/qena_shared_lib/remotelogging}/_base.py +47 -67
- qena_shared_lib-0.1.14/src/qena_shared_lib/remotelogging/logstash/__init__.py +9 -0
- qena_shared_lib-0.1.14/src/qena_shared_lib/remotelogging/logstash/_base.py +32 -0
- {qena_shared_lib-0.1.12/src/qena_shared_lib → qena_shared_lib-0.1.14/src/qena_shared_lib/remotelogging}/logstash/_http_sender.py +5 -4
- {qena_shared_lib-0.1.12/src/qena_shared_lib → qena_shared_lib-0.1.14/src/qena_shared_lib/remotelogging}/logstash/_tcp_sender.py +7 -5
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/scheduler.py +49 -24
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/security.py +2 -2
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/utils.py +9 -3
- qena_shared_lib-0.1.14/tests/conftest.py +25 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/tests/test_application.py +49 -48
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/tests/test_background.py +13 -13
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/tests/test_logstash.py +94 -79
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/tests/test_rabbitmq.py +349 -320
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/tests/test_scheduler.py +16 -13
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/tests/test_security.py +39 -28
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/uv.lock +801 -800
- qena_shared_lib-0.1.12/CHANGELOG.md +0 -14
- qena_shared_lib-0.1.12/requirements.txt +0 -55
- qena_shared_lib-0.1.12/src/qena_shared_lib/exception_handlers.py +0 -207
- qena_shared_lib-0.1.12/src/qena_shared_lib/logstash/__init__.py +0 -17
- qena_shared_lib-0.1.12/src/qena_shared_lib/rabbitmq/_exception_handlers.py +0 -147
- qena_shared_lib-0.1.12/tests/conftest.py +0 -25
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/.gitignore +0 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/dependencies/__init__.py +0 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/dependencies/http.py +0 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/dependencies/miscellaneous.py +0 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/logging.py +0 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/py.typed +0 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/rabbitmq/_pool.py +0 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/src/qena_shared_lib/rabbitmq/_publisher.py +0 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/tests/test_dependencies.py +0 -0
- {qena_shared_lib-0.1.12 → qena_shared_lib-0.1.14}/tests/utils.py +0 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [0.1.14] - 2025-06-16
|
4
|
+
|
5
|
+
### Changed
|
6
|
+
|
7
|
+
- Remove unused generics variables
|
8
|
+
- Remove requirements.txt
|
9
|
+
|
10
|
+
|
11
|
+
## [0.1.13] - 2025-06-16
|
12
|
+
|
13
|
+
### Added
|
14
|
+
|
15
|
+
- Added pytest as one of pre-commit steps.
|
16
|
+
- Made http and rabbitmq exception handlers class based.
|
17
|
+
- Gracefull shutdown for rabbitmq and scheduler.
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
|
21
|
+
- Moved logstash to remotelogging to generalize other forms of logging.
|
22
|
+
- Added loop class attribute for async event loop mixin.
|
23
|
+
- Made mypy type check slightly more strict.
|
24
|
+
|
25
|
+
|
26
|
+
## [0.1.12] - 2025-04-05
|
27
|
+
|
28
|
+
### Added
|
29
|
+
|
30
|
+
- Added a re-export for rabbitmq channel pool (ChannelPool) class.
|
31
|
+
|
32
|
+
|
33
|
+
[0.1.14]: https://github.com/Qena-Digital-Lending/qena-shared-kernel/compare/v0.1.13...v0.1.14
|
34
|
+
[0.1.13]: https://github.com/Qena-Digital-Lending/qena-shared-kernel/compare/v0.1.12...v0.1.13
|
35
|
+
[0.1.12]: https://github.com/Qena-Digital-Lending/qena-shared-kernel/compare/v0.1.11...v0.1.12
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: qena-shared-lib
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.14
|
4
4
|
Summary: A shared tools for other services
|
5
5
|
Requires-Python: >=3.10
|
6
6
|
Requires-Dist: cronsim==2.6
|
@@ -21,7 +21,8 @@ A shared tools for other services. It includes.
|
|
21
21
|
- FastAPI app builder
|
22
22
|
- A wrapper around fastapi to make it class based.
|
23
23
|
- RabbitMQ utility class to listen, respond, publish and make rpc request.
|
24
|
-
-
|
24
|
+
- Remote logging
|
25
|
+
- Logstash utility class to log message in `ecs` ( elastic common schema ).
|
25
26
|
- A simple task scheduler, to schedule task to run in specific time.
|
26
27
|
- Background task runner.
|
27
28
|
- Security tools ( password hasher, jwt, acl ).
|
@@ -178,41 +179,43 @@ def main() -> FastAPI:
|
|
178
179
|
...
|
179
180
|
```
|
180
181
|
|
181
|
-
##
|
182
|
+
## Remote logging
|
182
183
|
|
184
|
+
### Logstash
|
183
185
|
``` py
|
184
|
-
from qena_shared_lib.
|
186
|
+
from qena_shared_lib.remotelogging import BaseRemoteLogSender
|
187
|
+
from qena_shared_lib.remotelogging.logstash import HTTPSender, # TCPSender
|
185
188
|
|
186
189
|
|
187
190
|
@asynccontextmanager
|
188
191
|
async def lifespan(app: FastAPI):
|
189
|
-
|
192
|
+
remote_logger = get_service(BaseRemoteLogSender)
|
190
193
|
|
191
|
-
await
|
194
|
+
await remote_logger.start()
|
192
195
|
|
193
196
|
yield
|
194
197
|
|
195
|
-
await
|
198
|
+
await remote_logger.stop()
|
196
199
|
|
197
200
|
|
198
201
|
def main() -> FastAPI:
|
199
202
|
...
|
200
203
|
|
201
|
-
|
204
|
+
remote_logger = HTTPSender(
|
202
205
|
service_name="qena-shared-lib",
|
203
206
|
url="http://127.0.0.1:18080",
|
204
207
|
user="logstash",
|
205
208
|
password="logstash",
|
206
209
|
)
|
207
210
|
# or
|
208
|
-
#
|
211
|
+
# remote_logger = TCPSender(
|
209
212
|
# service_name="qena-shared-lib",
|
210
213
|
# host="127.0.0.1",
|
211
214
|
# port=18090
|
212
215
|
# )
|
213
216
|
builder.with_singleton(
|
214
|
-
service=
|
215
|
-
instance=
|
217
|
+
service=BaseRemoteLogSender,
|
218
|
+
instance=remote_logger,
|
216
219
|
)
|
217
220
|
|
218
221
|
...
|
@@ -220,13 +223,13 @@ def main() -> FastAPI:
|
|
220
223
|
|
221
224
|
@router.get("")
|
222
225
|
def log_message(
|
223
|
-
|
224
|
-
|
225
|
-
DependsOn(
|
226
|
+
remote_logger: Annotated[
|
227
|
+
BaseRemoteLogSender,
|
228
|
+
DependsOn(BaseRemoteLogSender),
|
226
229
|
],
|
227
230
|
message: str,
|
228
231
|
):
|
229
|
-
|
232
|
+
remote_logger.info(message)
|
230
233
|
```
|
231
234
|
|
232
235
|
## Rabbitmq
|
@@ -263,7 +266,7 @@ def main() -> FastAPI:
|
|
263
266
|
...
|
264
267
|
|
265
268
|
rabbitmq = RabbitMqManager(
|
266
|
-
|
269
|
+
remote_logger=remote_logger,
|
267
270
|
container=builder.container,
|
268
271
|
)
|
269
272
|
|
@@ -402,7 +405,7 @@ def main() -> FastAPI:
|
|
402
405
|
...
|
403
406
|
|
404
407
|
rabbitmq = RabbitMqManager(
|
405
|
-
|
408
|
+
remote_logger=remote_logger,
|
406
409
|
container=builder.container,
|
407
410
|
# or globally for all consumers
|
408
411
|
listener_global_retry_policy=RetryPolicy(
|
@@ -473,7 +476,7 @@ def main() -> FastAPI:
|
|
473
476
|
...
|
474
477
|
|
475
478
|
schedule_manager = ScheduleManager(
|
476
|
-
|
479
|
+
remote_logger=remote_logger,
|
477
480
|
container=builder.container
|
478
481
|
)
|
479
482
|
|
@@ -507,8 +510,8 @@ def main() -> FastAPI:
|
|
507
510
|
...
|
508
511
|
|
509
512
|
builder.with_singleton(
|
510
|
-
service=
|
511
|
-
instance=
|
513
|
+
service=BaseRemoteLogSender,
|
514
|
+
instance=remote_logger,
|
512
515
|
)
|
513
516
|
builder.with_singleton(Background)
|
514
517
|
|
@@ -5,7 +5,8 @@ A shared tools for other services. It includes.
|
|
5
5
|
- FastAPI app builder
|
6
6
|
- A wrapper around fastapi to make it class based.
|
7
7
|
- RabbitMQ utility class to listen, respond, publish and make rpc request.
|
8
|
-
-
|
8
|
+
- Remote logging
|
9
|
+
- Logstash utility class to log message in `ecs` ( elastic common schema ).
|
9
10
|
- A simple task scheduler, to schedule task to run in specific time.
|
10
11
|
- Background task runner.
|
11
12
|
- Security tools ( password hasher, jwt, acl ).
|
@@ -162,41 +163,43 @@ def main() -> FastAPI:
|
|
162
163
|
...
|
163
164
|
```
|
164
165
|
|
165
|
-
##
|
166
|
+
## Remote logging
|
166
167
|
|
168
|
+
### Logstash
|
167
169
|
``` py
|
168
|
-
from qena_shared_lib.
|
170
|
+
from qena_shared_lib.remotelogging import BaseRemoteLogSender
|
171
|
+
from qena_shared_lib.remotelogging.logstash import HTTPSender, # TCPSender
|
169
172
|
|
170
173
|
|
171
174
|
@asynccontextmanager
|
172
175
|
async def lifespan(app: FastAPI):
|
173
|
-
|
176
|
+
remote_logger = get_service(BaseRemoteLogSender)
|
174
177
|
|
175
|
-
await
|
178
|
+
await remote_logger.start()
|
176
179
|
|
177
180
|
yield
|
178
181
|
|
179
|
-
await
|
182
|
+
await remote_logger.stop()
|
180
183
|
|
181
184
|
|
182
185
|
def main() -> FastAPI:
|
183
186
|
...
|
184
187
|
|
185
|
-
|
188
|
+
remote_logger = HTTPSender(
|
186
189
|
service_name="qena-shared-lib",
|
187
190
|
url="http://127.0.0.1:18080",
|
188
191
|
user="logstash",
|
189
192
|
password="logstash",
|
190
193
|
)
|
191
194
|
# or
|
192
|
-
#
|
195
|
+
# remote_logger = TCPSender(
|
193
196
|
# service_name="qena-shared-lib",
|
194
197
|
# host="127.0.0.1",
|
195
198
|
# port=18090
|
196
199
|
# )
|
197
200
|
builder.with_singleton(
|
198
|
-
service=
|
199
|
-
instance=
|
201
|
+
service=BaseRemoteLogSender,
|
202
|
+
instance=remote_logger,
|
200
203
|
)
|
201
204
|
|
202
205
|
...
|
@@ -204,13 +207,13 @@ def main() -> FastAPI:
|
|
204
207
|
|
205
208
|
@router.get("")
|
206
209
|
def log_message(
|
207
|
-
|
208
|
-
|
209
|
-
DependsOn(
|
210
|
+
remote_logger: Annotated[
|
211
|
+
BaseRemoteLogSender,
|
212
|
+
DependsOn(BaseRemoteLogSender),
|
210
213
|
],
|
211
214
|
message: str,
|
212
215
|
):
|
213
|
-
|
216
|
+
remote_logger.info(message)
|
214
217
|
```
|
215
218
|
|
216
219
|
## Rabbitmq
|
@@ -247,7 +250,7 @@ def main() -> FastAPI:
|
|
247
250
|
...
|
248
251
|
|
249
252
|
rabbitmq = RabbitMqManager(
|
250
|
-
|
253
|
+
remote_logger=remote_logger,
|
251
254
|
container=builder.container,
|
252
255
|
)
|
253
256
|
|
@@ -386,7 +389,7 @@ def main() -> FastAPI:
|
|
386
389
|
...
|
387
390
|
|
388
391
|
rabbitmq = RabbitMqManager(
|
389
|
-
|
392
|
+
remote_logger=remote_logger,
|
390
393
|
container=builder.container,
|
391
394
|
# or globally for all consumers
|
392
395
|
listener_global_retry_policy=RetryPolicy(
|
@@ -457,7 +460,7 @@ def main() -> FastAPI:
|
|
457
460
|
...
|
458
461
|
|
459
462
|
schedule_manager = ScheduleManager(
|
460
|
-
|
463
|
+
remote_logger=remote_logger,
|
461
464
|
container=builder.container
|
462
465
|
)
|
463
466
|
|
@@ -491,8 +494,8 @@ def main() -> FastAPI:
|
|
491
494
|
...
|
492
495
|
|
493
496
|
builder.with_singleton(
|
494
|
-
service=
|
495
|
-
instance=
|
497
|
+
service=BaseRemoteLogSender,
|
498
|
+
instance=remote_logger,
|
496
499
|
)
|
497
500
|
builder.with_singleton(Background)
|
498
501
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "qena-shared-lib"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.14"
|
4
4
|
description = "A shared tools for other services"
|
5
5
|
readme = "README.md"
|
6
6
|
requires-python = ">=3.10"
|
@@ -74,13 +74,8 @@ skip-magic-trailing-comma = false
|
|
74
74
|
line-ending = "auto"
|
75
75
|
|
76
76
|
[tool.mypy]
|
77
|
+
strict = true
|
77
78
|
explicit_package_bases = true
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
check_untyped_defs = true
|
82
|
-
warn_redundant_casts = true
|
83
|
-
warn_unreachable = true
|
84
|
-
allow_redefinition = true
|
85
|
-
local_partial_types = true
|
86
|
-
strict_equality = true
|
79
|
+
disallow_untyped_decorators = false
|
80
|
+
disallow_subclassing_any = false
|
81
|
+
warn_unused_ignores = false
|
@@ -5,8 +5,8 @@ from . import (
|
|
5
5
|
exceptions,
|
6
6
|
http,
|
7
7
|
logging,
|
8
|
-
logstash,
|
9
8
|
rabbitmq,
|
9
|
+
remotelogging,
|
10
10
|
scheduler,
|
11
11
|
security,
|
12
12
|
utils,
|
@@ -19,7 +19,7 @@ __all__ = [
|
|
19
19
|
"exceptions",
|
20
20
|
"http",
|
21
21
|
"logging",
|
22
|
-
"
|
22
|
+
"remotelogging",
|
23
23
|
"rabbitmq",
|
24
24
|
"scheduler",
|
25
25
|
"security",
|
@@ -2,17 +2,17 @@ from enum import Enum
|
|
2
2
|
from typing import Any, TypeVar
|
3
3
|
|
4
4
|
from fastapi import APIRouter, FastAPI
|
5
|
-
from fastapi.exceptions import RequestValidationError
|
6
5
|
from prometheus_fastapi_instrumentator import Instrumentator
|
7
6
|
from punq import Container, Scope, empty
|
8
7
|
from starlette.types import Lifespan
|
8
|
+
from typing_extensions import Self
|
9
9
|
|
10
10
|
from .exception_handlers import (
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
AbstractHttpExceptionHandler,
|
12
|
+
GeneralHttpExceptionHandler,
|
13
|
+
HTTPServiceExceptionHandler,
|
14
|
+
RequestValidationErrorHandler,
|
14
15
|
)
|
15
|
-
from .exceptions import ServiceException
|
16
16
|
from .http import ControllerBase
|
17
17
|
|
18
18
|
__all__ = [
|
@@ -21,6 +21,7 @@ __all__ = [
|
|
21
21
|
"FastAPI",
|
22
22
|
]
|
23
23
|
|
24
|
+
|
24
25
|
D = TypeVar("D")
|
25
26
|
|
26
27
|
|
@@ -46,7 +47,7 @@ class Builder:
|
|
46
47
|
self._container = Container()
|
47
48
|
self._built = False
|
48
49
|
|
49
|
-
def with_environment(self, environment: Environment) ->
|
50
|
+
def with_environment(self, environment: Environment) -> Self:
|
50
51
|
match environment:
|
51
52
|
case Environment.DEVELOPMENT:
|
52
53
|
self._environment = Environment.DEVELOPMENT
|
@@ -60,35 +61,33 @@ class Builder:
|
|
60
61
|
|
61
62
|
return self
|
62
63
|
|
63
|
-
def with_title(self, title: str) ->
|
64
|
+
def with_title(self, title: str) -> Self:
|
64
65
|
self._title = title
|
65
66
|
|
66
67
|
return self
|
67
68
|
|
68
|
-
def with_description(self, description: str) ->
|
69
|
+
def with_description(self, description: str) -> Self:
|
69
70
|
self._description = description
|
70
71
|
|
71
72
|
return self
|
72
73
|
|
73
|
-
def with_version(self, version: str) ->
|
74
|
+
def with_version(self, version: str) -> Self:
|
74
75
|
self._version = version
|
75
76
|
|
76
77
|
return self
|
77
78
|
|
78
|
-
def with_lifespan(self, lifespan: Lifespan) ->
|
79
|
+
def with_lifespan(self, lifespan: Lifespan) -> Self:
|
79
80
|
self._lifespan = lifespan
|
80
81
|
|
81
82
|
return self
|
82
83
|
|
83
|
-
def with_controllers(
|
84
|
-
self, controllers: list[type[ControllerBase]]
|
85
|
-
) -> "Builder":
|
84
|
+
def with_controllers(self, *controllers: type[ControllerBase]) -> Self:
|
86
85
|
for index, controller in enumerate(controllers):
|
87
86
|
if not isinstance(controller, type) or not issubclass(
|
88
87
|
controller, ControllerBase
|
89
88
|
):
|
90
89
|
raise TypeError(
|
91
|
-
f"controller {index} is {type(
|
90
|
+
f"controller {index} is {type(controller)}, expected instance of type or subclass of `ControllerBase`"
|
92
91
|
)
|
93
92
|
|
94
93
|
self._container.register(
|
@@ -99,7 +98,7 @@ class Builder:
|
|
99
98
|
|
100
99
|
return self
|
101
100
|
|
102
|
-
def with_routers(self, routers:
|
101
|
+
def with_routers(self, *routers: APIRouter) -> Self:
|
103
102
|
if any(not isinstance(router, APIRouter) for router in routers):
|
104
103
|
raise TypeError("some routers are not type `APIRouter`")
|
105
104
|
|
@@ -107,13 +106,41 @@ class Builder:
|
|
107
106
|
|
108
107
|
return self
|
109
108
|
|
109
|
+
def with_exception_handlers(
|
110
|
+
self, *exception_handlers: type[AbstractHttpExceptionHandler]
|
111
|
+
) -> Self:
|
112
|
+
for index, exception_handler in enumerate(exception_handlers):
|
113
|
+
if not isinstance(exception_handler, type) or not issubclass(
|
114
|
+
exception_handler, AbstractHttpExceptionHandler
|
115
|
+
):
|
116
|
+
raise TypeError(
|
117
|
+
f"exception handler {index} is {type(exception_handler)}, expected instance of type or subclass of `AbstractHttpExceptionHandler`"
|
118
|
+
)
|
119
|
+
|
120
|
+
self._container.register(
|
121
|
+
service=AbstractHttpExceptionHandler,
|
122
|
+
factory=exception_handler,
|
123
|
+
scope=Scope.singleton,
|
124
|
+
)
|
125
|
+
|
126
|
+
return self
|
127
|
+
|
128
|
+
def with_default_exception_handlers(self) -> Self:
|
129
|
+
self.with_exception_handlers(
|
130
|
+
GeneralHttpExceptionHandler,
|
131
|
+
HTTPServiceExceptionHandler,
|
132
|
+
RequestValidationErrorHandler,
|
133
|
+
)
|
134
|
+
|
135
|
+
return self
|
136
|
+
|
110
137
|
def with_singleton(
|
111
138
|
self,
|
112
139
|
service: type[D],
|
113
140
|
factory: Any = empty,
|
114
141
|
instance: Any = empty,
|
115
142
|
**kwargs: Any,
|
116
|
-
) ->
|
143
|
+
) -> Self:
|
117
144
|
self._container.register(
|
118
145
|
service=service,
|
119
146
|
factory=factory,
|
@@ -126,7 +153,7 @@ class Builder:
|
|
126
153
|
|
127
154
|
def with_transient(
|
128
155
|
self, service: type[D], factory: Any = empty, **kwargs: Any
|
129
|
-
) ->
|
156
|
+
) -> Self:
|
130
157
|
self._container.register(
|
131
158
|
service=service,
|
132
159
|
factory=factory,
|
@@ -136,7 +163,7 @@ class Builder:
|
|
136
163
|
|
137
164
|
return self
|
138
165
|
|
139
|
-
def with_metrics(self, endpoint: str = "/metrics") ->
|
166
|
+
def with_metrics(self, endpoint: str = "/metrics") -> Self:
|
140
167
|
self._metrics_endpoint = endpoint
|
141
168
|
self._instrumentator = Instrumentator()
|
142
169
|
|
@@ -158,13 +185,8 @@ class Builder:
|
|
158
185
|
)
|
159
186
|
app.state.container = self._container
|
160
187
|
|
161
|
-
|
162
|
-
|
163
|
-
handle_request_validation_error
|
164
|
-
)
|
165
|
-
app.exception_handler(Exception)(handle_general_http_exception)
|
166
|
-
|
167
|
-
self._resolve_api_controllers(app)
|
188
|
+
self._register_api_controllers(app)
|
189
|
+
self._register_exception_handlers(app)
|
168
190
|
|
169
191
|
if self._instrumentator is not None:
|
170
192
|
self._instrumentator.instrument(app).expose(
|
@@ -177,14 +199,34 @@ class Builder:
|
|
177
199
|
|
178
200
|
return app
|
179
201
|
|
180
|
-
def
|
181
|
-
|
202
|
+
def _register_api_controllers(self, app: FastAPI) -> None:
|
203
|
+
for router in self._routers + self._resolve_api_controllers():
|
204
|
+
app.include_router(router)
|
205
|
+
|
206
|
+
def _resolve_api_controllers(self) -> list[APIRouter]:
|
207
|
+
return [
|
182
208
|
api_controller.register_route_handlers()
|
183
209
|
for api_controller in self._container.resolve_all(ControllerBase)
|
184
210
|
]
|
185
211
|
|
186
|
-
|
187
|
-
|
212
|
+
def _register_exception_handlers(self, app: FastAPI) -> None:
|
213
|
+
for exception_handler in self._resolve_exception_handlers():
|
214
|
+
if not callable(exception_handler):
|
215
|
+
raise ValueError(
|
216
|
+
f"exception handler {exception_handler.__class__.__name__} is not callable"
|
217
|
+
)
|
218
|
+
|
219
|
+
app.exception_handler(exception_handler.exception)(
|
220
|
+
exception_handler
|
221
|
+
)
|
222
|
+
|
223
|
+
def _resolve_exception_handlers(self) -> list[AbstractHttpExceptionHandler]:
|
224
|
+
return [
|
225
|
+
exception_handler
|
226
|
+
for exception_handler in self._container.resolve_all(
|
227
|
+
AbstractHttpExceptionHandler
|
228
|
+
)
|
229
|
+
]
|
188
230
|
|
189
231
|
@property
|
190
232
|
def environment(self) -> Environment:
|
@@ -3,13 +3,14 @@ from asyncio import (
|
|
3
3
|
Task,
|
4
4
|
gather,
|
5
5
|
)
|
6
|
+
from typing import Any
|
6
7
|
from uuid import uuid4
|
7
8
|
|
8
9
|
from prometheus_client import Enum as PrometheusEnum
|
9
10
|
from starlette.background import BackgroundTask
|
10
11
|
|
11
12
|
from .logging import LoggerProvider
|
12
|
-
from .
|
13
|
+
from .remotelogging import BaseRemoteLogSender
|
13
14
|
from .utils import AsyncEventLoopMixin
|
14
15
|
|
15
16
|
__all__ = [
|
@@ -27,14 +28,14 @@ class Background(AsyncEventLoopMixin):
|
|
27
28
|
|
28
29
|
def __init__(
|
29
30
|
self,
|
30
|
-
|
31
|
+
remote_logger: BaseRemoteLogSender,
|
31
32
|
) -> None:
|
32
33
|
self._queue: Queue[tuple[BackgroundTask | None, str | None]] = Queue()
|
33
34
|
self._started = False
|
34
35
|
self._stopped = False
|
35
|
-
self.
|
36
|
+
self._remote_logger = remote_logger
|
36
37
|
self._logger = LoggerProvider.default().get_logger("backgroud")
|
37
|
-
self._tasks: dict[str, Task] = {}
|
38
|
+
self._tasks: dict[str, Task[Any]] = {}
|
38
39
|
|
39
40
|
async def _task_manager(
|
40
41
|
self, task: BackgroundTask, task_id: str | None = None
|
@@ -53,7 +54,7 @@ class Background(AsyncEventLoopMixin):
|
|
53
54
|
|
54
55
|
await self._tasks[task_id]
|
55
56
|
except Exception:
|
56
|
-
self.
|
57
|
+
self._remote_logger.error(
|
57
58
|
"exception occured when running background task {task.func.__name__} with id {task_id}"
|
58
59
|
)
|
59
60
|
finally:
|