qena-shared-lib 0.1.6__py3-none-any.whl → 0.1.8__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.
@@ -9,8 +9,8 @@ from starlette.types import Lifespan
9
9
 
10
10
  from .exception_handlers import (
11
11
  handle_general_http_exception,
12
+ handle_http_service_error,
12
13
  handle_request_validation_error,
13
- handle_service_exception,
14
14
  )
15
15
  from .exceptions import ServiceException
16
16
  from .http import ControllerBase
@@ -153,7 +153,7 @@ class Builder:
153
153
  )
154
154
  app.state.container = self._container
155
155
 
156
- app.exception_handler(ServiceException)(handle_service_exception)
156
+ app.exception_handler(ServiceException)(handle_http_service_error)
157
157
  app.exception_handler(RequestValidationError)(
158
158
  handle_request_validation_error
159
159
  )
@@ -7,18 +7,23 @@ from fastapi.responses import JSONResponse
7
7
  from pydantic_core import to_jsonable_python
8
8
 
9
9
  from .dependencies.http import get_service
10
- from .exceptions import ServiceException, Severity
10
+ from .exceptions import (
11
+ HTTPServiceError,
12
+ RabbitMQServiceException,
13
+ ServiceException,
14
+ Severity,
15
+ )
11
16
  from .logging import LoggerProvider
12
17
  from .logstash._base import BaseLogstashSender
13
18
 
14
19
  __all__ = [
15
- "handle_service_exception",
20
+ "handle_http_service_error",
16
21
  "handle_request_validation_error",
17
22
  "handle_general_http_exception",
18
23
  ]
19
24
 
20
25
 
21
- def handle_service_exception(
26
+ def handle_http_service_error(
22
27
  request: Request, exception: ServiceException
23
28
  ) -> Response:
24
29
  logstash = get_service(app=request.app, service_key=BaseLogstashSender)
@@ -32,20 +37,12 @@ def handle_service_exception(
32
37
  request.url.path,
33
38
  exception.__class__.__name__,
34
39
  ]
35
-
36
- if exception.tags:
37
- exception.tags.extend(tags)
38
-
39
40
  extra = {
40
41
  "serviceType": "HTTP",
41
42
  "method": request.method,
42
43
  "path": request.url.path,
43
44
  "exception": exception.__class__.__name__,
44
45
  }
45
-
46
- if exception.extra:
47
- exception.extra.update(extra)
48
-
49
46
  exc_info = (
50
47
  (type(exception), exception, exception.__traceback__)
51
48
  if exception.extract_exc_info
@@ -64,6 +61,63 @@ def handle_service_exception(
64
61
  logstash_logger_method = logstash.error
65
62
  logger_method = logger.error
66
63
 
64
+ content: dict[str, Any] = {
65
+ "severity": exception_severity.name,
66
+ "message": message,
67
+ }
68
+ status_code = _status_code_from_severity(exception.severity)
69
+ headers = None
70
+
71
+ match exception:
72
+ case HTTPServiceError() as http_service_error:
73
+ if http_service_error.body is not None:
74
+ extra_body = to_jsonable_python(http_service_error.body)
75
+ is_updated = False
76
+
77
+ try:
78
+ if isinstance(extra_body, Iterable):
79
+ content.update(extra_body)
80
+
81
+ is_updated = True
82
+ except:
83
+ pass
84
+
85
+ if not is_updated:
86
+ content["data"] = extra_body
87
+
88
+ if http_service_error.response_code is not None:
89
+ content["code"] = http_service_error.response_code
90
+ str_response_code = str(http_service_error.response_code)
91
+ extra["responseCode"] = str_response_code
92
+
93
+ tags.append(str_response_code)
94
+
95
+ if http_service_error.corrective_action is not None:
96
+ content["correctiveAction"] = (
97
+ http_service_error.corrective_action
98
+ )
99
+
100
+ if http_service_error.status_code is not None:
101
+ status_code = http_service_error.status_code
102
+ str_status_code = str(status_code)
103
+ extra["statusCode"] = str_status_code
104
+
105
+ tags.append(str_status_code)
106
+
107
+ if http_service_error.headers is not None:
108
+ headers = http_service_error.headers
109
+ case RabbitMQServiceException() as rabbitmq_service_exception:
110
+ str_error_code = str(rabbitmq_service_exception.code)
111
+ extra["code"] = str_error_code
112
+
113
+ tags.append(str_error_code)
114
+
115
+ if exception.tags:
116
+ tags.extend(exception.tags)
117
+
118
+ if exception.extra:
119
+ extra.update(exception.extra)
120
+
67
121
  if exception.logstash_logging:
68
122
  logstash_logger_method(
69
123
  message=exception.message,
@@ -80,39 +134,10 @@ def handle_service_exception(
80
134
  exc_info=exc_info,
81
135
  )
82
136
 
83
- content: dict[str, Any] = {
84
- "severity": exception_severity.name
85
- if exception_severity
86
- else Severity.LOW.name,
87
- "message": message,
88
- }
89
-
90
- if exception.response_code is not None:
91
- content["code"] = exception.response_code
92
-
93
- if exception.corrective_action is not None:
94
- content["correctiveAction"] = exception.corrective_action
95
-
96
- if exception.body is not None:
97
- extra_body = to_jsonable_python(exception.body)
98
- is_updated = False
99
-
100
- try:
101
- if isinstance(extra_body, Iterable):
102
- content.update(extra_body)
103
-
104
- is_updated = True
105
- except:
106
- pass
107
-
108
- if not is_updated:
109
- content["data"] = extra_body
110
-
111
137
  return JSONResponse(
112
138
  content=content,
113
- status_code=exception.status_code
114
- or _status_code_from_severity(exception.severity),
115
- headers=exception.headers,
139
+ status_code=status_code,
140
+ headers=headers,
116
141
  )
117
142
 
118
143
 
@@ -2,50 +2,56 @@ from enum import Enum
2
2
  from typing import Any
3
3
 
4
4
  __all__ = [
5
- "Severity",
6
- "ServiceException",
7
- "ClientError",
5
+ "BadGateway",
8
6
  "BadRequest",
9
- "Unauthorized",
10
- "PaymentRequired",
11
- "Forbidden",
12
- "NotFound",
13
- "MethodNotAllowed",
14
- "NotAcceptable",
15
- "ProxyAuthenticationRequired",
16
- "RequestTimeout",
7
+ "ClientError",
17
8
  "Conflict",
18
- "Gone",
19
- "LengthRequired",
20
- "PreconditionFailed",
21
- "PayloadTooLarge",
22
- "URITooLong",
23
- "UnsupportedMediaType",
24
- "RangeNotSatisfiable",
25
9
  "ExpectationFailed",
10
+ "FailedDependency",
11
+ "Forbidden",
12
+ "GatewayTimeout",
13
+ "Gone",
14
+ "HTTPServiceError",
15
+ "HTTPVersionNotSupported",
26
16
  "IAmATeapot",
27
- "MisdirectedRequest",
28
- "UnprocessableEntity",
17
+ "InsufficientStorage",
18
+ "InternalServerError",
19
+ "LengthRequired",
29
20
  "Locked",
30
- "FailedDependency",
31
- "TooEarly",
32
- "UpgradeRequired",
21
+ "LoopDetected",
22
+ "MethodNotAllowed",
23
+ "MisdirectedRequest",
24
+ "NetworkAuthenticationRequired",
25
+ "NotAcceptable",
26
+ "NotExtended",
27
+ "NotFound",
28
+ "NotImplemented",
29
+ "PayloadTooLarge",
30
+ "PaymentRequired",
31
+ "PreconditionFailed",
33
32
  "PreconditionRequired",
34
- "TooManyRequests",
33
+ "ProxyAuthenticationRequired",
34
+ "RabbitMQBlockedError",
35
+ "RabbitMQConnectionUnhealthyError",
36
+ "RabbitMQRpcRequestPendingError",
37
+ "RabbitMQRpcRequestTimeoutError",
38
+ "RabbitMQServiceException",
39
+ "RangeNotSatisfiable",
35
40
  "RequestHeaderFieldsTooLarge",
36
- "UnavailableForLegalReasons",
41
+ "RequestTimeout",
37
42
  "ServerError",
38
- "InternalServerError",
39
- "NotImplemented",
40
- "BadGateway",
43
+ "ServiceException",
41
44
  "ServiceUnavailable",
42
- "GatewayTimeout",
43
- "HTTPVersionNotSupported",
45
+ "Severity",
46
+ "TooEarly",
47
+ "TooManyRequests",
48
+ "Unauthorized",
49
+ "UnavailableForLegalReasons",
50
+ "UnprocessableEntity",
51
+ "UnsupportedMediaType",
52
+ "UpgradeRequired",
53
+ "URITooLong",
44
54
  "VariantAlsoNegotiates",
45
- "InsufficientStorage",
46
- "LoopDetected",
47
- "NotExtended",
48
- "NetworkAuthenticationRequired",
49
55
  ]
50
56
 
51
57
 
@@ -56,100 +62,134 @@ class Severity(Enum):
56
62
 
57
63
 
58
64
  class ServiceException(Exception):
59
- _GENERAL_EXTRACT_EXC_INFO = None
60
65
  _GENERAL_SEVERITY = None
61
- _GENERAL_STATUS_CODE = None
66
+ _GENERAL_EXTRACT_EXC_INFO = None
62
67
 
63
68
  def __init__(
64
69
  self,
65
70
  message: str,
66
- body: Any | None = None,
67
- corrective_action: str | None = None,
68
71
  severity: Severity | None = None,
69
- status_code: int | None = None,
70
- response_code: int | None = None,
71
72
  tags: list[str] | None = None,
72
73
  extra: dict[str, str] | None = None,
73
- headers: dict[str, str] | None = None,
74
- logstash_logging: bool = True,
75
- extract_exc_info: bool = True,
74
+ logstash_logging: bool | None = None,
75
+ extract_exc_info: bool | None = None,
76
76
  ):
77
77
  self._message = message
78
- self._body = body
79
- self._corrective_action = corrective_action
80
- self._response_code = response_code
78
+
79
+ if severity is not None:
80
+ self._severity = severity
81
+ else:
82
+ self._severity = self._GENERAL_SEVERITY
83
+
81
84
  self._tags = tags
82
85
  self._extra = extra
83
- self._headers = headers
84
- self._logstash_logging = (
85
- logstash_logging if logstash_logging is not None else True
86
- )
87
- self._extract_exc_info = (
88
- extract_exc_info
89
- if extract_exc_info is not None
90
- else self._GENERAL_EXTRACT_EXC_INFO
91
- )
92
- self._severity = (
93
- severity if severity is not None else self._GENERAL_SEVERITY
94
- )
95
- self._status_code = (
96
- status_code
97
- if status_code is not None
98
- else self._GENERAL_STATUS_CODE
99
- )
86
+
87
+ if logstash_logging is not None:
88
+ self._logstash_logging = logstash_logging
89
+ else:
90
+ self._logstash_logging = True
91
+
92
+ if extract_exc_info is not None:
93
+ self._extract_exc_info = extract_exc_info
94
+ else:
95
+ self._extract_exc_info = self._GENERAL_EXTRACT_EXC_INFO
100
96
 
101
97
  @property
102
98
  def message(self) -> str:
103
99
  return self._message
104
100
 
105
101
  @property
106
- def body(self) -> Any | None:
107
- return self._body
102
+ def severity(self) -> Severity | None:
103
+ return self._severity
108
104
 
109
105
  @property
110
- def corrective_action(self) -> str | None:
111
- return self._corrective_action
106
+ def tags(self) -> list[str] | None:
107
+ return self._tags
112
108
 
113
109
  @property
114
- def severity(self) -> Severity | None:
115
- return self._severity
110
+ def extra(self) -> dict[str, str] | None:
111
+ return self._extra
116
112
 
117
113
  @property
118
- def status_code(self) -> int | None:
119
- return self._status_code
114
+ def logstash_logging(self) -> bool | None:
115
+ return self._logstash_logging
120
116
 
121
117
  @property
122
- def response_code(self) -> int | None:
123
- return self._response_code
118
+ def extract_exc_info(self) -> bool | None:
119
+ return self._extract_exc_info
120
+
121
+ def __str__(self) -> str:
122
+ return f"message `{self._message}`, tags {self._tags or []}"
123
+
124
+ def __repr__(self) -> str:
125
+ return f"{self.__class__.__name__} ( message: `{self._message}`, tags: {self._tags or []}, extra: {self._extra or {}} )"
126
+
127
+
128
+ class HTTPServiceError(ServiceException):
129
+ _GENERAL_STATUS_CODE = None
130
+
131
+ def __init__(
132
+ self,
133
+ message: str,
134
+ body: Any | None = None,
135
+ status_code: int | None = None,
136
+ headers: dict[str, str] | None = None,
137
+ response_code: int | None = None,
138
+ corrective_action: str | None = None,
139
+ severity: Severity | None = None,
140
+ tags: list[str] | None = None,
141
+ extra: dict[str, str] | None = None,
142
+ logstash_logging: bool = True,
143
+ extract_exc_info: bool = True,
144
+ ):
145
+ super().__init__(
146
+ message=message,
147
+ severity=severity,
148
+ tags=tags,
149
+ extra=extra,
150
+ logstash_logging=logstash_logging,
151
+ extract_exc_info=extract_exc_info,
152
+ )
153
+
154
+ self._body = body
155
+
156
+ if status_code is not None:
157
+ self._status_code = status_code
158
+ else:
159
+ self._status_code = self._GENERAL_STATUS_CODE
160
+
161
+ self._headers = headers
162
+ self._response_code = response_code
163
+ self._corrective_action = corrective_action
124
164
 
125
165
  @property
126
- def tags(self) -> list[str] | None:
127
- return self._tags
166
+ def body(self) -> Any | None:
167
+ return self._body
128
168
 
129
169
  @property
130
- def extra(self) -> dict[str, str] | None:
131
- return self._extra
170
+ def status_code(self) -> int | None:
171
+ return self._status_code
132
172
 
133
173
  @property
134
174
  def headers(self) -> dict[str, str] | None:
135
175
  return self._headers
136
176
 
137
177
  @property
138
- def logstash_logging(self) -> bool | None:
139
- return self._logstash_logging
178
+ def response_code(self) -> int | None:
179
+ return self._response_code
140
180
 
141
181
  @property
142
- def extract_exc_info(self) -> bool | None:
143
- return self._extract_exc_info
182
+ def corrective_action(self) -> str | None:
183
+ return self._corrective_action
144
184
 
145
185
  def __str__(self) -> str:
146
- return f"message `{self._message}`, tags `{self._tags or []}`"
186
+ return f"message `{self._message}`, status_code {self._status_code or 500}, response_code {self._response_code or 0}"
147
187
 
148
188
  def __repr__(self) -> str:
149
- return f"{self.__class__.__name__} ( message = `{self._message}`, tags = `{self._tags or []}`, extra = `{self._extra or {}}` )"
189
+ return f"{self.__class__.__name__} ( message: `{self._message}`, status_code: {self._status_code or 500}, response_code: {self._response_code or 0} )"
150
190
 
151
191
 
152
- class ClientError(ServiceException):
192
+ class ClientError(HTTPServiceError):
153
193
  _GENERAL_EXTRACT_EXC_INFO = False
154
194
  _GENERAL_SEVERITY = Severity.MEDIUM
155
195
 
@@ -270,7 +310,7 @@ class UnavailableForLegalReasons(ClientError):
270
310
  _GENERAL_STATUS_CODE = 451
271
311
 
272
312
 
273
- class ServerError(ServiceException):
313
+ class ServerError(HTTPServiceError):
274
314
  _GENERAL_EXTRACT_EXC_INFO = True
275
315
  _GENERAL_SEVERITY = Severity.HIGH
276
316
 
@@ -317,3 +357,65 @@ class NotExtended(ServerError):
317
357
 
318
358
  class NetworkAuthenticationRequired(ServerError):
319
359
  _GENERAL_STATUS_CODE = 511
360
+
361
+
362
+ class RabbitMQServiceException(ServiceException):
363
+ _GENERAL_SEVERITY = Severity.HIGH
364
+ _GENERAL_CODE = None
365
+
366
+ def __init__(
367
+ self,
368
+ message: str,
369
+ code: int | None = None,
370
+ data: Any | None = None,
371
+ severity: Severity | None = None,
372
+ tags: list[str] | None = None,
373
+ extra: dict[str, str] | None = None,
374
+ logstash_logging: bool | None = None,
375
+ extract_exc_info: bool | None = None,
376
+ ):
377
+ super().__init__(
378
+ message=message,
379
+ severity=severity,
380
+ tags=tags,
381
+ extra=extra,
382
+ logstash_logging=logstash_logging,
383
+ extract_exc_info=extract_exc_info,
384
+ )
385
+
386
+ if code is not None:
387
+ self._code = code
388
+ else:
389
+ self._code = self._GENERAL_CODE or 0
390
+
391
+ self._data = data
392
+
393
+ @property
394
+ def code(self) -> int:
395
+ return self._code
396
+
397
+ @property
398
+ def data(self) -> Any | None:
399
+ return self._data
400
+
401
+ def __str__(self) -> str:
402
+ return f"message `{self.message}`, code {self.code}"
403
+
404
+ def __repr__(self) -> str:
405
+ return f"{self.__class__.__name__} ( message: `{self._message}`, code: {self._code} )"
406
+
407
+
408
+ class RabbitMQBlockedError(Exception):
409
+ pass
410
+
411
+
412
+ class RabbitMQRpcRequestTimeoutError(Exception):
413
+ pass
414
+
415
+
416
+ class RabbitMQRpcRequestPendingError(Exception):
417
+ pass
418
+
419
+
420
+ class RabbitMQConnectionUnhealthyError(Exception):
421
+ pass
qena_shared_lib/http.py CHANGED
@@ -9,6 +9,7 @@ from fastapi.responses import JSONResponse
9
9
  from fastapi.types import IncEx
10
10
 
11
11
  __all__ = [
12
+ "api_controller",
12
13
  "ApiController",
13
14
  "ControllerBase",
14
15
  "delete",
@@ -41,7 +42,7 @@ class HTTPMethods(Enum):
41
42
  class RouteHandlerMeta:
42
43
  method: HTTPMethods
43
44
  path: str | None = None
44
- response_model: Any | None = Default(None)
45
+ response_model: Any | None = None
45
46
  status_code: int | None = None
46
47
  tags: list[str | Enum] | None = None
47
48
  dependencies: Sequence[Depends] | None = None
@@ -57,7 +58,7 @@ class RouteHandlerMeta:
57
58
  response_model_exclude_defaults: bool = False
58
59
  response_model_exclude_none: bool = False
59
60
  include_in_schema: bool = True
60
- response_class: type[Response] = Default(JSONResponse)
61
+ response_class: type[Response] = JSONResponse
61
62
  name: str | None = None
62
63
  openapi_extra: dict[str, Any] | None = None
63
64
 
@@ -69,7 +70,7 @@ class ApiController:
69
70
  *,
70
71
  tags: list[str | Enum] | None = None,
71
72
  dependencies: Sequence[Depends] | None = None,
72
- default_response_class: type[Response] = Default(JSONResponse),
73
+ default_response_class: type[Response] = JSONResponse,
73
74
  responses: dict[int | str, dict[str, Any]] | None = None,
74
75
  redirect_slashes: bool = True,
75
76
  deprecated: bool | None = None,
@@ -92,6 +93,29 @@ class ApiController:
92
93
  return controller
93
94
 
94
95
 
96
+ def api_controller(
97
+ prefix: str | None = None,
98
+ *,
99
+ tags: list[str | Enum] | None = None,
100
+ dependencies: Sequence[Depends] | None = None,
101
+ default_response_class: type[Response] = JSONResponse,
102
+ responses: dict[int | str, dict[str, Any]] | None = None,
103
+ redirect_slashes: bool = True,
104
+ deprecated: bool | None = None,
105
+ include_in_schema: bool = True,
106
+ ) -> ApiController:
107
+ return ApiController(
108
+ prefix=prefix,
109
+ tags=tags,
110
+ dependencies=dependencies,
111
+ default_response_class=default_response_class,
112
+ responses=responses,
113
+ redirect_slashes=redirect_slashes,
114
+ deprecated=deprecated,
115
+ include_in_schema=include_in_schema,
116
+ )
117
+
118
+
95
119
  def get(
96
120
  path: str | None = None,
97
121
  *,
@@ -102,13 +102,13 @@ class LogstashLogRecord:
102
102
  self, exception: BaseException
103
103
  ) -> dict[str, str]:
104
104
  causes = {}
105
- cause = exception.__cause__
105
+ cause = exception.__cause__ or exception.__context__
106
106
  cause_depth = 0
107
107
 
108
108
  while cause is not None:
109
109
  cause_depth += 1
110
110
  causes[self._depth_to_cause(cause_depth)] = cause.__class__.__name__
111
- cause = cause.__cause__
111
+ cause = cause.__cause__ or cause.__context__
112
112
 
113
113
  return causes
114
114
 
@@ -228,7 +228,6 @@ class BaseLogstashSender(AsyncEventLoopMixin):
228
228
  self._max_log_retry = max_log_retry
229
229
  self._started = False
230
230
  self._closed = False
231
- self._close_future = self.loop.create_future()
232
231
  self._log_queue: Queue[LogstashLogRecord | EndOfLogMarker] = Queue(
233
232
  log_queue_size
234
233
  )
@@ -246,6 +245,7 @@ class BaseLogstashSender(AsyncEventLoopMixin):
246
245
 
247
246
  self._started = True
248
247
  self._closed = False
248
+ self._close_future = self.loop.create_future()
249
249
  _, _ = await gather(
250
250
  self.loop.run_in_executor(executor=None, func=self._hook_on_start),
251
251
  self._hook_on_start_async(),
@@ -2,11 +2,9 @@ from ._base import AbstractRabbitMQService, RabbitMqManager
2
2
  from ._channel import BaseChannel
3
3
  from ._exception_handlers import (
4
4
  handle_general_mq_exception,
5
- handle_microservice_exception,
6
- handle_rabbitmq_exception,
5
+ handle_rabbit_mq_service_exception,
7
6
  handle_validation_error,
8
7
  )
9
- from ._exceptions import RabbitMQException
10
8
  from ._listener import (
11
9
  CONSUMER_ATTRIBUTE,
12
10
  LISTENER_ATTRIBUTE,
@@ -20,7 +18,9 @@ from ._listener import (
20
18
  RetryPolicy,
21
19
  RpcWorker,
22
20
  consume,
21
+ consumer,
23
22
  execute,
23
+ rpc_worker,
24
24
  )
25
25
  from ._publisher import Publisher
26
26
  from ._rpc_client import RpcClient
@@ -31,22 +31,22 @@ __all__ = [
31
31
  "BaseChannel",
32
32
  "consume",
33
33
  "CONSUMER_ATTRIBUTE",
34
+ "consumer",
34
35
  "Consumer",
35
36
  "execute",
36
37
  "FixedRetryDelay",
37
38
  "handle_general_mq_exception",
38
- "handle_microservice_exception",
39
- "handle_rabbitmq_exception",
39
+ "handle_rabbit_mq_service_exception",
40
40
  "handle_validation_error",
41
41
  "LISTENER_ATTRIBUTE",
42
42
  "ListenerBase",
43
43
  "ListenerContext",
44
44
  "Publisher",
45
- "RabbitMQException",
46
45
  "RabbitMqManager",
47
46
  "RetryDelayJitter",
48
47
  "RetryPolicy",
49
48
  "RPC_WORKER_ATTRIBUTE",
49
+ "rpc_worker",
50
50
  "RpcClient",
51
51
  "RpcWorker",
52
52
  ]