fastapi-factory-utilities 0.2.0__py3-none-any.whl → 0.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fastapi-factory-utilities might be problematic. Click here for more details.
- fastapi_factory_utilities/core/api/__init__.py +1 -1
- fastapi_factory_utilities/core/api/v1/sys/health.py +1 -1
- fastapi_factory_utilities/core/app/__init__.py +12 -3
- fastapi_factory_utilities/core/app/application.py +24 -26
- fastapi_factory_utilities/core/app/builder.py +23 -37
- fastapi_factory_utilities/core/app/config.py +22 -1
- fastapi_factory_utilities/core/app/fastapi_builder.py +3 -2
- fastapi_factory_utilities/core/exceptions.py +58 -22
- 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 +70 -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 +86 -0
- fastapi_factory_utilities/core/plugins/odm_plugin/__init__.py +25 -153
- fastapi_factory_utilities/core/plugins/odm_plugin/builder.py +59 -31
- fastapi_factory_utilities/core/plugins/odm_plugin/configs.py +1 -1
- fastapi_factory_utilities/core/plugins/odm_plugin/documents.py +2 -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 +112 -3
- fastapi_factory_utilities/core/plugins/opentelemetry_plugin/__init__.py +8 -115
- fastapi_factory_utilities/core/plugins/opentelemetry_plugin/builder.py +65 -14
- fastapi_factory_utilities/core/plugins/opentelemetry_plugin/configs.py +13 -0
- 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 +29 -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/jwt.py +159 -0
- fastapi_factory_utilities/core/security/kratos.py +98 -0
- fastapi_factory_utilities/core/services/hydra/__init__.py +13 -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 +122 -0
- fastapi_factory_utilities/core/services/kratos/__init__.py +13 -0
- fastapi_factory_utilities/core/services/kratos/enums.py +11 -0
- fastapi_factory_utilities/core/services/kratos/exceptions.py +15 -0
- fastapi_factory_utilities/core/services/kratos/objects.py +43 -0
- fastapi_factory_utilities/core/services/kratos/services.py +86 -0
- fastapi_factory_utilities/core/services/status/__init__.py +2 -2
- fastapi_factory_utilities/core/utils/status.py +2 -1
- fastapi_factory_utilities/core/utils/uvicorn.py +36 -0
- fastapi_factory_utilities/core/utils/yaml_reader.py +2 -2
- 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/py.typed +0 -0
- {fastapi_factory_utilities-0.2.0.dist-info → fastapi_factory_utilities-0.7.1.dist-info}/METADATA +23 -14
- fastapi_factory_utilities-0.7.1.dist-info/RECORD +101 -0
- {fastapi_factory_utilities-0.2.0.dist-info → fastapi_factory_utilities-0.7.1.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-0.2.0.dist-info/RECORD +0 -70
- {fastapi_factory_utilities-0.2.0.dist-info → fastapi_factory_utilities-0.7.1.dist-info}/entry_points.txt +0 -0
- {fastapi_factory_utilities-0.2.0.dist-info → fastapi_factory_utilities-0.7.1.dist-info/licenses}/LICENSE +0 -0
|
@@ -10,10 +10,10 @@ from pydantic import BaseModel
|
|
|
10
10
|
|
|
11
11
|
from fastapi_factory_utilities.core.services.status.enums import HealthStatusEnum
|
|
12
12
|
from fastapi_factory_utilities.core.services.status.services import (
|
|
13
|
-
ComponentInstanceKey,
|
|
14
13
|
StatusService,
|
|
15
14
|
depends_status_service,
|
|
16
15
|
)
|
|
16
|
+
from fastapi_factory_utilities.core.services.status.types import ComponentInstanceKey
|
|
17
17
|
|
|
18
18
|
api_v1_sys_health = APIRouter(prefix="/health")
|
|
19
19
|
|
|
@@ -2,13 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from .application import ApplicationAbstract
|
|
4
4
|
from .builder import ApplicationGenericBuilder
|
|
5
|
-
from .config import
|
|
5
|
+
from .config import (
|
|
6
|
+
BaseApplicationConfig,
|
|
7
|
+
DependencyConfig,
|
|
8
|
+
HttpServiceDependencyConfig,
|
|
9
|
+
RootConfig,
|
|
10
|
+
depends_dependency_config,
|
|
11
|
+
)
|
|
6
12
|
from .enums import EnvironmentEnum
|
|
7
13
|
|
|
8
14
|
__all__: list[str] = [
|
|
9
|
-
"BaseApplicationConfig",
|
|
10
|
-
"EnvironmentEnum",
|
|
11
15
|
"ApplicationAbstract",
|
|
12
16
|
"ApplicationGenericBuilder",
|
|
17
|
+
"BaseApplicationConfig",
|
|
18
|
+
"DependencyConfig",
|
|
19
|
+
"EnvironmentEnum",
|
|
20
|
+
"HttpServiceDependencyConfig",
|
|
13
21
|
"RootConfig",
|
|
22
|
+
"depends_dependency_config",
|
|
14
23
|
]
|
|
@@ -12,10 +12,7 @@ from structlog.stdlib import BoundLogger, get_logger
|
|
|
12
12
|
from fastapi_factory_utilities.core.api import api
|
|
13
13
|
from fastapi_factory_utilities.core.app.config import RootConfig
|
|
14
14
|
from fastapi_factory_utilities.core.app.fastapi_builder import FastAPIBuilder
|
|
15
|
-
from fastapi_factory_utilities.core.
|
|
16
|
-
PluginManager,
|
|
17
|
-
)
|
|
18
|
-
from fastapi_factory_utilities.core.plugins import PluginsEnum
|
|
15
|
+
from fastapi_factory_utilities.core.plugins import PluginAbstract
|
|
19
16
|
from fastapi_factory_utilities.core.services.status.services import StatusService
|
|
20
17
|
|
|
21
18
|
_logger: BoundLogger = get_logger(__name__)
|
|
@@ -31,12 +28,10 @@ class ApplicationAbstract(ABC):
|
|
|
31
28
|
# TODO: Find a way to remove this from here
|
|
32
29
|
ODM_DOCUMENT_MODELS: ClassVar[list[type[Document]]]
|
|
33
30
|
|
|
34
|
-
DEFAULT_PLUGINS_ACTIVATED: ClassVar[list[PluginsEnum]] = []
|
|
35
|
-
|
|
36
31
|
def __init__(
|
|
37
32
|
self,
|
|
38
33
|
root_config: RootConfig,
|
|
39
|
-
|
|
34
|
+
plugins: list[PluginAbstract],
|
|
40
35
|
fastapi_builder: FastAPIBuilder,
|
|
41
36
|
) -> None:
|
|
42
37
|
"""Instantiate the application."""
|
|
@@ -44,9 +39,25 @@ class ApplicationAbstract(ABC):
|
|
|
44
39
|
self.fastapi_builder: FastAPIBuilder = fastapi_builder
|
|
45
40
|
# Add the API router to the FastAPI application
|
|
46
41
|
self.fastapi_builder.add_api_router(router=api, without_resource_path=True)
|
|
47
|
-
self.
|
|
42
|
+
self.plugins: list[PluginAbstract] = plugins
|
|
48
43
|
self._add_to_state: dict[str, Any] = {}
|
|
49
44
|
|
|
45
|
+
def load_plugins(self) -> None:
|
|
46
|
+
"""Load the plugins."""
|
|
47
|
+
for plugin in self.plugins:
|
|
48
|
+
plugin.set_application(application=self)
|
|
49
|
+
plugin.on_load()
|
|
50
|
+
|
|
51
|
+
async def startup_plugins(self) -> None:
|
|
52
|
+
"""Startup the plugins."""
|
|
53
|
+
for plugin in self.plugins:
|
|
54
|
+
await plugin.on_startup()
|
|
55
|
+
|
|
56
|
+
async def shutdown_plugins(self) -> None:
|
|
57
|
+
"""Shutdown the plugins."""
|
|
58
|
+
for plugin in self.plugins:
|
|
59
|
+
await plugin.on_shutdown()
|
|
60
|
+
|
|
50
61
|
def setup(self) -> None:
|
|
51
62
|
"""Initialize the application."""
|
|
52
63
|
# Initialize FastAPI application
|
|
@@ -54,15 +65,14 @@ class ApplicationAbstract(ABC):
|
|
|
54
65
|
# Status service
|
|
55
66
|
self.status_service: StatusService = StatusService()
|
|
56
67
|
self.add_to_state(key="status_service", value=self.status_service)
|
|
68
|
+
self.add_to_state(key="config", value=self.config)
|
|
69
|
+
self.add_to_state(key="application", value=self)
|
|
57
70
|
self._apply_states_to_fastapi_app()
|
|
58
71
|
# Configure the application
|
|
59
72
|
self.configure()
|
|
60
73
|
self._apply_states_to_fastapi_app()
|
|
61
74
|
# Initialize PluginManager
|
|
62
|
-
self.
|
|
63
|
-
self.plugin_manager.load()
|
|
64
|
-
# Add the states to the FastAPI app
|
|
65
|
-
self._import_states_from_plugin_manager()
|
|
75
|
+
self.load_plugins()
|
|
66
76
|
|
|
67
77
|
def _apply_states_to_fastapi_app(self) -> None:
|
|
68
78
|
# Add manually added states to the FastAPI app
|
|
@@ -72,13 +82,6 @@ class ApplicationAbstract(ABC):
|
|
|
72
82
|
setattr(self._asgi_app.state, key, value)
|
|
73
83
|
self._add_to_state.clear()
|
|
74
84
|
|
|
75
|
-
def _import_states_from_plugin_manager(self) -> None:
|
|
76
|
-
"""Import the states from the plugins."""
|
|
77
|
-
for state in self.plugin_manager.states:
|
|
78
|
-
self.add_to_state(key=state.key, value=state.value)
|
|
79
|
-
self.plugin_manager.clear_states()
|
|
80
|
-
self._apply_states_to_fastapi_app()
|
|
81
|
-
|
|
82
85
|
def add_to_state(self, key: str, value: Any) -> None:
|
|
83
86
|
"""Add a value to the FastAPI app state."""
|
|
84
87
|
if key in self._add_to_state:
|
|
@@ -103,14 +106,13 @@ class ApplicationAbstract(ABC):
|
|
|
103
106
|
@asynccontextmanager
|
|
104
107
|
async def fastapi_lifespan(self, fastapi: FastAPI) -> AsyncGenerator[None, None]: # pylint: disable=unused-argument
|
|
105
108
|
"""FastAPI lifespan context manager."""
|
|
106
|
-
await self.
|
|
107
|
-
self._import_states_from_plugin_manager()
|
|
109
|
+
await self.startup_plugins()
|
|
108
110
|
await self.on_startup()
|
|
109
111
|
try:
|
|
110
112
|
yield
|
|
111
113
|
finally:
|
|
112
114
|
await self.on_shutdown()
|
|
113
|
-
await self.
|
|
115
|
+
await self.shutdown_plugins()
|
|
114
116
|
|
|
115
117
|
def get_config(self) -> RootConfig:
|
|
116
118
|
"""Get the configuration."""
|
|
@@ -120,10 +122,6 @@ class ApplicationAbstract(ABC):
|
|
|
120
122
|
"""Get the ASGI application."""
|
|
121
123
|
return self._asgi_app
|
|
122
124
|
|
|
123
|
-
def get_plugin_manager(self) -> PluginManager:
|
|
124
|
-
"""Get the plugin manager."""
|
|
125
|
-
return self.plugin_manager
|
|
126
|
-
|
|
127
125
|
def get_status_service(self) -> StatusService:
|
|
128
126
|
"""Get the status service."""
|
|
129
127
|
return self.status_service
|
|
@@ -4,10 +4,7 @@ from typing import Any, Generic, Self, TypeVar, get_args
|
|
|
4
4
|
|
|
5
5
|
from fastapi_factory_utilities.core.app.config import GenericConfigBuilder, RootConfig
|
|
6
6
|
from fastapi_factory_utilities.core.app.fastapi_builder import FastAPIBuilder
|
|
7
|
-
from fastapi_factory_utilities.core.
|
|
8
|
-
PluginManager,
|
|
9
|
-
)
|
|
10
|
-
from fastapi_factory_utilities.core.plugins import PluginsEnum
|
|
7
|
+
from fastapi_factory_utilities.core.plugins import PluginAbstract
|
|
11
8
|
from fastapi_factory_utilities.core.utils.log import LogModeEnum, setup_log
|
|
12
9
|
from fastapi_factory_utilities.core.utils.uvicorn import UvicornUtils
|
|
13
10
|
|
|
@@ -19,20 +16,16 @@ T = TypeVar("T", bound=ApplicationAbstract)
|
|
|
19
16
|
class ApplicationGenericBuilder(Generic[T]):
|
|
20
17
|
"""Application generic builder."""
|
|
21
18
|
|
|
22
|
-
def __init__(self,
|
|
19
|
+
def __init__(self, plugins: list[PluginAbstract] | None = None) -> None:
|
|
23
20
|
"""Instanciate the ApplicationGenericBuilder."""
|
|
21
|
+
self._uvicorn_utils: UvicornUtils | None = None
|
|
24
22
|
self._root_config: RootConfig | None = None
|
|
25
|
-
self.
|
|
23
|
+
self._plugins: list[PluginAbstract] = plugins or []
|
|
26
24
|
self._fastapi_builder: FastAPIBuilder | None = None
|
|
27
25
|
generic_args: tuple[Any, ...] = get_args(self.__orig_bases__[0]) # type: ignore
|
|
28
26
|
self._application_class: type[T] = generic_args[0]
|
|
29
|
-
self._plugins_activation_list: list[PluginsEnum]
|
|
30
|
-
if plugins_activation_list is None:
|
|
31
|
-
self._plugins_activation_list = self._application_class.DEFAULT_PLUGINS_ACTIVATED
|
|
32
|
-
else:
|
|
33
|
-
self._plugins_activation_list = plugins_activation_list
|
|
34
27
|
|
|
35
|
-
def add_plugin_to_activate(self, plugin:
|
|
28
|
+
def add_plugin_to_activate(self, plugin: PluginAbstract) -> Self:
|
|
36
29
|
"""Add a plugin to activate.
|
|
37
30
|
|
|
38
31
|
Args:
|
|
@@ -41,7 +34,7 @@ class ApplicationGenericBuilder(Generic[T]):
|
|
|
41
34
|
Returns:
|
|
42
35
|
Self: The builder.
|
|
43
36
|
"""
|
|
44
|
-
self.
|
|
37
|
+
self._plugins.append(plugin)
|
|
45
38
|
return self
|
|
46
39
|
|
|
47
40
|
def add_config(self, config: RootConfig) -> Self:
|
|
@@ -56,18 +49,6 @@ class ApplicationGenericBuilder(Generic[T]):
|
|
|
56
49
|
self._root_config = config
|
|
57
50
|
return self
|
|
58
51
|
|
|
59
|
-
def add_plugin_manager(self, plugin_manager: PluginManager) -> Self:
|
|
60
|
-
"""Add the plugin manager to the builder.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
plugin_manager (PluginManager): The plugin manager.
|
|
64
|
-
|
|
65
|
-
Returns:
|
|
66
|
-
Self: The builder.
|
|
67
|
-
"""
|
|
68
|
-
self._plugin_manager = plugin_manager
|
|
69
|
-
return self
|
|
70
|
-
|
|
71
52
|
def add_fastapi_builder(self, fastapi_builder: FastAPIBuilder) -> Self:
|
|
72
53
|
"""Add the FastAPI builder to the builder.
|
|
73
54
|
|
|
@@ -82,18 +63,20 @@ class ApplicationGenericBuilder(Generic[T]):
|
|
|
82
63
|
|
|
83
64
|
def _build_from_package_root_config(self) -> RootConfig:
|
|
84
65
|
"""Build the configuration from the package."""
|
|
85
|
-
return GenericConfigBuilder[self._application_class.CONFIG_CLASS](
|
|
66
|
+
return GenericConfigBuilder[self._application_class.CONFIG_CLASS]( # type: ignore
|
|
86
67
|
package_name=self._application_class.PACKAGE_NAME,
|
|
87
68
|
config_class=self._application_class.CONFIG_CLASS,
|
|
88
69
|
).build()
|
|
89
70
|
|
|
90
|
-
def build(self) -> T:
|
|
91
|
-
"""Build the application.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
71
|
+
def build(self, **kwargs: Any) -> T:
|
|
72
|
+
"""Build the application.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
**kwargs: The keyword arguments to pass to the application.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
T: The application.
|
|
79
|
+
"""
|
|
97
80
|
# RootConfig
|
|
98
81
|
self._root_config = self._root_config or self._build_from_package_root_config()
|
|
99
82
|
# FastAPIBuilder
|
|
@@ -101,21 +84,24 @@ class ApplicationGenericBuilder(Generic[T]):
|
|
|
101
84
|
|
|
102
85
|
application: T = self._application_class(
|
|
103
86
|
root_config=self._root_config,
|
|
104
|
-
|
|
87
|
+
plugins=self._plugins,
|
|
105
88
|
fastapi_builder=self._fastapi_builder,
|
|
89
|
+
**kwargs,
|
|
106
90
|
)
|
|
107
91
|
application.setup()
|
|
108
92
|
return application
|
|
109
93
|
|
|
110
94
|
def build_as_uvicorn_utils(self) -> UvicornUtils:
|
|
111
95
|
"""Build the application and provide UvicornUtils."""
|
|
112
|
-
|
|
96
|
+
self._uvicorn_utils = UvicornUtils(app=self.build())
|
|
97
|
+
return self._uvicorn_utils
|
|
113
98
|
|
|
114
99
|
def build_and_serve(self) -> None:
|
|
115
100
|
"""Build the application and serve it with Uvicorn."""
|
|
116
|
-
uvicorn_utils: UvicornUtils = self.build_as_uvicorn_utils()
|
|
101
|
+
uvicorn_utils: UvicornUtils = self._uvicorn_utils or self.build_as_uvicorn_utils()
|
|
117
102
|
|
|
118
|
-
|
|
103
|
+
assert self._root_config is not None
|
|
104
|
+
setup_log(mode=LogModeEnum.CONSOLE, logging_config=self._root_config.logging)
|
|
119
105
|
|
|
120
106
|
try:
|
|
121
107
|
uvicorn_utils.serve()
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, ClassVar, Generic, TypeVar, get_args
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from fastapi import Request
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, HttpUrl
|
|
6
7
|
|
|
7
8
|
from fastapi_factory_utilities.core.app.exceptions import ConfigBuilderError
|
|
8
9
|
from fastapi_factory_utilities.core.utils.configs import (
|
|
@@ -71,6 +72,20 @@ class BaseApplicationConfig(BaseModel):
|
|
|
71
72
|
root_path: str = Field(default="", description="Root path")
|
|
72
73
|
|
|
73
74
|
|
|
75
|
+
class HttpServiceDependencyConfig(BaseModel):
|
|
76
|
+
"""Http service dependency config."""
|
|
77
|
+
|
|
78
|
+
url: HttpUrl
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class DependencyConfig(BaseModel):
|
|
82
|
+
"""Dependency config."""
|
|
83
|
+
|
|
84
|
+
kratos: HttpServiceDependencyConfig | None = Field(default=None, description="Kratos dependency config")
|
|
85
|
+
hydra_admin: HttpServiceDependencyConfig | None = Field(default=None, description="Hydra admin dependency config")
|
|
86
|
+
hydra_public: HttpServiceDependencyConfig | None = Field(default=None, description="Hydra public dependency config")
|
|
87
|
+
|
|
88
|
+
|
|
74
89
|
class RootConfig(BaseModel):
|
|
75
90
|
"""Root configuration."""
|
|
76
91
|
|
|
@@ -84,6 +99,7 @@ class RootConfig(BaseModel):
|
|
|
84
99
|
cors: CorsConfig = Field(description="CORS configuration", default_factory=CorsConfig)
|
|
85
100
|
development: DevelopmentConfig = Field(description="Development configuration", default_factory=DevelopmentConfig)
|
|
86
101
|
logging: list[LoggingConfig] = Field(description="Logging configuration", default_factory=list)
|
|
102
|
+
dependencies: DependencyConfig = Field(description="Dependencies configuration", default_factory=DependencyConfig)
|
|
87
103
|
|
|
88
104
|
|
|
89
105
|
GenericConfig = TypeVar("GenericConfig", bound=BaseModel)
|
|
@@ -162,3 +178,8 @@ class GenericConfigBuilder(Generic[GenericConfig]):
|
|
|
162
178
|
) from exception
|
|
163
179
|
|
|
164
180
|
return config
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def depends_dependency_config(request: Request) -> DependencyConfig:
|
|
184
|
+
"""Get the dependency config."""
|
|
185
|
+
return request.app.state.config.dependencies
|
|
@@ -64,7 +64,7 @@ class FastAPIBuilder:
|
|
|
64
64
|
title=self._root_config.application.service_name,
|
|
65
65
|
description="",
|
|
66
66
|
version=self._root_config.application.version,
|
|
67
|
-
lifespan=lifespan,
|
|
67
|
+
lifespan=lifespan,
|
|
68
68
|
)
|
|
69
69
|
|
|
70
70
|
fastapi.add_middleware(
|
|
@@ -77,7 +77,8 @@ class FastAPIBuilder:
|
|
|
77
77
|
|
|
78
78
|
for middleware_args in self._middleware_list:
|
|
79
79
|
fastapi.add_middleware(
|
|
80
|
-
middleware_class=middleware_args.middleware_class,
|
|
80
|
+
middleware_class=middleware_args.middleware_class, # type: ignore
|
|
81
|
+
**middleware_args.kwargs, # pyright: ignore
|
|
81
82
|
)
|
|
82
83
|
|
|
83
84
|
fastapi.include_router(router=self._base_router)
|
|
@@ -1,43 +1,79 @@
|
|
|
1
1
|
"""FastAPI Factory Utilities exceptions."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from typing import NotRequired, TypedDict, Unpack
|
|
5
6
|
|
|
6
7
|
from opentelemetry.trace import Span, get_current_span
|
|
8
|
+
from opentelemetry.util.types import AttributeValue
|
|
7
9
|
from structlog.stdlib import BoundLogger, get_logger
|
|
8
10
|
|
|
9
11
|
_logger: BoundLogger = get_logger()
|
|
10
12
|
|
|
11
13
|
|
|
14
|
+
class ExceptionParameters(TypedDict):
|
|
15
|
+
"""Parameters for the exception."""
|
|
16
|
+
|
|
17
|
+
message: NotRequired[str]
|
|
18
|
+
level: NotRequired[int]
|
|
19
|
+
|
|
20
|
+
|
|
12
21
|
class FastAPIFactoryUtilitiesError(Exception):
|
|
13
22
|
"""Base exception for the FastAPI Factory Utilities."""
|
|
14
23
|
|
|
15
24
|
def __init__(
|
|
16
25
|
self,
|
|
17
|
-
*args:
|
|
18
|
-
|
|
19
|
-
level: int = logging.ERROR,
|
|
20
|
-
**kwargs: dict[str, Any],
|
|
26
|
+
*args: object,
|
|
27
|
+
**kwargs: Unpack[ExceptionParameters],
|
|
21
28
|
) -> None:
|
|
22
|
-
"""
|
|
29
|
+
"""Instantiate the exception.
|
|
23
30
|
|
|
24
31
|
Args:
|
|
25
|
-
*args
|
|
26
|
-
message
|
|
27
|
-
level
|
|
28
|
-
**kwargs
|
|
32
|
+
*args: The arguments.
|
|
33
|
+
message: The message.
|
|
34
|
+
level: The logging level.
|
|
35
|
+
**kwargs: The keyword arguments.
|
|
36
|
+
|
|
29
37
|
"""
|
|
38
|
+
# Extract the message and the level from the kwargs if they are present
|
|
39
|
+
self.message: str | None = kwargs.pop("message", None)
|
|
40
|
+
self.level: int = kwargs.pop("level", logging.ERROR)
|
|
41
|
+
|
|
42
|
+
# If the message is not present, try to extract it from the args
|
|
43
|
+
if self.message is None and len(args) > 0 and isinstance(args[0], str):
|
|
44
|
+
self.message = args[0]
|
|
45
|
+
|
|
30
46
|
# Log the Exception
|
|
31
|
-
if message:
|
|
32
|
-
_logger.log(level=level, event=message)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
if self.message:
|
|
48
|
+
_logger.log(level=self.level, event=self.message)
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
# Propagate the exception
|
|
52
|
+
span: Span = get_current_span()
|
|
53
|
+
# If not otel is setup, INVALID_SPAN is retrieved from get_current_span
|
|
54
|
+
# and it will respond False to the is_recording method
|
|
55
|
+
if span.is_recording():
|
|
56
|
+
span.record_exception(self)
|
|
57
|
+
for key, value in kwargs.items():
|
|
58
|
+
attribute_value: AttributeValue
|
|
59
|
+
if not isinstance(value, (str, bool, int, float, Sequence)):
|
|
60
|
+
attribute_value = str(value)
|
|
61
|
+
else:
|
|
62
|
+
attribute_value = value
|
|
63
|
+
span.set_attribute(key, attribute_value)
|
|
64
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
65
|
+
# Suppress any errors that occur while propagating the exception
|
|
66
|
+
pass
|
|
67
|
+
|
|
42
68
|
# Call the parent class
|
|
43
|
-
super().__init__(*args
|
|
69
|
+
super().__init__(*args)
|
|
70
|
+
|
|
71
|
+
def __str__(self) -> str:
|
|
72
|
+
"""Return the string representation of the exception.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
str: The message if available, otherwise the default exception string.
|
|
76
|
+
"""
|
|
77
|
+
if self.message is not None:
|
|
78
|
+
return self.message
|
|
79
|
+
return super().__str__()
|
|
@@ -1,36 +1,7 @@
|
|
|
1
1
|
"""Package for plugins."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class PluginsEnum(StrEnum):
|
|
8
|
-
"""Enumeration for the plugins."""
|
|
9
|
-
|
|
10
|
-
OPENTELEMETRY_PLUGIN = auto()
|
|
11
|
-
ODM_PLUGIN = auto()
|
|
12
|
-
HTTPX_PLUGIN = auto()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class PluginState:
|
|
16
|
-
"""PluginState represent the state a plugin which to be share to the application and other plugins."""
|
|
17
|
-
|
|
18
|
-
def __init__(self, key: str, value: Any) -> None:
|
|
19
|
-
"""Initialize the PluginState."""
|
|
20
|
-
self._key: str = key
|
|
21
|
-
self._value = value
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def key(self) -> str:
|
|
25
|
-
"""Get the key."""
|
|
26
|
-
return self._key
|
|
27
|
-
|
|
28
|
-
@property
|
|
29
|
-
def value(self) -> Any:
|
|
30
|
-
"""Get the value."""
|
|
31
|
-
return self._value
|
|
32
|
-
|
|
3
|
+
from .abstracts import PluginAbstract
|
|
33
4
|
|
|
34
5
|
__all__: list[str] = [
|
|
35
|
-
"
|
|
6
|
+
"PluginAbstract",
|
|
36
7
|
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Abstracts for the plugins."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from fastapi_factory_utilities.core.protocols import ApplicationAbstractProtocol
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PluginAbstract(ABC):
|
|
11
|
+
"""Abstract class for the plugins."""
|
|
12
|
+
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
"""Initialize the plugin."""
|
|
15
|
+
self._application: ApplicationAbstractProtocol | None = None
|
|
16
|
+
|
|
17
|
+
def set_application(self, application: "ApplicationAbstractProtocol") -> Self:
|
|
18
|
+
"""Set the application."""
|
|
19
|
+
self._application = application
|
|
20
|
+
return self
|
|
21
|
+
|
|
22
|
+
def _add_to_state(self, key: str, value: Any) -> None:
|
|
23
|
+
"""Add to the state."""
|
|
24
|
+
assert self._application is not None
|
|
25
|
+
setattr(self._application.get_asgi_app().state, key, value)
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def on_load(self) -> None:
|
|
29
|
+
"""On load."""
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
async def on_startup(self) -> None:
|
|
34
|
+
"""On startup."""
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
async def on_shutdown(self) -> None:
|
|
39
|
+
"""On shutdown."""
|
|
40
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Aiopika Plugin Module."""
|
|
2
|
+
|
|
3
|
+
from .configs import AiopikaConfig
|
|
4
|
+
from .depends import depends_aiopika_robust_connection
|
|
5
|
+
from .exceptions import AiopikaPluginBaseError, AiopikaPluginConfigError
|
|
6
|
+
from .exchange import Exchange
|
|
7
|
+
from .listener import AbstractListener
|
|
8
|
+
from .message import AbstractMessage, SenderModel
|
|
9
|
+
from .plugins import AiopikaPlugin
|
|
10
|
+
from .publisher import AbstractPublisher
|
|
11
|
+
from .queue import Queue
|
|
12
|
+
|
|
13
|
+
__all__: list[str] = [
|
|
14
|
+
"AbstractListener",
|
|
15
|
+
"AbstractMessage",
|
|
16
|
+
"AbstractPublisher",
|
|
17
|
+
"AiopikaConfig",
|
|
18
|
+
"AiopikaPlugin",
|
|
19
|
+
"AiopikaPluginBaseError",
|
|
20
|
+
"AiopikaPluginConfigError",
|
|
21
|
+
"Exchange",
|
|
22
|
+
"Queue",
|
|
23
|
+
"SenderModel",
|
|
24
|
+
"depends_aiopika_robust_connection",
|
|
25
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Provides the abstract class for the Aiopika plugin."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from typing import Self
|
|
5
|
+
|
|
6
|
+
from aio_pika.abc import AbstractChannel, AbstractRobustConnection
|
|
7
|
+
|
|
8
|
+
from .exceptions import AiopikaPluginBaseError, AiopikaPluginConnectionNotProvidedError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AbstractAiopikaResource(ABC):
|
|
12
|
+
"""Abstract class for the Aiopika resource."""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
"""Initialize the Aiopika resource."""
|
|
16
|
+
self._robust_connection: AbstractRobustConnection | None = None
|
|
17
|
+
self._channel: AbstractChannel | None = None
|
|
18
|
+
|
|
19
|
+
def set_robust_connection(self, robust_connection: AbstractRobustConnection) -> Self:
|
|
20
|
+
"""Set the robust connection."""
|
|
21
|
+
self._robust_connection = robust_connection
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def set_channel(self, channel: AbstractChannel) -> Self:
|
|
25
|
+
"""Set the channel."""
|
|
26
|
+
self._channel = channel
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
async def _acquire_channel(self) -> AbstractChannel:
|
|
30
|
+
"""Acquire the channel."""
|
|
31
|
+
if self._robust_connection is None:
|
|
32
|
+
raise AiopikaPluginConnectionNotProvidedError(
|
|
33
|
+
message="Robust connection not provided.",
|
|
34
|
+
)
|
|
35
|
+
if self._channel is None:
|
|
36
|
+
try:
|
|
37
|
+
self._channel = await self._robust_connection.channel()
|
|
38
|
+
except Exception as exception:
|
|
39
|
+
raise AiopikaPluginBaseError(
|
|
40
|
+
message="Failed to acquire the channel.",
|
|
41
|
+
) from exception
|
|
42
|
+
return self._channel
|
|
43
|
+
|
|
44
|
+
async def setup(self) -> Self:
|
|
45
|
+
"""Setup the Aiopika resource."""
|
|
46
|
+
if self._channel is None:
|
|
47
|
+
await self._acquire_channel()
|
|
48
|
+
return self
|