neuroglia-python 0.6.12__tar.gz → 0.6.14__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.6.12 → neuroglia_python-0.6.14}/PKG-INFO +1 -1
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/pyproject.toml +1 -1
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/__init__.py +1 -1
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/event_sourcing/event_sourcing_repository.py +31 -9
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/event_sourcing/read_model_reconciliator.py +29 -10
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/mongo/mongo_repository.py +12 -4
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/queryable.py +1 -1
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/LICENSE +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/README.md +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/application/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/application/background_scheduler.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/coordination/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/coordination/abstractions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/coordination/leader_election.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/coordination/redis_backend.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/core/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/core/module_loader.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/core/operation_result.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/core/problem_details.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/core/type_extensions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/core/type_finder.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/core/type_registry.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/abstractions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/exceptions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/abstractions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/event_sourcing/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/event_sourcing/abstractions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/event_sourcing/event_store/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/event_sourcing/event_store/event_store.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/filesystem/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/filesystem/filesystem_repository.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/memory/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/memory/memory_repository.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/mongo/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/mongo/enhanced_mongo_repository.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/mongo/motor_repository.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/mongo/serialization_helper.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/mongo/typed_mongo_query.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/resources/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/resources/in_memory_storage_backend.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/resources/postgres_resource_store.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/resources/redis_resource_store.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/resources/resource_repository.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/tracing_mixin.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/queries/generic/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/queries/generic/get_by_id_query.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/queries/generic/list_query.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/abstractions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/controller.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/serializers/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/serializers/xml_serializer.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/serializers/yaml_serializer.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/state_machine.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/watcher.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/unit_of_work.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/dependency_injection/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/dependency_injection/service_provider.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/dependency_injection/service_provider_backup.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/cloud_events/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/cloud_events/cloud_event.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/cloud_events/decorators.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/cloud_events/infrastructure/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_bus.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_ingestor.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_middleware.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_publisher.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/tracing.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/expressions/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/expressions/javascript_expression_translator.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/extensions/cqrs_metrics_extensions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/extensions/mediator_extensions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/extensions/state_persistence_extensions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/hosting/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/hosting/abstractions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/hosting/configuration/data_access_layer.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/hosting/web.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/integration/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/integration/cache_repository.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/integration/http_service_client.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/integration/models.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/logging/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/logging/logger.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mapping/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mapping/mapper.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/behaviors/domain_event_cloudevent_behavior.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/behaviors/domain_event_dispatching_middleware.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/mediator.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/metrics_middleware.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/pipeline_behavior.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/simple.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/tracing_middleware.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mvc/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mvc/controller_base.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/observability/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/observability/framework.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/observability/logging.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/observability/metrics.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/observability/otel_sdk.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/observability/settings.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/observability/tracing.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/py.typed +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/reactive/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/reactive/rx_async.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/serialization/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/serialization/abstractions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/serialization/aggregate_serializer.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/serialization/json.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/utils/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/utils/camel_model.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/utils/case_conversion.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/validation/__init__.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/validation/business_rules.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/validation/exceptions.py +0 -0
- {neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/validation/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "neuroglia-python"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.14"
|
|
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"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Generic, Optional
|
|
2
|
+
from typing import TYPE_CHECKING, Generic, Optional
|
|
3
3
|
|
|
4
4
|
from neuroglia.data.abstractions import DomainEvent, TAggregate, TKey
|
|
5
5
|
from neuroglia.data.infrastructure.abstractions import Repository
|
|
@@ -11,6 +11,9 @@ from neuroglia.data.infrastructure.event_sourcing.abstractions import (
|
|
|
11
11
|
)
|
|
12
12
|
from neuroglia.hosting.abstractions import ApplicationBuilderBase
|
|
13
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from neuroglia.mediation.mediator import Mediator
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
@dataclass
|
|
16
19
|
class EventSourcingRepositoryOptions(Generic[TAggregate, TKey]):
|
|
@@ -20,8 +23,9 @@ class EventSourcingRepositoryOptions(Generic[TAggregate, TKey]):
|
|
|
20
23
|
class EventSourcingRepository(Generic[TAggregate, TKey], Repository[TAggregate, TKey]):
|
|
21
24
|
"""Represents an event sourcing repository implementation"""
|
|
22
25
|
|
|
23
|
-
def __init__(self, eventstore: EventStore, aggregator: Aggregator):
|
|
26
|
+
def __init__(self, eventstore: EventStore, aggregator: Aggregator, mediator: Optional["Mediator"] = None):
|
|
24
27
|
"""Initialize a new event sourcing repository"""
|
|
28
|
+
super().__init__(mediator) # Pass mediator to base class for event publishing
|
|
25
29
|
self._eventstore = eventstore
|
|
26
30
|
self._aggregator = aggregator
|
|
27
31
|
|
|
@@ -40,33 +44,51 @@ class EventSourcingRepository(Generic[TAggregate, TKey], Repository[TAggregate,
|
|
|
40
44
|
events = await self._eventstore.read_async(stream_id, StreamReadDirection.FORWARDS, 0)
|
|
41
45
|
return self._aggregator.aggregate(events, self.__orig_class__.__args__[0])
|
|
42
46
|
|
|
43
|
-
async def
|
|
47
|
+
async def _do_add_async(self, aggregate: TAggregate) -> TAggregate:
|
|
44
48
|
"""Adds and persists the specified aggregate"""
|
|
45
49
|
stream_id = self._build_stream_id_for(aggregate.id())
|
|
46
50
|
events = aggregate._pending_events
|
|
47
51
|
if len(events) < 1:
|
|
48
|
-
raise Exception()
|
|
52
|
+
raise Exception("No pending events to persist")
|
|
49
53
|
encoded_events = [self._encode_event(e) for e in events]
|
|
50
54
|
await self._eventstore.append_async(stream_id, encoded_events)
|
|
51
55
|
aggregate.state.state_version = events[-1].aggregate_version
|
|
52
56
|
aggregate.clear_pending_events()
|
|
53
57
|
return aggregate
|
|
54
58
|
|
|
55
|
-
async def
|
|
56
|
-
"""
|
|
59
|
+
async def _do_update_async(self, aggregate: TAggregate) -> TAggregate:
|
|
60
|
+
"""Persists the changes made to the specified aggregate"""
|
|
57
61
|
stream_id = self._build_stream_id_for(aggregate.id())
|
|
58
62
|
events = aggregate._pending_events
|
|
59
63
|
if len(events) < 1:
|
|
60
|
-
raise Exception()
|
|
64
|
+
raise Exception("No pending events to persist")
|
|
61
65
|
encoded_events = [self._encode_event(e) for e in events]
|
|
62
66
|
await self._eventstore.append_async(stream_id, encoded_events, aggregate.state.state_version)
|
|
63
67
|
aggregate.state.state_version = events[-1].aggregate_version
|
|
64
68
|
aggregate.clear_pending_events()
|
|
65
69
|
return aggregate
|
|
66
70
|
|
|
67
|
-
async def
|
|
71
|
+
async def _do_remove_async(self, id: TKey) -> None:
|
|
68
72
|
"""Removes the aggregate root with the specified key, if any"""
|
|
69
|
-
raise NotImplementedError()
|
|
73
|
+
raise NotImplementedError("Event sourcing repositories do not support hard deletes")
|
|
74
|
+
|
|
75
|
+
async def _publish_domain_events(self, entity: TAggregate) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Override base class event publishing for event-sourced aggregates.
|
|
78
|
+
|
|
79
|
+
Event sourcing repositories DO NOT publish events directly because:
|
|
80
|
+
1. Events are already persisted to the EventStore
|
|
81
|
+
2. ReadModelReconciliator subscribes to EventStore and publishes ALL events
|
|
82
|
+
3. Publishing here would cause DOUBLE PUBLISHING (once here, once from ReadModelReconciliator)
|
|
83
|
+
|
|
84
|
+
For event-sourced aggregates:
|
|
85
|
+
- Events are persisted to EventStore by _do_add_async/_do_update_async
|
|
86
|
+
- ReadModelReconciliator.on_event_record_stream_next_async() publishes via mediator
|
|
87
|
+
- This ensures single, reliable event publishing from the source of truth (EventStore)
|
|
88
|
+
|
|
89
|
+
State-based repositories still use the base class _publish_domain_events() correctly.
|
|
90
|
+
"""
|
|
91
|
+
# Do nothing - ReadModelReconciliator handles event publishing from EventStore
|
|
70
92
|
|
|
71
93
|
def _build_stream_id_for(self, aggregate_id: TKey):
|
|
72
94
|
"""Builds a new stream id for the specified aggregate"""
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from dataclasses import dataclass
|
|
3
2
|
import logging
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
4
5
|
from rx.core.typing import Disposable
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
from neuroglia.data.infrastructure.event_sourcing.abstractions import (
|
|
8
|
+
EventRecord,
|
|
9
|
+
EventStore,
|
|
10
|
+
EventStoreOptions,
|
|
11
|
+
)
|
|
6
12
|
from neuroglia.dependency_injection.service_provider import ServiceProviderBase
|
|
7
13
|
from neuroglia.hosting.abstractions import HostedService
|
|
8
14
|
from neuroglia.mediation.mediator import Mediator
|
|
@@ -11,25 +17,25 @@ from neuroglia.reactive import AsyncRx
|
|
|
11
17
|
|
|
12
18
|
@dataclass
|
|
13
19
|
class ReadModelConciliationOptions:
|
|
14
|
-
|
|
20
|
+
"""Represents the options used to configure the application's read model reconciliation features"""
|
|
15
21
|
|
|
16
22
|
consumer_group: str
|
|
17
|
-
|
|
23
|
+
""" Gets the name of the group of consumers the application's read model is maintained by """
|
|
18
24
|
|
|
19
25
|
|
|
20
26
|
class ReadModelReconciliator(HostedService):
|
|
21
|
-
|
|
27
|
+
"""Represents the service used to reconciliate the read model by streaming and handling events recorded on the application's event store"""
|
|
22
28
|
|
|
23
29
|
_service_provider: ServiceProviderBase
|
|
24
|
-
|
|
30
|
+
""" Gets the current service provider """
|
|
25
31
|
|
|
26
32
|
_mediator: Mediator
|
|
27
33
|
|
|
28
34
|
_event_store_options: EventStoreOptions
|
|
29
|
-
|
|
35
|
+
""" Gets the options used to configure the event store """
|
|
30
36
|
|
|
31
37
|
_event_store: EventStore
|
|
32
|
-
|
|
38
|
+
""" Gets the service used to persist and stream domain events """
|
|
33
39
|
|
|
34
40
|
_subscription: Disposable
|
|
35
41
|
|
|
@@ -46,8 +52,21 @@ class ReadModelReconciliator(HostedService):
|
|
|
46
52
|
self._subscription.dispose()
|
|
47
53
|
|
|
48
54
|
async def subscribe_async(self):
|
|
49
|
-
observable = await self._event_store.observe_async(f
|
|
50
|
-
|
|
55
|
+
observable = await self._event_store.observe_async(f"$ce-{self._event_store_options.database_name}", self._event_store_options.consumer_group)
|
|
56
|
+
|
|
57
|
+
# Get the current event loop to schedule tasks on
|
|
58
|
+
loop = asyncio.get_event_loop()
|
|
59
|
+
|
|
60
|
+
def on_next(e):
|
|
61
|
+
"""Schedule the async handler on the main event loop without closing it."""
|
|
62
|
+
try:
|
|
63
|
+
# Use call_soon_threadsafe to schedule the coroutine on the main loop
|
|
64
|
+
# This prevents creating/closing new event loops which breaks Motor
|
|
65
|
+
loop.call_soon_threadsafe(lambda: asyncio.create_task(self.on_event_record_stream_next_async(e)))
|
|
66
|
+
except RuntimeError as ex:
|
|
67
|
+
logging.warning(f"Event loop closed, skipping event: {type(e.data).__name__ if hasattr(e, 'data') else 'unknown'} - {ex}")
|
|
68
|
+
|
|
69
|
+
self._subscription = AsyncRx.subscribe(observable, on_next)
|
|
51
70
|
|
|
52
71
|
async def on_event_record_stream_next_async(self, e: EventRecord):
|
|
53
72
|
try:
|
|
@@ -2,7 +2,7 @@ import ast
|
|
|
2
2
|
from ast import NodeVisitor, expr
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from inspect import isclass
|
|
5
|
-
from typing import Any, Generic, Optional
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Generic, List, Optional
|
|
6
6
|
|
|
7
7
|
import pymongo
|
|
8
8
|
from pymongo import MongoClient
|
|
@@ -23,6 +23,9 @@ from neuroglia.expressions.javascript_expression_translator import (
|
|
|
23
23
|
from neuroglia.hosting.abstractions import ApplicationBuilderBase
|
|
24
24
|
from neuroglia.serialization.json import JsonSerializer
|
|
25
25
|
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from neuroglia.mediation.mediator import Mediator
|
|
28
|
+
|
|
26
29
|
|
|
27
30
|
@dataclass
|
|
28
31
|
class MongoRepositoryOptions(Generic[TEntity, TKey]):
|
|
@@ -130,8 +133,10 @@ class MongoRepository(Generic[TEntity, TKey], QueryableRepository[TEntity, TKey]
|
|
|
130
133
|
options: MongoRepositoryOptions[TEntity, TKey],
|
|
131
134
|
mongo_client: MongoClient,
|
|
132
135
|
serializer: JsonSerializer,
|
|
136
|
+
mediator: Optional["Mediator"] = None,
|
|
133
137
|
):
|
|
134
138
|
"""Initializes a new Mongo repository"""
|
|
139
|
+
super().__init__(mediator) # Pass mediator to base Repository class
|
|
135
140
|
self._options = options
|
|
136
141
|
self._mongo_client = mongo_client
|
|
137
142
|
self._mongo_database = self._mongo_client[self._options.database_name]
|
|
@@ -209,7 +214,8 @@ class MongoRepository(Generic[TEntity, TKey], QueryableRepository[TEntity, TKey]
|
|
|
209
214
|
entity = self._serializer.deserialize_from_text(json, self._get_entity_type())
|
|
210
215
|
return entity
|
|
211
216
|
|
|
212
|
-
async def
|
|
217
|
+
async def _do_add_async(self, entity: TEntity) -> TEntity:
|
|
218
|
+
"""Template method implementation for adding an entity to MongoDB"""
|
|
213
219
|
entity_id = self._get_id(entity)
|
|
214
220
|
if entity_id is None:
|
|
215
221
|
raise Exception(f"Cannot add {self._get_entity_type().__name__} without an ID")
|
|
@@ -220,7 +226,8 @@ class MongoRepository(Generic[TEntity, TKey], QueryableRepository[TEntity, TKey]
|
|
|
220
226
|
self._get_mongo_collection().insert_one(attributes_dictionary)
|
|
221
227
|
return entity
|
|
222
228
|
|
|
223
|
-
async def
|
|
229
|
+
async def _do_update_async(self, entity: TEntity) -> TEntity:
|
|
230
|
+
"""Template method implementation for updating an entity in MongoDB"""
|
|
224
231
|
entity_id = self._get_id(entity)
|
|
225
232
|
if entity_id is None:
|
|
226
233
|
raise Exception(f"Cannot update {self._get_entity_type().__name__} without an ID")
|
|
@@ -235,7 +242,8 @@ class MongoRepository(Generic[TEntity, TKey], QueryableRepository[TEntity, TKey]
|
|
|
235
242
|
self._get_mongo_collection().replace_one(query_filter, attributes_dictionary)
|
|
236
243
|
return entity
|
|
237
244
|
|
|
238
|
-
async def
|
|
245
|
+
async def _do_remove_async(self, id: TKey) -> None:
|
|
246
|
+
"""Template method implementation for removing an entity from MongoDB"""
|
|
239
247
|
if not await self.contains_async(id) is not None:
|
|
240
248
|
raise Exception(f"Failed to find a {self._get_entity_type().__name__} with the specified id '{id}'")
|
|
241
249
|
self._get_mongo_collection().delete_one({"id": id})
|
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
from abc import ABC, abstractclassmethod
|
|
5
5
|
from ast import Attribute, Name, NodeTransformer, expr
|
|
6
6
|
from collections.abc import Callable
|
|
7
|
-
from typing import Any, Generic, Optional, TypeVar
|
|
7
|
+
from typing import Any, Generic, List, Optional, TypeVar
|
|
8
8
|
|
|
9
9
|
T = TypeVar("T")
|
|
10
10
|
""" Represents the type of data associated to a queryable """
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/coordination/abstractions.py
RENAMED
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/coordination/leader_election.py
RENAMED
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/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.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/infrastructure/__init__.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
|
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/queries/generic/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/queries/generic/list_query.py
RENAMED
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/__init__.py
RENAMED
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/abstractions.py
RENAMED
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/data/resources/state_machine.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/dependency_injection/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/eventing/cloud_events/__init__.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
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/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.6.12 → neuroglia_python-0.6.14}/src/neuroglia/integration/cache_repository.py
RENAMED
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/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.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/metrics_middleware.py
RENAMED
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/pipeline_behavior.py
RENAMED
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/mediation/tracing_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{neuroglia_python-0.6.12 → neuroglia_python-0.6.14}/src/neuroglia/observability/framework.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.6.12 → neuroglia_python-0.6.14}/src/neuroglia/serialization/abstractions.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.6.12 → neuroglia_python-0.6.14}/src/neuroglia/validation/business_rules.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|