fastapi-factory-utilities 0.3.3__py3-none-any.whl → 0.8.2__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.
- fastapi_factory_utilities/core/api/__init__.py +1 -1
- fastapi_factory_utilities/core/api/v1/sys/health.py +1 -1
- fastapi_factory_utilities/core/app/__init__.py +4 -4
- fastapi_factory_utilities/core/app/application.py +22 -26
- fastapi_factory_utilities/core/app/builder.py +19 -35
- fastapi_factory_utilities/core/app/config.py +2 -0
- fastapi_factory_utilities/core/app/fastapi_builder.py +3 -2
- fastapi_factory_utilities/core/exceptions.py +64 -28
- fastapi_factory_utilities/core/plugins/__init__.py +2 -31
- fastapi_factory_utilities/core/plugins/abstracts.py +40 -0
- fastapi_factory_utilities/core/plugins/aiopika/__init__.py +25 -0
- fastapi_factory_utilities/core/plugins/aiopika/abstract.py +48 -0
- fastapi_factory_utilities/core/plugins/aiopika/configs.py +85 -0
- fastapi_factory_utilities/core/plugins/aiopika/depends.py +20 -0
- fastapi_factory_utilities/core/plugins/aiopika/exceptions.py +29 -0
- fastapi_factory_utilities/core/plugins/aiopika/exchange.py +69 -0
- fastapi_factory_utilities/core/plugins/aiopika/listener/__init__.py +7 -0
- fastapi_factory_utilities/core/plugins/aiopika/listener/abstract.py +72 -0
- fastapi_factory_utilities/core/plugins/aiopika/message.py +86 -0
- fastapi_factory_utilities/core/plugins/aiopika/plugins.py +84 -0
- fastapi_factory_utilities/core/plugins/aiopika/publisher/__init__.py +7 -0
- fastapi_factory_utilities/core/plugins/aiopika/publisher/abstract.py +66 -0
- fastapi_factory_utilities/core/plugins/aiopika/queue.py +88 -0
- fastapi_factory_utilities/core/plugins/odm_plugin/__init__.py +14 -157
- fastapi_factory_utilities/core/plugins/odm_plugin/builder.py +4 -3
- fastapi_factory_utilities/core/plugins/odm_plugin/configs.py +1 -1
- fastapi_factory_utilities/core/plugins/odm_plugin/documents.py +1 -1
- fastapi_factory_utilities/core/plugins/odm_plugin/helpers.py +16 -0
- fastapi_factory_utilities/core/plugins/odm_plugin/plugins.py +155 -0
- fastapi_factory_utilities/core/plugins/odm_plugin/repositories.py +12 -23
- fastapi_factory_utilities/core/plugins/opentelemetry_plugin/__init__.py +8 -115
- fastapi_factory_utilities/core/plugins/opentelemetry_plugin/instruments/__init__.py +85 -0
- fastapi_factory_utilities/core/plugins/opentelemetry_plugin/plugins.py +137 -0
- fastapi_factory_utilities/core/plugins/taskiq_plugins/__init__.py +31 -0
- fastapi_factory_utilities/core/plugins/taskiq_plugins/configs.py +12 -0
- fastapi_factory_utilities/core/plugins/taskiq_plugins/depends.py +51 -0
- fastapi_factory_utilities/core/plugins/taskiq_plugins/exceptions.py +13 -0
- fastapi_factory_utilities/core/plugins/taskiq_plugins/plugin.py +41 -0
- fastapi_factory_utilities/core/plugins/taskiq_plugins/schedulers.py +187 -0
- fastapi_factory_utilities/core/protocols.py +1 -54
- fastapi_factory_utilities/core/security/__init__.py +5 -0
- fastapi_factory_utilities/core/security/abstracts.py +42 -0
- fastapi_factory_utilities/core/security/jwt/__init__.py +41 -0
- fastapi_factory_utilities/core/security/jwt/configs.py +32 -0
- fastapi_factory_utilities/core/security/jwt/decoders.py +130 -0
- fastapi_factory_utilities/core/security/jwt/exceptions.py +23 -0
- fastapi_factory_utilities/core/security/jwt/objects.py +107 -0
- fastapi_factory_utilities/core/security/jwt/services.py +176 -0
- fastapi_factory_utilities/core/security/jwt/stores.py +43 -0
- fastapi_factory_utilities/core/security/jwt/types.py +9 -0
- fastapi_factory_utilities/core/security/jwt/verifiers.py +46 -0
- fastapi_factory_utilities/core/security/kratos.py +53 -33
- fastapi_factory_utilities/core/services/hydra/__init__.py +20 -0
- fastapi_factory_utilities/core/services/hydra/exceptions.py +15 -0
- fastapi_factory_utilities/core/services/hydra/objects.py +26 -0
- fastapi_factory_utilities/core/services/hydra/services.py +200 -0
- fastapi_factory_utilities/core/services/status/__init__.py +2 -2
- fastapi_factory_utilities/core/services/status/exceptions.py +1 -1
- fastapi_factory_utilities/core/utils/status.py +2 -1
- fastapi_factory_utilities/core/utils/yaml_reader.py +1 -1
- fastapi_factory_utilities/example/app.py +15 -5
- fastapi_factory_utilities/example/entities/books/__init__.py +1 -1
- fastapi_factory_utilities/example/models/books/__init__.py +1 -1
- {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info}/METADATA +21 -15
- fastapi_factory_utilities-0.8.2.dist-info/RECORD +111 -0
- {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info}/WHEEL +1 -1
- fastapi_factory_utilities/core/app/plugin_manager/__init__.py +0 -15
- fastapi_factory_utilities/core/app/plugin_manager/exceptions.py +0 -33
- fastapi_factory_utilities/core/app/plugin_manager/plugin_manager.py +0 -190
- fastapi_factory_utilities/core/plugins/example/__init__.py +0 -31
- fastapi_factory_utilities/core/plugins/httpx_plugin/__init__.py +0 -31
- fastapi_factory_utilities/core/security/jwt.py +0 -158
- fastapi_factory_utilities-0.3.3.dist-info/RECORD +0 -78
- {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info}/entry_points.txt +0 -0
- {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,168 +1,25 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""ODM Plugin Module."""
|
|
2
2
|
|
|
3
|
-
from logging import INFO, Logger, getLogger
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
from beanie import init_beanie # pyright: ignore[reportUnknownVariableType]
|
|
7
|
-
from motor.motor_asyncio import AsyncIOMotorClient
|
|
8
|
-
from reactivex import Subject
|
|
9
|
-
from structlog.stdlib import BoundLogger, get_logger
|
|
10
|
-
|
|
11
|
-
from fastapi_factory_utilities.core.plugins import PluginState
|
|
12
|
-
from fastapi_factory_utilities.core.protocols import ApplicationAbstractProtocol
|
|
13
|
-
from fastapi_factory_utilities.core.services.status.enums import (
|
|
14
|
-
ComponentTypeEnum,
|
|
15
|
-
HealthStatusEnum,
|
|
16
|
-
ReadinessStatusEnum,
|
|
17
|
-
)
|
|
18
|
-
from fastapi_factory_utilities.core.services.status.services import StatusService
|
|
19
|
-
from fastapi_factory_utilities.core.services.status.types import (
|
|
20
|
-
ComponentInstanceType,
|
|
21
|
-
Status,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
from .builder import ODMBuilder
|
|
25
3
|
from .depends import depends_odm_client, depends_odm_database
|
|
26
4
|
from .documents import BaseDocument
|
|
27
|
-
from .exceptions import
|
|
5
|
+
from .exceptions import (
|
|
6
|
+
ODMPluginBaseException,
|
|
7
|
+
ODMPluginConfigError,
|
|
8
|
+
OperationError,
|
|
9
|
+
UnableToCreateEntityDueToDuplicateKeyError,
|
|
10
|
+
)
|
|
11
|
+
from .helpers import PersistedEntity
|
|
12
|
+
from .plugins import ODMPlugin
|
|
28
13
|
from .repositories import AbstractRepository
|
|
29
14
|
|
|
30
|
-
_logger: BoundLogger = get_logger()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def pre_conditions_check(application: ApplicationAbstractProtocol) -> bool:
|
|
34
|
-
"""Check the pre-conditions for the OpenTelemetry plugin.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
application (BaseApplicationProtocol): The application.
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
bool: True if the pre-conditions are met, False otherwise.
|
|
41
|
-
"""
|
|
42
|
-
del application
|
|
43
|
-
return True
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def on_load(
|
|
47
|
-
application: ApplicationAbstractProtocol,
|
|
48
|
-
) -> list["PluginState"] | None:
|
|
49
|
-
"""Actions to perform on load for the OpenTelemetry plugin.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
application (BaseApplicationProtocol): The application.
|
|
53
|
-
"""
|
|
54
|
-
del application
|
|
55
|
-
# Configure the pymongo logger to INFO level
|
|
56
|
-
pymongo_logger: Logger = getLogger("pymongo")
|
|
57
|
-
pymongo_logger.setLevel(INFO)
|
|
58
|
-
_logger.debug("ODM plugin loaded.")
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
async def on_startup(
|
|
62
|
-
application: ApplicationAbstractProtocol,
|
|
63
|
-
) -> list["PluginState"] | None:
|
|
64
|
-
"""Actions to perform on startup for the ODM plugin.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
application (BaseApplicationProtocol): The application.
|
|
68
|
-
odm_config (ODMConfig): The ODM configuration.
|
|
69
|
-
|
|
70
|
-
Returns:
|
|
71
|
-
None
|
|
72
|
-
"""
|
|
73
|
-
states: list[PluginState] = []
|
|
74
|
-
|
|
75
|
-
status_service: StatusService = application.get_status_service()
|
|
76
|
-
component_instance: ComponentInstanceType = ComponentInstanceType(
|
|
77
|
-
component_type=ComponentTypeEnum.DATABASE, identifier="MongoDB"
|
|
78
|
-
)
|
|
79
|
-
monitoring_subject: Subject[Status] = status_service.register_component_instance(
|
|
80
|
-
component_instance=component_instance
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
try:
|
|
84
|
-
odm_factory: ODMBuilder = ODMBuilder(application=application).build_all()
|
|
85
|
-
await odm_factory.wait_ping()
|
|
86
|
-
except Exception as exception: # pylint: disable=broad-except
|
|
87
|
-
_logger.error(f"ODM plugin failed to start. {exception}")
|
|
88
|
-
# TODO: Report the error to the status_service
|
|
89
|
-
# this will report the application as unhealthy
|
|
90
|
-
monitoring_subject.on_next(
|
|
91
|
-
value=Status(health=HealthStatusEnum.UNHEALTHY, readiness=ReadinessStatusEnum.NOT_READY)
|
|
92
|
-
)
|
|
93
|
-
return states
|
|
94
|
-
|
|
95
|
-
if odm_factory.odm_database is None or odm_factory.odm_client is None:
|
|
96
|
-
_logger.error(
|
|
97
|
-
f"ODM plugin failed to start. Database: {odm_factory.odm_database} - " f"Client: {odm_factory.odm_client}"
|
|
98
|
-
)
|
|
99
|
-
# TODO: Report the error to the status_service
|
|
100
|
-
# this will report the application as unhealthy
|
|
101
|
-
monitoring_subject.on_next(
|
|
102
|
-
value=Status(health=HealthStatusEnum.UNHEALTHY, readiness=ReadinessStatusEnum.NOT_READY)
|
|
103
|
-
)
|
|
104
|
-
return states
|
|
105
|
-
|
|
106
|
-
# Add the ODM client and database to the application state
|
|
107
|
-
states.append(
|
|
108
|
-
PluginState(key="odm_client", value=odm_factory.odm_client),
|
|
109
|
-
)
|
|
110
|
-
states.append(
|
|
111
|
-
PluginState(
|
|
112
|
-
key="odm_database",
|
|
113
|
-
value=odm_factory.odm_database,
|
|
114
|
-
),
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
# TODO: Find a better way to initialize beanie with the document models of the concrete application
|
|
118
|
-
# through an hook in the application, a dynamis import ?
|
|
119
|
-
try:
|
|
120
|
-
await init_beanie(
|
|
121
|
-
database=odm_factory.odm_database,
|
|
122
|
-
document_models=application.ODM_DOCUMENT_MODELS,
|
|
123
|
-
)
|
|
124
|
-
except Exception as exception: # pylint: disable=broad-except
|
|
125
|
-
_logger.error(f"ODM plugin failed to start. {exception}")
|
|
126
|
-
# TODO: Report the error to the status_service
|
|
127
|
-
# this will report the application as unhealthy
|
|
128
|
-
monitoring_subject.on_next(
|
|
129
|
-
value=Status(health=HealthStatusEnum.UNHEALTHY, readiness=ReadinessStatusEnum.NOT_READY)
|
|
130
|
-
)
|
|
131
|
-
return states
|
|
132
|
-
|
|
133
|
-
_logger.info(
|
|
134
|
-
f"ODM plugin started. Database: {odm_factory.odm_database.name} - "
|
|
135
|
-
f"Client: {odm_factory.odm_client.address} - "
|
|
136
|
-
f"Document models: {application.ODM_DOCUMENT_MODELS}"
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
monitoring_subject.on_next(value=Status(health=HealthStatusEnum.HEALTHY, readiness=ReadinessStatusEnum.READY))
|
|
140
|
-
|
|
141
|
-
return states
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
async def on_shutdown(application: ApplicationAbstractProtocol) -> None:
|
|
145
|
-
"""Actions to perform on shutdown for the ODM plugin.
|
|
146
|
-
|
|
147
|
-
Args:
|
|
148
|
-
application (BaseApplicationProtocol): The application.
|
|
149
|
-
|
|
150
|
-
Returns:
|
|
151
|
-
None
|
|
152
|
-
"""
|
|
153
|
-
# Skip if the ODM plugin was not started correctly
|
|
154
|
-
if not hasattr(application.get_asgi_app().state, "odm_client"):
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
client: AsyncIOMotorClient[Any] = application.get_asgi_app().state.odm_client
|
|
158
|
-
client.close()
|
|
159
|
-
_logger.debug("ODM plugin shutdown.")
|
|
160
|
-
|
|
161
|
-
|
|
162
15
|
__all__: list[str] = [
|
|
163
|
-
"BaseDocument",
|
|
164
16
|
"AbstractRepository",
|
|
17
|
+
"BaseDocument",
|
|
18
|
+
"ODMPlugin",
|
|
19
|
+
"ODMPluginBaseException",
|
|
20
|
+
"ODMPluginConfigError",
|
|
165
21
|
"OperationError",
|
|
22
|
+
"PersistedEntity",
|
|
166
23
|
"UnableToCreateEntityDueToDuplicateKeyError",
|
|
167
24
|
"depends_odm_client",
|
|
168
25
|
"depends_odm_database",
|
|
@@ -180,6 +180,7 @@ class ODMBuilder:
|
|
|
180
180
|
connectTimeoutMS=self._config.connection_timeout_ms,
|
|
181
181
|
serverSelectionTimeoutMS=self._config.connection_timeout_ms,
|
|
182
182
|
server_api=ServerApi(version=ServerApiVersion.V1),
|
|
183
|
+
tz_aware=True,
|
|
183
184
|
)
|
|
184
185
|
|
|
185
186
|
# KEEP IT, Waiting for additional tests
|
|
@@ -214,7 +215,7 @@ class ODMBuilder:
|
|
|
214
215
|
|
|
215
216
|
if self._odm_client is None:
|
|
216
217
|
raise ODMPluginConfigError(
|
|
217
|
-
"ODM client is not set. Provide the ODM client using
|
|
218
|
+
"ODM client is not set. Provide the ODM client using build_client method or through parameter."
|
|
218
219
|
)
|
|
219
220
|
|
|
220
221
|
self._odm_database = self._odm_client.get_database(
|
|
@@ -241,7 +242,7 @@ class ODMBuilder:
|
|
|
241
242
|
|
|
242
243
|
return self
|
|
243
244
|
|
|
244
|
-
async def wait_ping(self):
|
|
245
|
+
async def wait_ping(self) -> None:
|
|
245
246
|
"""Wait for the ODM client to be ready.
|
|
246
247
|
|
|
247
248
|
Returns:
|
|
@@ -249,7 +250,7 @@ class ODMBuilder:
|
|
|
249
250
|
"""
|
|
250
251
|
if self._odm_client is None:
|
|
251
252
|
raise ODMPluginConfigError(
|
|
252
|
-
"ODM client is not set. Provide the ODM client using
|
|
253
|
+
"ODM client is not set. Provide the ODM client using build_client method or through parameter."
|
|
253
254
|
)
|
|
254
255
|
|
|
255
256
|
try:
|
|
@@ -13,7 +13,7 @@ class BaseDocument(Document):
|
|
|
13
13
|
"""Base document class."""
|
|
14
14
|
|
|
15
15
|
# To be agnostic of MongoDN, we use UUID as the document ID.
|
|
16
|
-
id: UUID = Field( #
|
|
16
|
+
id: UUID = Field( # type: ignore
|
|
17
17
|
default_factory=uuid4, description="The document ID."
|
|
18
18
|
)
|
|
19
19
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Helper functions for ODM plugins."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PersistedEntity(BaseModel):
|
|
10
|
+
"""Base class for persisted entities."""
|
|
11
|
+
|
|
12
|
+
id: uuid.UUID = Field(default_factory=uuid.uuid4)
|
|
13
|
+
|
|
14
|
+
revision_id: uuid.UUID | None = Field(default=None)
|
|
15
|
+
created_at: datetime.datetime = Field(default_factory=datetime.datetime.now)
|
|
16
|
+
updated_at: datetime.datetime = Field(default_factory=datetime.datetime.now)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Oriented Data Model (ODM) plugin package."""
|
|
2
|
+
|
|
3
|
+
from logging import INFO, Logger, getLogger
|
|
4
|
+
from typing import Any, Self
|
|
5
|
+
|
|
6
|
+
from beanie import Document, init_beanie # pyright: ignore[reportUnknownVariableType]
|
|
7
|
+
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
|
|
8
|
+
from reactivex import Subject
|
|
9
|
+
from structlog.stdlib import BoundLogger, get_logger
|
|
10
|
+
|
|
11
|
+
from fastapi_factory_utilities.core.plugins.abstracts import PluginAbstract
|
|
12
|
+
from fastapi_factory_utilities.core.protocols import ApplicationAbstractProtocol
|
|
13
|
+
from fastapi_factory_utilities.core.services.status.enums import (
|
|
14
|
+
ComponentTypeEnum,
|
|
15
|
+
HealthStatusEnum,
|
|
16
|
+
ReadinessStatusEnum,
|
|
17
|
+
)
|
|
18
|
+
from fastapi_factory_utilities.core.services.status.services import StatusService
|
|
19
|
+
from fastapi_factory_utilities.core.services.status.types import (
|
|
20
|
+
ComponentInstanceType,
|
|
21
|
+
Status,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from .builder import ODMBuilder
|
|
25
|
+
from .configs import ODMConfig
|
|
26
|
+
from .depends import depends_odm_client, depends_odm_database
|
|
27
|
+
from .documents import BaseDocument
|
|
28
|
+
from .exceptions import OperationError, UnableToCreateEntityDueToDuplicateKeyError
|
|
29
|
+
from .helpers import PersistedEntity
|
|
30
|
+
from .repositories import AbstractRepository
|
|
31
|
+
|
|
32
|
+
_logger: BoundLogger = get_logger()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ODMPlugin(PluginAbstract):
|
|
36
|
+
"""ODM plugin."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self, document_models: list[type[Document]] | None = None, odm_config: ODMConfig | None = None
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Initialize the ODM plugin."""
|
|
42
|
+
super().__init__()
|
|
43
|
+
self._component_instance: ComponentInstanceType | None = None
|
|
44
|
+
self._monitoring_subject: Subject[Status] | None = None
|
|
45
|
+
self._document_models: list[type[Document]] | None = document_models
|
|
46
|
+
self._odm_config: ODMConfig | None = odm_config
|
|
47
|
+
self._odm_client: AsyncIOMotorClient[Any] | None = None
|
|
48
|
+
self._odm_database: AsyncIOMotorDatabase[Any] | None = None
|
|
49
|
+
|
|
50
|
+
def set_application(self, application: ApplicationAbstractProtocol) -> Self:
|
|
51
|
+
"""Set the application."""
|
|
52
|
+
self._document_models = self._document_models or application.ODM_DOCUMENT_MODELS
|
|
53
|
+
return super().set_application(application)
|
|
54
|
+
|
|
55
|
+
def on_load(self) -> None:
|
|
56
|
+
"""Actions to perform on load for the ODM plugin."""
|
|
57
|
+
# Configure the pymongo logger to INFO level
|
|
58
|
+
|
|
59
|
+
pymongo_logger: Logger = getLogger("pymongo")
|
|
60
|
+
pymongo_logger.setLevel(INFO)
|
|
61
|
+
_logger.debug("ODM plugin loaded.")
|
|
62
|
+
|
|
63
|
+
def _setup_status(self) -> None:
|
|
64
|
+
assert self._application is not None
|
|
65
|
+
status_service: StatusService = self._application.get_status_service()
|
|
66
|
+
self._component_instance = ComponentInstanceType(
|
|
67
|
+
component_type=ComponentTypeEnum.DATABASE, identifier="MongoDB"
|
|
68
|
+
)
|
|
69
|
+
self._monitoring_subject = status_service.register_component_instance(
|
|
70
|
+
component_instance=self._component_instance
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
async def _setup_beanie(self) -> None:
|
|
74
|
+
assert self._application is not None
|
|
75
|
+
assert self._odm_database is not None
|
|
76
|
+
assert self._document_models is not None
|
|
77
|
+
assert self._monitoring_subject is not None
|
|
78
|
+
# TODO: Find a better way to initialize beanie with the document models of the concrete application
|
|
79
|
+
# through an hook in the application, a dynamis import ?
|
|
80
|
+
try:
|
|
81
|
+
await init_beanie(
|
|
82
|
+
database=self._odm_database,
|
|
83
|
+
document_models=self._document_models,
|
|
84
|
+
)
|
|
85
|
+
except Exception as exception: # pylint: disable=broad-except
|
|
86
|
+
_logger.error(f"ODM plugin failed to start. {exception}")
|
|
87
|
+
# TODO: Report the error to the status_service
|
|
88
|
+
# this will report the application as unhealthy
|
|
89
|
+
self._monitoring_subject.on_next(
|
|
90
|
+
value=Status(health=HealthStatusEnum.UNHEALTHY, readiness=ReadinessStatusEnum.NOT_READY)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
async def on_startup(self) -> None:
|
|
94
|
+
"""Actions to perform on startup for the ODM plugin."""
|
|
95
|
+
assert self._application is not None
|
|
96
|
+
self._setup_status()
|
|
97
|
+
assert self._monitoring_subject is not None
|
|
98
|
+
assert self._component_instance is not None
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
odm_factory: ODMBuilder = ODMBuilder(application=self._application, odm_config=self._odm_config).build_all()
|
|
102
|
+
await odm_factory.wait_ping()
|
|
103
|
+
self._odm_database = odm_factory.odm_database
|
|
104
|
+
self._odm_client = odm_factory.odm_client
|
|
105
|
+
except Exception as exception: # pylint: disable=broad-except
|
|
106
|
+
_logger.error(f"ODM plugin failed to start. {exception}")
|
|
107
|
+
# TODO: Report the error to the status_service
|
|
108
|
+
# this will report the application as unhealthy
|
|
109
|
+
self._monitoring_subject.on_next(
|
|
110
|
+
value=Status(health=HealthStatusEnum.UNHEALTHY, readiness=ReadinessStatusEnum.NOT_READY)
|
|
111
|
+
)
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
if self._odm_database is None or self._odm_client is None:
|
|
115
|
+
_logger.error(
|
|
116
|
+
f"ODM plugin failed to start. Database: {odm_factory.odm_database} - Client: {odm_factory.odm_client}"
|
|
117
|
+
)
|
|
118
|
+
# TODO: Report the error to the status_service
|
|
119
|
+
# this will report the application as unhealthy
|
|
120
|
+
self._monitoring_subject.on_next(
|
|
121
|
+
value=Status(health=HealthStatusEnum.UNHEALTHY, readiness=ReadinessStatusEnum.NOT_READY)
|
|
122
|
+
)
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
self._add_to_state(key="odm_client", value=odm_factory.odm_client)
|
|
126
|
+
self._add_to_state(key="odm_database", value=odm_factory.odm_database)
|
|
127
|
+
|
|
128
|
+
await self._setup_beanie()
|
|
129
|
+
|
|
130
|
+
_logger.info(
|
|
131
|
+
f"ODM plugin started. Database: {self._odm_database.name} - "
|
|
132
|
+
f"Client: {self._odm_client.address} - "
|
|
133
|
+
f"Document models: {self._application.ODM_DOCUMENT_MODELS}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
self._monitoring_subject.on_next(
|
|
137
|
+
value=Status(health=HealthStatusEnum.HEALTHY, readiness=ReadinessStatusEnum.READY)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
async def on_shutdown(self) -> None:
|
|
141
|
+
"""Actions to perform on shutdown for the ODM plugin."""
|
|
142
|
+
if self._odm_client is not None:
|
|
143
|
+
self._odm_client.close()
|
|
144
|
+
_logger.debug("ODM plugin shutdown.")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
__all__: list[str] = [
|
|
148
|
+
"AbstractRepository",
|
|
149
|
+
"BaseDocument",
|
|
150
|
+
"OperationError",
|
|
151
|
+
"PersistedEntity",
|
|
152
|
+
"UnableToCreateEntityDueToDuplicateKeyError",
|
|
153
|
+
"depends_odm_client",
|
|
154
|
+
"depends_odm_database",
|
|
155
|
+
]
|
|
@@ -2,24 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import datetime
|
|
4
4
|
from abc import ABC
|
|
5
|
-
from collections.abc import AsyncGenerator, Callable
|
|
5
|
+
from collections.abc import AsyncGenerator, Callable, Mapping
|
|
6
6
|
from contextlib import asynccontextmanager
|
|
7
|
-
from typing import
|
|
8
|
-
Any,
|
|
9
|
-
Dict,
|
|
10
|
-
Generic,
|
|
11
|
-
List,
|
|
12
|
-
Mapping,
|
|
13
|
-
Optional,
|
|
14
|
-
Tuple,
|
|
15
|
-
TypeVar,
|
|
16
|
-
Union,
|
|
17
|
-
get_args,
|
|
18
|
-
)
|
|
7
|
+
from typing import Any, Generic, TypeVar, get_args
|
|
19
8
|
from uuid import UUID
|
|
20
9
|
|
|
21
10
|
from beanie import SortDirection
|
|
22
|
-
from beanie.odm.queries.find import FindMany
|
|
23
11
|
from motor.motor_asyncio import AsyncIOMotorClientSession, AsyncIOMotorDatabase
|
|
24
12
|
from pydantic import BaseModel
|
|
25
13
|
from pymongo.errors import DuplicateKeyError, PyMongoError
|
|
@@ -102,7 +90,7 @@ class AbstractRepository(ABC, Generic[DocumentGenericType, EntityGenericType]):
|
|
|
102
90
|
raise ValueError(f"Failed to create document from entity: {error}") from error
|
|
103
91
|
|
|
104
92
|
try:
|
|
105
|
-
document_created: DocumentGenericType = await document.
|
|
93
|
+
document_created: DocumentGenericType = await document.insert(session=session)
|
|
106
94
|
except DuplicateKeyError as error:
|
|
107
95
|
raise UnableToCreateEntityDueToDuplicateKeyError(f"Failed to insert document: {error}") from error
|
|
108
96
|
except PyMongoError as error:
|
|
@@ -229,19 +217,19 @@ class AbstractRepository(ABC, Generic[DocumentGenericType, EntityGenericType]):
|
|
|
229
217
|
raise OperationError("Failed to delete document.")
|
|
230
218
|
|
|
231
219
|
@managed_session()
|
|
232
|
-
async def find(
|
|
220
|
+
async def find( # noqa: PLR0913
|
|
233
221
|
self,
|
|
234
|
-
*args:
|
|
222
|
+
*args: Mapping[str, Any] | bool,
|
|
235
223
|
projection_model: None = None,
|
|
236
|
-
skip:
|
|
237
|
-
limit:
|
|
238
|
-
sort:
|
|
239
|
-
session:
|
|
224
|
+
skip: int | None = None,
|
|
225
|
+
limit: int | None = None,
|
|
226
|
+
sort: None | str | list[tuple[str, SortDirection]] = None,
|
|
227
|
+
session: AsyncIOMotorClientSession | None = None,
|
|
240
228
|
ignore_cache: bool = False,
|
|
241
229
|
fetch_links: bool = False,
|
|
242
230
|
lazy_parse: bool = False,
|
|
243
|
-
nesting_depth:
|
|
244
|
-
nesting_depths_per_field:
|
|
231
|
+
nesting_depth: int | None = None,
|
|
232
|
+
nesting_depths_per_field: dict[str, int] | None = None,
|
|
245
233
|
**pymongo_kwargs: Any,
|
|
246
234
|
) -> list[EntityGenericType]:
|
|
247
235
|
"""Find documents in the database.
|
|
@@ -280,6 +268,7 @@ class AbstractRepository(ABC, Generic[DocumentGenericType, EntityGenericType]):
|
|
|
280
268
|
lazy_parse=lazy_parse,
|
|
281
269
|
nesting_depth=nesting_depth,
|
|
282
270
|
nesting_depths_per_field=nesting_depths_per_field,
|
|
271
|
+
**pymongo_kwargs,
|
|
283
272
|
).to_list()
|
|
284
273
|
except PyMongoError as error:
|
|
285
274
|
raise OperationError(f"Failed to find documents: {error}") from error
|
|
@@ -1,124 +1,17 @@
|
|
|
1
1
|
"""OpenTelemetry Plugin Module."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from typing import cast
|
|
5
|
-
|
|
6
|
-
from opentelemetry.instrumentation.fastapi import ( # pyright: ignore[reportMissingTypeStubs]
|
|
7
|
-
FastAPIInstrumentor,
|
|
8
|
-
)
|
|
9
|
-
from opentelemetry.sdk.metrics import MeterProvider
|
|
10
|
-
from opentelemetry.sdk.trace import TracerProvider
|
|
11
|
-
from structlog.stdlib import BoundLogger, get_logger
|
|
12
|
-
|
|
13
|
-
from fastapi_factory_utilities.core.protocols import ApplicationAbstractProtocol
|
|
14
|
-
|
|
15
|
-
from .builder import OpenTelemetryPluginBuilder
|
|
16
|
-
from .configs import OpenTelemetryConfig
|
|
3
|
+
from .configs import OpenTelemetryConfig, OpenTelemetryMeterConfig, OpenTelemetryTracerConfig
|
|
17
4
|
from .exceptions import OpenTelemetryPluginBaseException, OpenTelemetryPluginConfigError
|
|
5
|
+
from .plugins import OpenTelemetryPlugin, depends_meter_provider, depends_otel_config, depends_tracer_provider
|
|
18
6
|
|
|
19
7
|
__all__: list[str] = [
|
|
20
8
|
"OpenTelemetryConfig",
|
|
9
|
+
"OpenTelemetryMeterConfig",
|
|
10
|
+
"OpenTelemetryPlugin",
|
|
21
11
|
"OpenTelemetryPluginBaseException",
|
|
22
12
|
"OpenTelemetryPluginConfigError",
|
|
23
|
-
"
|
|
13
|
+
"OpenTelemetryTracerConfig",
|
|
14
|
+
"depends_meter_provider",
|
|
15
|
+
"depends_otel_config",
|
|
16
|
+
"depends_tracer_provider",
|
|
24
17
|
]
|
|
25
|
-
|
|
26
|
-
_logger: BoundLogger = get_logger()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def pre_conditions_check(application: ApplicationAbstractProtocol) -> bool:
|
|
30
|
-
"""Check the pre-conditions for the OpenTelemetry plugin.
|
|
31
|
-
|
|
32
|
-
Args:
|
|
33
|
-
application (BaseApplicationProtocol): The application.
|
|
34
|
-
|
|
35
|
-
Returns:
|
|
36
|
-
bool: True if the pre-conditions are met, False otherwise.
|
|
37
|
-
"""
|
|
38
|
-
del application
|
|
39
|
-
return True
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def on_load(
|
|
43
|
-
application: ApplicationAbstractProtocol,
|
|
44
|
-
) -> None:
|
|
45
|
-
"""Actions to perform on load for the OpenTelemetry plugin.
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
application (BaseApplicationProtocol): The application.
|
|
49
|
-
"""
|
|
50
|
-
# Build the OpenTelemetry Resources, TracerProvider and MeterProvider
|
|
51
|
-
try:
|
|
52
|
-
otel_builder: OpenTelemetryPluginBuilder = OpenTelemetryPluginBuilder(application=application).build_all()
|
|
53
|
-
except OpenTelemetryPluginBaseException as exception:
|
|
54
|
-
_logger.error(f"OpenTelemetry plugin failed to start. {exception}")
|
|
55
|
-
return
|
|
56
|
-
# Configuration is never None at this point (checked in the builder and raises an exception)
|
|
57
|
-
otel_config: OpenTelemetryConfig = cast(OpenTelemetryConfig, otel_builder.config)
|
|
58
|
-
# Save as state in the FastAPI application
|
|
59
|
-
application.get_asgi_app().state.tracer_provider = otel_builder.tracer_provider
|
|
60
|
-
application.get_asgi_app().state.meter_provider = otel_builder.meter_provider
|
|
61
|
-
application.get_asgi_app().state.otel_config = otel_config
|
|
62
|
-
# Instrument the FastAPI application
|
|
63
|
-
FastAPIInstrumentor.instrument_app( # pyright: ignore[reportUnknownMemberType]
|
|
64
|
-
app=application.get_asgi_app(),
|
|
65
|
-
tracer_provider=otel_builder.tracer_provider,
|
|
66
|
-
meter_provider=otel_builder.meter_provider,
|
|
67
|
-
excluded_urls=otel_config.excluded_urls,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
_logger.debug(f"OpenTelemetry plugin loaded. {otel_config.activate=}")
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
async def on_startup(
|
|
74
|
-
application: ApplicationAbstractProtocol,
|
|
75
|
-
) -> None:
|
|
76
|
-
"""Actions to perform on startup for the OpenTelemetry plugin.
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
application (BaseApplicationProtocol): The application.
|
|
80
|
-
|
|
81
|
-
Returns:
|
|
82
|
-
None
|
|
83
|
-
"""
|
|
84
|
-
del application
|
|
85
|
-
_logger.debug("OpenTelemetry plugin started.")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
async def on_shutdown(application: ApplicationAbstractProtocol) -> None:
|
|
89
|
-
"""Actions to perform on shutdown for the OpenTelemetry plugin.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
application (BaseApplicationProtocol): The application.
|
|
93
|
-
|
|
94
|
-
Returns:
|
|
95
|
-
None
|
|
96
|
-
"""
|
|
97
|
-
tracer_provider: TracerProvider = application.get_asgi_app().state.tracer_provider
|
|
98
|
-
meter_provider: MeterProvider = application.get_asgi_app().state.meter_provider
|
|
99
|
-
otel_config: OpenTelemetryConfig = application.get_asgi_app().state.otel_config
|
|
100
|
-
|
|
101
|
-
seconds_to_ms_multiplier: int = 1000
|
|
102
|
-
|
|
103
|
-
async def close_tracer_provider() -> None:
|
|
104
|
-
"""Close the tracer provider."""
|
|
105
|
-
tracer_provider.force_flush(timeout_millis=otel_config.closing_timeout * seconds_to_ms_multiplier)
|
|
106
|
-
# No Delay for the shutdown of the tracer provider
|
|
107
|
-
tracer_provider.shutdown()
|
|
108
|
-
|
|
109
|
-
async def close_meter_provider() -> None:
|
|
110
|
-
"""Close the meter provider.
|
|
111
|
-
|
|
112
|
-
Split the timeout in half for the flush and shutdown.
|
|
113
|
-
"""
|
|
114
|
-
meter_provider.force_flush(timeout_millis=int(otel_config.closing_timeout / 2) * seconds_to_ms_multiplier)
|
|
115
|
-
meter_provider.shutdown(timeout_millis=int(otel_config.closing_timeout / 2) * seconds_to_ms_multiplier)
|
|
116
|
-
|
|
117
|
-
_logger.debug("OpenTelemetry plugin stop requested. Flushing and closing...")
|
|
118
|
-
|
|
119
|
-
await asyncio.gather(
|
|
120
|
-
close_tracer_provider(),
|
|
121
|
-
close_meter_provider(),
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
_logger.debug("OpenTelemetry plugin closed.")
|