fastapi-factory-utilities 0.3.3__py3-none-any.whl → 0.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. fastapi_factory_utilities/core/api/__init__.py +1 -1
  2. fastapi_factory_utilities/core/api/v1/sys/health.py +1 -1
  3. fastapi_factory_utilities/core/app/__init__.py +4 -4
  4. fastapi_factory_utilities/core/app/application.py +22 -26
  5. fastapi_factory_utilities/core/app/builder.py +19 -35
  6. fastapi_factory_utilities/core/app/config.py +2 -0
  7. fastapi_factory_utilities/core/app/fastapi_builder.py +3 -2
  8. fastapi_factory_utilities/core/exceptions.py +64 -28
  9. fastapi_factory_utilities/core/plugins/__init__.py +2 -31
  10. fastapi_factory_utilities/core/plugins/abstracts.py +40 -0
  11. fastapi_factory_utilities/core/plugins/aiopika/__init__.py +25 -0
  12. fastapi_factory_utilities/core/plugins/aiopika/abstract.py +48 -0
  13. fastapi_factory_utilities/core/plugins/aiopika/configs.py +85 -0
  14. fastapi_factory_utilities/core/plugins/aiopika/depends.py +20 -0
  15. fastapi_factory_utilities/core/plugins/aiopika/exceptions.py +29 -0
  16. fastapi_factory_utilities/core/plugins/aiopika/exchange.py +69 -0
  17. fastapi_factory_utilities/core/plugins/aiopika/listener/__init__.py +7 -0
  18. fastapi_factory_utilities/core/plugins/aiopika/listener/abstract.py +72 -0
  19. fastapi_factory_utilities/core/plugins/aiopika/message.py +86 -0
  20. fastapi_factory_utilities/core/plugins/aiopika/plugins.py +84 -0
  21. fastapi_factory_utilities/core/plugins/aiopika/publisher/__init__.py +7 -0
  22. fastapi_factory_utilities/core/plugins/aiopika/publisher/abstract.py +66 -0
  23. fastapi_factory_utilities/core/plugins/aiopika/queue.py +88 -0
  24. fastapi_factory_utilities/core/plugins/odm_plugin/__init__.py +14 -157
  25. fastapi_factory_utilities/core/plugins/odm_plugin/builder.py +4 -3
  26. fastapi_factory_utilities/core/plugins/odm_plugin/configs.py +1 -1
  27. fastapi_factory_utilities/core/plugins/odm_plugin/documents.py +1 -1
  28. fastapi_factory_utilities/core/plugins/odm_plugin/helpers.py +16 -0
  29. fastapi_factory_utilities/core/plugins/odm_plugin/plugins.py +155 -0
  30. fastapi_factory_utilities/core/plugins/odm_plugin/repositories.py +12 -23
  31. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/__init__.py +8 -115
  32. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/instruments/__init__.py +85 -0
  33. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/plugins.py +137 -0
  34. fastapi_factory_utilities/core/plugins/taskiq_plugins/__init__.py +31 -0
  35. fastapi_factory_utilities/core/plugins/taskiq_plugins/configs.py +12 -0
  36. fastapi_factory_utilities/core/plugins/taskiq_plugins/depends.py +51 -0
  37. fastapi_factory_utilities/core/plugins/taskiq_plugins/exceptions.py +13 -0
  38. fastapi_factory_utilities/core/plugins/taskiq_plugins/plugin.py +41 -0
  39. fastapi_factory_utilities/core/plugins/taskiq_plugins/schedulers.py +187 -0
  40. fastapi_factory_utilities/core/protocols.py +1 -54
  41. fastapi_factory_utilities/core/security/__init__.py +5 -0
  42. fastapi_factory_utilities/core/security/abstracts.py +42 -0
  43. fastapi_factory_utilities/core/security/jwt/__init__.py +41 -0
  44. fastapi_factory_utilities/core/security/jwt/configs.py +32 -0
  45. fastapi_factory_utilities/core/security/jwt/decoders.py +130 -0
  46. fastapi_factory_utilities/core/security/jwt/exceptions.py +23 -0
  47. fastapi_factory_utilities/core/security/jwt/objects.py +107 -0
  48. fastapi_factory_utilities/core/security/jwt/services.py +176 -0
  49. fastapi_factory_utilities/core/security/jwt/stores.py +43 -0
  50. fastapi_factory_utilities/core/security/jwt/types.py +9 -0
  51. fastapi_factory_utilities/core/security/jwt/verifiers.py +46 -0
  52. fastapi_factory_utilities/core/security/kratos.py +53 -33
  53. fastapi_factory_utilities/core/services/hydra/__init__.py +20 -0
  54. fastapi_factory_utilities/core/services/hydra/exceptions.py +15 -0
  55. fastapi_factory_utilities/core/services/hydra/objects.py +26 -0
  56. fastapi_factory_utilities/core/services/hydra/services.py +200 -0
  57. fastapi_factory_utilities/core/services/status/__init__.py +2 -2
  58. fastapi_factory_utilities/core/services/status/exceptions.py +1 -1
  59. fastapi_factory_utilities/core/utils/status.py +2 -1
  60. fastapi_factory_utilities/core/utils/yaml_reader.py +1 -1
  61. fastapi_factory_utilities/example/app.py +15 -5
  62. fastapi_factory_utilities/example/entities/books/__init__.py +1 -1
  63. fastapi_factory_utilities/example/models/books/__init__.py +1 -1
  64. {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info}/METADATA +21 -15
  65. fastapi_factory_utilities-0.8.2.dist-info/RECORD +111 -0
  66. {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info}/WHEEL +1 -1
  67. fastapi_factory_utilities/core/app/plugin_manager/__init__.py +0 -15
  68. fastapi_factory_utilities/core/app/plugin_manager/exceptions.py +0 -33
  69. fastapi_factory_utilities/core/app/plugin_manager/plugin_manager.py +0 -190
  70. fastapi_factory_utilities/core/plugins/example/__init__.py +0 -31
  71. fastapi_factory_utilities/core/plugins/httpx_plugin/__init__.py +0 -31
  72. fastapi_factory_utilities/core/security/jwt.py +0 -158
  73. fastapi_factory_utilities-0.3.3.dist-info/RECORD +0 -78
  74. {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info}/entry_points.txt +0 -0
  75. {fastapi_factory_utilities-0.3.3.dist-info → fastapi_factory_utilities-0.8.2.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,29 @@
1
+ """Provides the exceptions for the Aiopika plugin."""
2
+
3
+ from typing import Any
4
+
5
+ from fastapi_factory_utilities.core.exceptions import FastAPIFactoryUtilitiesError
6
+
7
+
8
+ class AiopikaPluginBaseError(FastAPIFactoryUtilitiesError):
9
+ """Base class for all exceptions raised by the Aiopika plugin."""
10
+
11
+ def __init__(self, message: str, **kwargs: Any) -> None:
12
+ """Initialize the Aiopika plugin base exception."""
13
+ super().__init__(message, **kwargs)
14
+
15
+
16
+ class AiopikaPluginConfigError(AiopikaPluginBaseError):
17
+ """Exception for the Aiopika plugin configuration."""
18
+
19
+
20
+ class AiopikaPluginConnectionNotProvidedError(AiopikaPluginBaseError):
21
+ """Exception for the Aiopika plugin connection not provided."""
22
+
23
+
24
+ class AiopikaPluginExchangeNotDeclaredError(AiopikaPluginBaseError):
25
+ """Exception for the Aiopika plugin exchange not declared."""
26
+
27
+
28
+ class AiopikaPluginQueueNotDeclaredError(AiopikaPluginBaseError):
29
+ """Exception for the Aiopika plugin queue not declared."""
@@ -0,0 +1,69 @@
1
+ """Provides the abstract class for the exchange port for the Aiopika plugin."""
2
+
3
+ from typing import ClassVar, Self
4
+
5
+ from aio_pika.abc import AbstractExchange, ExchangeType, TimeoutType
6
+
7
+ from .abstract import AbstractAiopikaResource
8
+ from .exceptions import AiopikaPluginBaseError, AiopikaPluginExchangeNotDeclaredError
9
+
10
+
11
+ class Exchange(AbstractAiopikaResource):
12
+ """Abstract class for the exchange port for the Aiopika plugin."""
13
+
14
+ DEFAULT_OPERATION_TIMEOUT: ClassVar[TimeoutType] = 10.0
15
+
16
+ def __init__( # pylint: disable=too-many-arguments # noqa: PLR0913
17
+ self,
18
+ name: str,
19
+ exchange_type: ExchangeType,
20
+ durable: bool = True,
21
+ auto_delete: bool = False,
22
+ internal: bool = False,
23
+ passive: bool = False,
24
+ timeout: TimeoutType = DEFAULT_OPERATION_TIMEOUT,
25
+ ) -> None:
26
+ """Initialize the exchange port."""
27
+ super().__init__()
28
+ self._name: str = name
29
+ self._exchange_type: ExchangeType = exchange_type
30
+ self._durable: bool = durable
31
+ self._auto_delete: bool = auto_delete
32
+ self._internal: bool = internal
33
+ self._passive: bool = passive
34
+ self._timeout: TimeoutType = timeout
35
+ self._aiopika_exchange: AbstractExchange | None = None
36
+ self._is_declared: bool = False
37
+
38
+ @property
39
+ def exchange(self) -> AbstractExchange:
40
+ """Get the Aiopika exchange."""
41
+ if self._aiopika_exchange is None:
42
+ raise AiopikaPluginExchangeNotDeclaredError(message="Exchange not declared.", exchange=self._name)
43
+ return self._aiopika_exchange
44
+
45
+ async def _declare(self) -> Self:
46
+ """Declare the exchange."""
47
+ assert self._channel is not None
48
+ try:
49
+ self._aiopika_exchange = await self._channel.declare_exchange( # pyright: ignore
50
+ name=self._name,
51
+ type=self._exchange_type,
52
+ durable=self._durable,
53
+ auto_delete=self._auto_delete,
54
+ internal=self._internal,
55
+ passive=self._passive,
56
+ timeout=self._timeout,
57
+ )
58
+ except Exception as exception:
59
+ raise AiopikaPluginBaseError(
60
+ message="Failed to declare the exchange.",
61
+ ) from exception
62
+ return self
63
+
64
+ async def setup(self) -> Self:
65
+ """Setup the exchange."""
66
+ await super().setup()
67
+ if self._aiopika_exchange is None:
68
+ await self._declare()
69
+ return self
@@ -0,0 +1,7 @@
1
+ """Provides the listener ports for the Aiopika plugin."""
2
+
3
+ from .abstract import AbstractListener
4
+
5
+ __all__: list[str] = [
6
+ "AbstractListener",
7
+ ]
@@ -0,0 +1,72 @@
1
+ """Provides the abstract class for the listener port for the Aiopika plugin."""
2
+
3
+ from abc import abstractmethod
4
+ from collections.abc import Awaitable, Callable
5
+ from typing import Any, ClassVar, Generic, Self, TypeVar, cast, get_args
6
+
7
+ from aio_pika.abc import AbstractIncomingMessage, ConsumerTag, TimeoutType
8
+
9
+ from ..abstract import AbstractAiopikaResource
10
+ from ..message import AbstractMessage
11
+ from ..queue import Queue
12
+
13
+ GenericMessage = TypeVar("GenericMessage", bound=AbstractMessage[Any])
14
+
15
+
16
+ class AbstractListener(AbstractAiopikaResource, Generic[GenericMessage]):
17
+ """Abstract class for the listener port for the Aiopika plugin."""
18
+
19
+ DEFAULT_OPERATION_TIMEOUT: ClassVar[TimeoutType] = 10.0
20
+
21
+ def __init__(self, queue: Queue, name: str | None = None) -> None:
22
+ """Initialize the listener port."""
23
+ super().__init__()
24
+ self._name: str = name or self.__class__.__name__
25
+ self._queue: Queue = queue
26
+ self._consumer_tag: ConsumerTag | None = None
27
+ generic_args: tuple[Any, ...] = get_args(self.__orig_bases__[0]) # type: ignore
28
+ self._message_type: type[GenericMessage] = generic_args[0]
29
+
30
+ async def setup(self) -> Self:
31
+ """Setup the listener."""
32
+ await super().setup()
33
+ await self._queue.setup()
34
+ return self
35
+
36
+ async def listen(self) -> None:
37
+ """Listen for messages."""
38
+ assert self._queue.queue is not None
39
+ self._consumer_tag = await self._queue.queue.consume( # pyright: ignore
40
+ callback=cast(Callable[[AbstractIncomingMessage], Awaitable[Any]], self._on_message), # pyright: ignore
41
+ exclusive=True,
42
+ )
43
+
44
+ async def _on_message(self, incoming_message: AbstractIncomingMessage) -> None:
45
+ """On message."""
46
+ message: GenericMessage = self._message_type.model_validate_json(incoming_message.body)
47
+ message.set_incoming_message(incoming_message=incoming_message)
48
+ await self.on_message(message=message)
49
+
50
+ async def close(self) -> None:
51
+ """Close the listener.
52
+
53
+ Returns:
54
+ - None: The listener is closed.
55
+
56
+ Raises:
57
+ - AiopikaPluginBaseException: If the listener cannot be closed.
58
+ """
59
+ if self._consumer_tag is not None:
60
+ await self._queue.queue.cancel(consumer_tag=self._consumer_tag)
61
+
62
+ @abstractmethod
63
+ async def on_message(self, message: GenericMessage) -> None:
64
+ """On message.
65
+
66
+ Args:
67
+ message (GenericMessage): The message.
68
+
69
+ Returns:
70
+ - None: The message is processed.
71
+ """
72
+ raise NotImplementedError
@@ -0,0 +1,86 @@
1
+ """Provides the message for the Aiopika plugin."""
2
+
3
+ from enum import StrEnum, auto
4
+ from typing import ClassVar, Generic, TypeVar
5
+
6
+ from aio_pika.abc import AbstractIncomingMessage, DeliveryMode, HeadersType
7
+ from aio_pika.message import Message
8
+ from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
9
+
10
+ GenericMessageData = TypeVar("GenericMessageData", bound=BaseModel)
11
+
12
+
13
+ class SenderModel(BaseModel):
14
+ """Sender model."""
15
+
16
+ model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid", frozen=True)
17
+
18
+ name: str = Field(description="The name of the sender.")
19
+
20
+
21
+ class MessageTypeEnum(StrEnum):
22
+ """Message type enum."""
23
+
24
+ FUNCTIONAL_EVENT = auto()
25
+
26
+
27
+ class AbstractMessage(BaseModel, Generic[GenericMessageData]):
28
+ """Abstract message."""
29
+
30
+ model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid", frozen=True)
31
+
32
+ message_type: MessageTypeEnum = Field(
33
+ description="The type of the message.", default=MessageTypeEnum.FUNCTIONAL_EVENT
34
+ )
35
+ sender: SenderModel = Field(description="The sender of the message.")
36
+ data: GenericMessageData = Field(description="The data of the message.")
37
+
38
+ _incoming_message: AbstractIncomingMessage | None = PrivateAttr()
39
+ _headers: HeadersType = PrivateAttr(default_factory=dict)
40
+
41
+ def get_headers(self) -> HeadersType:
42
+ """Get the headers of the message."""
43
+ return self._headers
44
+
45
+ def set_headers(self, headers: HeadersType) -> None:
46
+ """Set the headers of the message."""
47
+ self._headers = headers
48
+
49
+ def set_incoming_message(self, incoming_message: AbstractIncomingMessage) -> None:
50
+ """Set the incoming message."""
51
+ self._incoming_message = incoming_message
52
+ self.set_headers(headers=incoming_message.headers)
53
+
54
+ async def ack(self) -> None:
55
+ """Ack the message.
56
+
57
+ Raises:
58
+ - ValueError: If the incoming message is not set.
59
+ """
60
+ if self._incoming_message is None:
61
+ raise ValueError("Incoming message is not set.")
62
+ await self._incoming_message.ack(multiple=False)
63
+
64
+ async def reject(self, requeue: bool = True) -> None:
65
+ """Reject the message.
66
+
67
+ Args:
68
+ requeue (bool): Whether to requeue the message.
69
+
70
+ Raises:
71
+ - ValueError: If the incoming message is not set.
72
+ """
73
+ if self._incoming_message is None:
74
+ raise ValueError("Incoming message is not set.")
75
+ await self._incoming_message.reject(requeue=requeue)
76
+
77
+ def to_aiopika_message(self) -> Message:
78
+ """Convert the message to an Aiopika message."""
79
+ return Message(
80
+ body=self.model_dump_json().encode("utf-8"),
81
+ headers=self.get_headers(),
82
+ content_type="application/json",
83
+ content_encoding="utf-8",
84
+ delivery_mode=DeliveryMode.PERSISTENT,
85
+ priority=0,
86
+ )
@@ -0,0 +1,84 @@
1
+ """Provides the Aiopika plugin."""
2
+
3
+ from typing import cast
4
+
5
+ from aio_pika import connect_robust # pyright: ignore[reportUnknownMemberType]
6
+ from aio_pika.abc import AbstractRobustConnection
7
+ from fastapi import Request
8
+ from opentelemetry.instrumentation.aio_pika import AioPikaInstrumentor # pyright: ignore[reportMissingTypeStubs]
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.plugins.abstracts import PluginAbstract
14
+
15
+ from .configs import AiopikaConfig, build_config_from_package
16
+ from .depends import DEPENDS_AIOPIKA_ROBUST_CONNECTION_KEY
17
+ from .exceptions import AiopikaPluginBaseError
18
+
19
+ _logger: BoundLogger = get_logger(__package__)
20
+
21
+
22
+ class AiopikaPlugin(PluginAbstract):
23
+ """Aiopika plugin."""
24
+
25
+ def __init__(self, aiopika_config: AiopikaConfig | None = None) -> None:
26
+ """Initialize the Aiopika plugin."""
27
+ super().__init__()
28
+ self._aiopika_config: AiopikaConfig | None = aiopika_config
29
+ self._robust_connection: AbstractRobustConnection | None = None
30
+
31
+ @property
32
+ def robust_connection(self) -> AbstractRobustConnection:
33
+ """Get the robust connection."""
34
+ assert self._robust_connection is not None
35
+ return self._robust_connection
36
+
37
+ def on_load(self) -> None:
38
+ """On load."""
39
+ assert self._application is not None
40
+
41
+ # Build the configuration if not provided
42
+ if self._aiopika_config is None:
43
+ self._aiopika_config = build_config_from_package(package_name=self._application.PACKAGE_NAME)
44
+
45
+ async def on_startup(self) -> None:
46
+ """On startup."""
47
+ assert self._application is not None
48
+ assert self._aiopika_config is not None
49
+
50
+ tracer_provider: TracerProvider | None = cast(
51
+ TracerProvider | None, getattr(self._application.get_asgi_app().state, "tracer_provider", None)
52
+ )
53
+ meter_provider: MeterProvider | None = cast(
54
+ MeterProvider | None, getattr(self._application.get_asgi_app().state, "meter_provider", None)
55
+ )
56
+ if tracer_provider is None or meter_provider is None:
57
+ raise AiopikaPluginBaseError("Tracer provider or meter provider not found in the application state.")
58
+
59
+ AioPikaInstrumentor().instrument(
60
+ tracer_provider=tracer_provider,
61
+ meter_provider=meter_provider,
62
+ )
63
+
64
+ self._robust_connection = await connect_robust(url=str(self._aiopika_config.amqp_url))
65
+ self._add_to_state(key=DEPENDS_AIOPIKA_ROBUST_CONNECTION_KEY, value=self._robust_connection)
66
+ _logger.debug("Aiopika plugin connected to the AMQP server.", amqp_url=self._aiopika_config.amqp_url)
67
+
68
+ async def on_shutdown(self) -> None:
69
+ """On shutdown."""
70
+ if self._robust_connection is not None:
71
+ await self._robust_connection.close()
72
+ _logger.debug("Aiopika plugin shutdown.")
73
+
74
+
75
+ def depends_robust_connection(request: Request) -> AbstractRobustConnection:
76
+ """Depends on the robust connection.
77
+
78
+ Args:
79
+ request (Request): The request.
80
+
81
+ Returns:
82
+ AbstractRobustConnection: The robust connection.
83
+ """
84
+ return request.app.state.robust_connection
@@ -0,0 +1,7 @@
1
+ """Provides the publisher ports for the Aiopika plugin."""
2
+
3
+ from .abstract import AbstractPublisher
4
+
5
+ __all__: list[str] = [
6
+ "AbstractPublisher",
7
+ ]
@@ -0,0 +1,66 @@
1
+ """Provides the abstract class for the publisher port for the Aiopika plugin."""
2
+
3
+ from typing import Any, ClassVar, Generic, Self, TypeVar
4
+
5
+ from aio_pika.abc import TimeoutType
6
+ from aio_pika.message import Message
7
+ from aiormq.abc import ConfirmationFrameType, DeliveredMessage
8
+ from pamqp.commands import Basic
9
+
10
+ from ..abstract import AbstractAiopikaResource
11
+ from ..exceptions import AiopikaPluginBaseError
12
+ from ..exchange import Exchange
13
+ from ..message import AbstractMessage
14
+
15
+ GenericMessage = TypeVar("GenericMessage", bound=AbstractMessage[Any])
16
+
17
+
18
+ class AbstractPublisher(AbstractAiopikaResource, Generic[GenericMessage]):
19
+ """Abstract class for the publisher port for the Aiopika plugin."""
20
+
21
+ DEFAULT_OPERATION_TIMEOUT: ClassVar[TimeoutType] = 10.0
22
+
23
+ def __init__(self, exchange: Exchange, name: str | None = None) -> None:
24
+ """Initialize the publisher port."""
25
+ super().__init__()
26
+ self._name: str = name or self.__class__.__name__
27
+ self._exchange: Exchange = exchange
28
+
29
+ async def setup(self) -> Self:
30
+ """Setup the publisher."""
31
+ await super().setup()
32
+ await self._exchange.setup()
33
+ return self
34
+
35
+ async def publish(self, message: GenericMessage, routing_key: str) -> None:
36
+ """Publish a message."""
37
+ # Transform the message to an Aiopika message
38
+ aiopika_message: Message
39
+ try:
40
+ aiopika_message = message.to_aiopika_message()
41
+ except Exception as exception:
42
+ raise AiopikaPluginBaseError(
43
+ message="Failed to convert the message to an Aiopika message.",
44
+ ) from exception
45
+ # Publish the message
46
+ confirmation: ConfirmationFrameType | DeliveredMessage | None
47
+ try:
48
+ confirmation = await self._exchange.exchange.publish( # pyright: ignore
49
+ message=aiopika_message,
50
+ routing_key=routing_key,
51
+ mandatory=True,
52
+ timeout=self.DEFAULT_OPERATION_TIMEOUT,
53
+ )
54
+ except Exception as exception:
55
+ raise AiopikaPluginBaseError(
56
+ message="Failed to publish the message.",
57
+ ) from exception
58
+
59
+ if confirmation is None:
60
+ raise AiopikaPluginBaseError(
61
+ message="Failed to publish the message.",
62
+ )
63
+ if isinstance(confirmation, Basic.Return):
64
+ raise AiopikaPluginBaseError(
65
+ message="Failed to publish the message.",
66
+ )
@@ -0,0 +1,88 @@
1
+ """Provides the queue for the Aiopika plugin."""
2
+
3
+ from typing import ClassVar, Self
4
+
5
+ from aio_pika.abc import AbstractQueue, TimeoutType
6
+
7
+ from .abstract import AbstractAiopikaResource
8
+ from .exceptions import AiopikaPluginBaseError, AiopikaPluginQueueNotDeclaredError
9
+ from .exchange import Exchange
10
+
11
+
12
+ class Queue(AbstractAiopikaResource):
13
+ """Queue."""
14
+
15
+ DEFAULT_OPERATION_TIMEOUT: ClassVar[TimeoutType] = 10.0
16
+
17
+ def __init__( # pylint: disable=too-many-arguments # noqa: PLR0913
18
+ self,
19
+ name: str,
20
+ exchange: Exchange,
21
+ routing_key: str,
22
+ durable: bool = True,
23
+ auto_delete: bool = False,
24
+ exclusive: bool = True,
25
+ timeout: TimeoutType = DEFAULT_OPERATION_TIMEOUT,
26
+ ) -> None:
27
+ """Initialize the queue."""
28
+ super().__init__()
29
+ # Initialize the queue properties
30
+ self._name: str = name
31
+ self._routing_key: str = routing_key
32
+ self._durable: bool = durable
33
+ self._auto_delete: bool = auto_delete
34
+ self._exclusive: bool = exclusive
35
+ self._timeout: TimeoutType = timeout
36
+ # Behavior properties
37
+ self._exchange: Exchange = exchange
38
+ self._queue: AbstractQueue | None = None
39
+
40
+ @property
41
+ def queue(self) -> AbstractQueue:
42
+ """Get the Aiopika queue."""
43
+ if self._queue is None:
44
+ raise AiopikaPluginQueueNotDeclaredError(
45
+ message="Queue not declared.",
46
+ queue=self._name,
47
+ )
48
+ return self._queue
49
+
50
+ async def _declare(self) -> Self:
51
+ """Declare the queue."""
52
+ assert self._channel is not None
53
+ try:
54
+ self._queue = await self._channel.declare_queue( # pyright: ignore
55
+ name=self._name,
56
+ durable=self._durable,
57
+ auto_delete=self._auto_delete,
58
+ exclusive=self._exclusive,
59
+ timeout=self._timeout,
60
+ )
61
+ except Exception as exception:
62
+ raise AiopikaPluginBaseError(
63
+ message="Failed to declare the queue.",
64
+ ) from exception
65
+ return self
66
+
67
+ async def _bind(self) -> Self:
68
+ """Bind the queue to the exchange."""
69
+ assert self._queue is not None
70
+ try:
71
+ await self._queue.bind( # pyright: ignore
72
+ exchange=self._exchange.exchange,
73
+ routing_key=self._routing_key,
74
+ timeout=self._timeout,
75
+ )
76
+ except Exception as exception:
77
+ raise AiopikaPluginBaseError(
78
+ message="Failed to bind the queue to the exchange.",
79
+ ) from exception
80
+ return self
81
+
82
+ async def setup(self) -> Self:
83
+ """Setup the queue."""
84
+ await super().setup()
85
+ if self._queue is None:
86
+ await self._declare()
87
+ await self._bind()
88
+ return self