qena-shared-lib 0.1.0__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 +27 -0
- qena_shared_lib/application.py +190 -0
- qena_shared_lib/background.py +109 -0
- qena_shared_lib/dependencies/__init__.py +19 -0
- qena_shared_lib/dependencies/http.py +62 -0
- qena_shared_lib/dependencies/miscellaneous.py +35 -0
- qena_shared_lib/exception_handlers.py +165 -0
- qena_shared_lib/exceptions.py +319 -0
- qena_shared_lib/http.py +631 -0
- qena_shared_lib/logging.py +63 -0
- qena_shared_lib/logstash/__init__.py +17 -0
- qena_shared_lib/logstash/_base.py +573 -0
- qena_shared_lib/logstash/_http_sender.py +61 -0
- qena_shared_lib/logstash/_tcp_sender.py +84 -0
- qena_shared_lib/py.typed +0 -0
- qena_shared_lib/rabbitmq/__init__.py +52 -0
- qena_shared_lib/rabbitmq/_base.py +741 -0
- qena_shared_lib/rabbitmq/_channel.py +196 -0
- qena_shared_lib/rabbitmq/_exception_handlers.py +159 -0
- qena_shared_lib/rabbitmq/_exceptions.py +46 -0
- qena_shared_lib/rabbitmq/_listener.py +1292 -0
- qena_shared_lib/rabbitmq/_pool.py +74 -0
- qena_shared_lib/rabbitmq/_publisher.py +73 -0
- qena_shared_lib/rabbitmq/_rpc_client.py +286 -0
- qena_shared_lib/rabbitmq/_utils.py +18 -0
- qena_shared_lib/scheduler.py +402 -0
- qena_shared_lib/security.py +205 -0
- qena_shared_lib/utils.py +28 -0
- qena_shared_lib-0.1.0.dist-info/METADATA +473 -0
- qena_shared_lib-0.1.0.dist-info/RECORD +31 -0
- qena_shared_lib-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
from . import (
|
2
|
+
application,
|
3
|
+
background,
|
4
|
+
dependencies,
|
5
|
+
exceptions,
|
6
|
+
http,
|
7
|
+
logging,
|
8
|
+
logstash,
|
9
|
+
rabbitmq,
|
10
|
+
scheduler,
|
11
|
+
security,
|
12
|
+
utils,
|
13
|
+
)
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"application",
|
17
|
+
"background",
|
18
|
+
"dependencies",
|
19
|
+
"exceptions",
|
20
|
+
"http",
|
21
|
+
"logging",
|
22
|
+
"logstash",
|
23
|
+
"rabbitmq",
|
24
|
+
"scheduler",
|
25
|
+
"security",
|
26
|
+
"utils",
|
27
|
+
]
|
@@ -0,0 +1,190 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from typing import TypeVar
|
3
|
+
|
4
|
+
from fastapi import APIRouter, FastAPI
|
5
|
+
from fastapi.exceptions import RequestValidationError
|
6
|
+
from prometheus_fastapi_instrumentator import Instrumentator
|
7
|
+
from punq import Container, Scope, empty
|
8
|
+
from starlette.types import Lifespan
|
9
|
+
|
10
|
+
from .exception_handlers import (
|
11
|
+
handle_general_http_exception,
|
12
|
+
handle_request_validation_error,
|
13
|
+
handle_service_exception,
|
14
|
+
)
|
15
|
+
from .exceptions import ServiceException
|
16
|
+
from .http import ControllerBase
|
17
|
+
|
18
|
+
__all__ = [
|
19
|
+
"Builder",
|
20
|
+
"Environment",
|
21
|
+
]
|
22
|
+
|
23
|
+
D = TypeVar("D")
|
24
|
+
|
25
|
+
|
26
|
+
class Environment(Enum):
|
27
|
+
DEVELOPMENT = 0
|
28
|
+
PRODUCTION = 1
|
29
|
+
|
30
|
+
|
31
|
+
class Builder:
|
32
|
+
def __init__(self):
|
33
|
+
self._environment = Environment.DEVELOPMENT
|
34
|
+
self._debug = False
|
35
|
+
self._title = "Qena shared lib"
|
36
|
+
self._description = "Qena shared tools for microservice"
|
37
|
+
self._version = "0.1.0"
|
38
|
+
self._lifespan = None
|
39
|
+
self._openapi_url = "/openapi.json"
|
40
|
+
self._docs_url = "/docs"
|
41
|
+
self._redoc_url = "/redoc"
|
42
|
+
self._metrics_endpoint = None
|
43
|
+
self._instrumentator = None
|
44
|
+
self._routers = []
|
45
|
+
self._container = Container()
|
46
|
+
self._built = False
|
47
|
+
|
48
|
+
def with_environment(self, environment: Environment) -> "Builder":
|
49
|
+
match environment:
|
50
|
+
case Environment.DEVELOPMENT:
|
51
|
+
self._environment = Environment.DEVELOPMENT
|
52
|
+
self._debug = True
|
53
|
+
case Environment.PRODUCTION:
|
54
|
+
self._environment = Environment.PRODUCTION
|
55
|
+
self._debug = False
|
56
|
+
self._openapi_url = None
|
57
|
+
self._docs_url = None
|
58
|
+
self._redoc_url = None
|
59
|
+
|
60
|
+
return self
|
61
|
+
|
62
|
+
def with_title(self, title: str) -> "Builder":
|
63
|
+
self._title = title
|
64
|
+
|
65
|
+
return self
|
66
|
+
|
67
|
+
def with_description(self, description: str) -> "Builder":
|
68
|
+
self._description = description
|
69
|
+
|
70
|
+
return self
|
71
|
+
|
72
|
+
def with_version(self, version: str) -> "Builder":
|
73
|
+
self._version = version
|
74
|
+
|
75
|
+
return self
|
76
|
+
|
77
|
+
def with_lifespan(self, lifespan: Lifespan) -> "Builder":
|
78
|
+
self._lifespan = lifespan
|
79
|
+
|
80
|
+
return self
|
81
|
+
|
82
|
+
def with_controllers(
|
83
|
+
self, controllers: list[type[ControllerBase]]
|
84
|
+
) -> "Builder":
|
85
|
+
for index, controller in enumerate(controllers):
|
86
|
+
if not isinstance(controller, type) or not issubclass(
|
87
|
+
controller, ControllerBase
|
88
|
+
):
|
89
|
+
raise TypeError(
|
90
|
+
f"controller {index} is {type(ControllerBase)}, expected instance of type or subclass of `ControllerBase`"
|
91
|
+
)
|
92
|
+
|
93
|
+
self._container.register(
|
94
|
+
service=ControllerBase,
|
95
|
+
factory=controller,
|
96
|
+
scope=Scope.singleton,
|
97
|
+
)
|
98
|
+
|
99
|
+
return self
|
100
|
+
|
101
|
+
def with_routers(self, routers: list[APIRouter]) -> "Builder":
|
102
|
+
if any(not isinstance(router, APIRouter) for router in routers):
|
103
|
+
raise TypeError("some routers are not type `APIRouter`")
|
104
|
+
|
105
|
+
self._routers.extend(routers)
|
106
|
+
|
107
|
+
return self
|
108
|
+
|
109
|
+
def with_singleton(
|
110
|
+
self, service: type[D], factory=empty, instance=empty, **kwargs
|
111
|
+
) -> "Builder":
|
112
|
+
self._container.register(
|
113
|
+
service=service,
|
114
|
+
factory=factory,
|
115
|
+
instance=instance,
|
116
|
+
scope=Scope.singleton,
|
117
|
+
**kwargs,
|
118
|
+
)
|
119
|
+
|
120
|
+
return self
|
121
|
+
|
122
|
+
def with_transient(
|
123
|
+
self, service: type[D], factory=empty, **kwargs
|
124
|
+
) -> "Builder":
|
125
|
+
self._container.register(
|
126
|
+
service=service,
|
127
|
+
factory=factory,
|
128
|
+
scope=Scope.transient,
|
129
|
+
**kwargs,
|
130
|
+
)
|
131
|
+
|
132
|
+
return self
|
133
|
+
|
134
|
+
def with_metrics(self, endpoint: str = "/metrics") -> "Builder":
|
135
|
+
self._metrics_endpoint = endpoint
|
136
|
+
self._instrumentator = Instrumentator()
|
137
|
+
|
138
|
+
return self
|
139
|
+
|
140
|
+
def build(self) -> FastAPI:
|
141
|
+
if self._built:
|
142
|
+
raise RuntimeError("fastapi application aleady built")
|
143
|
+
|
144
|
+
app = FastAPI(
|
145
|
+
debug=self._debug,
|
146
|
+
title=self._title,
|
147
|
+
description=self._description,
|
148
|
+
version=self._version,
|
149
|
+
openapi_url=self._openapi_url,
|
150
|
+
docs_url=self._docs_url,
|
151
|
+
redoc_url=self._redoc_url,
|
152
|
+
lifespan=self._lifespan,
|
153
|
+
)
|
154
|
+
app.state.container = self._container
|
155
|
+
|
156
|
+
app.exception_handler(ServiceException)(handle_service_exception)
|
157
|
+
app.exception_handler(RequestValidationError)(
|
158
|
+
handle_request_validation_error
|
159
|
+
)
|
160
|
+
app.exception_handler(Exception)(handle_general_http_exception)
|
161
|
+
|
162
|
+
self._resolve_api_controllers(app)
|
163
|
+
|
164
|
+
if self._instrumentator is not None:
|
165
|
+
self._instrumentator.instrument(app).expose(
|
166
|
+
app=app,
|
167
|
+
endpoint=self._metrics_endpoint or "/metrics",
|
168
|
+
include_in_schema=False,
|
169
|
+
)
|
170
|
+
|
171
|
+
self._built = True
|
172
|
+
|
173
|
+
return app
|
174
|
+
|
175
|
+
def _resolve_api_controllers(self, app: FastAPI):
|
176
|
+
api_controller_routers = [
|
177
|
+
api_controller.register_route_handlers()
|
178
|
+
for api_controller in self._container.resolve_all(ControllerBase)
|
179
|
+
]
|
180
|
+
|
181
|
+
for router in self._routers + api_controller_routers:
|
182
|
+
app.include_router(router)
|
183
|
+
|
184
|
+
@property
|
185
|
+
def environment(self) -> Environment:
|
186
|
+
return self._environment
|
187
|
+
|
188
|
+
@property
|
189
|
+
def container(self) -> Container:
|
190
|
+
return self._container
|
@@ -0,0 +1,109 @@
|
|
1
|
+
from asyncio import (
|
2
|
+
Queue,
|
3
|
+
Task,
|
4
|
+
gather,
|
5
|
+
)
|
6
|
+
from uuid import uuid4
|
7
|
+
|
8
|
+
from prometheus_client import Enum as PrometheusEnum
|
9
|
+
from starlette.background import BackgroundTask
|
10
|
+
|
11
|
+
from .logging import LoggerProvider
|
12
|
+
from .logstash import BaseLogstashSender
|
13
|
+
from .utils import AsyncEventLoopMixin
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"Background",
|
17
|
+
"BackgroundTask",
|
18
|
+
]
|
19
|
+
|
20
|
+
|
21
|
+
class Background(AsyncEventLoopMixin):
|
22
|
+
BACKGROUND_RUNNER_STATE = PrometheusEnum(
|
23
|
+
name="background_runner_state",
|
24
|
+
documentation="Background runner state",
|
25
|
+
states=["running", "stopped"],
|
26
|
+
)
|
27
|
+
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
logstash: BaseLogstashSender,
|
31
|
+
):
|
32
|
+
self._queue = Queue()
|
33
|
+
self._started = False
|
34
|
+
self._stopped = False
|
35
|
+
self._logstash = logstash
|
36
|
+
self._logger = LoggerProvider.default().get_logger("backgroud")
|
37
|
+
self._tasks: dict[str, Task] = {}
|
38
|
+
|
39
|
+
async def _task_manager(
|
40
|
+
self, task: BackgroundTask, task_id: str | None = None
|
41
|
+
):
|
42
|
+
self._logger.info(
|
43
|
+
"running %s: %s with %s", task_id, task.func.__name__, task.args
|
44
|
+
)
|
45
|
+
|
46
|
+
if task_id is None:
|
47
|
+
task_id = str(uuid4())
|
48
|
+
|
49
|
+
try:
|
50
|
+
self._tasks[task_id] = self.loop.create_task(
|
51
|
+
task.func(*task.args, **task.kwargs)
|
52
|
+
)
|
53
|
+
|
54
|
+
await self._tasks[task_id]
|
55
|
+
except Exception:
|
56
|
+
self._logstash.error(
|
57
|
+
"exception occured when running background task {task.func.__name__} with id {task_id}"
|
58
|
+
)
|
59
|
+
finally:
|
60
|
+
self._logger.info("finished running %s", task.func.__name__)
|
61
|
+
self._tasks.pop(task_id, None)
|
62
|
+
|
63
|
+
def _run(self, task: BackgroundTask, task_id: str | None = None):
|
64
|
+
if not self._stopped and (
|
65
|
+
task_id is None or task_id not in self._tasks
|
66
|
+
):
|
67
|
+
self.loop.create_task(self._task_manager(task, task_id))
|
68
|
+
|
69
|
+
async def _run_tasks(self):
|
70
|
+
while not self._stopped or not self._queue.empty():
|
71
|
+
task, task_id = await self._queue.get()
|
72
|
+
|
73
|
+
if task is None and task_id is None:
|
74
|
+
break
|
75
|
+
|
76
|
+
self._run(task=task, task_id=task_id)
|
77
|
+
|
78
|
+
tasks = [t for _, t in self._tasks.items() if not t.done()]
|
79
|
+
|
80
|
+
await gather(*tasks)
|
81
|
+
|
82
|
+
def add_task(self, task: BackgroundTask, task_id: str | None = None):
|
83
|
+
self._queue.put_nowait((task, task_id))
|
84
|
+
|
85
|
+
def start(self):
|
86
|
+
if self._started:
|
87
|
+
raise RuntimeError("background runner already running")
|
88
|
+
|
89
|
+
self.loop.create_task(self._run_tasks())
|
90
|
+
self.BACKGROUND_RUNNER_STATE.state("running")
|
91
|
+
|
92
|
+
self._started = True
|
93
|
+
|
94
|
+
def stop(self):
|
95
|
+
if self._stopped:
|
96
|
+
raise RuntimeError("background runner already stopped")
|
97
|
+
|
98
|
+
self._stopped = True
|
99
|
+
self._queue.put_nowait((None, None))
|
100
|
+
self.BACKGROUND_RUNNER_STATE.state("stopped")
|
101
|
+
|
102
|
+
def is_alive(self, task_id: str):
|
103
|
+
if task_id in self._tasks and not self._tasks[task_id].done():
|
104
|
+
return True
|
105
|
+
|
106
|
+
return False
|
107
|
+
|
108
|
+
def count(self):
|
109
|
+
return len(self._tasks)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from punq import (
|
2
|
+
Container,
|
3
|
+
InvalidForwardReferenceError,
|
4
|
+
InvalidRegistrationError,
|
5
|
+
MissingDependencyError,
|
6
|
+
Scope,
|
7
|
+
)
|
8
|
+
|
9
|
+
from . import http, miscellaneous
|
10
|
+
|
11
|
+
__all__ = [
|
12
|
+
"Container",
|
13
|
+
"http",
|
14
|
+
"InvalidForwardReferenceError",
|
15
|
+
"InvalidRegistrationError",
|
16
|
+
"miscellaneous",
|
17
|
+
"MissingDependencyError",
|
18
|
+
"Scope",
|
19
|
+
]
|
@@ -0,0 +1,62 @@
|
|
1
|
+
from typing import Any, TypeVar
|
2
|
+
|
3
|
+
from fastapi import Depends, FastAPI, Request
|
4
|
+
from punq import Container, Scope, empty
|
5
|
+
|
6
|
+
__all__ = [
|
7
|
+
"add_service",
|
8
|
+
"DependsOn",
|
9
|
+
"get_service",
|
10
|
+
]
|
11
|
+
|
12
|
+
D = TypeVar("D")
|
13
|
+
|
14
|
+
|
15
|
+
def get_container(app: FastAPI) -> Container:
|
16
|
+
if not hasattr(app.state, "container"):
|
17
|
+
raise RuntimeError(
|
18
|
+
"application does include container, possibly not created with builder"
|
19
|
+
)
|
20
|
+
|
21
|
+
if not isinstance(app.state.container, Container):
|
22
|
+
raise TypeError("container is not type of `punq.Container`")
|
23
|
+
|
24
|
+
return app.state.container
|
25
|
+
|
26
|
+
|
27
|
+
def add_service(
|
28
|
+
app: FastAPI,
|
29
|
+
service: type[D],
|
30
|
+
factory=empty,
|
31
|
+
instance: D = empty,
|
32
|
+
scope: Scope = Scope.transient,
|
33
|
+
**kwargs,
|
34
|
+
):
|
35
|
+
get_container(app).register(
|
36
|
+
service=service,
|
37
|
+
factory=factory,
|
38
|
+
instance=instance,
|
39
|
+
scope=scope,
|
40
|
+
**kwargs,
|
41
|
+
)
|
42
|
+
|
43
|
+
|
44
|
+
def get_service(app: FastAPI, service_key: type[D]) -> D:
|
45
|
+
service = get_container(app).resolve(service_key=service_key)
|
46
|
+
|
47
|
+
if not isinstance(service, service_key):
|
48
|
+
raise TypeError(f"`{service}` not a `{service_key}`")
|
49
|
+
|
50
|
+
return service
|
51
|
+
|
52
|
+
|
53
|
+
class DependencyResolver:
|
54
|
+
def __init__(self, dependency: type):
|
55
|
+
self._dependency = dependency
|
56
|
+
|
57
|
+
def __call__(self, request: Request) -> Any:
|
58
|
+
return get_service(app=request.app, service_key=self._dependency)
|
59
|
+
|
60
|
+
|
61
|
+
def DependsOn(dependency: type) -> Any:
|
62
|
+
return Depends(DependencyResolver(dependency))
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from inspect import Parameter
|
3
|
+
from typing import Annotated, get_args, get_origin
|
4
|
+
|
5
|
+
__all__ = [
|
6
|
+
"DependsOn",
|
7
|
+
"validate_annotation",
|
8
|
+
]
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class DependsOn:
|
13
|
+
dependency: type
|
14
|
+
|
15
|
+
|
16
|
+
def validate_annotation(parameter: Parameter | type) -> type | None:
|
17
|
+
if isinstance(parameter, Parameter):
|
18
|
+
annotation = parameter.annotation
|
19
|
+
else:
|
20
|
+
annotation = parameter
|
21
|
+
|
22
|
+
if annotation is Parameter.empty or get_origin(annotation) is not Annotated:
|
23
|
+
return None
|
24
|
+
|
25
|
+
args = get_args(annotation)
|
26
|
+
|
27
|
+
if len(args) != 2:
|
28
|
+
return None
|
29
|
+
|
30
|
+
_, dependency_metadata = args
|
31
|
+
|
32
|
+
if not isinstance(dependency_metadata, DependsOn):
|
33
|
+
return None
|
34
|
+
|
35
|
+
return dependency_metadata.dependency
|
@@ -0,0 +1,165 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from fastapi import Request, Response, status
|
4
|
+
from fastapi.encoders import jsonable_encoder
|
5
|
+
from fastapi.exceptions import RequestValidationError
|
6
|
+
from fastapi.responses import JSONResponse
|
7
|
+
|
8
|
+
from .dependencies.http import get_service
|
9
|
+
from .exceptions import ServiceException, Severity
|
10
|
+
from .logging import LoggerProvider
|
11
|
+
from .logstash._base import BaseLogstashSender
|
12
|
+
|
13
|
+
__all__ = [
|
14
|
+
"handle_service_exception",
|
15
|
+
"handle_request_validation_error",
|
16
|
+
"handle_general_http_exception",
|
17
|
+
]
|
18
|
+
|
19
|
+
|
20
|
+
def handle_service_exception(
|
21
|
+
request: Request, exception: ServiceException
|
22
|
+
) -> Response:
|
23
|
+
logstash = get_service(app=request.app, service_key=BaseLogstashSender)
|
24
|
+
logger_provider = get_service(app=request.app, service_key=LoggerProvider)
|
25
|
+
logger = logger_provider.get_logger("http.exception_handler")
|
26
|
+
exception_severity = exception.severity or Severity.LOW
|
27
|
+
message = exception.message
|
28
|
+
tags = [
|
29
|
+
"HTTP",
|
30
|
+
request.method,
|
31
|
+
request.url.path,
|
32
|
+
exception.__class__.__name__,
|
33
|
+
]
|
34
|
+
|
35
|
+
if exception.tags:
|
36
|
+
exception.tags.extend(tags)
|
37
|
+
|
38
|
+
extra = {
|
39
|
+
"serviceType": "HTTP",
|
40
|
+
"method": request.method,
|
41
|
+
"path": request.url.path,
|
42
|
+
"exception": exception.__class__.__name__,
|
43
|
+
}
|
44
|
+
|
45
|
+
if exception.extra:
|
46
|
+
exception.extra.update(extra)
|
47
|
+
|
48
|
+
exc_info = (
|
49
|
+
(type(exception), exception, exception.__traceback__)
|
50
|
+
if exception.extract_exc_info
|
51
|
+
else None
|
52
|
+
)
|
53
|
+
|
54
|
+
match exception_severity:
|
55
|
+
case Severity.LOW:
|
56
|
+
logstash_logger_method = logstash.info
|
57
|
+
logger_method = logger.info
|
58
|
+
case Severity.MEDIUM:
|
59
|
+
logstash_logger_method = logstash.warning
|
60
|
+
logger_method = logger.warning
|
61
|
+
case _:
|
62
|
+
message = "something went wrong"
|
63
|
+
logstash_logger_method = logstash.error
|
64
|
+
logger_method = logger.error
|
65
|
+
|
66
|
+
if exception.logstash_logging:
|
67
|
+
logstash_logger_method(
|
68
|
+
message=exception.message,
|
69
|
+
tags=exception.tags or tags,
|
70
|
+
extra=exception.extra or extra,
|
71
|
+
exception=exception if exception.extract_exc_info else None,
|
72
|
+
)
|
73
|
+
else:
|
74
|
+
logger_method(
|
75
|
+
"\n%s %s\n%s",
|
76
|
+
request.method,
|
77
|
+
request.url.path,
|
78
|
+
exception.message,
|
79
|
+
exc_info=exc_info,
|
80
|
+
)
|
81
|
+
|
82
|
+
content: dict[str, Any] = {
|
83
|
+
"severity": exception_severity.name
|
84
|
+
if exception_severity
|
85
|
+
else Severity.LOW.name,
|
86
|
+
"message": message,
|
87
|
+
}
|
88
|
+
|
89
|
+
if exception.response_code is not None:
|
90
|
+
content["code"] = exception.response_code
|
91
|
+
|
92
|
+
if exception.corrective_action is not None:
|
93
|
+
content["correctiveAction"] = exception.corrective_action
|
94
|
+
|
95
|
+
if exception.body is not None:
|
96
|
+
content.update(dict(exception.body))
|
97
|
+
|
98
|
+
return JSONResponse(
|
99
|
+
content=content,
|
100
|
+
status_code=exception.status_code
|
101
|
+
or _status_code_from_severity(exception.severity),
|
102
|
+
headers=exception.headers,
|
103
|
+
)
|
104
|
+
|
105
|
+
|
106
|
+
def handle_request_validation_error(
|
107
|
+
request: Request, error: RequestValidationError
|
108
|
+
) -> Response:
|
109
|
+
logger_provider = get_service(app=request.app, service_key=LoggerProvider)
|
110
|
+
logger = logger_provider.get_logger("http.exception_handler")
|
111
|
+
message = "invalid request data"
|
112
|
+
|
113
|
+
logger.warning("\n%s %s\n%s", request.method, request.url.path, message)
|
114
|
+
|
115
|
+
return JSONResponse(
|
116
|
+
content={
|
117
|
+
"severity": Severity.MEDIUM.name,
|
118
|
+
"message": message,
|
119
|
+
"code": 100,
|
120
|
+
"detail": jsonable_encoder(error.errors()),
|
121
|
+
},
|
122
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
123
|
+
)
|
124
|
+
|
125
|
+
|
126
|
+
def handle_general_http_exception(
|
127
|
+
request: Request, exception: Exception
|
128
|
+
) -> Response:
|
129
|
+
logstash = get_service(app=request.app, service_key=BaseLogstashSender)
|
130
|
+
|
131
|
+
logstash.error(
|
132
|
+
message=f"something went wrong on endpoint `{request.method} {request.url.path}`",
|
133
|
+
tags=[
|
134
|
+
"HTTP",
|
135
|
+
request.method,
|
136
|
+
request.url.path,
|
137
|
+
exception.__class__.__name__,
|
138
|
+
],
|
139
|
+
extra={
|
140
|
+
"serviceType": "HTTP",
|
141
|
+
"method": request.method,
|
142
|
+
"path": request.url.path,
|
143
|
+
"exception": exception.__class__.__name__,
|
144
|
+
},
|
145
|
+
exception=exception,
|
146
|
+
)
|
147
|
+
|
148
|
+
return JSONResponse(
|
149
|
+
content={
|
150
|
+
"severity": Severity.HIGH.name,
|
151
|
+
"message": "something went wrong",
|
152
|
+
},
|
153
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
154
|
+
)
|
155
|
+
|
156
|
+
|
157
|
+
def _status_code_from_severity(severity: Severity | None) -> int:
|
158
|
+
if (
|
159
|
+
severity is None
|
160
|
+
or severity == Severity.LOW
|
161
|
+
or severity == Severity.MEDIUM
|
162
|
+
):
|
163
|
+
return status.HTTP_400_BAD_REQUEST
|
164
|
+
|
165
|
+
return status.HTTP_500_INTERNAL_SERVER_ERROR
|