neuroglia-python 0.6.17__tar.gz → 0.7.8__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 (124) hide show
  1. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/PKG-INFO +39 -38
  2. neuroglia_python-0.7.8/pyproject.toml +97 -0
  3. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/__init__.py +1 -1
  4. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/event_sourcing/__init__.py +12 -6
  5. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/event_sourcing/abstractions.py +15 -0
  6. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/event_sourcing/event_sourcing_repository.py +17 -5
  7. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/event_sourcing/event_store/event_store.py +24 -12
  8. neuroglia_python-0.7.8/src/neuroglia/data/infrastructure/event_sourcing/read_model_reconciliator.py +328 -0
  9. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/mongo/__init__.py +5 -0
  10. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/mongo/mongo_repository.py +3 -3
  11. neuroglia_python-0.7.8/src/neuroglia/data/infrastructure/mongo/motor_query.py +399 -0
  12. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/mongo/motor_repository.py +166 -20
  13. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/queryable.py +68 -13
  14. neuroglia_python-0.7.8/src/neuroglia/expressions/javascript_expression_translator.py +207 -0
  15. neuroglia_python-0.7.8/src/neuroglia/hosting/configuration/data_access_layer.py +642 -0
  16. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/serialization/json.py +114 -8
  17. neuroglia_python-0.6.17/pyproject.toml +0 -97
  18. neuroglia_python-0.6.17/src/neuroglia/data/infrastructure/event_sourcing/read_model_reconciliator.py +0 -89
  19. neuroglia_python-0.6.17/src/neuroglia/expressions/javascript_expression_translator.py +0 -147
  20. neuroglia_python-0.6.17/src/neuroglia/hosting/configuration/data_access_layer.py +0 -57
  21. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/LICENSE +0 -0
  22. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/README.md +0 -0
  23. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/application/__init__.py +0 -0
  24. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/application/background_scheduler.py +0 -0
  25. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/coordination/__init__.py +0 -0
  26. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/coordination/abstractions.py +0 -0
  27. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/coordination/leader_election.py +0 -0
  28. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/coordination/redis_backend.py +0 -0
  29. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/core/__init__.py +0 -0
  30. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/core/module_loader.py +0 -0
  31. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/core/operation_result.py +0 -0
  32. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/core/problem_details.py +0 -0
  33. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/core/type_extensions.py +0 -0
  34. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/core/type_finder.py +0 -0
  35. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/core/type_registry.py +0 -0
  36. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/__init__.py +0 -0
  37. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/abstractions.py +0 -0
  38. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/exceptions.py +0 -0
  39. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/__init__.py +0 -0
  40. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/abstractions.py +0 -0
  41. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/event_sourcing/event_store/__init__.py +0 -0
  42. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/filesystem/__init__.py +0 -0
  43. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/filesystem/filesystem_repository.py +0 -0
  44. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/memory/__init__.py +0 -0
  45. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/memory/memory_repository.py +0 -0
  46. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/mongo/enhanced_mongo_repository.py +0 -0
  47. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/mongo/serialization_helper.py +0 -0
  48. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/mongo/typed_mongo_query.py +0 -0
  49. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/resources/__init__.py +0 -0
  50. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/resources/in_memory_storage_backend.py +0 -0
  51. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/resources/postgres_resource_store.py +0 -0
  52. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/resources/redis_resource_store.py +0 -0
  53. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/resources/resource_repository.py +0 -0
  54. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/infrastructure/tracing_mixin.py +0 -0
  55. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/queries/generic/__init__.py +0 -0
  56. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/queries/generic/get_by_id_query.py +0 -0
  57. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/queries/generic/list_query.py +0 -0
  58. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/resources/__init__.py +0 -0
  59. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/resources/abstractions.py +0 -0
  60. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/resources/controller.py +0 -0
  61. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/resources/serializers/__init__.py +0 -0
  62. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/resources/serializers/xml_serializer.py +0 -0
  63. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/resources/serializers/yaml_serializer.py +0 -0
  64. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/resources/state_machine.py +0 -0
  65. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/resources/watcher.py +0 -0
  66. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/data/unit_of_work.py +0 -0
  67. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/dependency_injection/__init__.py +0 -0
  68. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/dependency_injection/service_provider.py +0 -0
  69. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/dependency_injection/service_provider_backup.py +0 -0
  70. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/eventing/__init__.py +0 -0
  71. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/eventing/cloud_events/__init__.py +0 -0
  72. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/eventing/cloud_events/cloud_event.py +0 -0
  73. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/eventing/cloud_events/decorators.py +0 -0
  74. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/eventing/cloud_events/infrastructure/__init__.py +0 -0
  75. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_bus.py +0 -0
  76. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_ingestor.py +0 -0
  77. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_middleware.py +0 -0
  78. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_publisher.py +0 -0
  79. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/eventing/tracing.py +0 -0
  80. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/expressions/__init__.py +0 -0
  81. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/extensions/cqrs_metrics_extensions.py +0 -0
  82. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/extensions/mediator_extensions.py +0 -0
  83. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/extensions/state_persistence_extensions.py +0 -0
  84. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/hosting/__init__.py +0 -0
  85. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/hosting/abstractions.py +0 -0
  86. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/hosting/web.py +0 -0
  87. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/integration/__init__.py +0 -0
  88. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/integration/cache_repository.py +0 -0
  89. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/integration/http_service_client.py +0 -0
  90. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/integration/models.py +0 -0
  91. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/logging/__init__.py +0 -0
  92. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/logging/logger.py +0 -0
  93. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mapping/__init__.py +0 -0
  94. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mapping/mapper.py +0 -0
  95. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mediation/__init__.py +0 -0
  96. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mediation/behaviors/domain_event_cloudevent_behavior.py +0 -0
  97. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mediation/behaviors/domain_event_dispatching_middleware.py +0 -0
  98. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mediation/mediator.py +0 -0
  99. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mediation/metrics_middleware.py +0 -0
  100. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mediation/pipeline_behavior.py +0 -0
  101. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mediation/simple.py +0 -0
  102. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mediation/tracing_middleware.py +0 -0
  103. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mvc/__init__.py +0 -0
  104. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/mvc/controller_base.py +0 -0
  105. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/observability/__init__.py +0 -0
  106. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/observability/framework.py +0 -0
  107. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/observability/logging.py +0 -0
  108. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/observability/metrics.py +0 -0
  109. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/observability/otel_sdk.py +0 -0
  110. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/observability/settings.py +0 -0
  111. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/observability/tracing.py +0 -0
  112. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/py.typed +0 -0
  113. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/reactive/__init__.py +0 -0
  114. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/reactive/rx_async.py +0 -0
  115. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/serialization/__init__.py +0 -0
  116. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/serialization/abstractions.py +0 -0
  117. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/serialization/aggregate_serializer.py +0 -0
  118. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/utils/__init__.py +0 -0
  119. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/utils/camel_model.py +0 -0
  120. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/utils/case_conversion.py +0 -0
  121. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/validation/__init__.py +0 -0
  122. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/validation/business_rules.py +0 -0
  123. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/src/neuroglia/validation/exceptions.py +0 -0
  124. {neuroglia_python-0.6.17 → neuroglia_python-0.7.8}/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.17
3
+ Version: 0.7.8
4
4
  Summary: Port from .NET to Python of the Neuroglia Framework
5
5
  License: Apache
6
6
  License-File: LICENSE
@@ -21,43 +21,44 @@ Provides-Extra: etcd
21
21
  Provides-Extra: eventstore
22
22
  Provides-Extra: mongodb
23
23
  Provides-Extra: redis
24
- Requires-Dist: annotated-types (>=0.7.0,<0.8.0)
25
- Requires-Dist: apscheduler (>=3.11.0,<4.0.0)
26
- Requires-Dist: boto3 (>=1.40.64,<2.0.0) ; extra == "aws" or extra == "all"
27
- Requires-Dist: classy-fastapi (>=0.6.1,<0.7.0)
28
- Requires-Dist: email-validator (>=2.2.0,<3.0.0)
29
- Requires-Dist: esdbclient (>=1.1.1,<2.0.0) ; extra == "eventstore" or extra == "all"
30
- Requires-Dist: etcd3-py (>=0.1.6,<0.2.0) ; extra == "etcd" or extra == "all"
31
- Requires-Dist: fastapi (>=0.115.5,<0.116.0)
32
- Requires-Dist: grpcio (>=1.68.1,<2.0.0)
33
- Requires-Dist: httpx (>=0.27.2,<0.28.0)
34
- Requires-Dist: itsdangerous (>=2.2.0,<3.0.0)
35
- Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
36
- Requires-Dist: motor (>=3.6.0,<4.0.0) ; extra == "mongodb" or extra == "all"
37
- Requires-Dist: multipledispatch (>=1.0.0,<2.0.0)
38
- Requires-Dist: opentelemetry-api (>=1.28.2,<2.0.0)
39
- Requires-Dist: opentelemetry-exporter-otlp-proto-grpc (>=1.28.2,<2.0.0)
40
- Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.28.2,<2.0.0)
41
- Requires-Dist: opentelemetry-exporter-prometheus (>=0.49b2,<0.50)
42
- Requires-Dist: opentelemetry-instrumentation (>=0.49b2,<0.50)
43
- Requires-Dist: opentelemetry-instrumentation-fastapi (>=0.49b2,<0.50)
44
- Requires-Dist: opentelemetry-instrumentation-httpx (>=0.49b2,<0.50)
45
- Requires-Dist: opentelemetry-instrumentation-logging (>=0.49b2,<0.50)
46
- Requires-Dist: opentelemetry-instrumentation-system-metrics (>=0.49b2,<0.50)
47
- Requires-Dist: opentelemetry-sdk (>=1.28.2,<2.0.0)
48
- Requires-Dist: passlib[bcrypt] (>=1.7.4,<2.0.0)
49
- Requires-Dist: prometheus-client (>=0.21.0,<0.22.0)
50
- Requires-Dist: protobuf (>=5.0,<6.0) ; extra == "etcd" or extra == "all"
51
- Requires-Dist: pydantic-settings (>=2.6.1,<3.0.0)
52
- Requires-Dist: pydantic[email] (>=2.10.3,<3.0.0)
53
- Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
54
- Requires-Dist: pymongo (>=4.10.1,<5.0.0) ; extra == "mongodb" or extra == "all"
55
- Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
56
- Requires-Dist: python-multipart (>=0.0.17,<0.0.18)
57
- Requires-Dist: redis (>=5.2.0,<6.0.0) ; extra == "redis" or extra == "all"
58
- Requires-Dist: rx (>=3.2.0,<4.0.0)
59
- Requires-Dist: typing-extensions (>=4.12.2,<5.0.0)
60
- Requires-Dist: uvicorn (>=0.32.1,<0.33.0)
24
+ Requires-Dist: annotated-types (==0.7.0)
25
+ Requires-Dist: apscheduler (==3.11.0)
26
+ Requires-Dist: boto3 (==1.40.64) ; extra == "aws" or extra == "all"
27
+ Requires-Dist: classy-fastapi (==0.6.1)
28
+ Requires-Dist: email-validator (==2.2.0)
29
+ Requires-Dist: etcd3-py (==0.1.6) ; extra == "etcd" or extra == "all"
30
+ Requires-Dist: fastapi (==0.115.5)
31
+ Requires-Dist: grpcio (==1.76.0)
32
+ Requires-Dist: h11 (==0.16.0)
33
+ Requires-Dist: httpx (==0.27.2)
34
+ Requires-Dist: itsdangerous (==2.2.0)
35
+ Requires-Dist: jinja2 (==3.1.4)
36
+ Requires-Dist: kurrentdbclient (==1.1.2) ; extra == "eventstore" or extra == "all"
37
+ Requires-Dist: motor (==3.7.0) ; extra == "mongodb" or extra == "all"
38
+ Requires-Dist: multipledispatch (==1.0.0)
39
+ Requires-Dist: opentelemetry-api (==1.38.0)
40
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc (==1.38.0)
41
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http (==1.38.0)
42
+ Requires-Dist: opentelemetry-exporter-prometheus (==0.59b0)
43
+ Requires-Dist: opentelemetry-instrumentation (==0.59b0)
44
+ Requires-Dist: opentelemetry-instrumentation-fastapi (==0.59b0)
45
+ Requires-Dist: opentelemetry-instrumentation-httpx (==0.59b0)
46
+ Requires-Dist: opentelemetry-instrumentation-logging (==0.59b0)
47
+ Requires-Dist: opentelemetry-instrumentation-system-metrics (==0.59b0)
48
+ Requires-Dist: opentelemetry-sdk (==1.38.0)
49
+ Requires-Dist: passlib[bcrypt] (==1.7.4)
50
+ Requires-Dist: prometheus-client (==0.21.0)
51
+ Requires-Dist: protobuf (==6.33.1) ; extra == "etcd" or extra == "all"
52
+ Requires-Dist: pydantic-settings (==2.6.1)
53
+ Requires-Dist: pydantic[email] (==2.10.3)
54
+ Requires-Dist: pyjwt (==2.10.1)
55
+ Requires-Dist: pymongo (==4.15.4) ; extra == "mongodb" or extra == "all"
56
+ Requires-Dist: python-dotenv (==1.0.1)
57
+ Requires-Dist: python-multipart (==0.0.17)
58
+ Requires-Dist: redis (==5.2.0) ; extra == "redis" or extra == "all"
59
+ Requires-Dist: rx (==3.2.0)
60
+ Requires-Dist: typing-extensions (==4.12.2)
61
+ Requires-Dist: uvicorn (==0.32.1)
61
62
  Description-Content-Type: text/markdown
62
63
 
63
64
  # Neuroglia Python Framework
@@ -0,0 +1,97 @@
1
+ [tool.poetry]
2
+ name = "neuroglia-python"
3
+ version = "0.7.8"
4
+ description = "Port from .NET to Python of the Neuroglia Framework"
5
+ authors = ["Charles d'Avernas <charles.davernas@neuroglia.io>", "Bruno van de Werve <bvandewe@gmail.com>"]
6
+ license = "Apache"
7
+ readme = "README.md"
8
+ packages = [
9
+ { include = "neuroglia", from = "src" },
10
+ ]
11
+ package-mode = true
12
+
13
+ [tool.poetry.dependencies]
14
+ python = "^3.9"
15
+ # Core framework dependencies - always required
16
+ annotated-types = "0.7.0"
17
+ classy-fastapi = "0.6.1"
18
+ fastapi = "0.115.5"
19
+ multipledispatch = "1.0.0"
20
+ pydantic-settings = "2.6.1"
21
+ python-dotenv = "1.0.1"
22
+ typing-extensions = "4.12.2"
23
+ rx = { version = "3.2.0" }
24
+ uvicorn = { version = "0.32.1" }
25
+ httpx = { version = "0.27.2" }
26
+ grpcio = { version = "1.76.0" }
27
+ h11 = { version = "0.16.0" } # Security fix for CVE-2025-43859
28
+
29
+ # Authentication dependencies
30
+ pyjwt = "2.10.1"
31
+ passlib = { extras = ["bcrypt"], version = "1.7.4" }
32
+ python-multipart = "0.0.17"
33
+ itsdangerous = "2.2.0"
34
+
35
+ # Core dependencies
36
+ protobuf = "6.33.1" # Pinned for compatibility with kurrentdbclient, OpenTelemetry, and etcd3-py
37
+ apscheduler = "3.11.0"
38
+ pydantic = {extras = ["email"], version = "2.10.3"}
39
+ email-validator = "2.2.0"
40
+ jinja2 = "3.1.4"
41
+
42
+ # Optional dependencies - install with extras
43
+ redis = { version = "5.2.0", optional = true }
44
+ pymongo = { version = "4.15.4", optional = true }
45
+ motor = { version = "3.7.0", optional = true }
46
+ kurrentdbclient = "1.1.2"
47
+ etcd3-py = { version = "0.1.6", optional = true } # Maintained fork with protobuf 5.x/6.x support
48
+
49
+ # OpenTelemetry dependencies - pinned versions for protobuf 6.x compatibility
50
+ opentelemetry-api = "1.38.0"
51
+ opentelemetry-sdk = "1.38.0"
52
+ opentelemetry-exporter-otlp-proto-grpc = "1.38.0"
53
+ opentelemetry-exporter-otlp-proto-http = "1.38.0"
54
+ opentelemetry-instrumentation = "0.59b0"
55
+ opentelemetry-instrumentation-fastapi = "0.59b0"
56
+ opentelemetry-instrumentation-httpx = "0.59b0"
57
+ opentelemetry-instrumentation-logging = "0.59b0"
58
+ opentelemetry-instrumentation-system-metrics = "0.59b0"
59
+ opentelemetry-exporter-prometheus = "0.59b0" # Prometheus /metrics endpoint support
60
+ prometheus-client = "0.21.0" # Required by opentelemetry-exporter-prometheus
61
+ boto3 = { version = "1.40.64", optional = true }
62
+
63
+ [tool.poetry.extras]
64
+ mongodb = ["pymongo", "motor"]
65
+ eventstore = ["kurrentdbclient"] # Formerly esdbclient
66
+ redis = ["redis"]
67
+ etcd = ["etcd3-py", "protobuf"]
68
+ aws = ["boto3"]
69
+ all = ["pymongo", "motor", "kurrentdbclient", "redis", "etcd3-py", "protobuf", "boto3"]
70
+
71
+ [tool.poetry.group.dev.dependencies]
72
+ # Development and testing dependencies
73
+ pre-commit = "^4.0.1"
74
+ pytest = "^8.3.3"
75
+ pytest-asyncio = "^0.24.0"
76
+ mypy = "^1.13.0"
77
+ autopep8 = "^2.3.1"
78
+ coverage = "^7.6.9"
79
+ mypy-extensions = "^1.0.0"
80
+ flake8 = "^7.1.1"
81
+ isort = "^5.13.2"
82
+
83
+ [tool.poetry.group.docs]
84
+ optional = true
85
+
86
+ [tool.poetry.group.docs.dependencies]
87
+ # Documentation dependencies
88
+ mkdocs = "^1.6.1"
89
+ mkdocs-material = "^9.5.48"
90
+ mkdocs-mermaid2-plugin = "^1.2.2"
91
+
92
+ [build-system]
93
+ requires = ["poetry-core"]
94
+ build-backend = "poetry.core.masonry.api"
95
+
96
+ [tool.black]
97
+ line-length = 500
@@ -62,7 +62,7 @@ __all__ = [
62
62
  ]
63
63
 
64
64
  # Framework metadata
65
- __version__ = "0.6.17"
65
+ __version__ = "0.7.8"
66
66
  __author__ = "Neuroglia Team"
67
67
  __email__ = "team@neuroglia.io"
68
68
  __license__ = "Apache"
@@ -5,23 +5,29 @@ Provides event store implementations and aggregate root support.
5
5
  """
6
6
 
7
7
  from .abstractions import (
8
- EventStore,
9
- EventRecord,
8
+ Aggregator,
10
9
  EventDescriptor,
10
+ EventRecord,
11
+ EventStore,
12
+ EventStoreOptions,
11
13
  StreamDescriptor,
12
14
  StreamReadDirection,
13
- EventStoreOptions,
14
- Aggregator
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",
20
24
  "EventSourcingRepository",
21
25
  "EventRecord",
22
- "EventDescriptor",
26
+ "EventDescriptor",
23
27
  "StreamDescriptor",
24
28
  "StreamReadDirection",
25
29
  "EventStoreOptions",
26
30
  "Aggregator",
27
- ]
31
+ "ReadModelConciliationOptions",
32
+ "ReadModelReconciliator",
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
@@ -113,10 +113,20 @@ class EventSourcingRepository(Generic[TAggregate, TKey], Repository[TAggregate,
113
113
  return self._eventstore.contains_stream(self._build_stream_id_for(id))
114
114
 
115
115
  async def get_async(self, id: TKey) -> Optional[TAggregate]:
116
- """Gets the aggregate with the specified id, if any"""
116
+ """
117
+ Gets the aggregate with the specified id, if any.
118
+
119
+ Returns None if the stream does not exist.
120
+ """
117
121
  stream_id = self._build_stream_id_for(id)
118
- events = await self._eventstore.read_async(stream_id, StreamReadDirection.FORWARDS, 0)
119
- return self._aggregator.aggregate(events, self.__orig_class__.__args__[0])
122
+ try:
123
+ events = await self._eventstore.read_async(stream_id, StreamReadDirection.FORWARDS, 0)
124
+ if not events:
125
+ return None
126
+ return self._aggregator.aggregate(events, self.__orig_class__.__args__[0])
127
+ except Exception:
128
+ # If stream doesn't exist or any other error occurs, return None
129
+ return None
120
130
 
121
131
  async def _do_add_async(self, aggregate: TAggregate) -> TAggregate:
122
132
  """Adds and persists the specified aggregate"""
@@ -127,7 +137,8 @@ class EventSourcingRepository(Generic[TAggregate, TKey], Repository[TAggregate,
127
137
  encoded_events = [self._encode_event(e) for e in events]
128
138
  await self._eventstore.append_async(stream_id, encoded_events)
129
139
  aggregate.state.state_version = events[-1].aggregate_version
130
- aggregate.clear_pending_events()
140
+ # DON'T clear pending events here - let base class do it after publishing!
141
+ # aggregate.clear_pending_events()
131
142
  return aggregate
132
143
 
133
144
  async def _do_update_async(self, aggregate: TAggregate) -> TAggregate:
@@ -139,7 +150,8 @@ class EventSourcingRepository(Generic[TAggregate, TKey], Repository[TAggregate,
139
150
  encoded_events = [self._encode_event(e) for e in events]
140
151
  await self._eventstore.append_async(stream_id, encoded_events, aggregate.state.state_version)
141
152
  aggregate.state.state_version = events[-1].aggregate_version
142
- aggregate.clear_pending_events()
153
+ # DON'T clear pending events here - let base class do it after publishing!
154
+ # aggregate.clear_pending_events()
143
155
  return aggregate
144
156
 
145
157
  async def _do_remove_async(self, id: TKey) -> None:
@@ -5,9 +5,9 @@ import sys
5
5
  from typing import Any, Dict, Optional
6
6
 
7
7
  import rx
8
- from esdbclient import AsyncioEventStoreDBClient as AsyncClientFactory
9
- from esdbclient import NewEvent, RecordedEvent, StreamState
10
- from esdbclient.exceptions import AlreadyExists
8
+ from kurrentdbclient import AsyncKurrentDBClient as AsyncClientFactory
9
+ from kurrentdbclient import NewEvent, RecordedEvent, StreamState
10
+ from kurrentdbclient.exceptions import AlreadyExistsError as AlreadyExists
11
11
  from rx.core.observable.observable import Observable
12
12
  from rx.disposable.disposable import Disposable
13
13
  from rx.subject.subject import Subject
@@ -64,11 +64,15 @@ class ESEventStore(EventStore):
64
64
  self._eventstore_client = connection_string_or_client
65
65
 
66
66
  async def _ensure_client(self) -> Any:
67
- """Lazily initialize the async EventStoreDB client on first use"""
67
+ """Lazily initialize the async KurrentDB client on first use"""
68
68
  if self._eventstore_client is None:
69
69
  if self._connection_string is None:
70
70
  raise RuntimeError("Neither connection string nor client provided")
71
- self._eventstore_client = await AsyncClientFactory(uri=self._connection_string)
71
+ # AsyncKurrentDBClient constructor is NOT awaitable - returns client directly
72
+ client = AsyncClientFactory(uri=self._connection_string)
73
+ # Must call connect() to establish the connection
74
+ await client.connect()
75
+ self._eventstore_client = client
72
76
  return self._eventstore_client
73
77
 
74
78
  async def contains_async(self, stream_id: str) -> bool:
@@ -101,7 +105,7 @@ class ESEventStore(EventStore):
101
105
  return None
102
106
  truncate_before = metadata.get("$tb")
103
107
  offset = 0 if truncate_before is None else truncate_before
104
- read_response = client.read_stream(
108
+ read_response = await client.read_stream(
105
109
  stream_name=stream_name,
106
110
  stream_position=offset,
107
111
  backwards=False,
@@ -109,7 +113,7 @@ class ESEventStore(EventStore):
109
113
  limit=1,
110
114
  )
111
115
  recorded_events = [event async for event in read_response]
112
- read_response = client.read_stream(
116
+ read_response = await client.read_stream(
113
117
  stream_name=stream_name,
114
118
  stream_position=offset,
115
119
  backwards=True,
@@ -131,7 +135,7 @@ class ESEventStore(EventStore):
131
135
  ) -> list[EventRecord]:
132
136
  client = await self._ensure_client()
133
137
  stream_name = self._get_stream_name(stream_id)
134
- read_response = client.read_stream(
138
+ read_response = await client.read_stream(
135
139
  stream_name=stream_name,
136
140
  stream_position=offset,
137
141
  backwards=True if read_direction == StreamReadDirection.BACKWARDS else False,
@@ -253,7 +257,9 @@ class ESEventStore(EventStore):
253
257
  logging.debug(f"Skipping tombstone event from stream: {e.stream_name}")
254
258
  # Acknowledge tombstone to continue processing
255
259
  if is_persistent:
256
- await subscription.ack(e.id)
260
+ # Use ack_id for resolved link events, fall back to id
261
+ ack_id = getattr(e, "ack_id", e.id)
262
+ await subscription.ack(ack_id)
257
263
  continue
258
264
 
259
265
  # Skip system event types (prefixed with $)
@@ -262,7 +268,9 @@ class ESEventStore(EventStore):
262
268
  logging.debug(f"Skipping system event type '{e.type}' from stream: {e.stream_name}")
263
269
  # Acknowledge system event to continue processing
264
270
  if is_persistent:
265
- await subscription.ack(e.id)
271
+ # Use ack_id for resolved link events, fall back to id
272
+ ack_id = getattr(e, "ack_id", e.id)
273
+ await subscription.ack(ack_id)
266
274
  continue
267
275
 
268
276
  try:
@@ -271,12 +279,16 @@ class ESEventStore(EventStore):
271
279
  logging.warning(f"Could not decode event with offset '{e.stream_position}' from stream '{e.stream_name}': {ex}")
272
280
  # Acknowledge failed decode to continue processing (don't park/retry invalid events)
273
281
  if is_persistent:
274
- await subscription.ack(e.id)
282
+ # Use ack_id for resolved link events, fall back to id
283
+ ack_id = getattr(e, "ack_id", e.id)
284
+ await subscription.ack(ack_id)
275
285
  continue
276
286
 
277
287
  # Convert to AckableEventRecord if subscription supports ack/nack
278
288
  if is_persistent:
279
- event_id = e.id
289
+ # Use ack_id for resolved link events (resolveLinktos=true), fall back to id
290
+ # This is critical for persistent subscriptions to category streams ($ce-*)
291
+ event_id = getattr(e, "ack_id", e.id)
280
292
 
281
293
  async def ack_delegate(eid=event_id, sub=subscription):
282
294
  """