mdb-engine 0.1.6__py3-none-any.whl → 0.2.0__py3-none-any.whl

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 (87) hide show
  1. mdb_engine/__init__.py +104 -11
  2. mdb_engine/auth/ARCHITECTURE.md +112 -0
  3. mdb_engine/auth/README.md +648 -11
  4. mdb_engine/auth/__init__.py +136 -29
  5. mdb_engine/auth/audit.py +592 -0
  6. mdb_engine/auth/base.py +252 -0
  7. mdb_engine/auth/casbin_factory.py +264 -69
  8. mdb_engine/auth/config_helpers.py +7 -6
  9. mdb_engine/auth/cookie_utils.py +3 -7
  10. mdb_engine/auth/csrf.py +373 -0
  11. mdb_engine/auth/decorators.py +3 -10
  12. mdb_engine/auth/dependencies.py +47 -50
  13. mdb_engine/auth/helpers.py +3 -3
  14. mdb_engine/auth/integration.py +53 -80
  15. mdb_engine/auth/jwt.py +2 -6
  16. mdb_engine/auth/middleware.py +77 -34
  17. mdb_engine/auth/oso_factory.py +18 -38
  18. mdb_engine/auth/provider.py +270 -171
  19. mdb_engine/auth/rate_limiter.py +504 -0
  20. mdb_engine/auth/restrictions.py +8 -24
  21. mdb_engine/auth/session_manager.py +14 -29
  22. mdb_engine/auth/shared_middleware.py +600 -0
  23. mdb_engine/auth/shared_users.py +759 -0
  24. mdb_engine/auth/token_store.py +14 -28
  25. mdb_engine/auth/users.py +54 -113
  26. mdb_engine/auth/utils.py +213 -15
  27. mdb_engine/cli/commands/generate.py +545 -9
  28. mdb_engine/cli/commands/validate.py +3 -7
  29. mdb_engine/cli/utils.py +3 -3
  30. mdb_engine/config.py +7 -21
  31. mdb_engine/constants.py +65 -0
  32. mdb_engine/core/README.md +117 -6
  33. mdb_engine/core/__init__.py +39 -7
  34. mdb_engine/core/app_registration.py +22 -41
  35. mdb_engine/core/app_secrets.py +290 -0
  36. mdb_engine/core/connection.py +18 -9
  37. mdb_engine/core/encryption.py +223 -0
  38. mdb_engine/core/engine.py +1057 -93
  39. mdb_engine/core/index_management.py +12 -16
  40. mdb_engine/core/manifest.py +459 -150
  41. mdb_engine/core/ray_integration.py +435 -0
  42. mdb_engine/core/seeding.py +10 -18
  43. mdb_engine/core/service_initialization.py +12 -23
  44. mdb_engine/core/types.py +2 -5
  45. mdb_engine/database/README.md +140 -17
  46. mdb_engine/database/__init__.py +17 -6
  47. mdb_engine/database/abstraction.py +25 -37
  48. mdb_engine/database/connection.py +11 -18
  49. mdb_engine/database/query_validator.py +367 -0
  50. mdb_engine/database/resource_limiter.py +204 -0
  51. mdb_engine/database/scoped_wrapper.py +713 -196
  52. mdb_engine/dependencies.py +426 -0
  53. mdb_engine/di/__init__.py +34 -0
  54. mdb_engine/di/container.py +248 -0
  55. mdb_engine/di/providers.py +205 -0
  56. mdb_engine/di/scopes.py +139 -0
  57. mdb_engine/embeddings/README.md +54 -24
  58. mdb_engine/embeddings/__init__.py +31 -24
  59. mdb_engine/embeddings/dependencies.py +37 -154
  60. mdb_engine/embeddings/service.py +11 -25
  61. mdb_engine/exceptions.py +92 -0
  62. mdb_engine/indexes/README.md +30 -13
  63. mdb_engine/indexes/__init__.py +1 -0
  64. mdb_engine/indexes/helpers.py +1 -1
  65. mdb_engine/indexes/manager.py +50 -114
  66. mdb_engine/memory/README.md +2 -2
  67. mdb_engine/memory/__init__.py +1 -2
  68. mdb_engine/memory/service.py +30 -87
  69. mdb_engine/observability/README.md +4 -2
  70. mdb_engine/observability/__init__.py +26 -9
  71. mdb_engine/observability/health.py +8 -9
  72. mdb_engine/observability/metrics.py +32 -12
  73. mdb_engine/repositories/__init__.py +34 -0
  74. mdb_engine/repositories/base.py +325 -0
  75. mdb_engine/repositories/mongo.py +233 -0
  76. mdb_engine/repositories/unit_of_work.py +166 -0
  77. mdb_engine/routing/README.md +1 -1
  78. mdb_engine/routing/__init__.py +1 -3
  79. mdb_engine/routing/websockets.py +25 -60
  80. mdb_engine-0.2.0.dist-info/METADATA +313 -0
  81. mdb_engine-0.2.0.dist-info/RECORD +96 -0
  82. mdb_engine-0.1.6.dist-info/METADATA +0 -213
  83. mdb_engine-0.1.6.dist-info/RECORD +0 -75
  84. {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.dist-info}/WHEEL +0 -0
  85. {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.dist-info}/entry_points.txt +0 -0
  86. {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.dist-info}/licenses/LICENSE +0 -0
  87. {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.dist-info}/top_level.txt +0 -0
@@ -1,97 +1,60 @@
1
1
  """
2
- Embedding Service Dependency Injection for FastAPI
2
+ Embedding Service Utilities
3
3
 
4
- This module provides FastAPI dependency functions to inject embedding services
5
- into route handlers. The embedding service is automatically initialized from
6
- the app's manifest.json configuration.
7
- """
4
+ This module provides utility functions for creating embedding services.
5
+ For FastAPI dependency injection, use the request-scoped dependencies
6
+ from `mdb_engine.dependencies` instead.
8
7
 
9
- from typing import Any, Optional
8
+ Usage:
9
+ # For FastAPI routes (RECOMMENDED):
10
+ from mdb_engine.dependencies import get_embedding_service
10
11
 
11
- # Optional FastAPI import (only needed if FastAPI is available)
12
- try:
13
- from fastapi import Depends, HTTPException
12
+ @app.post("/embed")
13
+ async def embed(embedding_service=Depends(get_embedding_service)):
14
+ ...
14
15
 
15
- FASTAPI_AVAILABLE = True
16
- except ImportError:
17
- FASTAPI_AVAILABLE = False
16
+ # For standalone/utility usage:
17
+ from mdb_engine.embeddings.dependencies import get_embedding_service_for_app
18
18
 
19
- # Stub for when FastAPI is not available
20
- def Depends(*args, **kwargs):
21
- return None
19
+ service = get_embedding_service_for_app("my_app", engine)
20
+ """
22
21
 
23
- class HTTPException(Exception):
24
- pass
22
+ from typing import TYPE_CHECKING, Optional
25
23
 
24
+ if TYPE_CHECKING:
25
+ from ..core.engine import MongoDBEngine
26
26
 
27
27
  from .service import EmbeddingService, get_embedding_service
28
28
 
29
- # Global engine registry (for apps that don't pass engine explicitly)
30
- _global_engine: Optional[Any] = None
31
- _global_app_slug: Optional[str] = None
32
-
33
-
34
- def set_global_engine(engine: Any, app_slug: Optional[str] = None) -> None:
35
- """
36
- Set global MongoDBEngine instance for embedding dependency injection.
37
-
38
- This is useful when you have a single engine instance that you want
39
- to use across all apps. Call this during application startup.
40
-
41
- Args:
42
- engine: MongoDBEngine instance
43
- app_slug: Optional app slug
44
- """
45
- global _global_engine, _global_app_slug
46
- _global_engine = engine
47
- _global_app_slug = app_slug
48
-
49
-
50
- def get_global_engine() -> Optional[Any]:
51
- """
52
- Get global MongoDBEngine instance.
53
-
54
- Returns:
55
- MongoDBEngine instance if set, None otherwise
56
- """
57
- return _global_engine
58
-
59
29
 
60
30
  def get_embedding_service_for_app(
61
- app_slug: str, engine: Optional[Any] = None
31
+ app_slug: str, engine: "MongoDBEngine"
62
32
  ) -> Optional[EmbeddingService]:
63
33
  """
64
- Get embedding service for a specific app.
34
+ Get embedding service for a specific app using the engine instance.
65
35
 
66
- This is a helper function that can be used with FastAPI's Depends()
67
- to inject the embedding service into route handlers.
36
+ This is a utility function for cases where you need to create an
37
+ embedding service outside of a FastAPI request context (e.g., in
38
+ background tasks, CLI tools, or tests).
39
+
40
+ For FastAPI routes, use `mdb_engine.dependencies.get_embedding_service` instead.
68
41
 
69
42
  Args:
70
- app_slug: App slug (typically extracted from route context)
71
- engine: MongoDBEngine instance (optional, will try to get from context)
43
+ app_slug: App slug to get embedding config from
44
+ engine: MongoDBEngine instance
72
45
 
73
46
  Returns:
74
- EmbeddingService instance if embedding is enabled for this app, None otherwise
47
+ EmbeddingService instance if embedding is enabled, None otherwise
75
48
 
76
49
  Example:
77
- ```python
78
- from fastapi import Depends
79
- from mdb_engine.embeddings.dependencies import get_embedding_service_for_app
80
-
81
- @app.post("/embed")
82
- async def embed_endpoint(
83
- embedding_service = Depends(lambda: get_embedding_service_for_app("my_app"))
84
- ):
85
- if not embedding_service:
86
- raise HTTPException(503, "Embedding service not available")
87
- embeddings = await embedding_service.embed_chunks(["Hello world"])
88
- return {"embeddings": embeddings}
89
- ```
90
- """
91
- # Try to get engine from context if not provided
92
- if engine is None:
93
- engine = _global_engine
50
+ # In a background task or CLI
51
+ engine = MongoDBEngine(...)
52
+ await engine.initialize()
94
53
 
54
+ service = get_embedding_service_for_app("my_app", engine)
55
+ if service:
56
+ embeddings = await service.embed_chunks(["Hello world"])
57
+ """
95
58
  if engine is None:
96
59
  return None
97
60
 
@@ -108,86 +71,6 @@ def get_embedding_service_for_app(
108
71
  return get_embedding_service(config=embedding_config)
109
72
 
110
73
 
111
- def create_embedding_dependency(app_slug: str, engine: Optional[Any] = None):
112
- """
113
- Create a FastAPI dependency function for embedding service.
114
-
115
- This creates a dependency function that can be used with Depends()
116
- to inject the embedding service into route handlers.
117
-
118
- Args:
119
- app_slug: App slug
120
- engine: MongoDBEngine instance (optional)
121
-
122
- Returns:
123
- Dependency function that returns EmbeddingService or raises HTTPException
124
-
125
- Example:
126
- ```python
127
- from fastapi import Depends
128
- from mdb_engine.embeddings.dependencies import create_embedding_dependency
129
-
130
- embedding_dep = create_embedding_dependency("my_app", engine)
131
-
132
- @app.post("/embed")
133
- async def embed_endpoint(embedding_service = Depends(embedding_dep)):
134
- embeddings = await embedding_service.embed_chunks(["Hello world"])
135
- return {"embeddings": embeddings}
136
- ```
137
- """
138
-
139
- def _get_embedding_service() -> EmbeddingService:
140
- embedding_service = get_embedding_service_for_app(app_slug, engine)
141
- if embedding_service is None:
142
- if FASTAPI_AVAILABLE:
143
- raise HTTPException(
144
- status_code=503,
145
- detail=f"Embedding service not available for app '{app_slug}'. "
146
- "Ensure 'embedding_config.enabled' is true in manifest.json and "
147
- "embedding dependencies are installed.",
148
- )
149
- else:
150
- raise RuntimeError(
151
- f"Embedding service not available for app '{app_slug}'"
152
- )
153
- return embedding_service
154
-
155
- return _get_embedding_service
156
-
157
-
158
- def get_embedding_service_dependency(app_slug: str):
159
- """
160
- Get embedding service dependency using global engine.
161
-
162
- This is a convenience function that uses the global engine registry.
163
- Set the engine with set_global_engine() during app startup.
164
-
165
- Args:
166
- app_slug: App slug
167
-
168
- Returns:
169
- Dependency function for FastAPI Depends()
170
-
171
- Example:
172
- ```python
173
- from fastapi import FastAPI, Depends
174
- from mdb_engine.embeddings.dependencies import (
175
- set_global_engine, get_embedding_service_dependency
176
- )
177
-
178
- app = FastAPI()
179
-
180
- # During startup
181
- set_global_engine(engine, app_slug="my_app")
182
-
183
- # In routes
184
- @app.post("/embed")
185
- async def embed(embedding_service = Depends(get_embedding_service_dependency("my_app"))):
186
- return await embedding_service.embed_chunks(["Hello world"])
187
- ```
188
- """
189
- return create_embedding_dependency(app_slug, _global_engine)
190
-
191
-
192
- # Alias for backward compatibility
193
- get_embedding_service_dep = get_embedding_service_dependency
74
+ __all__ = [
75
+ "get_embedding_service_for_app",
76
+ ]
@@ -134,7 +134,7 @@ class OpenAIEmbeddingProvider(BaseEmbeddingProvider):
134
134
  ConnectionError,
135
135
  OSError,
136
136
  ) as e:
137
- logger.error(f"OpenAI embedding failed: {e}")
137
+ logger.exception(f"OpenAI embedding failed: {e}")
138
138
  raise EmbeddingServiceError(f"OpenAI embedding failed: {str(e)}") from e
139
139
 
140
140
 
@@ -217,10 +217,8 @@ class AzureOpenAIEmbeddingProvider(BaseEmbeddingProvider):
217
217
  ConnectionError,
218
218
  OSError,
219
219
  ) as e:
220
- logger.error(f"Azure OpenAI embedding failed: {e}")
221
- raise EmbeddingServiceError(
222
- f"Azure OpenAI embedding failed: {str(e)}"
223
- ) from e
220
+ logger.exception(f"Azure OpenAI embedding failed: {e}")
221
+ raise EmbeddingServiceError(f"Azure OpenAI embedding failed: {str(e)}") from e
224
222
 
225
223
 
226
224
  def _detect_provider_from_env() -> str:
@@ -281,24 +279,16 @@ class EmbeddingProvider:
281
279
  else:
282
280
  # Auto-detect provider from environment variables
283
281
  provider_type = _detect_provider_from_env()
284
- default_model = (config or {}).get(
285
- "default_embedding_model", "text-embedding-3-small"
286
- )
282
+ default_model = (config or {}).get("default_embedding_model", "text-embedding-3-small")
287
283
 
288
284
  if provider_type == "azure":
289
- self.embedding_provider = AzureOpenAIEmbeddingProvider(
290
- default_model=default_model
291
- )
285
+ self.embedding_provider = AzureOpenAIEmbeddingProvider(default_model=default_model)
292
286
  logger.info(
293
287
  f"Auto-detected Azure OpenAI embedding provider (model: {default_model})"
294
288
  )
295
289
  else:
296
- self.embedding_provider = OpenAIEmbeddingProvider(
297
- default_model=default_model
298
- )
299
- logger.info(
300
- f"Auto-detected OpenAI embedding provider (model: {default_model})"
301
- )
290
+ self.embedding_provider = OpenAIEmbeddingProvider(default_model=default_model)
291
+ logger.info(f"Auto-detected OpenAI embedding provider (model: {default_model})")
302
292
 
303
293
  # Store config for potential future use
304
294
  self.config = config or {}
@@ -341,7 +331,7 @@ class EmbeddingProvider:
341
331
  return vectors
342
332
 
343
333
  except (AttributeError, TypeError, ValueError, RuntimeError, KeyError) as e:
344
- logger.error(f"EMBED_FAILED: {str(e)}")
334
+ logger.exception(f"EMBED_FAILED: {str(e)}")
345
335
  raise EmbeddingServiceError(f"Embedding failed: {str(e)}") from e
346
336
 
347
337
 
@@ -573,7 +563,7 @@ class EmbeddingService:
573
563
  ConnectionError,
574
564
  OSError,
575
565
  ) as e:
576
- logger.error(f"Failed to generate embeddings for {source_id}: {e}")
566
+ logger.exception(f"Failed to generate embeddings for {source_id}: {e}")
577
567
  raise EmbeddingServiceError(f"Embedding generation failed: {str(e)}") from e
578
568
 
579
569
  if len(vectors) != len(chunks):
@@ -614,9 +604,7 @@ class EmbeddingService:
614
604
  result = await collection.insert_many(documents_to_insert)
615
605
  inserted_count = len(result.inserted_ids)
616
606
 
617
- logger.info(
618
- f"Successfully inserted {inserted_count} documents for source: {source_id}"
619
- )
607
+ logger.info(f"Successfully inserted {inserted_count} documents for source: {source_id}")
620
608
 
621
609
  return {
622
610
  "chunks_created": len(chunks),
@@ -632,9 +620,7 @@ class EmbeddingService:
632
620
  KeyError,
633
621
  ConnectionError,
634
622
  ) as e:
635
- logger.error(
636
- f"Failed to store documents for {source_id}: {e}", exc_info=True
637
- )
623
+ logger.error(f"Failed to store documents for {source_id}: {e}", exc_info=True)
638
624
  raise EmbeddingServiceError(f"Storage failed: {str(e)}") from e
639
625
 
640
626
  async def process_text(
mdb_engine/exceptions.py CHANGED
@@ -165,3 +165,95 @@ class ConfigurationError(MongoDBEngineError):
165
165
  super().__init__(message, context=context)
166
166
  self.config_key = config_key
167
167
  self.config_value = config_value
168
+
169
+
170
+ class QueryValidationError(MongoDBEngineError):
171
+ """
172
+ Raised when a query fails validation checks.
173
+
174
+ This exception is raised when a query contains dangerous operators,
175
+ exceeds complexity limits, or violates security policies.
176
+
177
+ Attributes:
178
+ message: Error message
179
+ query_type: Type of query that failed (filter, pipeline, etc.)
180
+ operator: Dangerous operator that was found (if applicable)
181
+ path: JSON path where the issue was found (if applicable)
182
+ context: Additional context information
183
+ """
184
+
185
+ def __init__(
186
+ self,
187
+ message: str,
188
+ query_type: Optional[str] = None,
189
+ operator: Optional[str] = None,
190
+ path: Optional[str] = None,
191
+ context: Optional[Dict[str, Any]] = None,
192
+ ) -> None:
193
+ """
194
+ Initialize the query validation error.
195
+
196
+ Args:
197
+ message: Error message
198
+ query_type: Type of query that failed (filter, pipeline, etc.)
199
+ operator: Dangerous operator that was found (if applicable)
200
+ path: JSON path where the issue was found (if applicable)
201
+ context: Additional context information
202
+ """
203
+ context = context or {}
204
+ if query_type:
205
+ context["query_type"] = query_type
206
+ if operator:
207
+ context["operator"] = operator
208
+ if path:
209
+ context["path"] = path
210
+ super().__init__(message, context=context)
211
+ self.query_type = query_type
212
+ self.operator = operator
213
+ self.path = path
214
+
215
+
216
+ class ResourceLimitExceeded(MongoDBEngineError):
217
+ """
218
+ Raised when a resource limit is exceeded.
219
+
220
+ This exception is raised when queries exceed timeouts, result sizes,
221
+ or other resource limits.
222
+
223
+ Attributes:
224
+ message: Error message
225
+ limit_type: Type of limit that was exceeded (timeout, size, etc.)
226
+ limit_value: The limit value that was exceeded
227
+ actual_value: The actual value that exceeded the limit
228
+ context: Additional context information
229
+ """
230
+
231
+ def __init__(
232
+ self,
233
+ message: str,
234
+ limit_type: Optional[str] = None,
235
+ limit_value: Optional[Any] = None,
236
+ actual_value: Optional[Any] = None,
237
+ context: Optional[Dict[str, Any]] = None,
238
+ ) -> None:
239
+ """
240
+ Initialize the resource limit exceeded error.
241
+
242
+ Args:
243
+ message: Error message
244
+ limit_type: Type of limit that was exceeded (timeout, size, etc.)
245
+ limit_value: The limit value that was exceeded
246
+ actual_value: The actual value that exceeded the limit
247
+ context: Additional context information
248
+ """
249
+ context = context or {}
250
+ if limit_type:
251
+ context["limit_type"] = limit_type
252
+ if limit_value is not None:
253
+ context["limit_value"] = limit_value
254
+ if actual_value is not None:
255
+ context["actual_value"] = actual_value
256
+ super().__init__(message, context=context)
257
+ self.limit_type = limit_type
258
+ self.limit_value = limit_value
259
+ self.actual_value = actual_value
@@ -573,7 +573,7 @@ try:
573
573
  collection_name="users",
574
574
  index_definitions=index_definitions
575
575
  )
576
- except Exception as e:
576
+ except (PyMongoError, ValueError, TypeError) as e:
577
577
  logger.error(f"Failed to create indexes: {e}", exc_info=True)
578
578
  raise
579
579
  ```
@@ -613,12 +613,20 @@ index_definitions = [
613
613
  }
614
614
  ]
615
615
 
616
- await run_index_creation_for_collection(
617
- db=engine.mongo_db,
618
- slug="my_app",
619
- collection_name="users",
620
- index_definitions=index_definitions
621
- )
616
+ # Get scoped database and collection
617
+ db = engine.get_scoped_db("my_app")
618
+ collection = db.users
619
+
620
+ # Use collection.index_manager for index operations
621
+ index_manager = collection.index_manager
622
+ for index_def in index_definitions:
623
+ if index_def["type"] == "regular":
624
+ await index_manager.create_index(
625
+ keys=list(index_def["keys"].items()),
626
+ name=index_def.get("name"),
627
+ **index_def.get("options", {})
628
+ )
629
+ # Handle other index types similarly...
622
630
  ```
623
631
 
624
632
  ### Multiple Collections
@@ -635,13 +643,22 @@ collections_with_indexes = {
635
643
  ]
636
644
  }
637
645
 
646
+ # Get scoped database
647
+ db = engine.get_scoped_db("my_app")
648
+
649
+ # Use collection.index_manager for each collection
638
650
  for collection_name, index_definitions in collections_with_indexes.items():
639
- await run_index_creation_for_collection(
640
- db=engine.mongo_db,
641
- slug="my_app",
642
- collection_name=collection_name,
643
- index_definitions=index_definitions
644
- )
651
+ collection = getattr(db, collection_name)
652
+ index_manager = collection.index_manager
653
+
654
+ for index_def in index_definitions:
655
+ if index_def["type"] == "regular":
656
+ await index_manager.create_index(
657
+ keys=list(index_def["keys"].items()),
658
+ name=index_def.get("name"),
659
+ **index_def.get("options", {})
660
+ )
661
+ # Handle other index types similarly...
645
662
  ```
646
663
 
647
664
  ## Related Modules
@@ -8,6 +8,7 @@ This module is part of MDB_ENGINE - MongoDB Engine.
8
8
 
9
9
  # Re-export index managers from database module for convenience
10
10
  from ..database.scoped_wrapper import AsyncAtlasIndexManager, AutoIndexManager
11
+
11
12
  # Export high-level management functions
12
13
  from .manager import normalize_json_def, run_index_creation_for_collection
13
14
 
@@ -12,7 +12,7 @@ logger = logging.getLogger(__name__)
12
12
 
13
13
 
14
14
  def normalize_keys(
15
- keys: Union[Dict[str, Any], List[Tuple[str, Any]]]
15
+ keys: Union[Dict[str, Any], List[Tuple[str, Any]]],
16
16
  ) -> List[Tuple[str, Any]]:
17
17
  """
18
18
  Normalize index keys to a consistent format.