fastapi-factory-utilities 0.1.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.

Files changed (58) hide show
  1. fastapi_factory_utilities/__main__.py +6 -0
  2. fastapi_factory_utilities/core/__init__.py +1 -0
  3. fastapi_factory_utilities/core/api/__init__.py +25 -0
  4. fastapi_factory_utilities/core/api/tags.py +9 -0
  5. fastapi_factory_utilities/core/api/v1/sys/__init__.py +12 -0
  6. fastapi_factory_utilities/core/api/v1/sys/health.py +53 -0
  7. fastapi_factory_utilities/core/api/v1/sys/readiness.py +53 -0
  8. fastapi_factory_utilities/core/app/__init__.py +19 -0
  9. fastapi_factory_utilities/core/app/base/__init__.py +17 -0
  10. fastapi_factory_utilities/core/app/base/application.py +123 -0
  11. fastapi_factory_utilities/core/app/base/config_abstract.py +78 -0
  12. fastapi_factory_utilities/core/app/base/exceptions.py +25 -0
  13. fastapi_factory_utilities/core/app/base/fastapi_application_abstract.py +88 -0
  14. fastapi_factory_utilities/core/app/base/plugins_manager_abstract.py +136 -0
  15. fastapi_factory_utilities/core/app/enums.py +11 -0
  16. fastapi_factory_utilities/core/plugins/__init__.py +15 -0
  17. fastapi_factory_utilities/core/plugins/odm_plugin/__init__.py +97 -0
  18. fastapi_factory_utilities/core/plugins/odm_plugin/builder.py +239 -0
  19. fastapi_factory_utilities/core/plugins/odm_plugin/configs.py +17 -0
  20. fastapi_factory_utilities/core/plugins/odm_plugin/documents.py +31 -0
  21. fastapi_factory_utilities/core/plugins/odm_plugin/exceptions.py +25 -0
  22. fastapi_factory_utilities/core/plugins/odm_plugin/repositories.py +172 -0
  23. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/__init__.py +124 -0
  24. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/builder.py +266 -0
  25. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/configs.py +103 -0
  26. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/exceptions.py +13 -0
  27. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/helpers.py +42 -0
  28. fastapi_factory_utilities/core/protocols.py +82 -0
  29. fastapi_factory_utilities/core/utils/configs.py +80 -0
  30. fastapi_factory_utilities/core/utils/importlib.py +28 -0
  31. fastapi_factory_utilities/core/utils/log.py +178 -0
  32. fastapi_factory_utilities/core/utils/uvicorn.py +45 -0
  33. fastapi_factory_utilities/core/utils/yaml_reader.py +166 -0
  34. fastapi_factory_utilities/example/__init__.py +11 -0
  35. fastapi_factory_utilities/example/__main__.py +6 -0
  36. fastapi_factory_utilities/example/api/__init__.py +19 -0
  37. fastapi_factory_utilities/example/api/books/__init__.py +5 -0
  38. fastapi_factory_utilities/example/api/books/responses.py +26 -0
  39. fastapi_factory_utilities/example/api/books/routes.py +62 -0
  40. fastapi_factory_utilities/example/app/__init__.py +6 -0
  41. fastapi_factory_utilities/example/app/app.py +37 -0
  42. fastapi_factory_utilities/example/app/config.py +12 -0
  43. fastapi_factory_utilities/example/application.yaml +26 -0
  44. fastapi_factory_utilities/example/entities/books/__init__.py +7 -0
  45. fastapi_factory_utilities/example/entities/books/entities.py +16 -0
  46. fastapi_factory_utilities/example/entities/books/enums.py +16 -0
  47. fastapi_factory_utilities/example/entities/books/types.py +54 -0
  48. fastapi_factory_utilities/example/models/__init__.py +1 -0
  49. fastapi_factory_utilities/example/models/books/__init__.py +6 -0
  50. fastapi_factory_utilities/example/models/books/document.py +20 -0
  51. fastapi_factory_utilities/example/models/books/repository.py +11 -0
  52. fastapi_factory_utilities/example/services/books/__init__.py +5 -0
  53. fastapi_factory_utilities/example/services/books/services.py +167 -0
  54. fastapi_factory_utilities-0.1.0.dist-info/LICENSE +21 -0
  55. fastapi_factory_utilities-0.1.0.dist-info/METADATA +131 -0
  56. fastapi_factory_utilities-0.1.0.dist-info/RECORD +58 -0
  57. fastapi_factory_utilities-0.1.0.dist-info/WHEEL +4 -0
  58. fastapi_factory_utilities-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,124 @@
1
+ """OpenTelemetry Plugin Module."""
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 BaseApplicationProtocol
14
+
15
+ from .builder import OpenTelemetryPluginBuilder
16
+ from .configs import OpenTelemetryConfig
17
+ from .exceptions import OpenTelemetryPluginBaseException, OpenTelemetryPluginConfigError
18
+
19
+ __all__: list[str] = [
20
+ "OpenTelemetryConfig",
21
+ "OpenTelemetryPluginBaseException",
22
+ "OpenTelemetryPluginConfigError",
23
+ "OpenTelemetryPluginBuilder",
24
+ ]
25
+
26
+ _logger: BoundLogger = get_logger()
27
+
28
+
29
+ def pre_conditions_check(application: BaseApplicationProtocol) -> 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: BaseApplicationProtocol,
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: BaseApplicationProtocol,
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: BaseApplicationProtocol) -> 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.")
@@ -0,0 +1,266 @@
1
+ """Provides a factory function to build a objets for OpenTelemetry."""
2
+
3
+ from typing import Any, Self
4
+
5
+ from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
6
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
7
+ from opentelemetry.metrics import set_meter_provider
8
+ from opentelemetry.propagate import set_global_textmap
9
+ from opentelemetry.propagators.b3 import B3MultiFormat
10
+ from opentelemetry.sdk.metrics import MeterProvider
11
+ from opentelemetry.sdk.metrics.export import MetricReader, PeriodicExportingMetricReader
12
+ from opentelemetry.sdk.resources import (
13
+ DEPLOYMENT_ENVIRONMENT,
14
+ SERVICE_NAME,
15
+ SERVICE_NAMESPACE,
16
+ SERVICE_VERSION,
17
+ Resource,
18
+ )
19
+ from opentelemetry.sdk.trace import SynchronousMultiSpanProcessor, TracerProvider
20
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
21
+ from opentelemetry.trace import set_tracer_provider
22
+
23
+ from fastapi_factory_utilities.core.plugins.opentelemetry_plugin.configs import (
24
+ OpenTelemetryMeterConfig,
25
+ OpenTelemetryTracerConfig,
26
+ )
27
+ from fastapi_factory_utilities.core.protocols import BaseApplicationProtocol
28
+ from fastapi_factory_utilities.core.utils.importlib import get_path_file_in_package
29
+ from fastapi_factory_utilities.core.utils.yaml_reader import (
30
+ UnableToReadYamlFileError,
31
+ YamlFileReader,
32
+ )
33
+
34
+ from .configs import OpenTelemetryConfig
35
+ from .exceptions import OpenTelemetryPluginConfigError
36
+
37
+
38
+ class OpenTelemetryPluginBuilder:
39
+ """Configure the injection bindings for OpenTelemetryPlugin."""
40
+
41
+ def __init__(self, application: BaseApplicationProtocol) -> None:
42
+ """Instantiate the OpenTelemetryPluginFactory.
43
+
44
+ Args:
45
+ application (BaseApplicationProtocol): The application object.
46
+ """
47
+ self._application: BaseApplicationProtocol = application
48
+ self._resource: Resource | None = None
49
+ self._config: OpenTelemetryConfig | None = None
50
+ self._meter_provider: MeterProvider | None = None
51
+ self._tracer_provider: TracerProvider | None = None
52
+
53
+ @property
54
+ def resource(self) -> Resource | None:
55
+ """Provide the resource object for OpenTelemetry.
56
+
57
+ Returns:
58
+ Resource | None: The resource object for OpenTelemetry.
59
+ """
60
+ return self._resource
61
+
62
+ @property
63
+ def config(self) -> OpenTelemetryConfig | None:
64
+ """Provide the configuration object for OpenTelemetry.
65
+
66
+ Returns:
67
+ OpenTelemetryConfig | None: The configuration object for OpenTelemetry.
68
+ """
69
+ return self._config
70
+
71
+ @property
72
+ def meter_provider(self) -> MeterProvider | None:
73
+ """Provide the meter provider object for OpenTelemetry.
74
+
75
+ Returns:
76
+ MeterProvider | None: The meter provider object for OpenTelemetry.
77
+ """
78
+ return self._meter_provider
79
+
80
+ @property
81
+ def tracer_provider(self) -> TracerProvider | None:
82
+ """Provide the tracer provider object for OpenTelemetry.
83
+
84
+ Returns:
85
+ TracerProvider | None: The tracer provider object for OpenTelemetry.
86
+ """
87
+ return self._tracer_provider
88
+
89
+ def build_resource(self) -> Self:
90
+ """Build a resource object for OpenTelemetry from the application and configs.
91
+
92
+ Returns:
93
+ Self: The OpenTelemetryPluginFactory object.
94
+ """
95
+ self._resource = Resource(
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,
101
+ }
102
+ )
103
+ return self
104
+
105
+ def build_config(
106
+ self,
107
+ ) -> Self:
108
+ """Build the configuration object for OpenTelemetry from the application.
109
+
110
+ Returns:
111
+ Self: The OpenTelemetryPluginFactory object.
112
+
113
+ Raises:
114
+ OpenTelemetryPluginConfigError: If the package name is not set in the application.
115
+ OpenTelemetryPluginConfigError: If the application configuration file is not found.
116
+
117
+ """
118
+ if self._application.PACKAGE_NAME == "":
119
+ raise OpenTelemetryPluginConfigError("The package name must be set in the concrete application class.")
120
+
121
+ # Read the application configuration file
122
+ try:
123
+ yaml_file_content: dict[str, Any] = YamlFileReader(
124
+ file_path=get_path_file_in_package(
125
+ filename="application.yaml",
126
+ package=self._application.PACKAGE_NAME,
127
+ ),
128
+ yaml_base_key="opentelemetry",
129
+ use_environment_injection=True,
130
+ ).read()
131
+ except (FileNotFoundError, ImportError, UnableToReadYamlFileError) as exception:
132
+ raise OpenTelemetryPluginConfigError("Unable to read the application configuration file.") from exception
133
+
134
+ # Create the application configuration model
135
+ try:
136
+ self._config = OpenTelemetryConfig(**yaml_file_content)
137
+ except ValueError as exception:
138
+ raise OpenTelemetryPluginConfigError("Unable to create the application configuration model.") from exception
139
+
140
+ return self
141
+
142
+ def build_meter_provider(
143
+ self,
144
+ ) -> Self:
145
+ """Build a meter provider object for OpenTelemetry.
146
+
147
+ Returns:
148
+ Self: The OpenTelemetryPluginFactory object.
149
+ """
150
+ if self._resource is None:
151
+ raise OpenTelemetryPluginConfigError("The resource object is missing.")
152
+
153
+ if self._config is None:
154
+ raise OpenTelemetryPluginConfigError("The configuration object is missing.")
155
+
156
+ readers: list[MetricReader] = []
157
+ if self._config.activate is True:
158
+ if self._config.meter_config is None:
159
+ # TODO: switch to a custom exception
160
+ raise OpenTelemetryPluginConfigError("The meter configuration is missing.")
161
+
162
+ # TODO: Extract to a dedicated method for the exporter and period reader setup
163
+
164
+ # Setup the Exporter
165
+ exporter = OTLPMetricExporter(
166
+ endpoint=f"{self._config.endpoint.unicode_string()}v1/metrics",
167
+ timeout=self._config.timeout,
168
+ )
169
+
170
+ # Setup the Metric Reader
171
+ meter_config: OpenTelemetryMeterConfig = self._config.meter_config
172
+ reader = PeriodicExportingMetricReader(
173
+ exporter=exporter,
174
+ export_interval_millis=meter_config.reader_interval_millis,
175
+ export_timeout_millis=meter_config.reader_timeout_millis,
176
+ )
177
+ readers.append(reader)
178
+
179
+ # Setup the Meter Provider
180
+ self._meter_provider = MeterProvider(
181
+ resource=self._resource,
182
+ metric_readers=readers,
183
+ shutdown_on_exit=True,
184
+ views=[],
185
+ )
186
+
187
+ set_meter_provider(self._meter_provider)
188
+
189
+ return self
190
+
191
+ def build_tracer_provider(
192
+ self,
193
+ ) -> Self:
194
+ """Provides a tracer provider for OpenTelemetry.
195
+
196
+ Returns:
197
+ Self: The OpenTelemetryPluginFactory object.
198
+
199
+ Raises:
200
+ OpenTelemetryPluginConfigError: If the resource object is missing.
201
+ OpenTelemetryPluginConfigError: If the configuration object is missing.
202
+ """
203
+ if self._resource is None:
204
+ raise OpenTelemetryPluginConfigError("The resource object is missing.")
205
+
206
+ if self._config is None:
207
+ raise OpenTelemetryPluginConfigError("The configuration object is missing.")
208
+
209
+ active_span_processor: SynchronousMultiSpanProcessor | None = None
210
+
211
+ # Exit with a void TracerProvider if the export is not activated
212
+ if self._config.activate is True:
213
+ if self._config.tracer_config is None:
214
+ raise OpenTelemetryPluginConfigError("The tracer configuration is missing.")
215
+
216
+ # Setup the Exporter
217
+ exporter = OTLPSpanExporter(
218
+ endpoint=f"{self._config.endpoint.unicode_string()}v1/traces",
219
+ timeout=self._config.timeout,
220
+ )
221
+
222
+ # Setup the Span Processor
223
+ tracer_config: OpenTelemetryTracerConfig = self._config.tracer_config
224
+ span_processor = BatchSpanProcessor(
225
+ span_exporter=exporter,
226
+ max_queue_size=tracer_config.max_queue_size,
227
+ max_export_batch_size=tracer_config.max_export_batch_size,
228
+ schedule_delay_millis=tracer_config.schedule_delay_millis,
229
+ export_timeout_millis=tracer_config.export_timeout_millis,
230
+ )
231
+
232
+ # Setup the Multi Span Processor
233
+ active_span_processor = SynchronousMultiSpanProcessor()
234
+ active_span_processor.add_span_processor(span_processor=span_processor)
235
+
236
+ # Setup the TextMap Propagator for B3
237
+ set_global_textmap(http_text_format=B3MultiFormat())
238
+
239
+ # Setup the Tracer Provider
240
+ self._tracer_provider = TracerProvider(
241
+ sampler=None,
242
+ resource=self._resource,
243
+ active_span_processor=active_span_processor,
244
+ id_generator=None,
245
+ span_limits=None,
246
+ shutdown_on_exit=True,
247
+ )
248
+
249
+ set_tracer_provider(self._tracer_provider)
250
+
251
+ return self
252
+
253
+ def build_all(
254
+ self,
255
+ ) -> Self:
256
+ """Build all the objects for OpenTelemetry.
257
+
258
+ Returns:
259
+ Self: The OpenTelemetryPluginFactory object.
260
+ """
261
+ self.build_resource()
262
+ self.build_config()
263
+ self.build_meter_provider()
264
+ self.build_tracer_provider()
265
+
266
+ return self
@@ -0,0 +1,103 @@
1
+ """Provides the configuration model for the OpenTelemetry plugin."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field, UrlConstraints
6
+ from pydantic_core import Url
7
+
8
+
9
+ class OpenTelemetryMeterConfig(BaseModel):
10
+ """Provides the configuration model for the OpenTelemetry meter as sub-model."""
11
+
12
+ # Constants for time in milliseconds and seconds
13
+ ONE_MINUTE_IN_MILLIS: int = 60000
14
+ ONE_SECOND_IN_MILLIS: int = 1000
15
+
16
+ model_config = ConfigDict(frozen=True, extra="forbid")
17
+
18
+ reader_interval_millis: float = Field(
19
+ default=ONE_SECOND_IN_MILLIS,
20
+ description="The interval in miliseconds to read and export metrics.",
21
+ )
22
+
23
+ reader_timeout_millis: float = Field(
24
+ default=ONE_SECOND_IN_MILLIS,
25
+ description="The timeout in miliseconds for the reader.",
26
+ )
27
+
28
+
29
+ class OpenTelemetryTracerConfig(BaseModel):
30
+ """Provides the configuration model for the OpenTelemetry tracer as sub-model."""
31
+
32
+ # Constants for time in milliseconds and seconds
33
+ FIVE_SECONDS_IN_MILLIS: int = 5000
34
+ THIRTY_SECONDS_IN_MILLIS: int = 30000
35
+
36
+ # Default values for the OpenTelemetry tracer
37
+ DEFAULT_OTEL_TRACER_MAX_QUEUE_SIZE: int = 2048
38
+ DEFAULT_OTEL_TRACER_MAX_EXPORT_BATCH_SIZE: int = 512
39
+
40
+ model_config = ConfigDict(frozen=True, extra="forbid")
41
+
42
+ max_queue_size: int = Field(
43
+ default=DEFAULT_OTEL_TRACER_MAX_QUEUE_SIZE,
44
+ description="The maximum queue size for the tracer.",
45
+ )
46
+ max_export_batch_size: int = Field(
47
+ default=DEFAULT_OTEL_TRACER_MAX_EXPORT_BATCH_SIZE,
48
+ description="The maximum export batch size for the tracer.",
49
+ )
50
+ schedule_delay_millis: int = Field(
51
+ default=FIVE_SECONDS_IN_MILLIS,
52
+ description="The schedule delay in miliseconds for the tracer.",
53
+ )
54
+ export_timeout_millis: int = Field(
55
+ default=THIRTY_SECONDS_IN_MILLIS,
56
+ description="The export timeout in miliseconds for the tracer.",
57
+ )
58
+
59
+
60
+ class OpenTelemetryConfig(BaseModel):
61
+ """Provides the configuration model for the OpenTelemetry plugin."""
62
+
63
+ # Constants for time in milliseconds and seconds
64
+ TEN_SECONDS_IN_SECONDS: int = 10
65
+
66
+ # Default value for the collector endpoint
67
+ DEFAULT_COLLECTOR_ENDPOINT: str = "http://localhost:4318"
68
+
69
+ model_config = ConfigDict(frozen=True, extra="forbid")
70
+
71
+ activate: bool = Field(
72
+ default=False,
73
+ description="Whether to activate the OpenTelemetry collector export.",
74
+ )
75
+ endpoint: Annotated[Url, UrlConstraints(allowed_schemes=["http", "https"])] = Field(
76
+ default=Url(url=DEFAULT_COLLECTOR_ENDPOINT),
77
+ description="The collector endpoint.",
78
+ )
79
+
80
+ timeout: int = Field(
81
+ default=TEN_SECONDS_IN_SECONDS,
82
+ description="The timeout in seconds for the collector.",
83
+ )
84
+
85
+ closing_timeout: int = Field(
86
+ default=TEN_SECONDS_IN_SECONDS,
87
+ description="The closing timeout in seconds for the collector.",
88
+ )
89
+
90
+ meter_config: OpenTelemetryMeterConfig | None = Field(
91
+ default_factory=OpenTelemetryMeterConfig,
92
+ description="The meter configuration.",
93
+ )
94
+
95
+ tracer_config: OpenTelemetryTracerConfig | None = Field(
96
+ default_factory=OpenTelemetryTracerConfig,
97
+ description="The tracer configuration.",
98
+ )
99
+
100
+ excluded_urls: list[str] = Field(
101
+ default_factory=list,
102
+ description="The excluded URLs for both the metrics and traces.",
103
+ )
@@ -0,0 +1,13 @@
1
+ """Provides the exceptions for the OpenTelemetryPlugin."""
2
+
3
+
4
+ class OpenTelemetryPluginBaseException(BaseException):
5
+ """Base exception for the OpenTelemetryPlugin."""
6
+
7
+ pass
8
+
9
+
10
+ class OpenTelemetryPluginConfigError(OpenTelemetryPluginBaseException):
11
+ """Exception for the OpenTelemetryPlugin configuration."""
12
+
13
+ pass
@@ -0,0 +1,42 @@
1
+ """Provides helper functions for the OpenTelemetry plugin."""
2
+
3
+ from collections.abc import Callable
4
+ from functools import wraps
5
+ from typing import ParamSpec, TypeVar
6
+
7
+ from opentelemetry import trace
8
+ from opentelemetry.context import Context
9
+ from opentelemetry.trace import SpanKind
10
+ from opentelemetry.util import types
11
+
12
+ Param = ParamSpec("Param")
13
+ RetType = TypeVar("RetType") # pylint: disable=invalid-name
14
+
15
+
16
+ def trace_span(
17
+ name: str | None = None,
18
+ context: Context | None = None,
19
+ kind: SpanKind = SpanKind.INTERNAL,
20
+ attributes: types.Attributes = None,
21
+ ) -> Callable[[Callable[Param, RetType]], Callable[Param, RetType]]:
22
+ """Decorator to trace a function using OpenTelemetry."""
23
+
24
+ def decorator(func: Callable[Param, RetType]) -> Callable[Param, RetType]:
25
+ @wraps(wrapped=func)
26
+ def wrapper(*args: Param.args, **kwargs: Param.kwargs) -> RetType:
27
+ # Get Tracer from the instrumented function's module
28
+ tracer: trace.Tracer = trace.get_tracer(instrumenting_module_name=func.__module__)
29
+ # Use the function's name as the span name if no name is provided
30
+ trace_name: str = name if name is not None else func.__name__
31
+ # Start a span with the provided name
32
+ with tracer.start_as_current_span(
33
+ name=trace_name,
34
+ kind=kind,
35
+ context=context,
36
+ attributes=attributes,
37
+ ):
38
+ return func(*args, **kwargs)
39
+
40
+ return wrapper
41
+
42
+ return decorator
@@ -0,0 +1,82 @@
1
+ """Protocols for the base application."""
2
+
3
+ from abc import abstractmethod
4
+ from typing import TYPE_CHECKING, ClassVar, Protocol, runtime_checkable
5
+
6
+ from beanie import Document
7
+ from fastapi import FastAPI
8
+
9
+ if TYPE_CHECKING:
10
+ from fastapi_factory_utilities.core.app.base.config_abstract import (
11
+ AppConfigAbstract,
12
+ )
13
+
14
+
15
+ class BaseApplicationProtocol(Protocol):
16
+ """Protocol for the base application."""
17
+
18
+ PACKAGE_NAME: str
19
+
20
+ ODM_DOCUMENT_MODELS: ClassVar[list[type[Document]]]
21
+
22
+ @abstractmethod
23
+ def get_config(self) -> "AppConfigAbstract":
24
+ """Get the application configuration."""
25
+
26
+ @abstractmethod
27
+ def get_asgi_app(self) -> FastAPI:
28
+ """Get the ASGI application."""
29
+
30
+
31
+ @runtime_checkable
32
+ class PluginProtocol(Protocol):
33
+ """Defines the protocol for the plugin.
34
+
35
+ Attributes:
36
+ INJECTOR_MODULE (type[Module]): The module for the plugin.
37
+
38
+ """
39
+
40
+ @abstractmethod
41
+ def pre_conditions_check(self, application: BaseApplicationProtocol) -> bool:
42
+ """Check the pre-conditions for the plugin.
43
+
44
+ Args:
45
+ application (BaseApplicationProtocol): The application.
46
+
47
+ Returns:
48
+ bool: True if the pre-conditions are met, False otherwise.
49
+ """
50
+
51
+ @abstractmethod
52
+ def on_load(self, application: BaseApplicationProtocol) -> None:
53
+ """The actions to perform on load for the plugin.
54
+
55
+ Args:
56
+ application (BaseApplicationProtocol): The application.
57
+
58
+ Returns:
59
+ None
60
+ """
61
+
62
+ @abstractmethod
63
+ async def on_startup(self, application: BaseApplicationProtocol) -> None:
64
+ """The actions to perform on startup for the plugin.
65
+
66
+ Args:
67
+ application (BaseApplicationProtocol): The application.
68
+
69
+ Returns:
70
+ None
71
+ """
72
+
73
+ @abstractmethod
74
+ async def on_shutdown(self, application: BaseApplicationProtocol) -> None:
75
+ """The actions to perform on shutdown for the plugin.
76
+
77
+ Args:
78
+ application (BaseApplicationProtocol): The application.
79
+
80
+ Returns:
81
+ None
82
+ """