qena-shared-lib 0.1.7__tar.gz → 0.1.8__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.
Files changed (44) hide show
  1. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/.pre-commit-config.yaml +9 -1
  2. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/PKG-INFO +10 -11
  3. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/pyproject.toml +15 -16
  4. qena_shared_lib-0.1.8/requirements.txt +55 -0
  5. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/application.py +2 -2
  6. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/exception_handlers.py +67 -42
  7. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/exceptions.py +187 -85
  8. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/http.py +27 -3
  9. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/logstash/_base.py +2 -2
  10. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/rabbitmq/__init__.py +6 -6
  11. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/rabbitmq/_base.py +20 -17
  12. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/rabbitmq/_exception_handlers.py +56 -69
  13. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/rabbitmq/_listener.py +109 -42
  14. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/rabbitmq/_publisher.py +2 -1
  15. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/rabbitmq/_rpc_client.py +13 -6
  16. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/scheduler.py +5 -0
  17. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/security.py +3 -3
  18. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/tests/test_application.py +13 -13
  19. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/tests/test_logstash.py +50 -16
  20. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/tests/test_rabbitmq.py +109 -32
  21. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/tests/test_scheduler.py +3 -2
  22. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/tests/test_security.py +20 -18
  23. qena_shared_lib-0.1.8/tests/utils.py +5 -0
  24. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/uv.lock +16 -18
  25. qena_shared_lib-0.1.7/requirements.txt +0 -18
  26. qena_shared_lib-0.1.7/src/qena_shared_lib/rabbitmq/_exceptions.py +0 -46
  27. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/.gitignore +0 -0
  28. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/README.md +0 -0
  29. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/__init__.py +0 -0
  30. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/background.py +0 -0
  31. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/dependencies/__init__.py +0 -0
  32. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/dependencies/http.py +0 -0
  33. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/dependencies/miscellaneous.py +0 -0
  34. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/logging.py +0 -0
  35. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/logstash/__init__.py +0 -0
  36. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/logstash/_http_sender.py +0 -0
  37. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/logstash/_tcp_sender.py +0 -0
  38. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/py.typed +0 -0
  39. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/rabbitmq/_channel.py +0 -0
  40. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/rabbitmq/_pool.py +0 -0
  41. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/src/qena_shared_lib/utils.py +0 -0
  42. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/tests/conftest.py +0 -0
  43. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/tests/test_background.py +0 -0
  44. {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.8}/tests/test_dependencies.py +0 -0
@@ -5,9 +5,17 @@ repos:
5
5
  - id: check-yaml
6
6
  - id: end-of-file-fixer
7
7
  - id: trailing-whitespace
8
+ - id: check-toml
9
+ - id: name-tests-test
10
+ args: [ --pytest-test-first ]
11
+ - id: check-added-large-files
8
12
  - repo: https://github.com/astral-sh/ruff-pre-commit
9
- rev: v0.9.2
13
+ rev: v0.9.9
10
14
  hooks:
11
15
  - id: ruff
12
16
  args: [ --fix ]
13
17
  - id: ruff-format
18
+ - repo: https://github.com/pre-commit/mirrors-mypy
19
+ rev: v1.15.0
20
+ hooks:
21
+ - id: mypy
@@ -1,18 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qena-shared-lib
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: A shared tools for other services
5
5
  Requires-Python: >=3.10
6
- Requires-Dist: cronsim~=2.0
7
- Requires-Dist: fastapi[all]~=0.115.0
8
- Requires-Dist: httpx~=0.27.0
9
- Requires-Dist: jwt~=1.3.0
10
- Requires-Dist: passlib[bcrypt]~=1.7.0
11
- Requires-Dist: pika~=1.3.0
12
- Requires-Dist: prometheus-client~=0.21.0
13
- Requires-Dist: prometheus-fastapi-instrumentator~=7.0.0
14
- Requires-Dist: punq~=0.7.0
15
- Requires-Dist: pydantic~=2.10.0
6
+ Requires-Dist: cronsim==2.6
7
+ Requires-Dist: fastapi[all]==0.115.6
8
+ Requires-Dist: httpx==0.27.2
9
+ Requires-Dist: jwt==1.3.1
10
+ Requires-Dist: passlib[bcrypt]==1.7.4
11
+ Requires-Dist: pika==1.3.2
12
+ Requires-Dist: prometheus-client==0.21.1
13
+ Requires-Dist: prometheus-fastapi-instrumentator==7.0.2
14
+ Requires-Dist: punq==0.7.0
16
15
  Description-Content-Type: text/markdown
17
16
 
18
17
  # Qena shared lib
@@ -1,20 +1,19 @@
1
1
  [project]
2
2
  name = "qena-shared-lib"
3
- version = "0.1.7"
3
+ version = "0.1.8"
4
4
  description = "A shared tools for other services"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  dependencies = [
8
- "cronsim~=2.0",
9
- "fastapi[all]~=0.115.0",
10
- "httpx~=0.27.0",
11
- "jwt~=1.3.0",
12
- "passlib[bcrypt]~=1.7.0",
13
- "pika~=1.3.0",
14
- "prometheus-client~=0.21.0",
15
- "prometheus-fastapi-instrumentator~=7.0.0",
16
- "punq~=0.7.0",
17
- "pydantic~=2.10.0",
8
+ "cronsim==2.6",
9
+ "fastapi[all]==0.115.6",
10
+ "httpx==0.27.2",
11
+ "jwt==1.3.1",
12
+ "passlib[bcrypt]==1.7.4",
13
+ "pika==1.3.2",
14
+ "prometheus-client==0.21.1",
15
+ "prometheus-fastapi-instrumentator==7.0.2",
16
+ "punq==0.7.0",
18
17
  ]
19
18
 
20
19
  [build-system]
@@ -23,11 +22,11 @@ build-backend = "hatchling.build"
23
22
 
24
23
  [tool.uv]
25
24
  dev-dependencies = [
26
- "pre-commit~=4.0.0",
27
- "pytest-asyncio~=0.24.0",
28
- "pytest~=8.3.0",
29
- "testcontainers~=4.8.0",
30
- "pytest-cov~=6.0.0",
25
+ "pre-commit==4.0.1",
26
+ "pytest-asyncio==0.24.0",
27
+ "pytest==8.3.3",
28
+ "testcontainers==4.8.2",
29
+ "pytest-cov==6.0.0",
31
30
  ]
32
31
 
33
32
  [tool.ruff]
@@ -0,0 +1,55 @@
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv export --no-dev --no-hashes --format requirements-txt
3
+ -e .
4
+ annotated-types==0.7.0
5
+ anyio==4.6.0
6
+ bcrypt==4.2.1
7
+ certifi==2024.8.30
8
+ cffi==1.17.1 ; platform_python_implementation != 'PyPy'
9
+ click==8.1.8
10
+ colorama==0.4.6 ; sys_platform == 'win32'
11
+ cronsim==2.6
12
+ cryptography==44.0.0
13
+ dnspython==2.7.0
14
+ email-validator==2.2.0
15
+ exceptiongroup==1.2.2 ; python_full_version < '3.11'
16
+ fastapi==0.115.6
17
+ fastapi-cli==0.0.7
18
+ h11==0.14.0
19
+ httpcore==1.0.6
20
+ httptools==0.6.4
21
+ httpx==0.27.2
22
+ idna==3.10
23
+ itsdangerous==2.2.0
24
+ jinja2==3.1.5
25
+ jwt==1.3.1
26
+ markdown-it-py==3.0.0
27
+ markupsafe==3.0.2
28
+ mdurl==0.1.2
29
+ orjson==3.10.12
30
+ passlib==1.7.4
31
+ pika==1.3.2
32
+ prometheus-client==0.21.1
33
+ prometheus-fastapi-instrumentator==7.0.2
34
+ punq==0.7.0
35
+ pycparser==2.22 ; platform_python_implementation != 'PyPy'
36
+ pydantic==2.10.3
37
+ pydantic-core==2.27.1
38
+ pydantic-extra-types==2.10.1
39
+ pydantic-settings==2.7.0
40
+ pygments==2.18.0
41
+ python-dotenv==1.0.1
42
+ python-multipart==0.0.20
43
+ pyyaml==6.0.2
44
+ rich==13.9.4
45
+ rich-toolkit==0.12.0
46
+ shellingham==1.5.4
47
+ sniffio==1.3.1
48
+ starlette==0.41.3
49
+ typer==0.15.1
50
+ typing-extensions==4.12.2
51
+ ujson==5.10.0
52
+ uvicorn==0.34.0
53
+ uvloop==0.21.0 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'
54
+ watchfiles==1.0.3
55
+ websockets==14.1
@@ -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