neuroglia-python 0.7.5__tar.gz → 0.7.7__tar.gz
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.
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/PKG-INFO +1 -1
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/pyproject.toml +1 -1
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/__init__.py +1 -1
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/event_sourcing/__init__.py +6 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/event_sourcing/abstractions.py +15 -0
- neuroglia_python-0.7.7/src/neuroglia/data/infrastructure/event_sourcing/read_model_reconciliator.py +328 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/serialization/json.py +41 -3
- neuroglia_python-0.7.5/src/neuroglia/data/infrastructure/event_sourcing/read_model_reconciliator.py +0 -89
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/LICENSE +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/README.md +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/application/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/application/background_scheduler.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/coordination/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/coordination/abstractions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/coordination/leader_election.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/coordination/redis_backend.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/core/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/core/module_loader.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/core/operation_result.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/core/problem_details.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/core/type_extensions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/core/type_finder.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/core/type_registry.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/abstractions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/exceptions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/abstractions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/event_sourcing/event_sourcing_repository.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/event_sourcing/event_store/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/event_sourcing/event_store/event_store.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/filesystem/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/filesystem/filesystem_repository.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/memory/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/memory/memory_repository.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/mongo/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/mongo/enhanced_mongo_repository.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/mongo/mongo_repository.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/mongo/motor_query.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/mongo/motor_repository.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/mongo/serialization_helper.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/mongo/typed_mongo_query.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/resources/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/resources/in_memory_storage_backend.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/resources/postgres_resource_store.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/resources/redis_resource_store.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/resources/resource_repository.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/tracing_mixin.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/queries/generic/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/queries/generic/get_by_id_query.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/queries/generic/list_query.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/queryable.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/abstractions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/controller.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/serializers/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/serializers/xml_serializer.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/serializers/yaml_serializer.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/state_machine.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/watcher.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/unit_of_work.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/dependency_injection/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/dependency_injection/service_provider.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/dependency_injection/service_provider_backup.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/cloud_event.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/decorators.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/infrastructure/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_bus.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_ingestor.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_middleware.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_publisher.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/tracing.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/expressions/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/expressions/javascript_expression_translator.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/extensions/cqrs_metrics_extensions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/extensions/mediator_extensions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/extensions/state_persistence_extensions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/hosting/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/hosting/abstractions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/hosting/configuration/data_access_layer.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/hosting/web.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/integration/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/integration/cache_repository.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/integration/http_service_client.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/integration/models.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/logging/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/logging/logger.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mapping/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mapping/mapper.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/behaviors/domain_event_cloudevent_behavior.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/behaviors/domain_event_dispatching_middleware.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/mediator.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/metrics_middleware.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/pipeline_behavior.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/simple.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/tracing_middleware.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mvc/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mvc/controller_base.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/observability/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/observability/framework.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/observability/logging.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/observability/metrics.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/observability/otel_sdk.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/observability/settings.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/observability/tracing.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/py.typed +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/reactive/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/reactive/rx_async.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/serialization/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/serialization/abstractions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/serialization/aggregate_serializer.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/utils/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/utils/camel_model.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/utils/case_conversion.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/validation/__init__.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/validation/business_rules.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/validation/exceptions.py +0 -0
- {neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/validation/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "neuroglia-python"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.7"
|
|
4
4
|
description = "Port from .NET to Python of the Neuroglia Framework"
|
|
5
5
|
authors = ["Charles d'Avernas <charles.davernas@neuroglia.io>", "Bruno van de Werve <bvandewe@gmail.com>"]
|
|
6
6
|
license = "Apache"
|
|
@@ -14,6 +14,10 @@ from .abstractions import (
|
|
|
14
14
|
StreamReadDirection,
|
|
15
15
|
)
|
|
16
16
|
from .event_sourcing_repository import EventSourcingRepository
|
|
17
|
+
from .read_model_reconciliator import (
|
|
18
|
+
ReadModelConciliationOptions,
|
|
19
|
+
ReadModelReconciliator,
|
|
20
|
+
)
|
|
17
21
|
|
|
18
22
|
__all__ = [
|
|
19
23
|
"EventStore",
|
|
@@ -24,4 +28,6 @@ __all__ = [
|
|
|
24
28
|
"StreamReadDirection",
|
|
25
29
|
"EventStoreOptions",
|
|
26
30
|
"Aggregator",
|
|
31
|
+
"ReadModelConciliationOptions",
|
|
32
|
+
"ReadModelReconciliator",
|
|
27
33
|
]
|
|
@@ -202,8 +202,23 @@ class EventStore(ABC):
|
|
|
202
202
|
|
|
203
203
|
class Aggregator:
|
|
204
204
|
def aggregate(self, events: list, aggregate_type: type):
|
|
205
|
+
"""
|
|
206
|
+
Reconstitutes an aggregate from a list of domain events.
|
|
207
|
+
|
|
208
|
+
Creates a new aggregate instance using __new__() to bypass __init__,
|
|
209
|
+
then manually initializes the state and _pending_events list before
|
|
210
|
+
replaying events to rebuild the aggregate's state.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
events: List of event records to replay
|
|
214
|
+
aggregate_type: The aggregate root class to instantiate
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Reconstituted aggregate with replayed state
|
|
218
|
+
"""
|
|
205
219
|
aggregate: AggregateRoot = object.__new__(aggregate_type)
|
|
206
220
|
aggregate.state = aggregate.__orig_bases__[0].__args__[0]()
|
|
221
|
+
aggregate._pending_events = list() # Initialize _pending_events to prevent AttributeError
|
|
207
222
|
for e in events:
|
|
208
223
|
aggregate.state.on(e.data)
|
|
209
224
|
aggregate.state.state_version = e.data.aggregate_version
|
neuroglia_python-0.7.7/src/neuroglia/data/infrastructure/event_sourcing/read_model_reconciliator.py
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from collections.abc import Callable, Coroutine
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from rx.core.typing import Disposable
|
|
9
|
+
|
|
10
|
+
from neuroglia.data.infrastructure.event_sourcing.abstractions import (
|
|
11
|
+
EventRecord,
|
|
12
|
+
EventStore,
|
|
13
|
+
EventStoreOptions,
|
|
14
|
+
)
|
|
15
|
+
from neuroglia.dependency_injection.service_provider import ServiceProviderBase
|
|
16
|
+
from neuroglia.hosting.abstractions import HostedService
|
|
17
|
+
from neuroglia.mediation.mediator import Mediator
|
|
18
|
+
from neuroglia.reactive import AsyncRx
|
|
19
|
+
|
|
20
|
+
log = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ReadModelConciliationOptions:
|
|
25
|
+
"""Represents the options used to configure the application's read model reconciliation features.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
consumer_group: The name of the group of consumers the application's read model is maintained by
|
|
29
|
+
sequential_processing: If True, events are processed sequentially per aggregate to maintain
|
|
30
|
+
causal ordering. Events from different aggregates can still be processed in parallel.
|
|
31
|
+
Default is True to prevent race conditions in projections.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
```python
|
|
35
|
+
# Default: Sequential processing per aggregate (recommended)
|
|
36
|
+
options = ReadModelConciliationOptions(
|
|
37
|
+
consumer_group="my-read-model"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Parallel processing (for independent handlers only)
|
|
41
|
+
options = ReadModelConciliationOptions(
|
|
42
|
+
consumer_group="my-read-model",
|
|
43
|
+
sequential_processing=False
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
consumer_group: str
|
|
49
|
+
""" Gets the name of the group of consumers the application's read model is maintained by """
|
|
50
|
+
|
|
51
|
+
sequential_processing: bool = field(default=True)
|
|
52
|
+
"""
|
|
53
|
+
If True (default), events from the same aggregate are processed sequentially to maintain
|
|
54
|
+
causal ordering. This prevents race conditions where a projection handler for a subsequent
|
|
55
|
+
event runs before the handler for a preceding event has completed.
|
|
56
|
+
|
|
57
|
+
When False, events are processed concurrently which may improve throughput but can cause
|
|
58
|
+
race conditions if projection handlers have dependencies on prior events being processed.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AggregateEventQueue:
|
|
63
|
+
"""
|
|
64
|
+
Manages sequential event processing per aggregate.
|
|
65
|
+
|
|
66
|
+
This class ensures that events from the same aggregate are processed in order,
|
|
67
|
+
while allowing events from different aggregates to be processed concurrently.
|
|
68
|
+
|
|
69
|
+
This solves the race condition where:
|
|
70
|
+
1. Aggregate emits EventA followed by EventB
|
|
71
|
+
2. EventA handler creates a document in MongoDB
|
|
72
|
+
3. EventB handler updates that document
|
|
73
|
+
4. Without ordering, EventB handler may run before EventA completes
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
_queues: Maps aggregate_id to its event queue
|
|
77
|
+
_processors: Maps aggregate_id to its processing task
|
|
78
|
+
_lock: Protects concurrent access to queues and processors
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self):
|
|
82
|
+
self._queues: dict[str, asyncio.Queue] = defaultdict(asyncio.Queue)
|
|
83
|
+
self._processors: dict[str, Optional[asyncio.Task]] = {}
|
|
84
|
+
self._lock = asyncio.Lock()
|
|
85
|
+
|
|
86
|
+
async def enqueue(self, aggregate_id: str, event_record: Any, process_func: Callable[[Any], Coroutine[Any, Any, None]]) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Enqueue an event for sequential processing.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
aggregate_id: The aggregate identifier to group events by
|
|
92
|
+
event_record: The event record to process (must have 'data' attribute)
|
|
93
|
+
process_func: Async function to call for processing the event
|
|
94
|
+
"""
|
|
95
|
+
async with self._lock:
|
|
96
|
+
await self._queues[aggregate_id].put((event_record, process_func))
|
|
97
|
+
|
|
98
|
+
# Start processor for this aggregate if not already running
|
|
99
|
+
if aggregate_id not in self._processors or self._processors[aggregate_id] is None:
|
|
100
|
+
self._processors[aggregate_id] = asyncio.create_task(self._process_queue(aggregate_id))
|
|
101
|
+
|
|
102
|
+
async def _process_queue(self, aggregate_id: str) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Process events from an aggregate's queue sequentially.
|
|
105
|
+
|
|
106
|
+
This ensures causal ordering: each event completes before the next begins.
|
|
107
|
+
"""
|
|
108
|
+
queue = self._queues[aggregate_id]
|
|
109
|
+
|
|
110
|
+
while True:
|
|
111
|
+
try:
|
|
112
|
+
# Wait for next event with timeout to allow cleanup
|
|
113
|
+
try:
|
|
114
|
+
event_record, process_func = await asyncio.wait_for(queue.get(), timeout=30.0)
|
|
115
|
+
except asyncio.TimeoutError:
|
|
116
|
+
# No events for this aggregate in 30 seconds, clean up
|
|
117
|
+
async with self._lock:
|
|
118
|
+
if queue.empty():
|
|
119
|
+
self._processors[aggregate_id] = None
|
|
120
|
+
return
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
# Process the event sequentially (await completion)
|
|
124
|
+
try:
|
|
125
|
+
await process_func(event_record)
|
|
126
|
+
except Exception as ex:
|
|
127
|
+
log.error(f"Error processing event for aggregate {aggregate_id}: {ex}", exc_info=True)
|
|
128
|
+
|
|
129
|
+
queue.task_done()
|
|
130
|
+
|
|
131
|
+
except asyncio.CancelledError:
|
|
132
|
+
log.debug(f"Event processor for aggregate {aggregate_id} cancelled")
|
|
133
|
+
break
|
|
134
|
+
except Exception as ex:
|
|
135
|
+
log.error(f"Unexpected error in event processor: {ex}", exc_info=True)
|
|
136
|
+
|
|
137
|
+
async def shutdown(self) -> None:
|
|
138
|
+
"""Cancel all processing tasks during shutdown."""
|
|
139
|
+
async with self._lock:
|
|
140
|
+
for task in self._processors.values():
|
|
141
|
+
if task is not None:
|
|
142
|
+
task.cancel()
|
|
143
|
+
self._processors.clear()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class ReadModelReconciliator(HostedService):
|
|
147
|
+
"""
|
|
148
|
+
Reconciles the read model by streaming and handling events from the event store.
|
|
149
|
+
|
|
150
|
+
This service subscribes to the event store's category stream and processes events
|
|
151
|
+
to update read model projections. It supports two processing modes:
|
|
152
|
+
|
|
153
|
+
**Sequential Processing (Default - Recommended)**:
|
|
154
|
+
Events from the same aggregate are processed sequentially to maintain causal
|
|
155
|
+
ordering. This prevents race conditions where projection handlers for subsequent
|
|
156
|
+
events run before handlers for preceding events complete.
|
|
157
|
+
|
|
158
|
+
Example scenario protected against:
|
|
159
|
+
1. ToolGroupCreatedEvent → Creates ToolGroupDto in MongoDB
|
|
160
|
+
2. SelectorAddedEvent → Updates the ToolGroupDto with new selector
|
|
161
|
+
Without sequential processing, step 2 might run before step 1 completes,
|
|
162
|
+
causing "document not found" errors.
|
|
163
|
+
|
|
164
|
+
**Parallel Processing**:
|
|
165
|
+
Events are processed concurrently for higher throughput. Only use this mode
|
|
166
|
+
if your projection handlers are independent and don't rely on prior events
|
|
167
|
+
being processed.
|
|
168
|
+
|
|
169
|
+
Attributes:
|
|
170
|
+
_service_provider: The current service provider for dependency resolution
|
|
171
|
+
_mediator: The mediator for publishing events to handlers
|
|
172
|
+
_event_store_options: Configuration options for the event store
|
|
173
|
+
_event_store: The event store service for reading events
|
|
174
|
+
_options: Configuration options for read model reconciliation
|
|
175
|
+
_event_queue: Queue for sequential event processing per aggregate
|
|
176
|
+
_subscription: The event store subscription handle
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
```python
|
|
180
|
+
# Configure with sequential processing (default)
|
|
181
|
+
options = ReadModelConciliationOptions(
|
|
182
|
+
consumer_group="my-projections"
|
|
183
|
+
)
|
|
184
|
+
reconciliator = ReadModelReconciliator(
|
|
185
|
+
service_provider, mediator, event_store_options, event_store, options
|
|
186
|
+
)
|
|
187
|
+
await reconciliator.start_async()
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
See Also:
|
|
191
|
+
- Event Sourcing: https://bvandewe.github.io/pyneuro/patterns/event-sourcing/
|
|
192
|
+
- Read Model Projections: https://bvandewe.github.io/pyneuro/patterns/cqrs/
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
_service_provider: ServiceProviderBase
|
|
196
|
+
""" Gets the current service provider """
|
|
197
|
+
|
|
198
|
+
_mediator: Mediator
|
|
199
|
+
|
|
200
|
+
_event_store_options: EventStoreOptions
|
|
201
|
+
""" Gets the options used to configure the event store """
|
|
202
|
+
|
|
203
|
+
_event_store: EventStore
|
|
204
|
+
""" Gets the service used to persist and stream domain events """
|
|
205
|
+
|
|
206
|
+
_options: ReadModelConciliationOptions
|
|
207
|
+
""" Gets the options used to configure read model reconciliation """
|
|
208
|
+
|
|
209
|
+
_event_queue: Optional[AggregateEventQueue]
|
|
210
|
+
""" Queue for sequential event processing per aggregate """
|
|
211
|
+
|
|
212
|
+
_subscription: Optional[Disposable]
|
|
213
|
+
|
|
214
|
+
def __init__(self, service_provider: ServiceProviderBase, mediator: Mediator, event_store_options: EventStoreOptions, event_store: EventStore, options: Optional[ReadModelConciliationOptions] = None):
|
|
215
|
+
self._service_provider = service_provider
|
|
216
|
+
self._mediator = mediator
|
|
217
|
+
self._event_store_options = event_store_options
|
|
218
|
+
self._event_store = event_store
|
|
219
|
+
self._options = options or ReadModelConciliationOptions(consumer_group=event_store_options.consumer_group if hasattr(event_store_options, "consumer_group") else "default")
|
|
220
|
+
self._event_queue = AggregateEventQueue() if self._options.sequential_processing else None
|
|
221
|
+
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
222
|
+
self._subscription = None
|
|
223
|
+
|
|
224
|
+
async def start_async(self):
|
|
225
|
+
"""Start the read model reconciliator and begin processing events."""
|
|
226
|
+
self._loop = asyncio.get_event_loop()
|
|
227
|
+
await self.subscribe_async()
|
|
228
|
+
log.info(f"ReadModelReconciliator started with " f"{'sequential' if self._options.sequential_processing else 'parallel'} processing")
|
|
229
|
+
|
|
230
|
+
async def stop_async(self):
|
|
231
|
+
"""Stop the read model reconciliator and clean up resources."""
|
|
232
|
+
if self._subscription is not None:
|
|
233
|
+
self._subscription.dispose()
|
|
234
|
+
if self._event_queue:
|
|
235
|
+
await self._event_queue.shutdown()
|
|
236
|
+
log.info("ReadModelReconciliator stopped")
|
|
237
|
+
|
|
238
|
+
async def subscribe_async(self):
|
|
239
|
+
"""Subscribe to the event store's category stream."""
|
|
240
|
+
observable = await self._event_store.observe_async(f"$ce-{self._event_store_options.database_name}", self._event_store_options.consumer_group)
|
|
241
|
+
|
|
242
|
+
def on_next(e: EventRecord):
|
|
243
|
+
"""Schedule the async handler on the main event loop without closing it."""
|
|
244
|
+
try:
|
|
245
|
+
if self._loop is None:
|
|
246
|
+
log.warning("Event loop not initialized, skipping event")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
# Use call_soon_threadsafe to schedule the coroutine on the main loop
|
|
250
|
+
# This prevents creating/closing new event loops which breaks Motor
|
|
251
|
+
self._loop.call_soon_threadsafe(lambda: asyncio.create_task(self._handle_event_async(e)))
|
|
252
|
+
except RuntimeError as ex:
|
|
253
|
+
log.warning(f"Event loop closed, skipping event: " f"{type(e.data).__name__ if hasattr(e, 'data') else 'unknown'} - {ex}")
|
|
254
|
+
|
|
255
|
+
self._subscription = AsyncRx.subscribe(observable, on_next)
|
|
256
|
+
|
|
257
|
+
async def _handle_event_async(self, e: EventRecord) -> None:
|
|
258
|
+
"""
|
|
259
|
+
Handle an incoming event record.
|
|
260
|
+
|
|
261
|
+
Routes to either sequential or parallel processing based on configuration.
|
|
262
|
+
"""
|
|
263
|
+
if self._options.sequential_processing and self._event_queue:
|
|
264
|
+
# Extract aggregate ID from the event for grouping
|
|
265
|
+
aggregate_id = self._extract_aggregate_id(e)
|
|
266
|
+
await self._event_queue.enqueue(aggregate_id, e, self.on_event_record_stream_next_async)
|
|
267
|
+
else:
|
|
268
|
+
# Parallel processing (legacy behavior)
|
|
269
|
+
await self.on_event_record_stream_next_async(e)
|
|
270
|
+
|
|
271
|
+
def _extract_aggregate_id(self, e: EventRecord) -> str:
|
|
272
|
+
"""
|
|
273
|
+
Extract the aggregate ID from an event record for sequential processing grouping.
|
|
274
|
+
|
|
275
|
+
Tries multiple strategies to find the aggregate ID:
|
|
276
|
+
1. From event data's aggregate_id attribute
|
|
277
|
+
2. From event record's stream_id (format: aggregatetype-aggregateid)
|
|
278
|
+
3. Falls back to "unknown" if not found
|
|
279
|
+
"""
|
|
280
|
+
# Try to get from event data
|
|
281
|
+
if hasattr(e, "data") and e.data is not None and hasattr(e.data, "aggregate_id"):
|
|
282
|
+
agg_id = getattr(e.data, "aggregate_id", None)
|
|
283
|
+
if agg_id is not None:
|
|
284
|
+
return str(agg_id)
|
|
285
|
+
|
|
286
|
+
# Try to get from stream ID (format: aggregatetype-aggregateid)
|
|
287
|
+
if hasattr(e, "stream_id") and e.stream_id:
|
|
288
|
+
parts = str(e.stream_id).split("-", 1)
|
|
289
|
+
if len(parts) > 1:
|
|
290
|
+
return parts[1]
|
|
291
|
+
|
|
292
|
+
# Fallback - all events go to same queue (fully sequential)
|
|
293
|
+
log.debug(f"Could not extract aggregate_id from event {type(e.data).__name__}")
|
|
294
|
+
return "unknown"
|
|
295
|
+
|
|
296
|
+
async def on_event_record_stream_next_async(self, e: EventRecord):
|
|
297
|
+
"""
|
|
298
|
+
Process a single event record by publishing it through the mediator.
|
|
299
|
+
|
|
300
|
+
This method is called either directly (parallel mode) or from the
|
|
301
|
+
aggregate event queue (sequential mode).
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
e: The event record to process
|
|
305
|
+
"""
|
|
306
|
+
try:
|
|
307
|
+
# Publish the event through the mediator
|
|
308
|
+
await self._mediator.publish_async(e.data)
|
|
309
|
+
|
|
310
|
+
# Acknowledge successful processing
|
|
311
|
+
# Use duck typing to support both AckableEventRecord and mock objects
|
|
312
|
+
if hasattr(e, "ack_async") and callable(getattr(e, "ack_async", None)):
|
|
313
|
+
await e.ack_async()
|
|
314
|
+
|
|
315
|
+
log.debug(f"Successfully processed event: {type(e.data).__name__}")
|
|
316
|
+
|
|
317
|
+
except Exception as ex:
|
|
318
|
+
log.error(f"An exception occurred while publishing an event of type " f"'{type(e.data).__name__}': {ex}", exc_info=True)
|
|
319
|
+
|
|
320
|
+
# Negative acknowledge on processing failure
|
|
321
|
+
# Use duck typing to support both AckableEventRecord and mock objects
|
|
322
|
+
if hasattr(e, "nack_async") and callable(getattr(e, "nack_async", None)):
|
|
323
|
+
await e.nack_async()
|
|
324
|
+
|
|
325
|
+
async def on_event_record_stream_error(self, ex: Exception):
|
|
326
|
+
"""Handle errors from the event record stream by resubscribing."""
|
|
327
|
+
log.error(f"Event stream error, resubscribing: {ex}")
|
|
328
|
+
await self.subscribe_async()
|
|
@@ -502,12 +502,14 @@ class JsonSerializer(TextSerializer):
|
|
|
502
502
|
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
503
503
|
|
|
504
504
|
# Try to detect decimal/money fields by name patterns
|
|
505
|
-
|
|
505
|
+
# Only match when the field name ENDS with the pattern to avoid false positives
|
|
506
|
+
# from nested paths like "input_schema_properties_price_type"
|
|
507
|
+
if self._is_monetary_field(field_name) and self._looks_like_decimal(value):
|
|
506
508
|
try:
|
|
507
|
-
from decimal import Decimal
|
|
509
|
+
from decimal import Decimal, InvalidOperation
|
|
508
510
|
|
|
509
511
|
return Decimal(value)
|
|
510
|
-
except (ValueError, TypeError):
|
|
512
|
+
except (ValueError, TypeError, InvalidOperation):
|
|
511
513
|
pass
|
|
512
514
|
|
|
513
515
|
# Try to find matching enum types in the target class
|
|
@@ -524,6 +526,32 @@ class JsonSerializer(TextSerializer):
|
|
|
524
526
|
|
|
525
527
|
return value
|
|
526
528
|
|
|
529
|
+
# Monetary field patterns for decimal detection
|
|
530
|
+
_MONETARY_PATTERNS: tuple[str, ...] = ("price", "cost", "amount", "total", "fee", "balance", "rate", "tax")
|
|
531
|
+
|
|
532
|
+
def _is_monetary_field(self, field_name: str) -> bool:
|
|
533
|
+
"""
|
|
534
|
+
Check if field name represents a monetary/decimal field.
|
|
535
|
+
Only matches when the field name ENDS with a monetary pattern to avoid
|
|
536
|
+
false positives from nested paths like 'input_schema_properties_price_type'.
|
|
537
|
+
"""
|
|
538
|
+
field_name_lower = field_name.lower()
|
|
539
|
+
# Split by underscore and check if the last part is a monetary pattern
|
|
540
|
+
parts = field_name_lower.split("_")
|
|
541
|
+
last_part = parts[-1] if parts else ""
|
|
542
|
+
return last_part in self._MONETARY_PATTERNS
|
|
543
|
+
|
|
544
|
+
def _looks_like_decimal(self, value: str) -> bool:
|
|
545
|
+
"""
|
|
546
|
+
Check if a string value looks like a valid decimal number.
|
|
547
|
+
This prevents attempting Decimal conversion on arbitrary strings.
|
|
548
|
+
"""
|
|
549
|
+
import re
|
|
550
|
+
|
|
551
|
+
# Match optional negative sign, digits, optional decimal point with more digits
|
|
552
|
+
# Also match scientific notation like "1.5e-10"
|
|
553
|
+
return bool(re.match(r"^-?\d+\.?\d*(?:[eE][+-]?\d+)?$", value.strip()))
|
|
554
|
+
|
|
527
555
|
def _is_datetime_string(self, value: str) -> bool:
|
|
528
556
|
"""Check if a string looks like an ISO datetime."""
|
|
529
557
|
try:
|
|
@@ -585,6 +613,16 @@ class JsonSerializer(TextSerializer):
|
|
|
585
613
|
if isinstance(expected_type, str):
|
|
586
614
|
return value
|
|
587
615
|
|
|
616
|
+
# Handle typing.Any - return value as-is for primitives, recursively process containers
|
|
617
|
+
if expected_type is Any:
|
|
618
|
+
if isinstance(value, dict):
|
|
619
|
+
return {k: self._deserialize_nested(v, Any) for k, v in value.items()}
|
|
620
|
+
elif isinstance(value, list):
|
|
621
|
+
return [self._deserialize_nested(item, Any) for item in value]
|
|
622
|
+
else:
|
|
623
|
+
# For primitives (str, int, float, bool, etc.), return as-is
|
|
624
|
+
return value
|
|
625
|
+
|
|
588
626
|
origin_type = get_origin(expected_type)
|
|
589
627
|
if origin_type is not None:
|
|
590
628
|
# This is a generic type (e.g., Optional[SomeType], List[SomeType])
|
neuroglia_python-0.7.5/src/neuroglia/data/infrastructure/event_sourcing/read_model_reconciliator.py
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import logging
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
from rx.core.typing import Disposable
|
|
6
|
-
|
|
7
|
-
from neuroglia.data.infrastructure.event_sourcing.abstractions import (
|
|
8
|
-
AckableEventRecord,
|
|
9
|
-
EventRecord,
|
|
10
|
-
EventStore,
|
|
11
|
-
EventStoreOptions,
|
|
12
|
-
)
|
|
13
|
-
from neuroglia.dependency_injection.service_provider import ServiceProviderBase
|
|
14
|
-
from neuroglia.hosting.abstractions import HostedService
|
|
15
|
-
from neuroglia.mediation.mediator import Mediator
|
|
16
|
-
from neuroglia.reactive import AsyncRx
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@dataclass
|
|
20
|
-
class ReadModelConciliationOptions:
|
|
21
|
-
"""Represents the options used to configure the application's read model reconciliation features"""
|
|
22
|
-
|
|
23
|
-
consumer_group: str
|
|
24
|
-
""" Gets the name of the group of consumers the application's read model is maintained by """
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class ReadModelReconciliator(HostedService):
|
|
28
|
-
"""Represents the service used to reconciliate the read model by streaming and handling events recorded on the application's event store"""
|
|
29
|
-
|
|
30
|
-
_service_provider: ServiceProviderBase
|
|
31
|
-
""" Gets the current service provider """
|
|
32
|
-
|
|
33
|
-
_mediator: Mediator
|
|
34
|
-
|
|
35
|
-
_event_store_options: EventStoreOptions
|
|
36
|
-
""" Gets the options used to configure the event store """
|
|
37
|
-
|
|
38
|
-
_event_store: EventStore
|
|
39
|
-
""" Gets the service used to persist and stream domain events """
|
|
40
|
-
|
|
41
|
-
_subscription: Disposable
|
|
42
|
-
|
|
43
|
-
def __init__(self, service_provider: ServiceProviderBase, mediator: Mediator, event_store_options: EventStoreOptions, event_store: EventStore):
|
|
44
|
-
self._service_provider = service_provider
|
|
45
|
-
self._mediator = mediator
|
|
46
|
-
self._event_store_options = event_store_options
|
|
47
|
-
self._event_store = event_store
|
|
48
|
-
|
|
49
|
-
async def start_async(self):
|
|
50
|
-
await self.subscribe_async()
|
|
51
|
-
|
|
52
|
-
async def stop_async(self):
|
|
53
|
-
self._subscription.dispose()
|
|
54
|
-
|
|
55
|
-
async def subscribe_async(self):
|
|
56
|
-
observable = await self._event_store.observe_async(f"$ce-{self._event_store_options.database_name}", self._event_store_options.consumer_group)
|
|
57
|
-
|
|
58
|
-
# Get the current event loop to schedule tasks on
|
|
59
|
-
loop = asyncio.get_event_loop()
|
|
60
|
-
|
|
61
|
-
def on_next(e):
|
|
62
|
-
"""Schedule the async handler on the main event loop without closing it."""
|
|
63
|
-
try:
|
|
64
|
-
# Use call_soon_threadsafe to schedule the coroutine on the main loop
|
|
65
|
-
# This prevents creating/closing new event loops which breaks Motor
|
|
66
|
-
loop.call_soon_threadsafe(lambda: asyncio.create_task(self.on_event_record_stream_next_async(e)))
|
|
67
|
-
except RuntimeError as ex:
|
|
68
|
-
logging.warning(f"Event loop closed, skipping event: {type(e.data).__name__ if hasattr(e, 'data') else 'unknown'} - {ex}")
|
|
69
|
-
|
|
70
|
-
self._subscription = AsyncRx.subscribe(observable, on_next)
|
|
71
|
-
|
|
72
|
-
async def on_event_record_stream_next_async(self, e: EventRecord):
|
|
73
|
-
try:
|
|
74
|
-
# todo: migrate event
|
|
75
|
-
await self._mediator.publish_async(e.data)
|
|
76
|
-
|
|
77
|
-
# Acknowledge successful processing
|
|
78
|
-
if isinstance(e, AckableEventRecord):
|
|
79
|
-
await e.ack_async()
|
|
80
|
-
|
|
81
|
-
except Exception as ex:
|
|
82
|
-
logging.error(f"An exception occured while publishing an event of type '{type(e.data).__name__}': {ex}")
|
|
83
|
-
|
|
84
|
-
# Negative acknowledge on processing failure
|
|
85
|
-
if isinstance(e, AckableEventRecord):
|
|
86
|
-
await e.nack_async()
|
|
87
|
-
|
|
88
|
-
async def on_event_record_stream_error(self, ex: Exception):
|
|
89
|
-
await self.subscribe_async()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/application/background_scheduler.py
RENAMED
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/coordination/abstractions.py
RENAMED
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/coordination/leader_election.py
RENAMED
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/coordination/redis_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/__init__.py
RENAMED
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/abstractions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/infrastructure/tracing_mixin.py
RENAMED
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/queries/generic/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/queries/generic/list_query.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/abstractions.py
RENAMED
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/data/resources/state_machine.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/dependency_injection/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/__init__.py
RENAMED
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/cloud_event.py
RENAMED
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/eventing/cloud_events/decorators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/extensions/mediator_extensions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/integration/cache_repository.py
RENAMED
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/integration/http_service_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/metrics_middleware.py
RENAMED
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/pipeline_behavior.py
RENAMED
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/mediation/tracing_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/serialization/abstractions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.7.5 → neuroglia_python-0.7.7}/src/neuroglia/validation/business_rules.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|