neuroglia-python 0.6.21__tar.gz → 0.6.23__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.
Files changed (121) hide show
  1. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/PKG-INFO +1 -1
  2. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/pyproject.toml +1 -1
  3. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/__init__.py +1 -1
  4. neuroglia_python-0.6.23/src/neuroglia/hosting/configuration/data_access_layer.py +346 -0
  5. neuroglia_python-0.6.21/src/neuroglia/hosting/configuration/data_access_layer.py +0 -169
  6. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/LICENSE +0 -0
  7. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/README.md +0 -0
  8. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/application/__init__.py +0 -0
  9. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/application/background_scheduler.py +0 -0
  10. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/coordination/__init__.py +0 -0
  11. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/coordination/abstractions.py +0 -0
  12. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/coordination/leader_election.py +0 -0
  13. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/coordination/redis_backend.py +0 -0
  14. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/core/__init__.py +0 -0
  15. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/core/module_loader.py +0 -0
  16. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/core/operation_result.py +0 -0
  17. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/core/problem_details.py +0 -0
  18. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/core/type_extensions.py +0 -0
  19. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/core/type_finder.py +0 -0
  20. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/core/type_registry.py +0 -0
  21. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/__init__.py +0 -0
  22. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/abstractions.py +0 -0
  23. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/exceptions.py +0 -0
  24. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/__init__.py +0 -0
  25. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/abstractions.py +0 -0
  26. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/event_sourcing/__init__.py +0 -0
  27. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/event_sourcing/abstractions.py +0 -0
  28. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/event_sourcing/event_sourcing_repository.py +0 -0
  29. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/event_sourcing/event_store/__init__.py +0 -0
  30. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/event_sourcing/event_store/event_store.py +0 -0
  31. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/event_sourcing/patches.py +0 -0
  32. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/event_sourcing/read_model_reconciliator.py +0 -0
  33. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/filesystem/__init__.py +0 -0
  34. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/filesystem/filesystem_repository.py +0 -0
  35. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/memory/__init__.py +0 -0
  36. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/memory/memory_repository.py +0 -0
  37. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/mongo/__init__.py +0 -0
  38. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/mongo/enhanced_mongo_repository.py +0 -0
  39. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/mongo/mongo_repository.py +0 -0
  40. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/mongo/motor_repository.py +0 -0
  41. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/mongo/serialization_helper.py +0 -0
  42. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/mongo/typed_mongo_query.py +0 -0
  43. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/resources/__init__.py +0 -0
  44. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/resources/in_memory_storage_backend.py +0 -0
  45. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/resources/postgres_resource_store.py +0 -0
  46. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/resources/redis_resource_store.py +0 -0
  47. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/resources/resource_repository.py +0 -0
  48. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/infrastructure/tracing_mixin.py +0 -0
  49. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/queries/generic/__init__.py +0 -0
  50. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/queries/generic/get_by_id_query.py +0 -0
  51. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/queries/generic/list_query.py +0 -0
  52. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/queryable.py +0 -0
  53. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/resources/__init__.py +0 -0
  54. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/resources/abstractions.py +0 -0
  55. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/resources/controller.py +0 -0
  56. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/resources/serializers/__init__.py +0 -0
  57. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/resources/serializers/xml_serializer.py +0 -0
  58. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/resources/serializers/yaml_serializer.py +0 -0
  59. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/resources/state_machine.py +0 -0
  60. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/resources/watcher.py +0 -0
  61. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/data/unit_of_work.py +0 -0
  62. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/dependency_injection/__init__.py +0 -0
  63. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/dependency_injection/service_provider.py +0 -0
  64. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/dependency_injection/service_provider_backup.py +0 -0
  65. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/eventing/__init__.py +0 -0
  66. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/eventing/cloud_events/__init__.py +0 -0
  67. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/eventing/cloud_events/cloud_event.py +0 -0
  68. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/eventing/cloud_events/decorators.py +0 -0
  69. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/eventing/cloud_events/infrastructure/__init__.py +0 -0
  70. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_bus.py +0 -0
  71. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_ingestor.py +0 -0
  72. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_middleware.py +0 -0
  73. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_publisher.py +0 -0
  74. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/eventing/tracing.py +0 -0
  75. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/expressions/__init__.py +0 -0
  76. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/expressions/javascript_expression_translator.py +0 -0
  77. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/extensions/cqrs_metrics_extensions.py +0 -0
  78. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/extensions/mediator_extensions.py +0 -0
  79. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/extensions/state_persistence_extensions.py +0 -0
  80. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/hosting/__init__.py +0 -0
  81. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/hosting/abstractions.py +0 -0
  82. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/hosting/web.py +0 -0
  83. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/integration/__init__.py +0 -0
  84. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/integration/cache_repository.py +0 -0
  85. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/integration/http_service_client.py +0 -0
  86. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/integration/models.py +0 -0
  87. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/logging/__init__.py +0 -0
  88. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/logging/logger.py +0 -0
  89. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mapping/__init__.py +0 -0
  90. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mapping/mapper.py +0 -0
  91. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mediation/__init__.py +0 -0
  92. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mediation/behaviors/domain_event_cloudevent_behavior.py +0 -0
  93. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mediation/behaviors/domain_event_dispatching_middleware.py +0 -0
  94. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mediation/mediator.py +0 -0
  95. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mediation/metrics_middleware.py +0 -0
  96. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mediation/pipeline_behavior.py +0 -0
  97. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mediation/simple.py +0 -0
  98. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mediation/tracing_middleware.py +0 -0
  99. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mvc/__init__.py +0 -0
  100. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/mvc/controller_base.py +0 -0
  101. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/observability/__init__.py +0 -0
  102. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/observability/framework.py +0 -0
  103. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/observability/logging.py +0 -0
  104. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/observability/metrics.py +0 -0
  105. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/observability/otel_sdk.py +0 -0
  106. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/observability/settings.py +0 -0
  107. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/observability/tracing.py +0 -0
  108. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/py.typed +0 -0
  109. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/reactive/__init__.py +0 -0
  110. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/reactive/rx_async.py +0 -0
  111. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/serialization/__init__.py +0 -0
  112. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/serialization/abstractions.py +0 -0
  113. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/serialization/aggregate_serializer.py +0 -0
  114. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/serialization/json.py +0 -0
  115. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/utils/__init__.py +0 -0
  116. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/utils/camel_model.py +0 -0
  117. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/utils/case_conversion.py +0 -0
  118. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/validation/__init__.py +0 -0
  119. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/validation/business_rules.py +0 -0
  120. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/validation/exceptions.py +0 -0
  121. {neuroglia_python-0.6.21 → neuroglia_python-0.6.23}/src/neuroglia/validation/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neuroglia-python
3
- Version: 0.6.21
3
+ Version: 0.6.23
4
4
  Summary: Port from .NET to Python of the Neuroglia Framework
5
5
  License: Apache
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "neuroglia-python"
3
- version = "0.6.21"
3
+ version = "0.6.23"
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"
@@ -62,7 +62,7 @@ __all__ = [
62
62
  ]
63
63
 
64
64
  # Framework metadata
65
- __version__ = "0.6.18"
65
+ __version__ = "0.6.23"
66
66
  __author__ = "Neuroglia Team"
67
67
  __email__ = "team@neuroglia.io"
68
68
  __license__ = "Apache"
@@ -0,0 +1,346 @@
1
+ import inspect
2
+ from collections.abc import Callable
3
+ from typing import TYPE_CHECKING, Optional
4
+
5
+ from neuroglia.core import ModuleLoader, TypeFinder
6
+ from neuroglia.data.abstractions import AggregateRoot
7
+ from neuroglia.data.infrastructure.event_sourcing.read_model_reconciliator import (
8
+ ReadModelConciliationOptions,
9
+ ReadModelReconciliator,
10
+ )
11
+ from neuroglia.data.queries.generic import GetByIdQueryHandler, ListQueryHandler
12
+ from neuroglia.hosting.abstractions import ApplicationBuilderBase, HostedService
13
+ from neuroglia.mediation.mediator import RequestHandler
14
+
15
+ if TYPE_CHECKING:
16
+ from neuroglia.data.infrastructure.event_sourcing.event_sourcing_repository import (
17
+ EventSourcingRepositoryOptions,
18
+ )
19
+
20
+
21
+ class DataAccessLayer:
22
+ class WriteModel:
23
+ """Represents a helper class used to configure an application's Write Model DAL
24
+
25
+ Supports two configuration patterns:
26
+ 1. Simplified: Pass options directly to constructor
27
+ 2. Custom: Pass custom repository_setup function to configure()
28
+
29
+ Examples:
30
+ # Simple configuration with default options
31
+ DataAccessLayer.WriteModel().configure(builder, ["domain.entities"])
32
+
33
+ # With custom delete mode
34
+ from neuroglia.data.infrastructure.event_sourcing.abstractions import DeleteMode
35
+ from neuroglia.data.infrastructure.event_sourcing.event_sourcing_repository import (
36
+ EventSourcingRepositoryOptions
37
+ )
38
+
39
+ DataAccessLayer.WriteModel(
40
+ options=EventSourcingRepositoryOptions(delete_mode=DeleteMode.HARD)
41
+ ).configure(builder, ["domain.entities"])
42
+
43
+ # Custom factory (advanced, backwards compatible)
44
+ def custom_setup(builder_, entity_type, key_type):
45
+ # Custom configuration logic
46
+ pass
47
+ DataAccessLayer.WriteModel().configure(
48
+ builder, ["domain.entities"], custom_setup
49
+ )
50
+ """
51
+
52
+ def __init__(self, options: Optional["EventSourcingRepositoryOptions"] = None):
53
+ """Initialize WriteModel configuration
54
+
55
+ Args:
56
+ options: Optional repository options (e.g., delete_mode).
57
+ If not provided, default options will be used.
58
+ """
59
+ self._options = options
60
+
61
+ def configure(self, builder: ApplicationBuilderBase, modules: list[str], repository_setup: Optional[Callable[[ApplicationBuilderBase, type, type], None]] = None) -> ApplicationBuilderBase:
62
+ """Configures the application's Write Model DAL, scanning for aggregate root types within the specified modules
63
+
64
+ Args:
65
+ builder (ApplicationBuilderBase): the application builder to configure
66
+ modules (List[str]): a list containing the names of the modules to scan for aggregate root types
67
+ repository_setup (Optional[Callable[[ApplicationBuilderBase, Type, Type], None]]):
68
+ Optional custom function to setup repositories. If provided, takes precedence over options.
69
+ If not provided, uses simplified configuration with options (if any).
70
+
71
+ Returns:
72
+ ApplicationBuilderBase: The configured builder
73
+
74
+ Raises:
75
+ ImportError: If EventSourcingRepository cannot be imported
76
+ """
77
+ # If custom setup provided, use it (backwards compatible)
78
+ if repository_setup is not None:
79
+ for module in [ModuleLoader.load(module_name) for module_name in modules]:
80
+ for aggregate_type in TypeFinder.get_types(module, lambda cls: inspect.isclass(cls) and issubclass(cls, AggregateRoot) and not cls == AggregateRoot):
81
+ key_type = str # todo: reflect from DTO base type
82
+ repository_setup(builder, aggregate_type, key_type)
83
+ return builder
84
+
85
+ # Otherwise use simplified configuration with options
86
+ return self._configure_with_options(builder, modules)
87
+
88
+ def _configure_with_options(self, builder: ApplicationBuilderBase, modules: list[str]) -> ApplicationBuilderBase:
89
+ """Configure repositories using simplified options pattern
90
+
91
+ Args:
92
+ builder: The application builder
93
+ modules: List of module names to scan for aggregates
94
+
95
+ Returns:
96
+ The configured builder
97
+ """
98
+ from neuroglia.data.infrastructure.abstractions import Repository
99
+ from neuroglia.data.infrastructure.event_sourcing.abstractions import (
100
+ Aggregator,
101
+ EventStore,
102
+ )
103
+ from neuroglia.data.infrastructure.event_sourcing.event_sourcing_repository import (
104
+ EventSourcingRepository,
105
+ EventSourcingRepositoryOptions,
106
+ )
107
+ from neuroglia.dependency_injection import ServiceProvider
108
+ from neuroglia.mediation import Mediator
109
+
110
+ # Discover and configure each aggregate type
111
+ for module in [ModuleLoader.load(module_name) for module_name in modules]:
112
+ for aggregate_type in TypeFinder.get_types(
113
+ module,
114
+ lambda cls: inspect.isclass(cls) and issubclass(cls, AggregateRoot) and not cls == AggregateRoot,
115
+ ):
116
+ key_type = str # todo: reflect from DTO base type
117
+
118
+ # Create type-specific options if global options provided
119
+ typed_options = None
120
+ if self._options:
121
+ typed_options = EventSourcingRepositoryOptions[aggregate_type, key_type]( # type: ignore
122
+ delete_mode=self._options.delete_mode,
123
+ soft_delete_method_name=self._options.soft_delete_method_name,
124
+ )
125
+
126
+ # Create factory function with proper closure
127
+ def make_factory(et, kt, opts):
128
+ def repository_factory(sp: ServiceProvider):
129
+ return EventSourcingRepository[et, kt]( # type: ignore
130
+ eventstore=sp.get_required_service(EventStore),
131
+ aggregator=sp.get_required_service(Aggregator),
132
+ mediator=sp.get_service(Mediator),
133
+ options=opts,
134
+ )
135
+
136
+ return repository_factory
137
+
138
+ # Register repository with factory
139
+ builder.services.add_singleton(
140
+ Repository[aggregate_type, key_type], # type: ignore
141
+ implementation_factory=make_factory(aggregate_type, key_type, typed_options),
142
+ )
143
+
144
+ return builder
145
+
146
+ class ReadModel:
147
+ """Represents a helper class used to configure an application's Read Model DAL
148
+
149
+ Supports three configuration patterns:
150
+ 1. Simplified Sync: Pass database_name with repository_type='mongo' (default)
151
+ 2. Simplified Async: Pass database_name with repository_type='motor' for FastAPI
152
+ 3. Custom: Pass custom repository_setup function to configure()
153
+
154
+ Examples:
155
+ # Simple synchronous configuration (default)
156
+ DataAccessLayer.ReadModel(database_name="myapp").configure(
157
+ builder, ["integration.models"]
158
+ )
159
+
160
+ # Async configuration with Motor for FastAPI
161
+ DataAccessLayer.ReadModel(
162
+ database_name="myapp",
163
+ repository_type='motor'
164
+ ).configure(builder, ["integration.models"])
165
+
166
+ # Custom factory (advanced, backwards compatible)
167
+ def custom_setup(builder_, entity_type, key_type):
168
+ # Custom configuration logic
169
+ pass
170
+ DataAccessLayer.ReadModel().configure(
171
+ builder, ["integration.models"], custom_setup
172
+ )
173
+ """
174
+
175
+ def __init__(self, database_name: Optional[str] = None, repository_type: str = "mongo"):
176
+ """Initialize ReadModel configuration
177
+
178
+ Args:
179
+ database_name: Optional database name for MongoDB repositories.
180
+ If not provided, custom repository_setup must be used.
181
+ repository_type: Type of repository to use ('mongo' or 'motor'). Defaults to 'mongo'.
182
+ - 'mongo': Use MongoRepository with PyMongo (synchronous driver)
183
+ - 'motor': Use MotorRepository with Motor (async driver for FastAPI)
184
+
185
+ Example:
186
+ ```python
187
+ # Simplified sync configuration
188
+ DataAccessLayer.ReadModel(database_name="myapp").configure(...)
189
+
190
+ # Async configuration with Motor
191
+ DataAccessLayer.ReadModel(
192
+ database_name="myapp",
193
+ repository_type='motor'
194
+ ).configure(...)
195
+ ```
196
+ """
197
+ self._database_name = database_name
198
+ self._repository_type = repository_type
199
+
200
+ # Validate repository_type
201
+ if repository_type not in ("mongo", "motor"):
202
+ raise ValueError(f"Invalid repository_type '{repository_type}'. " "Must be either 'mongo' (synchronous PyMongo) or 'motor' (async Motor)")
203
+
204
+ def configure(
205
+ self,
206
+ builder: ApplicationBuilderBase,
207
+ modules: list[str],
208
+ repository_setup: Optional[Callable[[ApplicationBuilderBase, type, type], None]] = None,
209
+ ) -> ApplicationBuilderBase:
210
+ """Configures the application's Read Model DAL, scanning for types marked with the 'queryable' decorator within the specified modules
211
+
212
+ Args:
213
+ builder (ApplicationBuilderBase): the application builder to configure
214
+ modules (List[str]): a list containing the names of the modules to scan for types decorated with 'queryable'
215
+ repository_setup (Optional[Callable[[ApplicationBuilderBase, Type, Type], None]]):
216
+ Optional custom function to setup repositories. If provided, takes precedence over database_name.
217
+ If not provided, uses simplified configuration with database_name.
218
+
219
+ Returns:
220
+ ApplicationBuilderBase: The configured builder
221
+
222
+ Raises:
223
+ ValueError: If consumer_group not specified in settings
224
+ ValueError: If neither repository_setup nor database_name is provided
225
+ """
226
+ consumer_group = builder.settings.consumer_group
227
+ if not consumer_group:
228
+ raise ValueError("Cannot configure Read Model DAL: consumer group not specified in application settings")
229
+ builder.services.add_singleton(ReadModelConciliationOptions, singleton=ReadModelConciliationOptions(consumer_group))
230
+ builder.services.add_singleton(HostedService, ReadModelReconciliator)
231
+
232
+ # If custom setup provided, use it (backwards compatible)
233
+ if repository_setup is not None:
234
+ for module in [ModuleLoader.load(module_name) for module_name in modules]:
235
+ for queryable_type in TypeFinder.get_types(module, lambda cls: inspect.isclass(cls) and hasattr(cls, "__queryable__")):
236
+ key_type = str # todo: reflect from DTO base type
237
+ repository_setup(builder, queryable_type, key_type)
238
+ builder.services.add_transient(RequestHandler, GetByIdQueryHandler[queryable_type, key_type]) # type: ignore
239
+ builder.services.add_transient(RequestHandler, ListQueryHandler[queryable_type, key_type]) # type: ignore
240
+ return builder
241
+
242
+ # Otherwise use simplified configuration with database_name
243
+ return self._configure_with_database_name(builder, modules)
244
+
245
+ def _configure_with_database_name(
246
+ self,
247
+ builder: ApplicationBuilderBase,
248
+ modules: list[str],
249
+ ) -> ApplicationBuilderBase:
250
+ """Configure repositories using simplified database_name pattern
251
+
252
+ Args:
253
+ builder: The application builder
254
+ modules: List of module names to scan for queryable types
255
+
256
+ Returns:
257
+ The configured builder
258
+
259
+ Raises:
260
+ ValueError: If database_name was not provided
261
+ """
262
+ if not self._database_name:
263
+ raise ValueError("Cannot configure Read Model with simplified API: " "database_name not provided. Either pass database_name to ReadModel() " "or use custom repository_setup function.")
264
+
265
+ from neuroglia.data.infrastructure.abstractions import (
266
+ QueryableRepository,
267
+ Repository,
268
+ )
269
+ from neuroglia.dependency_injection import ServiceProvider
270
+
271
+ # Configure based on repository type
272
+ if self._repository_type == "mongo":
273
+ # Synchronous MongoRepository configuration
274
+ from pymongo import MongoClient
275
+
276
+ from neuroglia.data.infrastructure.mongo.mongo_repository import (
277
+ MongoRepository,
278
+ MongoRepositoryOptions,
279
+ )
280
+
281
+ # Get MongoDB connection string
282
+ connection_string_name = "mongo"
283
+ connection_string = builder.settings.connection_strings.get(connection_string_name, None)
284
+ if connection_string is None:
285
+ raise ValueError(f"Missing '{connection_string_name}' connection string in application settings")
286
+
287
+ # Register MongoClient singleton (shared across all repositories)
288
+ builder.services.try_add_singleton(MongoClient, singleton=MongoClient(connection_string))
289
+
290
+ # Discover and configure each queryable type
291
+ for module in [ModuleLoader.load(module_name) for module_name in modules]:
292
+ for queryable_type in TypeFinder.get_types(module, lambda cls: inspect.isclass(cls) and hasattr(cls, "__queryable__")):
293
+ key_type = str # todo: reflect from DTO base type
294
+
295
+ # Register options for this entity type
296
+ builder.services.try_add_singleton(
297
+ MongoRepositoryOptions[queryable_type, key_type], # type: ignore
298
+ singleton=MongoRepositoryOptions[queryable_type, key_type](self._database_name), # type: ignore
299
+ )
300
+
301
+ # Register repository
302
+ builder.services.try_add_singleton(
303
+ Repository[queryable_type, key_type], # type: ignore
304
+ MongoRepository[queryable_type, key_type], # type: ignore
305
+ )
306
+
307
+ # Register queryable repository alias
308
+ def make_queryable_factory(qt, kt):
309
+ def queryable_factory(provider: ServiceProvider):
310
+ return provider.get_required_service(Repository[qt, kt]) # type: ignore
311
+
312
+ return queryable_factory
313
+
314
+ builder.services.try_add_singleton(
315
+ QueryableRepository[queryable_type, key_type], # type: ignore
316
+ implementation_factory=make_queryable_factory(queryable_type, key_type),
317
+ )
318
+
319
+ # Register query handlers
320
+ builder.services.add_transient(RequestHandler, GetByIdQueryHandler[queryable_type, key_type]) # type: ignore
321
+ builder.services.add_transient(RequestHandler, ListQueryHandler[queryable_type, key_type]) # type: ignore
322
+
323
+ elif self._repository_type == "motor":
324
+ # Async MotorRepository configuration using MotorRepository.configure()
325
+ from neuroglia.data.infrastructure.mongo.motor_repository import (
326
+ MotorRepository,
327
+ )
328
+
329
+ # Use MotorRepository.configure() for each discovered type
330
+ # Note: MotorRepository types are NOT queryable yet, so we scan for all classes
331
+ # (not just those with __queryable__ attribute)
332
+ # This configuration supports direct Repository[T, K] injection in handlers
333
+ for module in [ModuleLoader.load(module_name) for module_name in modules]:
334
+ for entity_type in TypeFinder.get_types(module, lambda cls: inspect.isclass(cls)):
335
+ key_type = str # todo: reflect from entity type
336
+
337
+ # Use MotorRepository's static configure method
338
+ # This handles AsyncIOMotorClient, repository registration, etc.
339
+ MotorRepository.configure(
340
+ builder=builder,
341
+ entity_type=entity_type,
342
+ key_type=key_type,
343
+ database_name=self._database_name,
344
+ )
345
+
346
+ return builder
@@ -1,169 +0,0 @@
1
- import inspect
2
- from collections.abc import Callable
3
- from typing import TYPE_CHECKING, Optional
4
-
5
- from neuroglia.core import ModuleLoader, TypeFinder
6
- from neuroglia.data.abstractions import AggregateRoot
7
- from neuroglia.data.infrastructure.event_sourcing.read_model_reconciliator import (
8
- ReadModelConciliationOptions,
9
- ReadModelReconciliator,
10
- )
11
- from neuroglia.data.queries.generic import GetByIdQueryHandler, ListQueryHandler
12
- from neuroglia.hosting.abstractions import ApplicationBuilderBase, HostedService
13
- from neuroglia.mediation.mediator import RequestHandler
14
-
15
- if TYPE_CHECKING:
16
- from neuroglia.data.infrastructure.event_sourcing.event_sourcing_repository import (
17
- EventSourcingRepositoryOptions,
18
- )
19
-
20
-
21
- class DataAccessLayer:
22
- class WriteModel:
23
- """Represents a helper class used to configure an application's Write Model DAL
24
-
25
- Supports two configuration patterns:
26
- 1. Simplified: Pass options directly to constructor
27
- 2. Custom: Pass custom repository_setup function to configure()
28
-
29
- Examples:
30
- # Simple configuration with default options
31
- DataAccessLayer.WriteModel().configure(builder, ["domain.entities"])
32
-
33
- # With custom delete mode
34
- from neuroglia.data.infrastructure.event_sourcing.abstractions import DeleteMode
35
- from neuroglia.data.infrastructure.event_sourcing.event_sourcing_repository import (
36
- EventSourcingRepositoryOptions
37
- )
38
-
39
- DataAccessLayer.WriteModel(
40
- options=EventSourcingRepositoryOptions(delete_mode=DeleteMode.HARD)
41
- ).configure(builder, ["domain.entities"])
42
-
43
- # Custom factory (advanced, backwards compatible)
44
- def custom_setup(builder_, entity_type, key_type):
45
- # Custom configuration logic
46
- pass
47
- DataAccessLayer.WriteModel().configure(
48
- builder, ["domain.entities"], custom_setup
49
- )
50
- """
51
-
52
- def __init__(self, options: Optional["EventSourcingRepositoryOptions"] = None):
53
- """Initialize WriteModel configuration
54
-
55
- Args:
56
- options: Optional repository options (e.g., delete_mode).
57
- If not provided, default options will be used.
58
- """
59
- self._options = options
60
-
61
- def configure(self, builder: ApplicationBuilderBase, modules: list[str], repository_setup: Optional[Callable[[ApplicationBuilderBase, type, type], None]] = None) -> ApplicationBuilderBase:
62
- """Configures the application's Write Model DAL, scanning for aggregate root types within the specified modules
63
-
64
- Args:
65
- builder (ApplicationBuilderBase): the application builder to configure
66
- modules (List[str]): a list containing the names of the modules to scan for aggregate root types
67
- repository_setup (Optional[Callable[[ApplicationBuilderBase, Type, Type], None]]):
68
- Optional custom function to setup repositories. If provided, takes precedence over options.
69
- If not provided, uses simplified configuration with options (if any).
70
-
71
- Returns:
72
- ApplicationBuilderBase: The configured builder
73
-
74
- Raises:
75
- ImportError: If EventSourcingRepository cannot be imported
76
- """
77
- # If custom setup provided, use it (backwards compatible)
78
- if repository_setup is not None:
79
- for module in [ModuleLoader.load(module_name) for module_name in modules]:
80
- for aggregate_type in TypeFinder.get_types(module, lambda cls: inspect.isclass(cls) and issubclass(cls, AggregateRoot) and not cls == AggregateRoot):
81
- key_type = str # todo: reflect from DTO base type
82
- repository_setup(builder, aggregate_type, key_type)
83
- return builder
84
-
85
- # Otherwise use simplified configuration with options
86
- return self._configure_with_options(builder, modules)
87
-
88
- def _configure_with_options(self, builder: ApplicationBuilderBase, modules: list[str]) -> ApplicationBuilderBase:
89
- """Configure repositories using simplified options pattern
90
-
91
- Args:
92
- builder: The application builder
93
- modules: List of module names to scan for aggregates
94
-
95
- Returns:
96
- The configured builder
97
- """
98
- from neuroglia.data.infrastructure.abstractions import Repository
99
- from neuroglia.data.infrastructure.event_sourcing.abstractions import (
100
- Aggregator,
101
- EventStore,
102
- )
103
- from neuroglia.data.infrastructure.event_sourcing.event_sourcing_repository import (
104
- EventSourcingRepository,
105
- EventSourcingRepositoryOptions,
106
- )
107
- from neuroglia.dependency_injection import ServiceProvider
108
- from neuroglia.mediation import Mediator
109
-
110
- # Discover and configure each aggregate type
111
- for module in [ModuleLoader.load(module_name) for module_name in modules]:
112
- for aggregate_type in TypeFinder.get_types(
113
- module,
114
- lambda cls: inspect.isclass(cls) and issubclass(cls, AggregateRoot) and not cls == AggregateRoot,
115
- ):
116
- key_type = str # todo: reflect from DTO base type
117
-
118
- # Create type-specific options if global options provided
119
- typed_options = None
120
- if self._options:
121
- typed_options = EventSourcingRepositoryOptions[aggregate_type, key_type]( # type: ignore
122
- delete_mode=self._options.delete_mode,
123
- soft_delete_method_name=self._options.soft_delete_method_name,
124
- )
125
-
126
- # Create factory function with proper closure
127
- def make_factory(et, kt, opts):
128
- def repository_factory(sp: ServiceProvider):
129
- return EventSourcingRepository[et, kt]( # type: ignore
130
- eventstore=sp.get_required_service(EventStore),
131
- aggregator=sp.get_required_service(Aggregator),
132
- mediator=sp.get_service(Mediator),
133
- options=opts,
134
- )
135
-
136
- return repository_factory
137
-
138
- # Register repository with factory
139
- builder.services.add_singleton(
140
- Repository[aggregate_type, key_type], # type: ignore
141
- implementation_factory=make_factory(aggregate_type, key_type, typed_options),
142
- )
143
-
144
- return builder
145
-
146
- class ReadModel:
147
- """Represents an helper class used to configure an application's Read Model DAL"""
148
-
149
- @staticmethod
150
- def configure(builder: ApplicationBuilderBase, modules: list[str], repository_setup: Callable[[ApplicationBuilderBase, type, type], None]) -> ApplicationBuilderBase:
151
- """Configures the application's Read Model DAL, scanning for types marked with the 'queryable' decorator within the specified modules
152
-
153
- Args:
154
- builder (ApplicationBuilderBase): the application builder to configure
155
- modules (List[str]): a list containing the names of the modules to scan for types decorated with 'queryable'
156
- repository_setup (Callable[[ApplicationBuilderBase, Type, Type], None]): a function used to setup the repository for the specified entity and key types
157
- """
158
- consumer_group = builder.settings.consumer_group
159
- if not consumer_group:
160
- raise ValueError("Cannot configure Read Model DAL: consumer group not specified in application settings")
161
- builder.services.add_singleton(ReadModelConciliationOptions, singleton=ReadModelConciliationOptions(consumer_group))
162
- builder.services.add_singleton(HostedService, ReadModelReconciliator)
163
- for module in [ModuleLoader.load(module_name) for module_name in modules]:
164
- for queryable_type in TypeFinder.get_types(module, lambda cls: inspect.isclass(cls) and hasattr(cls, "__queryable__")):
165
- key_type = str # todo: reflect from DTO base type
166
- repository_setup(builder, queryable_type, key_type)
167
- builder.services.add_transient(RequestHandler, GetByIdQueryHandler[queryable_type, key_type]) # type: ignore
168
- builder.services.add_transient(RequestHandler, ListQueryHandler[queryable_type, key_type]) # type: ignore
169
- return builder