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,6 @@
1
+ """Re-Use the main function from the example module."""
2
+
3
+ from fastapi_factory_utilities.example import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1 @@
1
+ """Python Factory Core Module."""
@@ -0,0 +1,25 @@
1
+ """Define the API for the Python Factory."""
2
+
3
+ from fastapi import APIRouter
4
+
5
+ from .tags import TagEnum
6
+ from .v1.sys import api_v1_sys
7
+
8
+ api: APIRouter = APIRouter(prefix="/api")
9
+
10
+ ### API v1 ###
11
+ # Prefix the API with /api/v1
12
+ api_v1: APIRouter = APIRouter(prefix="/v1")
13
+ api_v1.include_router(router=api_v1_sys)
14
+
15
+
16
+ ### API v2 ###
17
+ # Prefix the API with /api/v2
18
+ api_v2: APIRouter = APIRouter(prefix="/v2")
19
+
20
+
21
+ ### Include the API routers ###
22
+ api.include_router(router=api_v1)
23
+ api.include_router(router=api_v2)
24
+
25
+ __all__: list[str] = ["api", "TagEnum"]
@@ -0,0 +1,9 @@
1
+ """Provides the Tag as Enum."""
2
+
3
+ from enum import StrEnum, auto
4
+
5
+
6
+ class TagEnum(StrEnum):
7
+ """Define Tag for OpenAPI Description."""
8
+
9
+ SYS = auto()
@@ -0,0 +1,12 @@
1
+ """Package for system related API endpoints."""
2
+
3
+ from fastapi import APIRouter
4
+
5
+ from .health import api_v1_sys_health
6
+ from .readiness import api_v1_sys_readiness
7
+
8
+ api_v1_sys = APIRouter(prefix="/sys")
9
+ api_v1_sys.include_router(router=api_v1_sys_health)
10
+ api_v1_sys.include_router(router=api_v1_sys_readiness)
11
+
12
+ __all__: list[str] = ["api_v1_sys"]
@@ -0,0 +1,53 @@
1
+ """API v1 sys health module.
2
+
3
+ Provide the Get health endpoint
4
+ """
5
+
6
+ from enum import StrEnum
7
+ from http import HTTPStatus
8
+
9
+ from fastapi import APIRouter, Response
10
+ from pydantic import BaseModel
11
+
12
+ api_v1_sys_health = APIRouter(prefix="/health")
13
+
14
+
15
+ class HealthStatusEnum(StrEnum):
16
+ """Health status enum."""
17
+
18
+ HEALTHY = "healthy"
19
+ UNHEALTHY = "unhealthy"
20
+
21
+
22
+ class HealthResponseModel(BaseModel):
23
+ """Health response schema."""
24
+
25
+ status: HealthStatusEnum
26
+
27
+
28
+ @api_v1_sys_health.get(
29
+ path="",
30
+ tags=["sys"],
31
+ response_model=HealthResponseModel,
32
+ responses={
33
+ HTTPStatus.OK.value: {
34
+ "model": HealthResponseModel,
35
+ "description": "Health status.",
36
+ },
37
+ HTTPStatus.INTERNAL_SERVER_ERROR.value: {
38
+ "model": HealthResponseModel,
39
+ "description": "Internal server error.",
40
+ },
41
+ },
42
+ )
43
+ def get_api_v1_sys_health(response: Response) -> HealthResponseModel:
44
+ """Get the health of the system.
45
+
46
+ Args:
47
+ response (Response): The response object.
48
+
49
+ Returns:
50
+ HealthResponse: The health status.
51
+ """
52
+ response.status_code = HTTPStatus.OK
53
+ return HealthResponseModel(status=HealthStatusEnum.HEALTHY)
@@ -0,0 +1,53 @@
1
+ """API v1 sys readiness module.
2
+
3
+ Provide the Get readiness endpoint
4
+ """
5
+
6
+ from enum import StrEnum
7
+ from http import HTTPStatus
8
+
9
+ from fastapi import APIRouter, Response
10
+ from pydantic import BaseModel
11
+
12
+ api_v1_sys_readiness = APIRouter(prefix="/readiness")
13
+
14
+
15
+ class ReadinessStatusEnum(StrEnum):
16
+ """Readiness status enum."""
17
+
18
+ READY = "ready"
19
+ NOT_READY = "not_ready"
20
+
21
+
22
+ class ReadinessResponseModel(BaseModel):
23
+ """Readiness response schema."""
24
+
25
+ status: ReadinessStatusEnum
26
+
27
+
28
+ @api_v1_sys_readiness.get(
29
+ path="",
30
+ tags=["sys"],
31
+ response_model=ReadinessResponseModel,
32
+ responses={
33
+ HTTPStatus.OK.value: {
34
+ "model": ReadinessResponseModel,
35
+ "description": "Readiness status.",
36
+ },
37
+ HTTPStatus.INTERNAL_SERVER_ERROR.value: {
38
+ "model": ReadinessResponseModel,
39
+ "description": "Internal server error.",
40
+ },
41
+ },
42
+ )
43
+ def get_api_v1_sys_readiness(response: Response) -> ReadinessResponseModel:
44
+ """Get the readiness of the system.
45
+
46
+ Args:
47
+ response (Response): The response object.
48
+
49
+ Returns:
50
+ ReadinessResponse: The readiness status.
51
+ """
52
+ response.status_code = HTTPStatus.OK
53
+ return ReadinessResponseModel(status=ReadinessStatusEnum.READY)
@@ -0,0 +1,19 @@
1
+ """Provides the core application module for the Python Factory."""
2
+
3
+ from .base import (
4
+ AppConfigAbstract,
5
+ ApplicationConfigFactoryException,
6
+ ApplicationFactoryException,
7
+ BaseApplication,
8
+ BaseApplicationException,
9
+ )
10
+ from .enums import EnvironmentEnum
11
+
12
+ __all__: list[str] = [
13
+ "BaseApplication",
14
+ "AppConfigAbstract",
15
+ "EnvironmentEnum",
16
+ "ApplicationConfigFactoryException",
17
+ "ApplicationFactoryException",
18
+ "BaseApplicationException",
19
+ ]
@@ -0,0 +1,17 @@
1
+ """Package for the base application, abstract config classes and related exceptions."""
2
+
3
+ from .application import BaseApplication
4
+ from .config_abstract import AppConfigAbstract
5
+ from .exceptions import (
6
+ ApplicationConfigFactoryException,
7
+ ApplicationFactoryException,
8
+ BaseApplicationException,
9
+ )
10
+
11
+ __all__: list[str] = [
12
+ "BaseApplication",
13
+ "AppConfigAbstract",
14
+ "ApplicationConfigFactoryException",
15
+ "ApplicationFactoryException",
16
+ "BaseApplicationException",
17
+ ]
@@ -0,0 +1,123 @@
1
+ """Provides the abstract class for the application."""
2
+
3
+ from collections.abc import AsyncGenerator
4
+ from contextlib import asynccontextmanager
5
+ from typing import ClassVar, Self, cast
6
+
7
+ import starlette.types
8
+ from beanie import Document
9
+ from fastapi import FastAPI
10
+
11
+ from fastapi_factory_utilities.core.api import api
12
+ from fastapi_factory_utilities.core.utils.log import LogModeEnum, setup_log
13
+
14
+ from .config_abstract import AppConfigAbstract, AppConfigBuilder
15
+ from .fastapi_application_abstract import FastAPIAbstract
16
+ from .plugins_manager_abstract import (
17
+ ApplicationPluginManagerAbstract,
18
+ PluginsActivationList,
19
+ )
20
+
21
+
22
+ class BaseApplication(FastAPIAbstract, ApplicationPluginManagerAbstract):
23
+ """Application abstract class."""
24
+
25
+ PACKAGE_NAME: str = ""
26
+
27
+ CONFIG_CLASS: ClassVar[type[AppConfigAbstract]] = AppConfigAbstract
28
+
29
+ ODM_DOCUMENT_MODELS: ClassVar[list[type[Document]]] = []
30
+
31
+ def __init__(self, config: AppConfigAbstract, plugin_activation_list: PluginsActivationList | None = None) -> None:
32
+ """Instantiate the application.
33
+
34
+ Args:
35
+ config (AppConfigAbstract): The application configuration.
36
+ plugin_activation_list (PluginsActivationList | None, optional): The plugins activation list.
37
+
38
+ Returns:
39
+ None
40
+
41
+ Raises:
42
+ ValueError: If the package name is not set.
43
+ """
44
+ if self.PACKAGE_NAME == "":
45
+ raise ValueError("The package name must be set in the concrete application class.")
46
+
47
+ self._config: AppConfigAbstract = config
48
+ FastAPIAbstract.__init__(
49
+ self=cast(FastAPIAbstract, self),
50
+ config=self._config,
51
+ api_router=api,
52
+ lifespan=cast(starlette.types.StatelessLifespan[starlette.types.ASGIApp], self.fastapi_lifespan),
53
+ )
54
+ ApplicationPluginManagerAbstract.__init__(
55
+ self=cast(ApplicationPluginManagerAbstract, self), plugin_activation_list=plugin_activation_list
56
+ )
57
+ self._on_load()
58
+
59
+ @classmethod
60
+ def main(cls) -> None:
61
+ """Main function.
62
+
63
+ This must be the same for all applications.
64
+ """
65
+ from fastapi_factory_utilities.core.utils.uvicorn import ( # pylint: disable=import-outside-toplevel
66
+ UvicornUtils,
67
+ )
68
+
69
+ setup_log(mode=LogModeEnum.CONSOLE)
70
+ application: BaseApplication = cls.build()
71
+ uvicorn_utils = UvicornUtils(app=application)
72
+
73
+ try:
74
+ uvicorn_utils.serve()
75
+ except KeyboardInterrupt:
76
+ pass
77
+
78
+ @classmethod
79
+ def build_config(cls) -> AppConfigAbstract:
80
+ """Build the application configuration.
81
+
82
+ Returns:
83
+ AppConfigAbstract: The application configuration.
84
+ """
85
+ config_builder: AppConfigBuilder = AppConfigBuilder(
86
+ package_name=cls.PACKAGE_NAME, config_class=cls.CONFIG_CLASS
87
+ )
88
+ return config_builder.build()
89
+
90
+ @classmethod
91
+ def build(
92
+ cls, config: AppConfigAbstract | None = None, plugin_activation_list: PluginsActivationList | None = None
93
+ ) -> Self:
94
+ """Build the application.
95
+
96
+ Args:
97
+ config (AppConfigAbstract | None, optional): The application configuration. Defaults to None.
98
+ plugin_activation_list (PluginsActivationList | None, optional): The plugins activation list.
99
+ Defaults to None.
100
+ """
101
+ if config is None:
102
+ config = cls.build_config()
103
+
104
+ return cls(config=config, plugin_activation_list=plugin_activation_list)
105
+
106
+ @asynccontextmanager
107
+ async def fastapi_lifespan(self, fastapi_application: FastAPI) -> AsyncGenerator[None, None]:
108
+ """Provide the lifespan context manager for FastAPI.
109
+
110
+ Args:
111
+ fastapi_application (FastAPI): The FastAPI application.
112
+
113
+ Returns:
114
+ AsyncGenerator[None]: The lifespan context manager.
115
+ """
116
+ del fastapi_application
117
+ await self.plugins_on_startup()
118
+ yield
119
+ await self.plugins_on_shutdown()
120
+
121
+ def get_config(self) -> AppConfigAbstract:
122
+ """Get the application configuration."""
123
+ return self._config
@@ -0,0 +1,78 @@
1
+ """Provide the configuration for the app server."""
2
+
3
+ from pydantic import Field
4
+
5
+ from fastapi_factory_utilities.core.app.base.exceptions import (
6
+ ApplicationConfigFactoryException,
7
+ )
8
+ from fastapi_factory_utilities.core.app.base.plugins_manager_abstract import (
9
+ PluginsActivationList,
10
+ )
11
+ from fastapi_factory_utilities.core.utils.configs import (
12
+ UnableToReadConfigFileError,
13
+ ValueErrorConfigError,
14
+ build_config_from_file_in_package,
15
+ )
16
+ from fastapi_factory_utilities.core.utils.log import LoggingConfig
17
+
18
+ from ..enums import EnvironmentEnum
19
+ from .fastapi_application_abstract import FastAPIConfigAbstract
20
+
21
+
22
+ class AppConfigAbstract(FastAPIConfigAbstract, PluginsActivationList):
23
+ """Application configuration abstract class."""
24
+
25
+ environment: EnvironmentEnum
26
+ service_name: str
27
+ service_namespace: str
28
+
29
+ logging: list[LoggingConfig] = Field(default_factory=list, description="Logging configuration.")
30
+
31
+
32
+ class AppConfigBuilder:
33
+ """Application configuration builder."""
34
+
35
+ DEFAULT_FILENAME: str = "application.yaml"
36
+ DEFAULT_YAML_BASE_KEY: str = "application"
37
+
38
+ def __init__(
39
+ self,
40
+ package_name: str,
41
+ config_class: type[AppConfigAbstract],
42
+ filename: str = DEFAULT_FILENAME,
43
+ yaml_base_key: str = DEFAULT_YAML_BASE_KEY,
44
+ ) -> None:
45
+ """Instantiate the builder.
46
+
47
+ Args:
48
+ package_name (str): The package name.
49
+ config_class (Type[AppConfigAbstract]): The configuration class.
50
+ filename (str, optional): The filename. Defaults to DEFAULT_FILENAME.
51
+ yaml_base_key (str, optional): The YAML base key. Defaults to DEFAULT_YAML_BASE_KEY.
52
+ """
53
+ self.package_name: str = package_name
54
+ self.config_class: type[AppConfigAbstract] = config_class
55
+ self.filename: str = filename
56
+ self.yaml_base_key: str = yaml_base_key
57
+
58
+ def build(self) -> AppConfigAbstract:
59
+ """Build the configuration.
60
+
61
+ Returns:
62
+ AppConfigAbstract: The configuration.
63
+ """
64
+ try:
65
+ config: AppConfigAbstract = build_config_from_file_in_package(
66
+ package_name=self.package_name,
67
+ config_class=self.config_class,
68
+ filename=self.filename,
69
+ yaml_base_key=self.yaml_base_key,
70
+ )
71
+ except UnableToReadConfigFileError as exception:
72
+ raise ApplicationConfigFactoryException("Unable to read the application configuration file.") from exception
73
+ except ValueErrorConfigError as exception:
74
+ raise ApplicationConfigFactoryException(
75
+ "Unable to create the application configuration model."
76
+ ) from exception
77
+
78
+ return config
@@ -0,0 +1,25 @@
1
+ """Provides the exceptions for the application factory."""
2
+
3
+
4
+ class BaseApplicationException(BaseException):
5
+ """Base application exception."""
6
+
7
+ pass
8
+
9
+
10
+ class ApplicationFactoryException(BaseApplicationException):
11
+ """Application factory exception."""
12
+
13
+ pass
14
+
15
+
16
+ class ApplicationConfigFactoryException(BaseApplicationException):
17
+ """Application configuration factory exception."""
18
+
19
+ pass
20
+
21
+
22
+ class ApplicationPluginManagerException(BaseApplicationException):
23
+ """Application plugin manager exception."""
24
+
25
+ pass
@@ -0,0 +1,88 @@
1
+ """Provides an abstract class for FastAPI application integration."""
2
+
3
+ from abc import ABC
4
+ from typing import Any
5
+
6
+ import starlette.types
7
+ from fastapi import APIRouter, FastAPI
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from pydantic import BaseModel, ConfigDict, Field
10
+
11
+
12
+ class FastAPIConfigAbstract(ABC, BaseModel):
13
+ """Partial configuration for FastAPI."""
14
+
15
+ model_config = ConfigDict(strict=False)
16
+
17
+ # Application metadata
18
+ title: str
19
+ description: str
20
+ version: str
21
+
22
+ # Host configuration
23
+ host: str = Field(default="0.0.0.0")
24
+ port: int = Field(default=8000)
25
+
26
+ # Root configuration
27
+ root_path: str = Field(default="")
28
+
29
+ # Debug mode
30
+ debug: bool = Field(default=False, strict=False)
31
+
32
+ # Uvicorn configuration
33
+ reload: bool = Field(default=False, strict=False)
34
+ workers: int = Field(default=1, strict=False)
35
+
36
+
37
+ class FastAPIAbstract(ABC):
38
+ """Application integration with FastAPI.
39
+
40
+ TODO: Replace by a Factory pattern.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ config: FastAPIConfigAbstract,
46
+ api_router: APIRouter | None = None,
47
+ lifespan: starlette.types.StatelessLifespan[starlette.types.ASGIApp] | None = None,
48
+ ) -> None:
49
+ """Instanciate the FastAPI application.
50
+
51
+ Args:
52
+ config (FastAPIConfigAbstract): The FastAPI configuration.
53
+ api_router (APIRouter, optional): The API router to include.
54
+ Defaults to None.
55
+ lifespan (AsyncGenerator[None, None], optional): The lifespan
56
+
57
+ Returns:
58
+ None
59
+
60
+ """
61
+ self._fastapi_app: FastAPI = FastAPI(
62
+ title=config.title,
63
+ description=config.description,
64
+ version=config.version,
65
+ root_path=config.root_path,
66
+ debug=config.debug,
67
+ lifespan=lifespan,
68
+ )
69
+
70
+ # TODO: Add CORS middleware Configuration
71
+ self._fastapi_app.add_middleware(
72
+ middleware_class=CORSMiddleware,
73
+ allow_origins=["*"],
74
+ allow_credentials=True,
75
+ allow_methods=["*"],
76
+ allow_headers=["*"],
77
+ )
78
+
79
+ if api_router is not None:
80
+ self._fastapi_app.include_router(router=api_router)
81
+
82
+ def get_asgi_app(self) -> FastAPI:
83
+ """Get the ASGI application."""
84
+ return self._fastapi_app
85
+
86
+ async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
87
+ """Forward the call to the FastAPI app."""
88
+ return await self._fastapi_app.__call__(scope=scope, receive=receive, send=send)
@@ -0,0 +1,136 @@
1
+ """Plugins manager abstract module."""
2
+
3
+ from abc import ABC
4
+ from importlib import import_module
5
+ from types import ModuleType
6
+ from typing import cast
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+ from fastapi_factory_utilities.core.plugins import PluginsEnum
11
+ from fastapi_factory_utilities.core.protocols import (
12
+ BaseApplicationProtocol,
13
+ PluginProtocol,
14
+ )
15
+ from fastapi_factory_utilities.core.utils.configs import (
16
+ UnableToReadConfigFileError,
17
+ ValueErrorConfigError,
18
+ build_config_from_file_in_package,
19
+ )
20
+
21
+ from .exceptions import ApplicationPluginManagerException
22
+
23
+
24
+ class PluginsActivationList(BaseModel):
25
+ """Model for the plugins activation list."""
26
+
27
+ model_config = ConfigDict(extra="forbid")
28
+
29
+ activate: list[PluginsEnum] = Field(default=list())
30
+
31
+
32
+ class ApplicationPluginManagerAbstract(ABC):
33
+ """Abstract class for the application plugin manager.
34
+
35
+ Responsibilities:
36
+ - Retrieve the plugins for the application.
37
+ - Check the pre-conditions for the plugins.
38
+ - Perform actions on startup for the plugins.
39
+ - Perform actions on shutdown for the plugins.
40
+ """
41
+
42
+ PACKAGE_NAME: str = ""
43
+
44
+ PLUGIN_PACKAGE_NAME: str = "fastapi_factory_utilities.core.plugins"
45
+
46
+ def __init__(self, plugin_activation_list: PluginsActivationList | None = None) -> None:
47
+ """Instanciate the application plugin manager."""
48
+ if self.PACKAGE_NAME == "":
49
+ raise ValueError("The package name must be set in the concrete plugin manager class.")
50
+
51
+ self._plugins: list[PluginProtocol] = []
52
+ self._plugins_activation_list: PluginsActivationList
53
+ if plugin_activation_list is not None:
54
+ self._plugins_activation_list = plugin_activation_list
55
+ else:
56
+ self._plugins_activation_list = self._build_plugins_activation_list()
57
+
58
+ self._check_pre_conditions()
59
+
60
+ def _check_pre_conditions(self) -> None:
61
+ """Check the pre-conditions for the plugins.
62
+
63
+ Raises:
64
+ ApplicationPluginManagerException: If a plugin is not
65
+ activated.
66
+
67
+ """
68
+ for plugin in self._plugins_activation_list.activate:
69
+ try:
70
+ plugin_module: ModuleType = import_module(name=f"{self.PLUGIN_PACKAGE_NAME}.{plugin.value}")
71
+ except ImportError as exception:
72
+ raise ApplicationPluginManagerException(f"Unable to import the plugin {plugin.value}") from exception
73
+
74
+ if not isinstance(plugin_module, PluginProtocol):
75
+ raise ApplicationPluginManagerException(
76
+ f"The plugin {plugin.value} does not implement the PluginProtocol"
77
+ )
78
+
79
+ if not plugin_module.pre_conditions_check(application=cast(BaseApplicationProtocol, self)):
80
+ raise ApplicationPluginManagerException(f"The plugin {plugin.value} does not meet the pre-conditions")
81
+
82
+ self._plugins.append(plugin_module)
83
+
84
+ def _on_load(self) -> None:
85
+ """Actions to perform on load for the plugins."""
86
+ for plugin in self._plugins:
87
+ plugin.on_load(application=cast(BaseApplicationProtocol, self))
88
+
89
+ def _build_plugins_activation_list(self) -> PluginsActivationList:
90
+ """Build the plugins activation list.
91
+
92
+ Returns:
93
+ PluginsActivationList: The plugins activation list.
94
+
95
+ Raises:
96
+ ApplicationPluginManagerException: If there is an error
97
+ reading the configuration file.
98
+ ApplicationPluginManagerException: If there is an error
99
+ creating the configuration model.
100
+
101
+ """
102
+ try:
103
+ config: PluginsActivationList = build_config_from_file_in_package(
104
+ package_name=self.PACKAGE_NAME,
105
+ filename="application.yaml",
106
+ config_class=PluginsActivationList,
107
+ yaml_base_key="plugins",
108
+ )
109
+ except UnableToReadConfigFileError as exception:
110
+ raise ApplicationPluginManagerException("Unable to read the application configuration file") from exception
111
+ except ValueErrorConfigError as exception:
112
+ raise ApplicationPluginManagerException(
113
+ "Unable to create the application configuration model"
114
+ ) from exception
115
+
116
+ return config
117
+
118
+ async def plugins_on_startup(self) -> None:
119
+ """Actions to perform on startup for the plugins."""
120
+ for plugin in self._plugins:
121
+ try:
122
+ await plugin.on_startup(application=cast(BaseApplicationProtocol, self))
123
+ except Exception as exception:
124
+ raise ApplicationPluginManagerException(
125
+ f"Error during the startup of the plugin {plugin.__class__.__name__}"
126
+ ) from exception
127
+
128
+ async def plugins_on_shutdown(self) -> None:
129
+ """Actions to perform on shutdown for the plugins."""
130
+ for plugin in self._plugins:
131
+ try:
132
+ await plugin.on_shutdown(application=cast(BaseApplicationProtocol, self))
133
+ except Exception as exception:
134
+ raise ApplicationPluginManagerException(
135
+ f"Error during the shutdown of the plugin {plugin.__class__.__name__}"
136
+ ) from exception
@@ -0,0 +1,11 @@
1
+ """Provides enums for the app module."""
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class EnvironmentEnum(StrEnum):
7
+ """Represents the environment."""
8
+
9
+ DEVELOPMENT = "development"
10
+ STAGING = "staging"
11
+ PRODUCTION = "production"
@@ -0,0 +1,15 @@
1
+ """Package for plugins."""
2
+
3
+ from enum import StrEnum, auto
4
+
5
+
6
+ class PluginsEnum(StrEnum):
7
+ """Enumeration for the plugins."""
8
+
9
+ OPENTELEMETRY_PLUGIN = auto()
10
+ ODM_PLUGIN = auto()
11
+
12
+
13
+ __all__: list[str] = [
14
+ "PluginsEnum",
15
+ ]