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.

Files changed (72) 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 +12 -3
  4. fastapi_factory_utilities/core/app/application.py +24 -26
  5. fastapi_factory_utilities/core/app/builder.py +23 -37
  6. fastapi_factory_utilities/core/app/config.py +22 -1
  7. fastapi_factory_utilities/core/app/fastapi_builder.py +3 -2
  8. fastapi_factory_utilities/core/exceptions.py +58 -22
  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 +70 -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 +86 -0
  24. fastapi_factory_utilities/core/plugins/odm_plugin/__init__.py +25 -153
  25. fastapi_factory_utilities/core/plugins/odm_plugin/builder.py +59 -31
  26. fastapi_factory_utilities/core/plugins/odm_plugin/configs.py +1 -1
  27. fastapi_factory_utilities/core/plugins/odm_plugin/documents.py +2 -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 +112 -3
  31. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/__init__.py +8 -115
  32. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/builder.py +65 -14
  33. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/configs.py +13 -0
  34. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/instruments/__init__.py +85 -0
  35. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/plugins.py +137 -0
  36. fastapi_factory_utilities/core/plugins/taskiq_plugins/__init__.py +29 -0
  37. fastapi_factory_utilities/core/plugins/taskiq_plugins/configs.py +12 -0
  38. fastapi_factory_utilities/core/plugins/taskiq_plugins/depends.py +51 -0
  39. fastapi_factory_utilities/core/plugins/taskiq_plugins/exceptions.py +13 -0
  40. fastapi_factory_utilities/core/plugins/taskiq_plugins/plugin.py +41 -0
  41. fastapi_factory_utilities/core/plugins/taskiq_plugins/schedulers.py +187 -0
  42. fastapi_factory_utilities/core/protocols.py +1 -54
  43. fastapi_factory_utilities/core/security/jwt.py +159 -0
  44. fastapi_factory_utilities/core/security/kratos.py +98 -0
  45. fastapi_factory_utilities/core/services/hydra/__init__.py +13 -0
  46. fastapi_factory_utilities/core/services/hydra/exceptions.py +15 -0
  47. fastapi_factory_utilities/core/services/hydra/objects.py +26 -0
  48. fastapi_factory_utilities/core/services/hydra/services.py +122 -0
  49. fastapi_factory_utilities/core/services/kratos/__init__.py +13 -0
  50. fastapi_factory_utilities/core/services/kratos/enums.py +11 -0
  51. fastapi_factory_utilities/core/services/kratos/exceptions.py +15 -0
  52. fastapi_factory_utilities/core/services/kratos/objects.py +43 -0
  53. fastapi_factory_utilities/core/services/kratos/services.py +86 -0
  54. fastapi_factory_utilities/core/services/status/__init__.py +2 -2
  55. fastapi_factory_utilities/core/utils/status.py +2 -1
  56. fastapi_factory_utilities/core/utils/uvicorn.py +36 -0
  57. fastapi_factory_utilities/core/utils/yaml_reader.py +2 -2
  58. fastapi_factory_utilities/example/app.py +15 -5
  59. fastapi_factory_utilities/example/entities/books/__init__.py +1 -1
  60. fastapi_factory_utilities/example/models/books/__init__.py +1 -1
  61. fastapi_factory_utilities/py.typed +0 -0
  62. {fastapi_factory_utilities-0.2.0.dist-info → fastapi_factory_utilities-0.7.1.dist-info}/METADATA +23 -14
  63. fastapi_factory_utilities-0.7.1.dist-info/RECORD +101 -0
  64. {fastapi_factory_utilities-0.2.0.dist-info → fastapi_factory_utilities-0.7.1.dist-info}/WHEEL +1 -1
  65. fastapi_factory_utilities/core/app/plugin_manager/__init__.py +0 -15
  66. fastapi_factory_utilities/core/app/plugin_manager/exceptions.py +0 -33
  67. fastapi_factory_utilities/core/app/plugin_manager/plugin_manager.py +0 -190
  68. fastapi_factory_utilities/core/plugins/example/__init__.py +0 -31
  69. fastapi_factory_utilities/core/plugins/httpx_plugin/__init__.py +0 -31
  70. fastapi_factory_utilities-0.2.0.dist-info/RECORD +0 -70
  71. {fastapi_factory_utilities-0.2.0.dist-info → fastapi_factory_utilities-0.7.1.dist-info}/entry_points.txt +0 -0
  72. {fastapi_factory_utilities-0.2.0.dist-info → fastapi_factory_utilities-0.7.1.dist-info/licenses}/LICENSE +0 -0
@@ -22,4 +22,4 @@ api_v2: APIRouter = APIRouter(prefix="/v2")
22
22
  api.include_router(router=api_v1)
23
23
  api.include_router(router=api_v2)
24
24
 
25
- __all__: list[str] = ["api", "TagEnum"]
25
+ __all__: list[str] = ["TagEnum", "api"]
@@ -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 BaseApplicationConfig, RootConfig
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.app.plugin_manager.plugin_manager import (
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
- plugin_manager: PluginManager,
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.plugin_manager: PluginManager = plugin_manager
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.plugin_manager.add_application_context(application=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.plugin_manager.trigger_startup()
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.plugin_manager.trigger_shutdown()
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.app.plugin_manager.plugin_manager import (
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, plugins_activation_list: list[PluginsEnum] | None = None) -> None:
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._plugin_manager: PluginManager | None = None
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: PluginsEnum) -> Self:
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._plugins_activation_list.append(plugin)
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
- # Plugin manager
93
- if self._plugin_manager is None:
94
- self._plugin_manager = PluginManager()
95
- for plugin in self._plugins_activation_list:
96
- self._plugin_manager.activate(plugin=plugin)
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
- plugin_manager=self._plugin_manager,
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
- return UvicornUtils(app=self.build())
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
- setup_log(mode=LogModeEnum.CONSOLE)
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 pydantic import BaseModel, ConfigDict, Field
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, # type: ignore
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, **middleware_args.kwargs # type: ignore
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 typing import Any
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: tuple[Any],
18
- message: str | None = None,
19
- level: int = logging.ERROR,
20
- **kwargs: dict[str, Any],
26
+ *args: object,
27
+ **kwargs: Unpack[ExceptionParameters],
21
28
  ) -> None:
22
- """Instanciate the exception.
29
+ """Instantiate the exception.
23
30
 
24
31
  Args:
25
- *args (Tuple[Any]): The arguments.
26
- message (str | None): The message.
27
- level (int): The logging level.
28
- **kwargs (dict[str, Any]): The keyword arguments
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
- self.message = message
34
- self.level = level
35
- args = (message, *args) # type: ignore
36
- # Propagate the exception
37
- span: Span = get_current_span()
38
- # If not otel is setup, INVALID_SPAN is retrive from get_current_span
39
- # and it will respond False to the is_recording method
40
- if span.is_recording():
41
- span.record_exception(self)
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, **kwargs)
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 enum import StrEnum, auto
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
- "PluginsEnum",
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