fastapi-factory-utilities 0.1.0__py3-none-any.whl → 0.2.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.
Potentially problematic release.
This version of fastapi-factory-utilities might be problematic. Click here for more details.
- fastapi_factory_utilities/core/api/v1/sys/health.py +56 -12
- fastapi_factory_utilities/core/api/v1/sys/readiness.py +19 -12
- fastapi_factory_utilities/core/app/__init__.py +7 -12
- fastapi_factory_utilities/core/app/application.py +133 -0
- fastapi_factory_utilities/core/app/builder.py +123 -0
- fastapi_factory_utilities/core/app/config.py +164 -0
- fastapi_factory_utilities/core/app/exceptions.py +20 -0
- fastapi_factory_utilities/core/app/fastapi_builder.py +85 -0
- fastapi_factory_utilities/core/app/plugin_manager/__init__.py +15 -0
- fastapi_factory_utilities/core/app/plugin_manager/exceptions.py +33 -0
- fastapi_factory_utilities/core/app/plugin_manager/plugin_manager.py +190 -0
- fastapi_factory_utilities/core/exceptions.py +43 -0
- fastapi_factory_utilities/core/plugins/__init__.py +21 -0
- fastapi_factory_utilities/core/plugins/example/__init__.py +31 -0
- fastapi_factory_utilities/core/plugins/httpx_plugin/__init__.py +31 -0
- fastapi_factory_utilities/core/plugins/odm_plugin/__init__.py +74 -17
- fastapi_factory_utilities/core/plugins/odm_plugin/builder.py +27 -35
- fastapi_factory_utilities/core/plugins/odm_plugin/configs.py +1 -3
- fastapi_factory_utilities/core/plugins/odm_plugin/depends.py +30 -0
- fastapi_factory_utilities/core/plugins/opentelemetry_plugin/__init__.py +5 -5
- fastapi_factory_utilities/core/plugins/opentelemetry_plugin/builder.py +7 -7
- fastapi_factory_utilities/core/protocols.py +19 -16
- fastapi_factory_utilities/core/services/status/__init__.py +14 -0
- fastapi_factory_utilities/core/services/status/enums.py +30 -0
- fastapi_factory_utilities/core/services/status/exceptions.py +27 -0
- fastapi_factory_utilities/core/services/status/health_calculator_strategies.py +48 -0
- fastapi_factory_utilities/core/services/status/readiness_calculator_strategies.py +41 -0
- fastapi_factory_utilities/core/services/status/services.py +218 -0
- fastapi_factory_utilities/core/services/status/types.py +128 -0
- fastapi_factory_utilities/core/utils/configs.py +1 -1
- fastapi_factory_utilities/core/utils/status.py +71 -0
- fastapi_factory_utilities/core/utils/uvicorn.py +7 -8
- fastapi_factory_utilities/example/__init__.py +3 -3
- fastapi_factory_utilities/example/api/books/routes.py +7 -10
- fastapi_factory_utilities/example/app.py +50 -0
- fastapi_factory_utilities/example/application.yaml +5 -9
- fastapi_factory_utilities/example/services/books/__init__.py +2 -2
- fastapi_factory_utilities/example/services/books/services.py +9 -0
- {fastapi_factory_utilities-0.1.0.dist-info → fastapi_factory_utilities-0.2.0.dist-info}/METADATA +6 -4
- fastapi_factory_utilities-0.2.0.dist-info/RECORD +70 -0
- {fastapi_factory_utilities-0.1.0.dist-info → fastapi_factory_utilities-0.2.0.dist-info}/WHEEL +1 -1
- fastapi_factory_utilities/core/app/base/__init__.py +0 -17
- fastapi_factory_utilities/core/app/base/application.py +0 -123
- fastapi_factory_utilities/core/app/base/config_abstract.py +0 -78
- fastapi_factory_utilities/core/app/base/exceptions.py +0 -25
- fastapi_factory_utilities/core/app/base/fastapi_application_abstract.py +0 -88
- fastapi_factory_utilities/core/app/base/plugins_manager_abstract.py +0 -136
- fastapi_factory_utilities/example/app/__init__.py +0 -6
- fastapi_factory_utilities/example/app/app.py +0 -37
- fastapi_factory_utilities/example/app/config.py +0 -12
- fastapi_factory_utilities-0.1.0.dist-info/RECORD +0 -58
- {fastapi_factory_utilities-0.1.0.dist-info → fastapi_factory_utilities-0.2.0.dist-info}/LICENSE +0 -0
- {fastapi_factory_utilities-0.1.0.dist-info → fastapi_factory_utilities-0.2.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"""Provides the module for the ODM plugin."""
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import time
|
|
5
|
-
from typing import Any, Self
|
|
4
|
+
from typing import Any, ClassVar, Self
|
|
6
5
|
|
|
7
6
|
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
|
|
8
7
|
from structlog.stdlib import get_logger
|
|
9
8
|
|
|
10
|
-
from fastapi_factory_utilities.core.protocols import
|
|
9
|
+
from fastapi_factory_utilities.core.protocols import ApplicationAbstractProtocol
|
|
11
10
|
from fastapi_factory_utilities.core.utils.importlib import get_path_file_in_package
|
|
12
11
|
from fastapi_factory_utilities.core.utils.yaml_reader import (
|
|
13
12
|
UnableToReadYamlFileError,
|
|
@@ -38,9 +37,12 @@ class ODMBuilder:
|
|
|
38
37
|
|
|
39
38
|
"""
|
|
40
39
|
|
|
40
|
+
MS_TO_S: ClassVar[int] = 1000
|
|
41
|
+
SLEEP_TIME_S: ClassVar[float] = 0.001
|
|
42
|
+
|
|
41
43
|
def __init__(
|
|
42
44
|
self,
|
|
43
|
-
application:
|
|
45
|
+
application: ApplicationAbstractProtocol,
|
|
44
46
|
odm_config: ODMConfig | None = None,
|
|
45
47
|
odm_client: AsyncIOMotorClient[Any] | None = None,
|
|
46
48
|
odm_database: AsyncIOMotorDatabase[Any] | None = None,
|
|
@@ -54,7 +56,7 @@ class ODMBuilder:
|
|
|
54
56
|
odm_database (AsyncIOMotorDatabase): The ODM database for injection. (Default is None)
|
|
55
57
|
|
|
56
58
|
"""
|
|
57
|
-
self._application:
|
|
59
|
+
self._application: ApplicationAbstractProtocol = application
|
|
58
60
|
self._config: ODMConfig | None = odm_config
|
|
59
61
|
self._odm_client: AsyncIOMotorClient[Any] | None = odm_client
|
|
60
62
|
self._odm_database: AsyncIOMotorDatabase[Any] | None = odm_database
|
|
@@ -123,40 +125,30 @@ class ODMBuilder:
|
|
|
123
125
|
return self
|
|
124
126
|
|
|
125
127
|
@classmethod
|
|
126
|
-
def _wait_client_to_be_ready(cls, client: AsyncIOMotorClient[Any],
|
|
128
|
+
def _wait_client_to_be_ready(cls, client: AsyncIOMotorClient[Any], timeout_s: int) -> None:
|
|
127
129
|
"""Wait for the ODM client to be ready.
|
|
128
130
|
|
|
129
131
|
Args:
|
|
130
132
|
client (AsyncIOMotorClient): The ODM client.
|
|
131
|
-
|
|
133
|
+
timeout_s (int): The timeout in seconds.
|
|
132
134
|
|
|
133
135
|
Raises:
|
|
134
|
-
|
|
136
|
+
TimeoutError: If the ODM client is not ready in the given timeout.
|
|
135
137
|
"""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
while not loop.run_in_executor(None, asyncio.Task(is_connected(client))) and ( # type: ignore
|
|
152
|
-
(time.monotonic() - start_timer) < timeout_ms
|
|
153
|
-
):
|
|
154
|
-
if time.monotonic() - start_timer > timeout_ms:
|
|
155
|
-
raise ODMPluginConfigError("ODM client is not ready.")
|
|
156
|
-
time.sleep(0.01)
|
|
157
|
-
|
|
158
|
-
if not loop.run_in_executor(None, asyncio.Task(is_connected(client))): # type: ignore
|
|
159
|
-
raise ODMPluginConfigError("ODM client is not ready.")
|
|
138
|
+
start_time: float = time.time()
|
|
139
|
+
message_time: float = time.time()
|
|
140
|
+
while (time.time() - start_time) < (timeout_s):
|
|
141
|
+
if len(client.nodes) > 0: # type: ignore
|
|
142
|
+
_logger.info(f"Waiting {(time.time() - start_time)*cls.MS_TO_S}ms for the ODM client to be ready.")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
if (time.time() - message_time) > 1:
|
|
146
|
+
elaps_time: float = time.time() - start_time
|
|
147
|
+
_logger.debug(f"Waiting for the ODM client to be ready. (from {int(elaps_time)}s) ")
|
|
148
|
+
message_time = time.time()
|
|
149
|
+
time.sleep(cls.SLEEP_TIME_S)
|
|
150
|
+
|
|
151
|
+
raise TimeoutError("The ODM client is not ready in the given timeout.")
|
|
160
152
|
|
|
161
153
|
def build_client(
|
|
162
154
|
self,
|
|
@@ -181,11 +173,11 @@ class ODMBuilder:
|
|
|
181
173
|
self._odm_client = AsyncIOMotorClient(
|
|
182
174
|
host=self._config.uri,
|
|
183
175
|
connect=True,
|
|
184
|
-
connectTimeoutMS=self._config.
|
|
185
|
-
serverSelectionTimeoutMS=self._config.
|
|
176
|
+
connectTimeoutMS=self._config.connection_timeout_s,
|
|
177
|
+
serverSelectionTimeoutMS=self._config.connection_timeout_s,
|
|
186
178
|
)
|
|
187
179
|
|
|
188
|
-
self._wait_client_to_be_ready(client=self._odm_client,
|
|
180
|
+
self._wait_client_to_be_ready(client=self._odm_client, timeout_s=self._config.connection_timeout_s)
|
|
189
181
|
|
|
190
182
|
return self
|
|
191
183
|
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, ConfigDict
|
|
4
4
|
|
|
5
|
-
S_TO_MS = 1000
|
|
6
|
-
|
|
7
5
|
|
|
8
6
|
class ODMConfig(BaseModel):
|
|
9
7
|
"""Provides the configuration model for the ODM plugin."""
|
|
@@ -14,4 +12,4 @@ class ODMConfig(BaseModel):
|
|
|
14
12
|
|
|
15
13
|
database: str = "test"
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
connection_timeout_s: int = 10
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Provide FastAPI dependency for ODM."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from fastapi import Request
|
|
6
|
+
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def depends_odm_client(request: Request) -> AsyncIOMotorClient[Any]:
|
|
10
|
+
"""Acquire the ODM client from the request.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
request (Request): The request.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
AsyncIOMotorClient: The ODM client.
|
|
17
|
+
"""
|
|
18
|
+
return request.app.state.odm_client
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def depends_odm_database(request: Request) -> AsyncIOMotorDatabase[Any]:
|
|
22
|
+
"""Acquire the ODM database from the request.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
request (Request): The request.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
AsyncIOMotorClient: The ODM database.
|
|
29
|
+
"""
|
|
30
|
+
return request.app.state.odm_database
|
|
@@ -10,7 +10,7 @@ from opentelemetry.sdk.metrics import MeterProvider
|
|
|
10
10
|
from opentelemetry.sdk.trace import TracerProvider
|
|
11
11
|
from structlog.stdlib import BoundLogger, get_logger
|
|
12
12
|
|
|
13
|
-
from fastapi_factory_utilities.core.protocols import
|
|
13
|
+
from fastapi_factory_utilities.core.protocols import ApplicationAbstractProtocol
|
|
14
14
|
|
|
15
15
|
from .builder import OpenTelemetryPluginBuilder
|
|
16
16
|
from .configs import OpenTelemetryConfig
|
|
@@ -26,7 +26,7 @@ __all__: list[str] = [
|
|
|
26
26
|
_logger: BoundLogger = get_logger()
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def pre_conditions_check(application:
|
|
29
|
+
def pre_conditions_check(application: ApplicationAbstractProtocol) -> bool:
|
|
30
30
|
"""Check the pre-conditions for the OpenTelemetry plugin.
|
|
31
31
|
|
|
32
32
|
Args:
|
|
@@ -40,7 +40,7 @@ def pre_conditions_check(application: BaseApplicationProtocol) -> bool:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def on_load(
|
|
43
|
-
application:
|
|
43
|
+
application: ApplicationAbstractProtocol,
|
|
44
44
|
) -> None:
|
|
45
45
|
"""Actions to perform on load for the OpenTelemetry plugin.
|
|
46
46
|
|
|
@@ -71,7 +71,7 @@ def on_load(
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
async def on_startup(
|
|
74
|
-
application:
|
|
74
|
+
application: ApplicationAbstractProtocol,
|
|
75
75
|
) -> None:
|
|
76
76
|
"""Actions to perform on startup for the OpenTelemetry plugin.
|
|
77
77
|
|
|
@@ -85,7 +85,7 @@ async def on_startup(
|
|
|
85
85
|
_logger.debug("OpenTelemetry plugin started.")
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
async def on_shutdown(application:
|
|
88
|
+
async def on_shutdown(application: ApplicationAbstractProtocol) -> None:
|
|
89
89
|
"""Actions to perform on shutdown for the OpenTelemetry plugin.
|
|
90
90
|
|
|
91
91
|
Args:
|
|
@@ -24,7 +24,7 @@ from fastapi_factory_utilities.core.plugins.opentelemetry_plugin.configs import
|
|
|
24
24
|
OpenTelemetryMeterConfig,
|
|
25
25
|
OpenTelemetryTracerConfig,
|
|
26
26
|
)
|
|
27
|
-
from fastapi_factory_utilities.core.protocols import
|
|
27
|
+
from fastapi_factory_utilities.core.protocols import ApplicationAbstractProtocol
|
|
28
28
|
from fastapi_factory_utilities.core.utils.importlib import get_path_file_in_package
|
|
29
29
|
from fastapi_factory_utilities.core.utils.yaml_reader import (
|
|
30
30
|
UnableToReadYamlFileError,
|
|
@@ -38,13 +38,13 @@ from .exceptions import OpenTelemetryPluginConfigError
|
|
|
38
38
|
class OpenTelemetryPluginBuilder:
|
|
39
39
|
"""Configure the injection bindings for OpenTelemetryPlugin."""
|
|
40
40
|
|
|
41
|
-
def __init__(self, application:
|
|
41
|
+
def __init__(self, application: ApplicationAbstractProtocol) -> None:
|
|
42
42
|
"""Instantiate the OpenTelemetryPluginFactory.
|
|
43
43
|
|
|
44
44
|
Args:
|
|
45
45
|
application (BaseApplicationProtocol): The application object.
|
|
46
46
|
"""
|
|
47
|
-
self._application:
|
|
47
|
+
self._application: ApplicationAbstractProtocol = application
|
|
48
48
|
self._resource: Resource | None = None
|
|
49
49
|
self._config: OpenTelemetryConfig | None = None
|
|
50
50
|
self._meter_provider: MeterProvider | None = None
|
|
@@ -94,10 +94,10 @@ class OpenTelemetryPluginBuilder:
|
|
|
94
94
|
"""
|
|
95
95
|
self._resource = Resource(
|
|
96
96
|
attributes={
|
|
97
|
-
DEPLOYMENT_ENVIRONMENT: self._application.get_config().environment.value,
|
|
98
|
-
SERVICE_NAME: self._application.get_config().service_name,
|
|
99
|
-
SERVICE_NAMESPACE: self._application.get_config().service_namespace,
|
|
100
|
-
SERVICE_VERSION: self._application.get_config().version,
|
|
97
|
+
DEPLOYMENT_ENVIRONMENT: self._application.get_config().application.environment.value,
|
|
98
|
+
SERVICE_NAME: self._application.get_config().application.service_name,
|
|
99
|
+
SERVICE_NAMESPACE: self._application.get_config().application.service_namespace,
|
|
100
|
+
SERVICE_VERSION: self._application.get_config().application.version,
|
|
101
101
|
}
|
|
102
102
|
)
|
|
103
103
|
return self
|
|
@@ -6,39 +6,42 @@ from typing import TYPE_CHECKING, ClassVar, Protocol, runtime_checkable
|
|
|
6
6
|
from beanie import Document
|
|
7
7
|
from fastapi import FastAPI
|
|
8
8
|
|
|
9
|
+
from fastapi_factory_utilities.core.plugins import PluginsEnum
|
|
10
|
+
from fastapi_factory_utilities.core.services.status.services import StatusService
|
|
11
|
+
|
|
9
12
|
if TYPE_CHECKING:
|
|
10
|
-
from fastapi_factory_utilities.core.app.
|
|
11
|
-
|
|
12
|
-
)
|
|
13
|
+
from fastapi_factory_utilities.core.app.config import RootConfig
|
|
14
|
+
from fastapi_factory_utilities.core.plugins import PluginState
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
class
|
|
17
|
+
class ApplicationAbstractProtocol(Protocol):
|
|
16
18
|
"""Protocol for the base application."""
|
|
17
19
|
|
|
18
|
-
PACKAGE_NAME: str
|
|
20
|
+
PACKAGE_NAME: ClassVar[str]
|
|
19
21
|
|
|
20
22
|
ODM_DOCUMENT_MODELS: ClassVar[list[type[Document]]]
|
|
21
23
|
|
|
24
|
+
DEFAULT_PLUGINS_ACTIVATED: ClassVar[list[PluginsEnum]]
|
|
25
|
+
|
|
22
26
|
@abstractmethod
|
|
23
|
-
def get_config(self) -> "
|
|
27
|
+
def get_config(self) -> "RootConfig":
|
|
24
28
|
"""Get the application configuration."""
|
|
25
29
|
|
|
26
30
|
@abstractmethod
|
|
27
31
|
def get_asgi_app(self) -> FastAPI:
|
|
28
32
|
"""Get the ASGI application."""
|
|
29
33
|
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def get_status_service(self) -> StatusService:
|
|
36
|
+
"""Get the status service."""
|
|
37
|
+
|
|
30
38
|
|
|
31
39
|
@runtime_checkable
|
|
32
40
|
class PluginProtocol(Protocol):
|
|
33
|
-
"""Defines the protocol for the
|
|
34
|
-
|
|
35
|
-
Attributes:
|
|
36
|
-
INJECTOR_MODULE (type[Module]): The module for the plugin.
|
|
37
|
-
|
|
38
|
-
"""
|
|
41
|
+
"""Defines the protocol for the plugins."""
|
|
39
42
|
|
|
40
43
|
@abstractmethod
|
|
41
|
-
def pre_conditions_check(self, application:
|
|
44
|
+
def pre_conditions_check(self, application: ApplicationAbstractProtocol) -> bool:
|
|
42
45
|
"""Check the pre-conditions for the plugin.
|
|
43
46
|
|
|
44
47
|
Args:
|
|
@@ -49,7 +52,7 @@ class PluginProtocol(Protocol):
|
|
|
49
52
|
"""
|
|
50
53
|
|
|
51
54
|
@abstractmethod
|
|
52
|
-
def on_load(self, application:
|
|
55
|
+
def on_load(self, application: ApplicationAbstractProtocol) -> list["PluginState"] | None:
|
|
53
56
|
"""The actions to perform on load for the plugin.
|
|
54
57
|
|
|
55
58
|
Args:
|
|
@@ -60,7 +63,7 @@ class PluginProtocol(Protocol):
|
|
|
60
63
|
"""
|
|
61
64
|
|
|
62
65
|
@abstractmethod
|
|
63
|
-
async def on_startup(self, application:
|
|
66
|
+
async def on_startup(self, application: ApplicationAbstractProtocol) -> list["PluginState"] | None:
|
|
64
67
|
"""The actions to perform on startup for the plugin.
|
|
65
68
|
|
|
66
69
|
Args:
|
|
@@ -71,7 +74,7 @@ class PluginProtocol(Protocol):
|
|
|
71
74
|
"""
|
|
72
75
|
|
|
73
76
|
@abstractmethod
|
|
74
|
-
async def on_shutdown(self, application:
|
|
77
|
+
async def on_shutdown(self, application: ApplicationAbstractProtocol) -> None:
|
|
75
78
|
"""The actions to perform on shutdown for the plugin.
|
|
76
79
|
|
|
77
80
|
Args:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Package covering the StatusService."""
|
|
2
|
+
|
|
3
|
+
from .enums import ComponentTypeEnum, HealthStatusEnum, ReadinessStatusEnum
|
|
4
|
+
from .services import StatusService
|
|
5
|
+
from .types import ComponentInstanceType, Status
|
|
6
|
+
|
|
7
|
+
__all__: list[str] = [
|
|
8
|
+
"ComponentTypeEnum",
|
|
9
|
+
"ComponentInstanceType",
|
|
10
|
+
"HealthStatusEnum",
|
|
11
|
+
"ReadinessStatusEnum",
|
|
12
|
+
"StatusService",
|
|
13
|
+
"Status",
|
|
14
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Provides the status enums for the service."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HealthStatusEnum(StrEnum):
|
|
7
|
+
"""Health status enum."""
|
|
8
|
+
|
|
9
|
+
HEALTHY = "healthy"
|
|
10
|
+
UNHEALTHY = "unhealthy"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ReadinessStatusEnum(StrEnum):
|
|
14
|
+
"""Readiness status enum."""
|
|
15
|
+
|
|
16
|
+
READY = "ready"
|
|
17
|
+
NOT_READY = "not_ready"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ComponentTypeEnum(StrEnum):
|
|
21
|
+
"""Component type enum."""
|
|
22
|
+
|
|
23
|
+
SERVICE = "service"
|
|
24
|
+
DATABASE = "database"
|
|
25
|
+
CACHE = "cache"
|
|
26
|
+
STORAGE = "storage"
|
|
27
|
+
MESSAGE_BROKER = "message_broker"
|
|
28
|
+
SEARCH_ENGINE = "search_engine"
|
|
29
|
+
TASK_QUEUE = "task_queue"
|
|
30
|
+
OTHER = "other"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Provides exceptions for the status service."""
|
|
2
|
+
|
|
3
|
+
from fastapi_factory_utilities.core.exceptions import FastAPIFactoryUtilitiesError
|
|
4
|
+
from fastapi_factory_utilities.core.services.status.types import ComponentInstanceType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StatusServiceError(FastAPIFactoryUtilitiesError):
|
|
8
|
+
"""Status service error."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ComponentRegistrationError(StatusServiceError):
|
|
12
|
+
"""Component registration error."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
component_instance: ComponentInstanceType,
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Initialize the component registration error.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
component_instance (ComponentInstanceType): The component instance.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
super().__init__(
|
|
25
|
+
message="An error occurred while registering the component instance.",
|
|
26
|
+
component_instance=component_instance, # type: ignore
|
|
27
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Health calculator strategies."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol, runtime_checkable
|
|
4
|
+
|
|
5
|
+
from .enums import HealthStatusEnum
|
|
6
|
+
from .types import ComponentInstanceKey, Status
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class HealthCalculatorStrategy(Protocol):
|
|
11
|
+
"""Health calculator strategy."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, components_status: dict[ComponentInstanceKey, Status]) -> None:
|
|
14
|
+
"""Initialize the health calculator.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
components_status (dict[str, Status]): The components status.
|
|
18
|
+
"""
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
|
|
21
|
+
def calculate(self) -> HealthStatusEnum:
|
|
22
|
+
"""Calculate the health status."""
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HealthSimpleCalculatorStrategy:
|
|
27
|
+
"""Health calculator.
|
|
28
|
+
|
|
29
|
+
This class calculates the health status based on the components status.
|
|
30
|
+
It's a simple implementation that returns unhealthy if at least one component is unhealthy for now.
|
|
31
|
+
We want to enhance this class to support more complex health calculation in the future.
|
|
32
|
+
( )
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, components_status: dict[ComponentInstanceKey, Status]) -> None:
|
|
36
|
+
"""Initialize the health calculator.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
components_status (dict[str, Status]): The components status.
|
|
40
|
+
"""
|
|
41
|
+
self._components_status: dict[ComponentInstanceKey, Status] = components_status
|
|
42
|
+
|
|
43
|
+
def calculate(self) -> HealthStatusEnum:
|
|
44
|
+
"""Calculate the health status."""
|
|
45
|
+
for status in self._components_status.values():
|
|
46
|
+
if status["health"] != HealthStatusEnum.HEALTHY:
|
|
47
|
+
return HealthStatusEnum.UNHEALTHY
|
|
48
|
+
return HealthStatusEnum.HEALTHY
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Readiness calculator strategies."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from .enums import ReadinessStatusEnum
|
|
6
|
+
from .types import ComponentInstanceKey, Status
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ReadinessCalculatorStrategy(Protocol):
|
|
10
|
+
"""Readiness calculator strategy."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, components_status: dict[ComponentInstanceKey, Status]) -> None:
|
|
13
|
+
"""Initialize the readiness calculator.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
components_status (dict[str, Status]): The components status.
|
|
17
|
+
"""
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
|
|
20
|
+
def calculate(self) -> ReadinessStatusEnum:
|
|
21
|
+
"""Calculate the readiness status."""
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ReadinessSimpleCalculatorStrategy:
|
|
26
|
+
"""Readiness calculator."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, components_status: dict[ComponentInstanceKey, Status]) -> None:
|
|
29
|
+
"""Initialize the readiness calculator.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
components_status (dict[str, Status]): The components status.
|
|
33
|
+
"""
|
|
34
|
+
self._components_status: dict[ComponentInstanceKey, Status] = components_status
|
|
35
|
+
|
|
36
|
+
def calculate(self) -> ReadinessStatusEnum:
|
|
37
|
+
"""Calculate the readiness status."""
|
|
38
|
+
for status in self._components_status.values():
|
|
39
|
+
if status["readiness"] != ReadinessStatusEnum.READY:
|
|
40
|
+
return ReadinessStatusEnum.NOT_READY
|
|
41
|
+
return ReadinessStatusEnum.READY
|