buz 2.15.10rc6__py3-none-any.whl → 2.15.12__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.
- buz/command/asynchronous/base_command_handler.py +13 -6
- buz/command/asynchronous/command_handler.py +8 -5
- buz/command/synchronous/base_command_handler.py +14 -6
- buz/command/synchronous/command_handler.py +7 -5
- buz/event/infrastructure/buz_kafka/kafka_event_async_subscriber_executor.py +20 -3
- buz/event/infrastructure/buz_kafka/kafka_event_sync_subscriber_executor.py +20 -5
- buz/event/infrastructure/buz_kafka/models/kafka_delivery_context.py +10 -0
- buz/event/infrastructure/kombu/kombu_consumer.py +10 -2
- buz/event/infrastructure/kombu/models/kombu_delivery_context.py +7 -0
- buz/event/infrastructure/models/__init__.py +0 -0
- buz/event/infrastructure/models/delivery_context.py +6 -0
- buz/event/infrastructure/models/execution_context.py +8 -0
- buz/event/middleware/async_consume_middleware.py +9 -2
- buz/event/middleware/async_consume_middleware_chain_resolver.py +16 -5
- buz/event/middleware/base_async_consume_middleware.py +23 -6
- buz/event/middleware/base_consume_middleware.py +9 -6
- buz/event/middleware/consume_middleware.py +5 -2
- buz/event/middleware/consume_middleware_chain_resolver.py +13 -4
- buz/event/sync/models/__init__.py +0 -0
- buz/event/sync/models/sync_delivery_context.py +7 -0
- buz/event/sync/sync_event_bus.py +10 -2
- buz/handler.py +6 -3
- buz/kafka/domain/models/kafka_poll_record.py +1 -0
- buz/query/asynchronous/base_query_handler.py +15 -6
- buz/query/asynchronous/query_handler.py +8 -4
- buz/query/synchronous/base_query_handler.py +15 -6
- buz/query/synchronous/query_handler.py +8 -5
- {buz-2.15.10rc6.dist-info → buz-2.15.12.dist-info}/LICENSE +1 -1
- buz-2.15.12.dist-info/METADATA +438 -0
- {buz-2.15.10rc6.dist-info → buz-2.15.12.dist-info}/RECORD +31 -24
- buz-2.15.10rc6.dist-info/METADATA +0 -41
- {buz-2.15.10rc6.dist-info → buz-2.15.12.dist-info}/WHEEL +0 -0
|
@@ -1,27 +1,34 @@
|
|
|
1
|
-
from typing import Type, get_type_hints, Any
|
|
1
|
+
from typing import Generic, Type, TypeVar, cast, get_type_hints, Any
|
|
2
2
|
|
|
3
3
|
from buz.command import Command
|
|
4
4
|
from buz.command.asynchronous.command_handler import CommandHandler
|
|
5
5
|
|
|
6
|
+
TCommand = TypeVar("TCommand", bound=Command)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
class BaseCommandHandler(Generic[TCommand], CommandHandler[TCommand]):
|
|
8
10
|
@classmethod
|
|
9
11
|
def fqn(cls) -> str:
|
|
10
12
|
return f"command_handler.{cls.__module__}.{cls.__name__}"
|
|
11
13
|
|
|
12
14
|
@classmethod
|
|
13
|
-
def handles(cls) -> Type[
|
|
15
|
+
def handles(cls) -> Type[TCommand]:
|
|
14
16
|
handle_types = get_type_hints(cls.handle)
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
t_command = handle_types.get("command")
|
|
19
|
+
if t_command is None:
|
|
17
20
|
raise TypeError(
|
|
18
21
|
f"The method 'handle' in '{cls.fqn()}' doesn't have a parameter named 'command'. Found parameters: {cls.__get_method_parameter_names(handle_types)}"
|
|
19
22
|
)
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
# TypeVar mask the actual bound type
|
|
25
|
+
if hasattr(t_command, "__bound__"):
|
|
26
|
+
t_command = t_command.__bound__
|
|
27
|
+
|
|
28
|
+
if not issubclass(t_command, Command):
|
|
22
29
|
raise TypeError(f"The parameter 'command' in '{cls.fqn()}.handle' is not a 'buz.command.Command' subclass")
|
|
23
30
|
|
|
24
|
-
return
|
|
31
|
+
return cast(Type[TCommand], t_command)
|
|
25
32
|
|
|
26
33
|
@classmethod
|
|
27
34
|
def __get_method_parameter_names(cls, handle_types: dict[str, Any]) -> list[str]:
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
from abc import abstractmethod
|
|
2
|
-
from typing import Type
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Generic, Type, TypeVar
|
|
3
3
|
|
|
4
4
|
from buz import Handler
|
|
5
5
|
from buz.command.command import Command
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
TCommand = TypeVar("TCommand", bound=Command)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CommandHandler(Generic[TCommand], Handler[TCommand], ABC):
|
|
9
12
|
@classmethod
|
|
10
13
|
@abstractmethod
|
|
11
|
-
def handles(cls) -> Type[
|
|
14
|
+
def handles(cls) -> Type[TCommand]:
|
|
12
15
|
pass
|
|
13
16
|
|
|
14
17
|
@abstractmethod
|
|
15
|
-
async def handle(self, command:
|
|
18
|
+
async def handle(self, command: TCommand) -> None:
|
|
16
19
|
pass
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
from typing import Type, get_type_hints, Any
|
|
1
|
+
from typing import Generic, Type, TypeVar, get_type_hints, Any, cast
|
|
2
2
|
|
|
3
3
|
from buz.command import Command
|
|
4
4
|
from buz.command.synchronous.command_handler import CommandHandler
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
TCommand = TypeVar("TCommand", bound=Command)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseCommandHandler(Generic[TCommand], CommandHandler[TCommand]):
|
|
8
11
|
@classmethod
|
|
9
12
|
def fqn(cls) -> str:
|
|
10
13
|
return f"command_handler.{cls.__module__}.{cls.__name__}"
|
|
11
14
|
|
|
12
15
|
@classmethod
|
|
13
|
-
def handles(cls) -> Type[
|
|
16
|
+
def handles(cls) -> Type[TCommand]:
|
|
14
17
|
handle_types = get_type_hints(cls.handle)
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
t_command = handle_types.get("command")
|
|
20
|
+
if t_command is None:
|
|
17
21
|
raise TypeError(
|
|
18
22
|
f"The method 'handle' in '{cls.fqn()}' doesn't have a parameter named 'command'. Found parameters: {cls.__get_method_parameter_names(handle_types)}"
|
|
19
23
|
)
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
# TypeVar mask the actual bound type
|
|
26
|
+
if hasattr(t_command, "__bound__"):
|
|
27
|
+
t_command = t_command.__bound__
|
|
28
|
+
|
|
29
|
+
if not issubclass(t_command, Command):
|
|
22
30
|
raise TypeError(f"The parameter 'command' in '{cls.fqn()}.handle' is not a 'buz.command.Command' subclass")
|
|
23
31
|
|
|
24
|
-
return
|
|
32
|
+
return cast(Type[TCommand], t_command)
|
|
25
33
|
|
|
26
34
|
@classmethod
|
|
27
35
|
def __get_method_parameter_names(cls, handle_types: dict[str, Any]) -> list[str]:
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
from abc import abstractmethod
|
|
2
|
-
from typing import Type
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Generic, Type, TypeVar
|
|
3
3
|
|
|
4
4
|
from buz import Handler
|
|
5
5
|
from buz.command import Command
|
|
6
6
|
|
|
7
|
+
TCommand = TypeVar("TCommand", bound=Command)
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
class CommandHandler(Generic[TCommand], Handler[TCommand], ABC):
|
|
9
11
|
@classmethod
|
|
10
12
|
@abstractmethod
|
|
11
|
-
def handles(cls) -> Type[
|
|
13
|
+
def handles(cls) -> Type[TCommand]:
|
|
12
14
|
pass
|
|
13
15
|
|
|
14
16
|
@abstractmethod
|
|
15
|
-
def handle(self, command:
|
|
17
|
+
def handle(self, command: TCommand) -> None:
|
|
16
18
|
pass
|
|
@@ -8,6 +8,7 @@ from buz.event.infrastructure.buz_kafka.consume_strategy.kafka_on_fail_strategy
|
|
|
8
8
|
from buz.event.infrastructure.buz_kafka.exceptions.max_consumer_retry_exception import MaxConsumerRetryException
|
|
9
9
|
from buz.event.infrastructure.buz_kafka.exceptions.retry_exception import ConsumerRetryException
|
|
10
10
|
from buz.event.infrastructure.buz_kafka.kafka_event_subscriber_executor import KafkaEventSubscriberExecutor
|
|
11
|
+
from buz.event.infrastructure.models.execution_context import ExecutionContext
|
|
11
12
|
from buz.event.middleware.async_consume_middleware import AsyncConsumeMiddleware
|
|
12
13
|
from buz.event.middleware.async_consume_middleware_chain_resolver import AsyncConsumeMiddlewareChainResolver
|
|
13
14
|
from buz.event.strategies.retry.consume_retrier import ConsumeRetrier
|
|
@@ -15,6 +16,7 @@ from buz.event.strategies.retry.reject_callback import RejectCallback
|
|
|
15
16
|
from buz.kafka.domain.exceptions.not_valid_kafka_message_exception import NotValidKafkaMessageException
|
|
16
17
|
from buz.kafka.domain.models.kafka_consumer_record import KafkaConsumerRecord
|
|
17
18
|
from buz.kafka.domain.models.kafka_poll_record import KafkaPollRecord
|
|
19
|
+
from buz.event.infrastructure.buz_kafka.models.kafka_delivery_context import KafkaDeliveryContext
|
|
18
20
|
from buz.kafka.infrastructure.deserializers.byte_deserializer import ByteDeserializer
|
|
19
21
|
from buz.kafka.infrastructure.serializers.kafka_header_serializer import KafkaHeaderSerializer
|
|
20
22
|
|
|
@@ -67,18 +69,33 @@ class KafkaEventAsyncSubscriberExecutor(KafkaEventSubscriberExecutor):
|
|
|
67
69
|
value=deserialized_value,
|
|
68
70
|
headers=self.__header_deserializer.deserialize(kafka_poll_record.headers),
|
|
69
71
|
),
|
|
72
|
+
ExecutionContext(
|
|
73
|
+
delivery_context=KafkaDeliveryContext(
|
|
74
|
+
topic=kafka_poll_record.topic,
|
|
75
|
+
consumer_group=self.__subscriber.fqn(),
|
|
76
|
+
partition=kafka_poll_record.partition,
|
|
77
|
+
timestamp=kafka_poll_record.timestamp,
|
|
78
|
+
)
|
|
79
|
+
),
|
|
70
80
|
)
|
|
71
81
|
except NotValidKafkaMessageException:
|
|
72
82
|
self.__logger.error(
|
|
73
83
|
f'The message "{str(kafka_poll_record.value)}" is not valid, it will be consumed but not processed'
|
|
74
84
|
)
|
|
75
85
|
|
|
76
|
-
async def __consumption_callback(
|
|
86
|
+
async def __consumption_callback(
|
|
87
|
+
self, subscriber: AsyncSubscriber, message: KafkaConsumerRecord[Event], execution_context: ExecutionContext
|
|
88
|
+
) -> None:
|
|
77
89
|
await self.__consume_middleware_chain_resolver.resolve(
|
|
78
|
-
event=message.value,
|
|
90
|
+
event=message.value,
|
|
91
|
+
subscriber=subscriber,
|
|
92
|
+
execution_context=execution_context,
|
|
93
|
+
consume=self.__perform_consume,
|
|
79
94
|
)
|
|
80
95
|
|
|
81
|
-
async def __perform_consume(
|
|
96
|
+
async def __perform_consume(
|
|
97
|
+
self, event: Event, subscriber: AsyncSubscriber, execution_context: ExecutionContext
|
|
98
|
+
) -> None:
|
|
82
99
|
number_of_executions = 0
|
|
83
100
|
should_retry = True
|
|
84
101
|
|
|
@@ -7,6 +7,8 @@ from buz.event.infrastructure.buz_kafka.consume_strategy.kafka_on_fail_strategy
|
|
|
7
7
|
from buz.event.infrastructure.buz_kafka.exceptions.max_consumer_retry_exception import MaxConsumerRetryException
|
|
8
8
|
from buz.event.infrastructure.buz_kafka.exceptions.retry_exception import ConsumerRetryException
|
|
9
9
|
from buz.event.infrastructure.buz_kafka.kafka_event_subscriber_executor import KafkaEventSubscriberExecutor
|
|
10
|
+
from buz.event.infrastructure.buz_kafka.models.kafka_delivery_context import KafkaDeliveryContext
|
|
11
|
+
from buz.event.infrastructure.models.execution_context import ExecutionContext
|
|
10
12
|
from buz.event.middleware.consume_middleware import ConsumeMiddleware
|
|
11
13
|
from buz.event.middleware.consume_middleware_chain_resolver import ConsumeMiddlewareChainResolver
|
|
12
14
|
from buz.event.strategies.retry.consume_retrier import ConsumeRetrier
|
|
@@ -64,11 +66,19 @@ class KafkaEventSyncSubscriberExecutor(KafkaEventSubscriberExecutor):
|
|
|
64
66
|
await get_running_loop().run_in_executor(
|
|
65
67
|
None,
|
|
66
68
|
lambda: self.__execution_callback(
|
|
67
|
-
self.__subscriber,
|
|
68
|
-
KafkaConsumerRecord(
|
|
69
|
+
subscriber=self.__subscriber,
|
|
70
|
+
message=KafkaConsumerRecord(
|
|
69
71
|
value=deserialized_value,
|
|
70
72
|
headers=self.__header_deserializer.deserialize(kafka_poll_record.headers),
|
|
71
73
|
),
|
|
74
|
+
execution_context=ExecutionContext(
|
|
75
|
+
delivery_context=KafkaDeliveryContext(
|
|
76
|
+
topic=kafka_poll_record.topic,
|
|
77
|
+
consumer_group=self.__subscriber.fqn(),
|
|
78
|
+
partition=kafka_poll_record.partition,
|
|
79
|
+
timestamp=kafka_poll_record.timestamp,
|
|
80
|
+
)
|
|
81
|
+
),
|
|
72
82
|
),
|
|
73
83
|
)
|
|
74
84
|
|
|
@@ -77,12 +87,17 @@ class KafkaEventSyncSubscriberExecutor(KafkaEventSubscriberExecutor):
|
|
|
77
87
|
f'The message "{str(kafka_poll_record.value)}" is not valid, it will be consumed but not processed'
|
|
78
88
|
)
|
|
79
89
|
|
|
80
|
-
def __execution_callback(
|
|
90
|
+
def __execution_callback(
|
|
91
|
+
self, subscriber: Subscriber, message: KafkaConsumerRecord[Event], execution_context: ExecutionContext
|
|
92
|
+
) -> None:
|
|
81
93
|
self.__consume_middleware_chain_resolver.resolve(
|
|
82
|
-
event=message.value,
|
|
94
|
+
event=message.value,
|
|
95
|
+
subscriber=subscriber,
|
|
96
|
+
execution_context=execution_context,
|
|
97
|
+
consume=self.__perform_consume,
|
|
83
98
|
)
|
|
84
99
|
|
|
85
|
-
def __perform_consume(self, event: Event, subscriber: Subscriber) -> None:
|
|
100
|
+
def __perform_consume(self, event: Event, subscriber: Subscriber, execution_context: ExecutionContext) -> None:
|
|
86
101
|
number_of_executions = 0
|
|
87
102
|
should_retry = True
|
|
88
103
|
|
|
@@ -2,6 +2,8 @@ import asyncio
|
|
|
2
2
|
from logging import Logger
|
|
3
3
|
from typing import Optional, Callable, cast
|
|
4
4
|
|
|
5
|
+
from buz.event.infrastructure.kombu.models.kombu_delivery_context import KombuDeliveryContext
|
|
6
|
+
from buz.event.infrastructure.models.execution_context import ExecutionContext
|
|
5
7
|
from kombu import Connection, Queue, Consumer as MessageConsumer, Message
|
|
6
8
|
from kombu.mixins import ConsumerMixin
|
|
7
9
|
from kombu.transport.pyamqp import Channel
|
|
@@ -110,12 +112,18 @@ class KombuConsumer(ConsumerMixin, Consumer):
|
|
|
110
112
|
# The problem here is that the chain resolver works with syncsubscribers, an asyncsubscriber would require of a async function,
|
|
111
113
|
# but we are using run-until-complete to run the async function, so we are not really using the async function, we are just running it as a sync function, so we can cast the asyncsubscriber to a subscriber
|
|
112
114
|
subscriber = cast(Subscriber, meta_subscriber)
|
|
113
|
-
|
|
115
|
+
execution_context = ExecutionContext(delivery_context=KombuDeliveryContext())
|
|
116
|
+
self.__consume_middleware_chain_resolver.resolve(
|
|
117
|
+
event=event,
|
|
118
|
+
subscriber=subscriber,
|
|
119
|
+
execution_context=execution_context,
|
|
120
|
+
consume=self.__perform_consume,
|
|
121
|
+
)
|
|
114
122
|
message.ack()
|
|
115
123
|
except Exception as exc:
|
|
116
124
|
self.__on_consume_exception(message, event, meta_subscribers, exc)
|
|
117
125
|
|
|
118
|
-
def __perform_consume(self, event: Event, subscriber: MetaSubscriber) -> None:
|
|
126
|
+
def __perform_consume(self, event: Event, subscriber: MetaSubscriber, execution_context: ExecutionContext) -> None:
|
|
119
127
|
if isinstance(subscriber, AsyncSubscriber):
|
|
120
128
|
self.__get_or_create_event_loop().run_until_complete(subscriber.consume(event))
|
|
121
129
|
return
|
|
File without changes
|
|
@@ -3,12 +3,19 @@ from typing import Awaitable, Callable
|
|
|
3
3
|
|
|
4
4
|
from buz.event import Event
|
|
5
5
|
from buz.event.async_subscriber import AsyncSubscriber
|
|
6
|
+
from buz.event.infrastructure.models.execution_context import ExecutionContext
|
|
6
7
|
from buz.middleware import Middleware
|
|
7
8
|
|
|
8
|
-
AsyncConsumeCallable = Callable[[Event, AsyncSubscriber], Awaitable[None]]
|
|
9
|
+
AsyncConsumeCallable = Callable[[Event, AsyncSubscriber, ExecutionContext], Awaitable[None]]
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class AsyncConsumeMiddleware(Middleware):
|
|
12
13
|
@abstractmethod
|
|
13
|
-
async def on_consume(
|
|
14
|
+
async def on_consume(
|
|
15
|
+
self,
|
|
16
|
+
event: Event,
|
|
17
|
+
subscriber: AsyncSubscriber,
|
|
18
|
+
consume: AsyncConsumeCallable,
|
|
19
|
+
execution_context: ExecutionContext,
|
|
20
|
+
) -> None:
|
|
14
21
|
pass
|
|
@@ -2,6 +2,7 @@ from typing import Sequence
|
|
|
2
2
|
from buz.event import Event
|
|
3
3
|
from buz.event.async_subscriber import AsyncSubscriber
|
|
4
4
|
|
|
5
|
+
from buz.event.infrastructure.models.execution_context import ExecutionContext
|
|
5
6
|
from buz.event.middleware.async_consume_middleware import AsyncConsumeCallable, AsyncConsumeMiddleware
|
|
6
7
|
from buz.middleware import MiddlewareChainBuilder
|
|
7
8
|
|
|
@@ -14,16 +15,26 @@ class AsyncConsumeMiddlewareChainResolver:
|
|
|
14
15
|
self.__middlewares = middlewares
|
|
15
16
|
self.__middleware_chain_builder: MiddlewareChainBuilder[
|
|
16
17
|
AsyncConsumeCallable, AsyncConsumeMiddleware
|
|
17
|
-
] = MiddlewareChainBuilder(
|
|
18
|
+
] = MiddlewareChainBuilder(self.__middlewares)
|
|
18
19
|
|
|
19
|
-
async def resolve(
|
|
20
|
+
async def resolve(
|
|
21
|
+
self,
|
|
22
|
+
event: Event,
|
|
23
|
+
subscriber: AsyncSubscriber,
|
|
24
|
+
consume: AsyncConsumeCallable,
|
|
25
|
+
execution_context: ExecutionContext,
|
|
26
|
+
) -> None:
|
|
20
27
|
chain_callable: AsyncConsumeCallable = self.__middleware_chain_builder.get_chain_callable(
|
|
21
28
|
consume, self.__get_middleware_callable
|
|
22
29
|
)
|
|
23
30
|
|
|
24
|
-
await chain_callable(event, subscriber)
|
|
31
|
+
await chain_callable(event, subscriber, execution_context)
|
|
25
32
|
|
|
26
33
|
def __get_middleware_callable(
|
|
27
|
-
self,
|
|
34
|
+
self,
|
|
35
|
+
middleware: AsyncConsumeMiddleware,
|
|
36
|
+
consume_callable: AsyncConsumeCallable,
|
|
28
37
|
) -> AsyncConsumeCallable:
|
|
29
|
-
return lambda event, subscriber: middleware.on_consume(
|
|
38
|
+
return lambda event, subscriber, execution_context: middleware.on_consume(
|
|
39
|
+
event, subscriber, consume_callable, execution_context
|
|
40
|
+
)
|
|
@@ -2,19 +2,36 @@ from abc import abstractmethod
|
|
|
2
2
|
|
|
3
3
|
from buz.event import Event
|
|
4
4
|
from buz.event.async_subscriber import AsyncSubscriber
|
|
5
|
+
from buz.event.infrastructure.models.execution_context import ExecutionContext
|
|
5
6
|
from buz.event.middleware.async_consume_middleware import AsyncConsumeMiddleware, AsyncConsumeCallable
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class BaseAsyncConsumeMiddleware(AsyncConsumeMiddleware):
|
|
9
|
-
async def on_consume(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
async def on_consume(
|
|
11
|
+
self,
|
|
12
|
+
event: Event,
|
|
13
|
+
subscriber: AsyncSubscriber,
|
|
14
|
+
consume: AsyncConsumeCallable,
|
|
15
|
+
execution_context: ExecutionContext,
|
|
16
|
+
) -> None:
|
|
17
|
+
await self._before_on_consume(event, subscriber, execution_context)
|
|
18
|
+
await consume(event, subscriber, execution_context)
|
|
19
|
+
await self._after_on_consume(event, subscriber, execution_context)
|
|
13
20
|
|
|
14
21
|
@abstractmethod
|
|
15
|
-
async def _before_on_consume(
|
|
22
|
+
async def _before_on_consume(
|
|
23
|
+
self,
|
|
24
|
+
event: Event,
|
|
25
|
+
subscriber: AsyncSubscriber,
|
|
26
|
+
execution_context: ExecutionContext,
|
|
27
|
+
) -> None:
|
|
16
28
|
pass
|
|
17
29
|
|
|
18
30
|
@abstractmethod
|
|
19
|
-
async def _after_on_consume(
|
|
31
|
+
async def _after_on_consume(
|
|
32
|
+
self,
|
|
33
|
+
event: Event,
|
|
34
|
+
subscriber: AsyncSubscriber,
|
|
35
|
+
execution_context: ExecutionContext,
|
|
36
|
+
) -> None:
|
|
20
37
|
pass
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
2
|
|
|
3
3
|
from buz.event import Event, Subscriber
|
|
4
|
+
from buz.event.infrastructure.models.execution_context import ExecutionContext
|
|
4
5
|
from buz.event.middleware import ConsumeMiddleware, ConsumeCallable
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class BaseConsumeMiddleware(ConsumeMiddleware):
|
|
8
|
-
def on_consume(
|
|
9
|
-
self
|
|
10
|
-
|
|
11
|
-
self.
|
|
9
|
+
def on_consume(
|
|
10
|
+
self, event: Event, subscriber: Subscriber, consume: ConsumeCallable, execution_context: ExecutionContext
|
|
11
|
+
) -> None:
|
|
12
|
+
self._before_on_consume(event, subscriber, execution_context)
|
|
13
|
+
consume(event, subscriber, execution_context)
|
|
14
|
+
self._after_on_consume(event, subscriber, execution_context)
|
|
12
15
|
|
|
13
16
|
@abstractmethod
|
|
14
|
-
def _before_on_consume(self, event: Event, subscriber: Subscriber) -> None:
|
|
17
|
+
def _before_on_consume(self, event: Event, subscriber: Subscriber, execution_context: ExecutionContext) -> None:
|
|
15
18
|
pass
|
|
16
19
|
|
|
17
20
|
@abstractmethod
|
|
18
|
-
def _after_on_consume(self, event: Event, subscriber: Subscriber) -> None:
|
|
21
|
+
def _after_on_consume(self, event: Event, subscriber: Subscriber, execution_context: ExecutionContext) -> None:
|
|
19
22
|
pass
|
|
@@ -2,12 +2,15 @@ from abc import abstractmethod
|
|
|
2
2
|
from typing import Callable
|
|
3
3
|
|
|
4
4
|
from buz.event import Event, Subscriber
|
|
5
|
+
from buz.event.infrastructure.models.execution_context import ExecutionContext
|
|
5
6
|
from buz.middleware import Middleware
|
|
6
7
|
|
|
7
|
-
ConsumeCallable = Callable[[Event, Subscriber], None]
|
|
8
|
+
ConsumeCallable = Callable[[Event, Subscriber, ExecutionContext], None]
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ConsumeMiddleware(Middleware):
|
|
11
12
|
@abstractmethod
|
|
12
|
-
def on_consume(
|
|
13
|
+
def on_consume(
|
|
14
|
+
self, event: Event, subscriber: Subscriber, consume: ConsumeCallable, execution_context: ExecutionContext
|
|
15
|
+
) -> None:
|
|
13
16
|
pass
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Sequence
|
|
2
2
|
from buz.event import Event, Subscriber
|
|
3
|
+
from buz.event.infrastructure.models.execution_context import ExecutionContext
|
|
3
4
|
from buz.event.middleware import ConsumeMiddleware, ConsumeCallable
|
|
4
5
|
from buz.middleware import MiddlewareChainBuilder
|
|
5
6
|
|
|
@@ -9,15 +10,23 @@ class ConsumeMiddlewareChainResolver:
|
|
|
9
10
|
self.__middlewares = middlewares
|
|
10
11
|
self.__middleware_chain_builder: MiddlewareChainBuilder[
|
|
11
12
|
ConsumeCallable, ConsumeMiddleware
|
|
12
|
-
] = MiddlewareChainBuilder(
|
|
13
|
+
] = MiddlewareChainBuilder(self.__middlewares)
|
|
13
14
|
|
|
14
|
-
def resolve(
|
|
15
|
+
def resolve(
|
|
16
|
+
self,
|
|
17
|
+
event: Event,
|
|
18
|
+
subscriber: Subscriber,
|
|
19
|
+
consume: ConsumeCallable,
|
|
20
|
+
execution_context: ExecutionContext,
|
|
21
|
+
) -> None:
|
|
15
22
|
chain_callable: ConsumeCallable = self.__middleware_chain_builder.get_chain_callable(
|
|
16
23
|
consume, self.__get_middleware_callable
|
|
17
24
|
)
|
|
18
|
-
chain_callable(event, subscriber)
|
|
25
|
+
chain_callable(event, subscriber, execution_context)
|
|
19
26
|
|
|
20
27
|
def __get_middleware_callable(
|
|
21
28
|
self, middleware: ConsumeMiddleware, consume_callable: ConsumeCallable
|
|
22
29
|
) -> ConsumeCallable:
|
|
23
|
-
return lambda event, subscriber: middleware.on_consume(
|
|
30
|
+
return lambda event, subscriber, execution_context: middleware.on_consume(
|
|
31
|
+
event, subscriber, consume_callable, execution_context
|
|
32
|
+
)
|
|
File without changes
|
buz/event/sync/sync_event_bus.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from typing import Optional, Iterable
|
|
2
2
|
|
|
3
3
|
from buz.event import Event, EventBus, Subscriber
|
|
4
|
+
from buz.event.infrastructure.models.execution_context import ExecutionContext
|
|
4
5
|
from buz.event.middleware import (
|
|
5
6
|
PublishMiddleware,
|
|
6
7
|
ConsumeMiddleware,
|
|
7
8
|
PublishMiddlewareChainResolver,
|
|
8
9
|
ConsumeMiddlewareChainResolver,
|
|
9
10
|
)
|
|
11
|
+
from buz.event.sync.models.sync_delivery_context import SyncDeliveryContext
|
|
10
12
|
from buz.locator import Locator
|
|
11
13
|
|
|
12
14
|
|
|
@@ -26,10 +28,16 @@ class SyncEventBus(EventBus):
|
|
|
26
28
|
|
|
27
29
|
def __perform_publish(self, event: Event) -> None:
|
|
28
30
|
subscribers = self.__locator.get(event)
|
|
31
|
+
execution_context = ExecutionContext(delivery_context=SyncDeliveryContext())
|
|
29
32
|
for subscriber in subscribers:
|
|
30
|
-
self.__consume_middleware_chain_resolver.resolve(
|
|
33
|
+
self.__consume_middleware_chain_resolver.resolve(
|
|
34
|
+
event=event,
|
|
35
|
+
subscriber=subscriber,
|
|
36
|
+
execution_context=execution_context,
|
|
37
|
+
consume=self.__perform_consume,
|
|
38
|
+
)
|
|
31
39
|
|
|
32
|
-
def __perform_consume(self, event: Event, subscriber: Subscriber) -> None:
|
|
40
|
+
def __perform_consume(self, event: Event, subscriber: Subscriber, execution_context: ExecutionContext) -> None:
|
|
33
41
|
subscriber.consume(event)
|
|
34
42
|
|
|
35
43
|
def bulk_publish(self, events: Iterable[Event]) -> None:
|
buz/handler.py
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Type
|
|
2
|
+
from typing import Generic, Type, TypeVar
|
|
3
3
|
|
|
4
4
|
from buz import Message
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
TMessage = TypeVar("TMessage", bound=Message)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Handler(Generic[TMessage], ABC):
|
|
8
11
|
@classmethod
|
|
9
12
|
@abstractmethod
|
|
10
|
-
def handles(cls) -> Type[
|
|
13
|
+
def handles(cls) -> Type[TMessage]:
|
|
11
14
|
pass
|
|
12
15
|
|
|
13
16
|
@classmethod
|
|
@@ -1,27 +1,36 @@
|
|
|
1
|
-
from typing import Type, get_type_hints, Any
|
|
1
|
+
from typing import Generic, Type, TypeVar, cast, get_type_hints, Any
|
|
2
2
|
|
|
3
3
|
from buz.query import Query
|
|
4
4
|
from buz.query.asynchronous.query_handler import QueryHandler
|
|
5
|
+
from buz.query.query_response import QueryResponse
|
|
5
6
|
|
|
7
|
+
TQuery = TypeVar("TQuery", bound=Query)
|
|
8
|
+
TQueryResponse = TypeVar("TQueryResponse", bound=QueryResponse)
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
|
|
11
|
+
class BaseQueryHandler(Generic[TQuery, TQueryResponse], QueryHandler[TQuery, TQueryResponse]):
|
|
8
12
|
@classmethod
|
|
9
13
|
def fqn(cls) -> str:
|
|
10
14
|
return f"query_handler.{cls.__module__}.{cls.__name__}"
|
|
11
15
|
|
|
12
16
|
@classmethod
|
|
13
|
-
def handles(cls) -> Type[
|
|
17
|
+
def handles(cls) -> Type[TQuery]:
|
|
14
18
|
handle_types = get_type_hints(cls.handle)
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
t_query = handle_types.get("query")
|
|
21
|
+
if t_query is None:
|
|
17
22
|
raise TypeError(
|
|
18
23
|
f"The method 'handle' in '{cls.fqn()}' doesn't have a parameter named 'query'. Found parameters: {cls.__get_method_parameter_names(handle_types)}"
|
|
19
24
|
)
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
# TypeVar mask the actual bound type
|
|
27
|
+
if hasattr(t_query, "__bound__"):
|
|
28
|
+
t_query = t_query.__bound__
|
|
29
|
+
|
|
30
|
+
if not issubclass(t_query, Query):
|
|
22
31
|
raise TypeError(f"The parameter 'query' in '{cls.fqn()}.handle' is not a 'buz.query.Query' subclass")
|
|
23
32
|
|
|
24
|
-
return
|
|
33
|
+
return cast(Type[TQuery], t_query)
|
|
25
34
|
|
|
26
35
|
@classmethod
|
|
27
36
|
def __get_method_parameter_names(cls, handle_types: dict[str, Any]) -> list[str]:
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
-
from typing import Type
|
|
2
|
+
from typing import Generic, Type, TypeVar
|
|
3
3
|
|
|
4
4
|
from buz import Handler
|
|
5
5
|
from buz.query import Query, QueryResponse
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
TQuery = TypeVar("TQuery", bound=Query)
|
|
9
|
+
TQueryResponse = TypeVar("TQueryResponse", bound=QueryResponse)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class QueryHandler(Generic[TQuery, TQueryResponse], Handler[TQuery]):
|
|
9
13
|
@classmethod
|
|
10
14
|
@abstractmethod
|
|
11
|
-
def handles(cls) -> Type[
|
|
15
|
+
def handles(cls) -> Type[TQuery]:
|
|
12
16
|
pass
|
|
13
17
|
|
|
14
18
|
@abstractmethod
|
|
15
|
-
async def handle(self, query: Query) ->
|
|
19
|
+
async def handle(self, query: Query) -> TQueryResponse:
|
|
16
20
|
pass
|