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.
Files changed (75) hide show
  1. fastapi_factory_utilities/core/api/__init__.py +1 -1
  2. fastapi_factory_utilities/core/api/v1/sys/health.py +1 -1
  3. fastapi_factory_utilities/core/app/__init__.py +4 -4
  4. fastapi_factory_utilities/core/app/application.py +22 -26
  5. fastapi_factory_utilities/core/app/builder.py +19 -35
  6. fastapi_factory_utilities/core/app/config.py +2 -0
  7. fastapi_factory_utilities/core/app/fastapi_builder.py +3 -2
  8. fastapi_factory_utilities/core/exceptions.py +64 -28
  9. fastapi_factory_utilities/core/plugins/__init__.py +2 -31
  10. fastapi_factory_utilities/core/plugins/abstracts.py +40 -0
  11. fastapi_factory_utilities/core/plugins/aiopika/__init__.py +25 -0
  12. fastapi_factory_utilities/core/plugins/aiopika/abstract.py +48 -0
  13. fastapi_factory_utilities/core/plugins/aiopika/configs.py +85 -0
  14. fastapi_factory_utilities/core/plugins/aiopika/depends.py +20 -0
  15. fastapi_factory_utilities/core/plugins/aiopika/exceptions.py +29 -0
  16. fastapi_factory_utilities/core/plugins/aiopika/exchange.py +69 -0
  17. fastapi_factory_utilities/core/plugins/aiopika/listener/__init__.py +7 -0
  18. fastapi_factory_utilities/core/plugins/aiopika/listener/abstract.py +72 -0
  19. fastapi_factory_utilities/core/plugins/aiopika/message.py +86 -0
  20. fastapi_factory_utilities/core/plugins/aiopika/plugins.py +84 -0
  21. fastapi_factory_utilities/core/plugins/aiopika/publisher/__init__.py +7 -0
  22. fastapi_factory_utilities/core/plugins/aiopika/publisher/abstract.py +66 -0
  23. fastapi_factory_utilities/core/plugins/aiopika/queue.py +88 -0
  24. fastapi_factory_utilities/core/plugins/odm_plugin/__init__.py +14 -157
  25. fastapi_factory_utilities/core/plugins/odm_plugin/builder.py +4 -3
  26. fastapi_factory_utilities/core/plugins/odm_plugin/configs.py +1 -1
  27. fastapi_factory_utilities/core/plugins/odm_plugin/documents.py +1 -1
  28. fastapi_factory_utilities/core/plugins/odm_plugin/helpers.py +16 -0
  29. fastapi_factory_utilities/core/plugins/odm_plugin/plugins.py +155 -0
  30. fastapi_factory_utilities/core/plugins/odm_plugin/repositories.py +12 -23
  31. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/__init__.py +8 -115
  32. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/instruments/__init__.py +85 -0
  33. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/plugins.py +137 -0
  34. fastapi_factory_utilities/core/plugins/taskiq_plugins/__init__.py +31 -0
  35. fastapi_factory_utilities/core/plugins/taskiq_plugins/configs.py +12 -0
  36. fastapi_factory_utilities/core/plugins/taskiq_plugins/depends.py +51 -0
  37. fastapi_factory_utilities/core/plugins/taskiq_plugins/exceptions.py +13 -0
  38. fastapi_factory_utilities/core/plugins/taskiq_plugins/plugin.py +41 -0
  39. fastapi_factory_utilities/core/plugins/taskiq_plugins/schedulers.py +187 -0
  40. fastapi_factory_utilities/core/protocols.py +1 -54
  41. fastapi_factory_utilities/core/security/__init__.py +5 -0
  42. fastapi_factory_utilities/core/security/abstracts.py +42 -0
  43. fastapi_factory_utilities/core/security/jwt/__init__.py +41 -0
  44. fastapi_factory_utilities/core/security/jwt/configs.py +32 -0
  45. fastapi_factory_utilities/core/security/jwt/decoders.py +130 -0
  46. fastapi_factory_utilities/core/security/jwt/exceptions.py +23 -0
  47. fastapi_factory_utilities/core/security/jwt/objects.py +107 -0
  48. fastapi_factory_utilities/core/security/jwt/services.py +176 -0
  49. fastapi_factory_utilities/core/security/jwt/stores.py +43 -0
  50. fastapi_factory_utilities/core/security/jwt/types.py +9 -0
  51. fastapi_factory_utilities/core/security/jwt/verifiers.py +46 -0
  52. fastapi_factory_utilities/core/security/kratos.py +53 -33
  53. fastapi_factory_utilities/core/services/hydra/__init__.py +20 -0
  54. fastapi_factory_utilities/core/services/hydra/exceptions.py +15 -0
  55. fastapi_factory_utilities/core/services/hydra/objects.py +26 -0
  56. fastapi_factory_utilities/core/services/hydra/services.py +200 -0
  57. fastapi_factory_utilities/core/services/status/__init__.py +2 -2
  58. fastapi_factory_utilities/core/services/status/exceptions.py +1 -1
  59. fastapi_factory_utilities/core/utils/status.py +2 -1
  60. fastapi_factory_utilities/core/utils/yaml_reader.py +1 -1
  61. fastapi_factory_utilities/example/app.py +15 -5
  62. fastapi_factory_utilities/example/entities/books/__init__.py +1 -1
  63. fastapi_factory_utilities/example/models/books/__init__.py +1 -1
  64. {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info}/METADATA +21 -15
  65. fastapi_factory_utilities-0.8.2.dist-info/RECORD +111 -0
  66. {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info}/WHEEL +1 -1
  67. fastapi_factory_utilities/core/app/plugin_manager/__init__.py +0 -15
  68. fastapi_factory_utilities/core/app/plugin_manager/exceptions.py +0 -33
  69. fastapi_factory_utilities/core/app/plugin_manager/plugin_manager.py +0 -190
  70. fastapi_factory_utilities/core/plugins/example/__init__.py +0 -31
  71. fastapi_factory_utilities/core/plugins/httpx_plugin/__init__.py +0 -31
  72. fastapi_factory_utilities/core/security/jwt.py +0 -158
  73. fastapi_factory_utilities-0.3.3.dist-info/RECORD +0 -78
  74. {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info}/entry_points.txt +0 -0
  75. {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
- """Oriented Data Model (ODM) plugin package."""
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 OperationError, UnableToCreateEntityDueToDuplicateKeyError
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 " "build_client method or through parameter."
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 " "build_client method or through parameter."
253
+ "ODM client is not set. Provide the ODM client using build_client method or through parameter."
253
254
  )
254
255
 
255
256
  try:
@@ -12,4 +12,4 @@ class ODMConfig(BaseModel):
12
12
 
13
13
  database: str = "test"
14
14
 
15
- connection_timeout_ms: int = 1000
15
+ connection_timeout_ms: int = 4000
@@ -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( # pyright: ignore[reportIncompatibleVariableOverride]
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.save(session=session)
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: Union[Mapping[str, Any], bool],
222
+ *args: Mapping[str, Any] | bool,
235
223
  projection_model: None = None,
236
- skip: Optional[int] = None,
237
- limit: Optional[int] = None,
238
- sort: Union[None, str, List[Tuple[str, SortDirection]]] = None,
239
- session: Optional[AsyncIOMotorClientSession] = None,
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: Optional[int] = None,
244
- nesting_depths_per_field: Optional[Dict[str, int]] = None,
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 asyncio
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
- "OpenTelemetryPluginBuilder",
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.")