Metadata-Version: 2.1
Name: DiMessageProcessor
Version: 2.1.0
Author-email: Eduardo Almeida <eduardo.almeida@e-deploy.com.br>, Max Guenes <max.santos@e-deploy.com.br>
Maintainer-email: Eduardo Almeida <eduardo.almeida@e-deploy.com.br>, Max Guenes <max.santos@e-deploy.com.br>
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: MessageProcessor<5,>=4.3.0
Requires-Dist: EdpDependencyInjection<4,>=3.0.0

# DiMessageProcessor

Bridge library that integrates [EdpDependencyInjection](https://pip.e-deploy.com.br) with [MessageProcessor](https://pip.e-deploy.com.br), enabling dependency-injected message processor construction.

Instead of manually instantiating processors, this library resolves them from a `DependencyContext` at build time. Definitions accept either a type (resolved via DI) or a real `MessageProcessor` instance (used directly).

## Installation

```bash
pip install DiMessageProcessor --extra-index-url=https://pip.e-deploy.com.br
```

## Requirements

- Python >= 3.8
- `MessageProcessor >=4.3.0,<5`
- `EdpDependencyInjection >=3.0.0,<4`
- `messagebus` (provides `SubscriptionInfo`)

## Usage

### Message handler (recommended)

`DiMessageProcessorMessageHandler` is the top-level entry point that wires everything together. It also auto-discovers `AutoMappedEventProcessorBuilder` and `AutoMappedMessageProcessorBuilder` implementations registered in the DI context.

```python
from dimessageprocessor import DiMessageProcessorMessageHandler

handler = DiMessageProcessorMessageHandler(
    dependency_context=dependency_context,
    event_processor_definitions={
        "order_created": OrderCreatedProcessor,
        "order_cancelled": OrderCancelledProcessor,
    },
    message_processor_definitions={
        0x01: LoginProcessor,
        0x02: LogoutProcessor,
    },
    callback_definitions=[LoggingCallback, MetricsCallback],
)
```

### Auto-mapped builders

Modules can declare their processor mappings by implementing `AutoMappedEventProcessorBuilder` or `AutoMappedMessageProcessorBuilder`. These are automatically discovered by `DiMessageProcessorMessageHandler` via the DI context.

```python
from messagebus import SubscriptionInfo
from dimessageprocessor import AutoMappedEventProcessorBuilder, EventProcessorDefinition

class MyEventProcessorBuilder(AutoMappedEventProcessorBuilder):
    def build_events(self):
        return {
            "order_created": EventProcessorDefinition(processor_type=OrderCreatedProcessor, subscription_type=SubscriptionInfo.TYPE_REENTRANT),
            "order_cancelled": OrderCancelledProcessor,  # type reference also accepted
        }
```

```python
from dimessageprocessor import AutoMappedMessageProcessorBuilder, MessageProcessorDefinition

class MyMessageProcessorBuilder(AutoMappedMessageProcessorBuilder):
    def build_messages(self):
        return {
            0x01: MessageProcessorDefinition(processor_type=LoginProcessor),
            0x02: LogoutProcessor,
        }
```

### EventProcessorDefinition

`EventProcessorDefinition` wraps a processor type with a `subscription_type` that controls how the event bus delivers messages. Valid values come from `messagebus.SubscriptionInfo`:

| Constant | Value | Meaning |
|---|---|---|
| `SubscriptionInfo.TYPE_REENTRANT` | `1` | Processor can handle multiple events concurrently |
| `SubscriptionInfo.TYPE_NON_REENTRANT` | `2` | Processor handles events one at a time |

An invalid `subscription_type` raises `ValueError`.

### Event-based processor builder

Maps string event keys to processor types or instances resolved from the DI context.

```python
from dimessageprocessor import DiEventMessageProcessorBuilder

builder = DiEventMessageProcessorBuilder(
    dependency_context=dependency_context,
    processor_definitions={
        "order_created": OrderCreatedProcessor,
        "order_cancelled": already_built_instance,  # real instance also accepted
    }
)

processor = builder.build_message_processor("order_created")
```

### Message token-based processor builder

Maps integer tokens to processor types or instances resolved from the DI context.

```python
from dimessageprocessor import DiMessageMessageProcessorBuilder

builder = DiMessageMessageProcessorBuilder(
    dependency_context=dependency_context,
    processor_definitions={
        0x01: LoginProcessor,
        0x02: LogoutProcessor,
    }
)

processor = builder.build_message_processor(0x01)
```

### Generic processor with injected interactor and collaborators

Instead of writing one near-identical `MessageProcessor` subclass per event/token (each
differing only by the interactor it delegates to), map a **single generic processor** and
inject its collaborators per definition. `EventProcessorDefinition` and
`MessageProcessorDefinition` accept:

- `interactor_type` — resolved from the DI context and injected as the `interactor` argument
- `injected_types` — a `{name: type}` mapping; each type is resolved from the DI context and injected under that name
- `params` — literal values injected by name

When any of these is present, a **fresh processor instance is built per resolution** (via
`DependencyContext.call_function`), so the same generic `processor_type` can safely serve
many keys with different collaborators — this avoids the DI container's per-type instance
caching that `get_instance` would otherwise apply.

```python
from dimessageprocessor import (
    DiEventMessageProcessorBuilder,
    EventProcessorDefinition,
)
from messageprocessorutil import JsonUtf8EventProcessor  # or your own generic base
from messagebus import SubscriptionInfo

builder = DiEventMessageProcessorBuilder(
    dependency_context=dependency_context,
    processor_definitions={
        "order_created": EventProcessorDefinition(
            processor_type=JsonUtf8EventProcessor,
            subscription_type=SubscriptionInfo.TYPE_REENTRANT,
            interactor_type=CreateOrderInteractor,        # -> processor(interactor=...)
            injected_types={"parser": OrderParser},       # resolved from DI, by name
            params={"sender": None},                       # literal value
        ),
        "order_cancelled": EventProcessorDefinition(
            processor_type=JsonUtf8EventProcessor,         # same generic type, different interactor
            subscription_type=SubscriptionInfo.TYPE_REENTRANT,
            interactor_type=CancelOrderInteractor,
        ),
    }
)

processor = builder.build_message_processor("order_created")
```

`MessageProcessorDefinition` supports the same `interactor_type` / `injected_types` /
`params` fields (without `subscription_type`) for the message-token side. Definitions with
none of these fields keep the previous behaviour: `processor_type` is resolved once via
`get_instance` (singleton).

### Executor factory with DI-resolved callbacks

Resolves `MessageProcessorCallback` instances from the DI context and creates `DefaultMessageProcessorExecutor` instances.

```python
from dimessageprocessor import DiMessageProcessorExecutorFactory

factory = DiMessageProcessorExecutorFactory(
    dependency_context=dependency_context,
    callback_definitions=[LoggingCallback, MetricsCallback]
)

executor = factory.build_executor(processor)
```

## Testing

```bash
poetry run python -m pytest test/ -v
```

## Authors

- Eduardo Almeida (eduardo.almeida@e-deploy.com.br)
- Max Guenes (max.santos@e-deploy.com.br)
