neuroglia-python 0.7.2__tar.gz → 0.7.4__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.7.2 → neuroglia_python-0.7.4}/PKG-INFO +1 -1
  2. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/pyproject.toml +1 -1
  3. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/__init__.py +1 -1
  4. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/mongo/motor_query.py +89 -7
  5. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/mongo/motor_repository.py +1 -1
  6. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/queryable.py +62 -12
  7. neuroglia_python-0.7.4/src/neuroglia/hosting/configuration/data_access_layer.py +642 -0
  8. neuroglia_python-0.7.2/src/neuroglia/hosting/configuration/data_access_layer.py +0 -438
  9. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/LICENSE +0 -0
  10. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/README.md +0 -0
  11. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/application/__init__.py +0 -0
  12. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/application/background_scheduler.py +0 -0
  13. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/coordination/__init__.py +0 -0
  14. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/coordination/abstractions.py +0 -0
  15. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/coordination/leader_election.py +0 -0
  16. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/coordination/redis_backend.py +0 -0
  17. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/core/__init__.py +0 -0
  18. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/core/module_loader.py +0 -0
  19. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/core/operation_result.py +0 -0
  20. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/core/problem_details.py +0 -0
  21. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/core/type_extensions.py +0 -0
  22. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/core/type_finder.py +0 -0
  23. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/core/type_registry.py +0 -0
  24. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/__init__.py +0 -0
  25. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/abstractions.py +0 -0
  26. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/exceptions.py +0 -0
  27. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/__init__.py +0 -0
  28. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/abstractions.py +0 -0
  29. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/event_sourcing/__init__.py +0 -0
  30. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/event_sourcing/abstractions.py +0 -0
  31. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/event_sourcing/event_sourcing_repository.py +0 -0
  32. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/event_sourcing/event_store/__init__.py +0 -0
  33. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/event_sourcing/event_store/event_store.py +0 -0
  34. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/event_sourcing/read_model_reconciliator.py +0 -0
  35. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/filesystem/__init__.py +0 -0
  36. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/filesystem/filesystem_repository.py +0 -0
  37. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/memory/__init__.py +0 -0
  38. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/memory/memory_repository.py +0 -0
  39. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/mongo/__init__.py +0 -0
  40. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/mongo/enhanced_mongo_repository.py +0 -0
  41. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/mongo/mongo_repository.py +0 -0
  42. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/mongo/serialization_helper.py +0 -0
  43. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/mongo/typed_mongo_query.py +0 -0
  44. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/resources/__init__.py +0 -0
  45. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/resources/in_memory_storage_backend.py +0 -0
  46. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/resources/postgres_resource_store.py +0 -0
  47. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/resources/redis_resource_store.py +0 -0
  48. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/resources/resource_repository.py +0 -0
  49. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/infrastructure/tracing_mixin.py +0 -0
  50. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/queries/generic/__init__.py +0 -0
  51. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/queries/generic/get_by_id_query.py +0 -0
  52. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/queries/generic/list_query.py +0 -0
  53. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/resources/__init__.py +0 -0
  54. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/resources/abstractions.py +0 -0
  55. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/resources/controller.py +0 -0
  56. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/resources/serializers/__init__.py +0 -0
  57. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/resources/serializers/xml_serializer.py +0 -0
  58. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/resources/serializers/yaml_serializer.py +0 -0
  59. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/resources/state_machine.py +0 -0
  60. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/resources/watcher.py +0 -0
  61. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/data/unit_of_work.py +0 -0
  62. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/dependency_injection/__init__.py +0 -0
  63. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/dependency_injection/service_provider.py +0 -0
  64. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/dependency_injection/service_provider_backup.py +0 -0
  65. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/eventing/__init__.py +0 -0
  66. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/eventing/cloud_events/__init__.py +0 -0
  67. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/eventing/cloud_events/cloud_event.py +0 -0
  68. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/eventing/cloud_events/decorators.py +0 -0
  69. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/eventing/cloud_events/infrastructure/__init__.py +0 -0
  70. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_bus.py +0 -0
  71. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_ingestor.py +0 -0
  72. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_middleware.py +0 -0
  73. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/eventing/cloud_events/infrastructure/cloud_event_publisher.py +0 -0
  74. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/eventing/tracing.py +0 -0
  75. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/expressions/__init__.py +0 -0
  76. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/expressions/javascript_expression_translator.py +0 -0
  77. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/extensions/cqrs_metrics_extensions.py +0 -0
  78. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/extensions/mediator_extensions.py +0 -0
  79. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/extensions/state_persistence_extensions.py +0 -0
  80. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/hosting/__init__.py +0 -0
  81. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/hosting/abstractions.py +0 -0
  82. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/hosting/web.py +0 -0
  83. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/integration/__init__.py +0 -0
  84. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/integration/cache_repository.py +0 -0
  85. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/integration/http_service_client.py +0 -0
  86. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/integration/models.py +0 -0
  87. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/logging/__init__.py +0 -0
  88. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/logging/logger.py +0 -0
  89. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mapping/__init__.py +0 -0
  90. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mapping/mapper.py +0 -0
  91. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mediation/__init__.py +0 -0
  92. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mediation/behaviors/domain_event_cloudevent_behavior.py +0 -0
  93. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mediation/behaviors/domain_event_dispatching_middleware.py +0 -0
  94. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mediation/mediator.py +0 -0
  95. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mediation/metrics_middleware.py +0 -0
  96. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mediation/pipeline_behavior.py +0 -0
  97. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mediation/simple.py +0 -0
  98. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mediation/tracing_middleware.py +0 -0
  99. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mvc/__init__.py +0 -0
  100. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/mvc/controller_base.py +0 -0
  101. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/observability/__init__.py +0 -0
  102. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/observability/framework.py +0 -0
  103. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/observability/logging.py +0 -0
  104. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/observability/metrics.py +0 -0
  105. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/observability/otel_sdk.py +0 -0
  106. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/observability/settings.py +0 -0
  107. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/observability/tracing.py +0 -0
  108. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/py.typed +0 -0
  109. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/reactive/__init__.py +0 -0
  110. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/reactive/rx_async.py +0 -0
  111. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/serialization/__init__.py +0 -0
  112. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/serialization/abstractions.py +0 -0
  113. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/serialization/aggregate_serializer.py +0 -0
  114. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/serialization/json.py +0 -0
  115. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/utils/__init__.py +0 -0
  116. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/utils/camel_model.py +0 -0
  117. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/utils/case_conversion.py +0 -0
  118. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/validation/__init__.py +0 -0
  119. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/validation/business_rules.py +0 -0
  120. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/src/neuroglia/validation/exceptions.py +0 -0
  121. {neuroglia_python-0.7.2 → neuroglia_python-0.7.4}/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.7.2
3
+ Version: 0.7.4
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.7.2"
3
+ version = "0.7.4"
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.7.2"
65
+ __version__ = "0.7.4"
66
66
  __author__ = "Neuroglia Team"
67
67
  __email__ = "team@neuroglia.io"
68
68
  __license__ = "Apache"
@@ -33,7 +33,7 @@ See Also:
33
33
 
34
34
  import ast
35
35
  from ast import NodeVisitor, expr
36
- from typing import Any, Generic, List, Optional
36
+ from typing import TYPE_CHECKING, Any, Generic, List, Optional
37
37
 
38
38
  import pymongo
39
39
  from motor.motor_asyncio import AsyncIOMotorCollection, AsyncIOMotorCursor
@@ -43,6 +43,9 @@ from neuroglia.expressions.javascript_expression_translator import (
43
43
  JavaScriptExpressionTranslator,
44
44
  )
45
45
 
46
+ if TYPE_CHECKING:
47
+ from neuroglia.serialization import JsonSerializer
48
+
46
49
 
47
50
  class MotorQuery(Generic[T], Queryable[T]):
48
51
  """
@@ -71,6 +74,62 @@ class MotorQuery(Generic[T], Queryable[T]):
71
74
  """
72
75
  super().__init__(query_provider, expression)
73
76
 
77
+ async def to_list_async(self) -> list[T]:
78
+ """
79
+ Asynchronously execute the query and return results as a list.
80
+
81
+ This is the async equivalent of to_list() for Motor queries.
82
+
83
+ Returns:
84
+ List of entities matching the query
85
+
86
+ Example:
87
+ ```python
88
+ products = await repo.query_async() \\
89
+ .where(lambda p: p.price > 10) \\
90
+ .order_by(lambda p: p.name) \\
91
+ .to_list_async()
92
+ ```
93
+ """
94
+ # Get the provider and ensure it's a MotorQueryProvider
95
+ provider = self.provider
96
+ if not isinstance(provider, MotorQueryProvider):
97
+ raise TypeError("MotorQuery requires a MotorQueryProvider")
98
+
99
+ # Execute the query asynchronously
100
+ return await provider.execute_async(self.expression, List)
101
+
102
+ async def first_or_default_async(self, predicate=None) -> Optional[T]:
103
+ """
104
+ Asynchronously get the first element matching the predicate, or None.
105
+
106
+ Args:
107
+ predicate: Optional filter predicate
108
+
109
+ Returns:
110
+ First matching entity or None
111
+
112
+ Example:
113
+ ```python
114
+ product = await repo.query_async() \\
115
+ .where(lambda p: p.id == "prod123") \\
116
+ .first_or_default_async()
117
+ ```
118
+ """
119
+ # Get the provider and ensure it's a MotorQueryProvider
120
+ provider = self.provider
121
+ if not isinstance(provider, MotorQueryProvider):
122
+ raise TypeError("MotorQuery requires a MotorQueryProvider")
123
+
124
+ # If predicate provided, apply it as a where clause
125
+ query = self
126
+ if predicate is not None:
127
+ query = self.where(predicate)
128
+
129
+ # Execute and get list, then return first or None
130
+ results = await provider.execute_async(query.expression, List)
131
+ return results[0] if results else None
132
+
74
133
 
75
134
  class MotorQueryBuilder(NodeVisitor):
76
135
  """
@@ -228,25 +287,28 @@ class MotorQueryProvider(QueryProvider):
228
287
  Attributes:
229
288
  _collection: Motor async collection to query
230
289
  _entity_type: Type of entities in the collection
290
+ _serializer: JSON serializer for entity conversion
231
291
 
232
292
  Example:
233
293
  ```python
234
- provider = MotorQueryProvider(motor_collection, Product)
294
+ provider = MotorQueryProvider(motor_collection, Product, serializer)
235
295
  query = provider.create_query(Product, expression)
236
296
  results = await provider.execute_async(expression, List[Product])
237
297
  ```
238
298
  """
239
299
 
240
- def __init__(self, collection: AsyncIOMotorCollection, entity_type: type):
300
+ def __init__(self, collection: AsyncIOMotorCollection, entity_type: type, serializer: "JsonSerializer"):
241
301
  """
242
302
  Initialize the Motor query provider.
243
303
 
244
304
  Args:
245
305
  collection: Motor async collection
246
306
  entity_type: Type of entities being queried
307
+ serializer: JSON serializer for entity conversion
247
308
  """
248
309
  self._collection = collection
249
310
  self._entity_type = entity_type
311
+ self._serializer = serializer
250
312
 
251
313
  def create_query(self, element_type: type, expression: expr) -> Queryable:
252
314
  """
@@ -308,9 +370,29 @@ class MotorQueryProvider(QueryProvider):
308
370
  is_list_type = query_type.__origin__ in (list, List)
309
371
 
310
372
  if is_list_type:
311
- # Return list of documents
312
- return [doc async for doc in cursor]
373
+ # Return list of deserialized entities
374
+ documents = [doc async for doc in cursor]
375
+ return [self._deserialize_entity(doc) for doc in documents]
313
376
  else:
314
- # Return single document
377
+ # Return single deserialized entity
315
378
  results = await cursor.to_list(length=1)
316
- return results[0] if results else None
379
+ return self._deserialize_entity(results[0]) if results else None
380
+
381
+ def _deserialize_entity(self, doc: dict) -> Any:
382
+ """
383
+ Deserialize a MongoDB document to an entity instance.
384
+
385
+ Args:
386
+ doc: MongoDB document dictionary
387
+
388
+ Returns:
389
+ Deserialized entity instance
390
+ """
391
+ # Remove MongoDB's _id field
392
+ doc.pop("_id", None)
393
+
394
+ # Serialize dict back to JSON for deserialization
395
+ json_str = self._serializer.serialize_to_text(doc)
396
+
397
+ # Deserialize using the entity type
398
+ return self._serializer.deserialize_from_text(json_str, self._entity_type)
@@ -552,7 +552,7 @@ class MotorRepository(Generic[TEntity, TKey], QueryableRepository[TEntity, TKey]
552
552
  if entity_type is None:
553
553
  raise TypeError("Cannot create query: entity type not set. Pass entity_type to constructor.")
554
554
 
555
- return MotorQuery[TEntity](MotorQueryProvider(self.collection, entity_type))
555
+ return MotorQuery[TEntity](MotorQueryProvider(self.collection, entity_type, self._serializer))
556
556
 
557
557
  async def get_all_async(self) -> list[TEntity]:
558
558
  """
@@ -1,6 +1,5 @@
1
1
  import ast
2
2
  import inspect
3
- import os
4
3
  from abc import ABC, abstractclassmethod
5
4
  from ast import Attribute, Name, NodeTransformer, expr
6
5
  from collections.abc import Callable
@@ -146,7 +145,9 @@ class Queryable(Generic[T]):
146
145
 
147
146
  def order_by(self, selector: Callable[[T], Any]):
148
147
  """Orders the sequence using the specified attribute"""
149
- selector_source = self._get_lambda_source_code(selector)
148
+ frame = inspect.currentframe().f_back
149
+ frame_info = inspect.getframeinfo(frame)
150
+ selector_source = self._get_lambda_source_code(selector, frame_info.positions.end_col_offset)
150
151
  selector_tree = ast.parse(selector_source)
151
152
  selector_lambda_expression = selector_tree.body[0].value
152
153
  if not isinstance(selector_lambda_expression.body, Attribute):
@@ -160,7 +161,9 @@ class Queryable(Generic[T]):
160
161
 
161
162
  def order_by_descending(self, selector: Callable[[T], Any]):
162
163
  """Orders the sequence in a descending fashion using the specified attribute"""
163
- selector_source = self._get_lambda_source_code(selector)
164
+ frame = inspect.currentframe().f_back
165
+ frame_info = inspect.getframeinfo(frame)
166
+ selector_source = self._get_lambda_source_code(selector, frame_info.positions.end_col_offset)
164
167
  selector_tree = ast.parse(selector_source)
165
168
  selector_lambda_expression = selector_tree.body[0].value
166
169
  if not isinstance(selector_lambda_expression.body, Attribute):
@@ -175,8 +178,9 @@ class Queryable(Generic[T]):
175
178
  def select(self, selector: Callable[[T], Any]):
176
179
  """Projects each element of a sequence into a new form"""
177
180
  frame = inspect.currentframe().f_back
181
+ frame_info = inspect.getframeinfo(frame)
178
182
  variables = {**frame.f_locals}
179
- selector_source = self._get_lambda_source_code(selector)
183
+ selector_source = self._get_lambda_source_code(selector, frame_info.positions.end_col_offset)
180
184
  selector_tree = ast.parse(selector_source)
181
185
  selector_lambda_expression = selector_tree.body[0].value
182
186
  if not isinstance(selector_lambda_expression.body, Attribute) and not isinstance(selector_lambda_expression.body, ast.List):
@@ -239,18 +243,64 @@ class Queryable(Generic[T]):
239
243
  function (Callable): The lambda to get the source code of
240
244
  max_col_offset (int): The maximum column offset to walk the AST tree for the target lamba
241
245
 
246
+ Returns:
247
+ Optional[str]: The lambda source code, or None if extraction fails
248
+
242
249
  Notes:
243
250
  Credits to https://gist.github.com/Xion/617c1496ff45f3673a5692c3b0e3f75a
251
+
252
+ This method handles the case where the lambda is on a continuation line
253
+ that starts with '.' (common in method chaining). In such cases, the raw
254
+ source line is invalid Python syntax, so we prepend a dummy identifier
255
+ to make it parseable.
256
+
257
+ For multi-line continuations (backslash or implicit), we only process
258
+ the first line containing the lambda, as that's sufficient for extraction.
244
259
  """
245
- source_lines, _ = inspect.getsourcelines(function)
246
- if len(source_lines) != 1:
260
+ try:
261
+ source_lines, _ = inspect.getsourcelines(function)
262
+ except (OSError, TypeError) as e:
263
+ # Cannot extract source (e.g., built-in functions, C extensions)
247
264
  return None
248
- source_text = os.linesep.join(source_lines).strip()
249
- source_ast = ast.parse(source_text)
250
- lambda_node = next(
251
- (node for node in ast.walk(source_ast) if isinstance(node, ast.Lambda) and node.col_offset <= max_col_offset),
252
- None,
253
- )
265
+
266
+ # For multi-line sources (continuation), use only the first line
267
+ # The lambda itself is typically on the first line
268
+ if len(source_lines) == 0:
269
+ return None
270
+
271
+ # Strip the line and remove trailing backslash if present (line continuation)
272
+ source_text = source_lines[0].strip().rstrip("\\").strip()
273
+
274
+ # Handle continuation lines that start with '.' (method chaining)
275
+ # These are invalid Python syntax on their own
276
+ offset_adjustment = 0
277
+ if source_text.startswith("."):
278
+ source_text = "_" + source_text # Prepend dummy identifier
279
+ offset_adjustment = 1
280
+
281
+ try:
282
+ source_ast = ast.parse(source_text)
283
+ except SyntaxError:
284
+ return None
285
+
286
+ # Adjust max_col_offset if we prepended a dummy identifier
287
+ # Set to None if not provided to match all lambdas
288
+ adjusted_max_col_offset = None
289
+ if max_col_offset is not None:
290
+ adjusted_max_col_offset = max_col_offset + offset_adjustment
291
+
292
+ # Find lambda node within the adjusted column offset
293
+ if adjusted_max_col_offset is not None:
294
+ lambda_node = next(
295
+ (node for node in ast.walk(source_ast) if isinstance(node, ast.Lambda) and node.col_offset <= adjusted_max_col_offset),
296
+ None,
297
+ )
298
+ else:
299
+ lambda_node = next(
300
+ (node for node in ast.walk(source_ast) if isinstance(node, ast.Lambda)),
301
+ None,
302
+ )
303
+
254
304
  if lambda_node is None:
255
305
  return None
256
306
  lambda_text = source_text[lambda_node.col_offset : lambda_node.end_col_offset]