mdb-engine 0.1.7__py3-none-any.whl → 0.2.1__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.
- mdb_engine/__init__.py +71 -10
- mdb_engine/auth/ARCHITECTURE.md +112 -0
- mdb_engine/auth/README.md +125 -11
- mdb_engine/auth/__init__.py +7 -1
- mdb_engine/auth/base.py +252 -0
- mdb_engine/auth/casbin_factory.py +258 -59
- mdb_engine/auth/dependencies.py +10 -5
- mdb_engine/auth/integration.py +23 -7
- mdb_engine/auth/oso_factory.py +2 -2
- mdb_engine/auth/provider.py +263 -143
- mdb_engine/core/engine.py +307 -6
- mdb_engine/core/manifest.py +35 -15
- mdb_engine/database/README.md +28 -1
- mdb_engine/dependencies.py +426 -0
- mdb_engine/di/__init__.py +34 -0
- mdb_engine/di/container.py +248 -0
- mdb_engine/di/providers.py +205 -0
- mdb_engine/di/scopes.py +139 -0
- mdb_engine/embeddings/README.md +54 -24
- mdb_engine/embeddings/__init__.py +22 -23
- mdb_engine/embeddings/dependencies.py +37 -152
- mdb_engine/repositories/__init__.py +34 -0
- mdb_engine/repositories/base.py +325 -0
- mdb_engine/repositories/mongo.py +233 -0
- mdb_engine/repositories/unit_of_work.py +166 -0
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.1.dist-info}/METADATA +42 -14
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.1.dist-info}/RECORD +31 -20
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.1.dist-info}/WHEEL +0 -0
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.1.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MongoDB Repository Implementation
|
|
3
|
+
|
|
4
|
+
Implements the Repository interface using MongoDB through ScopedCollectionWrapper.
|
|
5
|
+
This provides automatic app scoping and security features.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any, Dict, Generic, List, Optional, Type, TypeVar
|
|
11
|
+
|
|
12
|
+
from bson import ObjectId
|
|
13
|
+
|
|
14
|
+
from .base import Entity, Repository
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
T = TypeVar("T", bound=Entity)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MongoRepository(Repository[T], Generic[T]):
|
|
22
|
+
"""
|
|
23
|
+
MongoDB implementation of the Repository interface.
|
|
24
|
+
|
|
25
|
+
Uses ScopedCollectionWrapper for automatic app_id scoping
|
|
26
|
+
and security features.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
# Direct usage
|
|
30
|
+
users_collection = db.users # ScopedCollectionWrapper
|
|
31
|
+
user_repo = MongoRepository(users_collection, User)
|
|
32
|
+
|
|
33
|
+
# Find users
|
|
34
|
+
users = await user_repo.find({"role": "admin"})
|
|
35
|
+
|
|
36
|
+
# Add new user
|
|
37
|
+
user = User(email="john@example.com", name="John")
|
|
38
|
+
user_id = await user_repo.add(user)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
collection: Any, # ScopedCollectionWrapper - avoid import cycle
|
|
44
|
+
entity_class: Type[T],
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Initialize the MongoDB repository.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
collection: ScopedCollectionWrapper for the collection
|
|
51
|
+
entity_class: Entity subclass for this repository
|
|
52
|
+
"""
|
|
53
|
+
self._collection = collection
|
|
54
|
+
self._entity_class = entity_class
|
|
55
|
+
|
|
56
|
+
def _to_entity(self, doc: Optional[Dict[str, Any]]) -> Optional[T]:
|
|
57
|
+
"""Convert a MongoDB document to an entity."""
|
|
58
|
+
if doc is None:
|
|
59
|
+
return None
|
|
60
|
+
return self._entity_class.from_dict(doc)
|
|
61
|
+
|
|
62
|
+
def _to_document(self, entity: T, include_id: bool = False) -> Dict[str, Any]:
|
|
63
|
+
"""Convert an entity to a MongoDB document."""
|
|
64
|
+
doc = entity.to_dict()
|
|
65
|
+
if not include_id and "_id" in doc:
|
|
66
|
+
del doc["_id"]
|
|
67
|
+
return doc
|
|
68
|
+
|
|
69
|
+
async def get(self, id: str) -> Optional[T]:
|
|
70
|
+
"""Get entity by ID."""
|
|
71
|
+
try:
|
|
72
|
+
object_id = ObjectId(id) if ObjectId.is_valid(id) else id
|
|
73
|
+
except (TypeError, ValueError):
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
doc = await self._collection.find_one({"_id": object_id})
|
|
77
|
+
return self._to_entity(doc)
|
|
78
|
+
|
|
79
|
+
async def find(
|
|
80
|
+
self,
|
|
81
|
+
filter: Optional[Dict[str, Any]] = None,
|
|
82
|
+
skip: int = 0,
|
|
83
|
+
limit: int = 100,
|
|
84
|
+
sort: Optional[List[tuple]] = None,
|
|
85
|
+
) -> List[T]:
|
|
86
|
+
"""Find entities matching a filter."""
|
|
87
|
+
cursor = self._collection.find(filter or {})
|
|
88
|
+
|
|
89
|
+
if skip > 0:
|
|
90
|
+
cursor = cursor.skip(skip)
|
|
91
|
+
if limit > 0:
|
|
92
|
+
cursor = cursor.limit(limit)
|
|
93
|
+
if sort:
|
|
94
|
+
cursor = cursor.sort(sort)
|
|
95
|
+
|
|
96
|
+
docs = await cursor.to_list(length=limit)
|
|
97
|
+
return [self._to_entity(doc) for doc in docs]
|
|
98
|
+
|
|
99
|
+
async def find_one(self, filter: Dict[str, Any]) -> Optional[T]:
|
|
100
|
+
"""Find a single entity matching a filter."""
|
|
101
|
+
doc = await self._collection.find_one(filter)
|
|
102
|
+
return self._to_entity(doc)
|
|
103
|
+
|
|
104
|
+
async def add(self, entity: T) -> str:
|
|
105
|
+
"""Add a new entity and return its ID."""
|
|
106
|
+
entity.created_at = datetime.utcnow()
|
|
107
|
+
doc = self._to_document(entity)
|
|
108
|
+
|
|
109
|
+
result = await self._collection.insert_one(doc)
|
|
110
|
+
entity.id = str(result.inserted_id)
|
|
111
|
+
|
|
112
|
+
logger.debug(f"Added {self._entity_class.__name__} with id={entity.id}")
|
|
113
|
+
return entity.id
|
|
114
|
+
|
|
115
|
+
async def add_many(self, entities: List[T]) -> List[str]:
|
|
116
|
+
"""Add multiple entities and return their IDs."""
|
|
117
|
+
now = datetime.utcnow()
|
|
118
|
+
docs = []
|
|
119
|
+
|
|
120
|
+
for entity in entities:
|
|
121
|
+
entity.created_at = now
|
|
122
|
+
docs.append(self._to_document(entity))
|
|
123
|
+
|
|
124
|
+
result = await self._collection.insert_many(docs)
|
|
125
|
+
ids = [str(id) for id in result.inserted_ids]
|
|
126
|
+
|
|
127
|
+
for entity, id in zip(entities, ids):
|
|
128
|
+
entity.id = id
|
|
129
|
+
|
|
130
|
+
logger.debug(f"Added {len(ids)} {self._entity_class.__name__} entities")
|
|
131
|
+
return ids
|
|
132
|
+
|
|
133
|
+
async def update(self, id: str, entity: T) -> bool:
|
|
134
|
+
"""Update an entity by ID."""
|
|
135
|
+
try:
|
|
136
|
+
object_id = ObjectId(id) if ObjectId.is_valid(id) else id
|
|
137
|
+
except (TypeError, ValueError):
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
entity.updated_at = datetime.utcnow()
|
|
141
|
+
doc = self._to_document(entity)
|
|
142
|
+
|
|
143
|
+
result = await self._collection.update_one({"_id": object_id}, {"$set": doc})
|
|
144
|
+
|
|
145
|
+
return result.modified_count > 0
|
|
146
|
+
|
|
147
|
+
async def update_fields(self, id: str, fields: Dict[str, Any]) -> bool:
|
|
148
|
+
"""Update specific fields of an entity."""
|
|
149
|
+
try:
|
|
150
|
+
object_id = ObjectId(id) if ObjectId.is_valid(id) else id
|
|
151
|
+
except (TypeError, ValueError):
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
fields["updated_at"] = datetime.utcnow()
|
|
155
|
+
|
|
156
|
+
result = await self._collection.update_one({"_id": object_id}, {"$set": fields})
|
|
157
|
+
|
|
158
|
+
return result.modified_count > 0
|
|
159
|
+
|
|
160
|
+
async def delete(self, id: str) -> bool:
|
|
161
|
+
"""Delete an entity by ID."""
|
|
162
|
+
try:
|
|
163
|
+
object_id = ObjectId(id) if ObjectId.is_valid(id) else id
|
|
164
|
+
except (TypeError, ValueError):
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
result = await self._collection.delete_one({"_id": object_id})
|
|
168
|
+
return result.deleted_count > 0
|
|
169
|
+
|
|
170
|
+
async def count(self, filter: Optional[Dict[str, Any]] = None) -> int:
|
|
171
|
+
"""Count entities matching a filter."""
|
|
172
|
+
return await self._collection.count_documents(filter or {})
|
|
173
|
+
|
|
174
|
+
async def exists(self, id: str) -> bool:
|
|
175
|
+
"""Check if an entity exists."""
|
|
176
|
+
try:
|
|
177
|
+
object_id = ObjectId(id) if ObjectId.is_valid(id) else id
|
|
178
|
+
except (TypeError, ValueError):
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
doc = await self._collection.find_one({"_id": object_id}, projection={"_id": 1})
|
|
182
|
+
return doc is not None
|
|
183
|
+
|
|
184
|
+
# Additional MongoDB-specific methods
|
|
185
|
+
|
|
186
|
+
async def aggregate(self, pipeline: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
187
|
+
"""
|
|
188
|
+
Run an aggregation pipeline.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
pipeline: MongoDB aggregation pipeline
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
List of result documents
|
|
195
|
+
"""
|
|
196
|
+
cursor = self._collection.aggregate(pipeline)
|
|
197
|
+
return await cursor.to_list(length=None)
|
|
198
|
+
|
|
199
|
+
async def update_many(
|
|
200
|
+
self,
|
|
201
|
+
filter: Dict[str, Any],
|
|
202
|
+
update: Dict[str, Any],
|
|
203
|
+
) -> int:
|
|
204
|
+
"""
|
|
205
|
+
Update multiple documents matching a filter.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
filter: MongoDB filter
|
|
209
|
+
update: Update operations
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Number of modified documents
|
|
213
|
+
"""
|
|
214
|
+
if "$set" not in update:
|
|
215
|
+
update = {"$set": update}
|
|
216
|
+
|
|
217
|
+
update["$set"]["updated_at"] = datetime.utcnow()
|
|
218
|
+
|
|
219
|
+
result = await self._collection.update_many(filter, update)
|
|
220
|
+
return result.modified_count
|
|
221
|
+
|
|
222
|
+
async def delete_many(self, filter: Dict[str, Any]) -> int:
|
|
223
|
+
"""
|
|
224
|
+
Delete multiple documents matching a filter.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
filter: MongoDB filter
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Number of deleted documents
|
|
231
|
+
"""
|
|
232
|
+
result = await self._collection.delete_many(filter)
|
|
233
|
+
return result.deleted_count
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit of Work Pattern
|
|
3
|
+
|
|
4
|
+
Manages repository access and provides a clean interface for data operations.
|
|
5
|
+
The UnitOfWork acts as a factory for repositories and manages their lifecycle.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, Generic, Optional, Type, TypeVar
|
|
10
|
+
|
|
11
|
+
from .base import Entity, Repository
|
|
12
|
+
from .mongo import MongoRepository
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T", bound=Entity)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UnitOfWork:
|
|
20
|
+
"""
|
|
21
|
+
Unit of Work for managing repository access.
|
|
22
|
+
|
|
23
|
+
Provides a clean interface for accessing repositories through
|
|
24
|
+
attribute access (e.g., uow.users, uow.orders).
|
|
25
|
+
|
|
26
|
+
The UnitOfWork is request-scoped - one instance per HTTP request.
|
|
27
|
+
Repositories are created lazily and cached for the duration of the request.
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
# In a route handler
|
|
31
|
+
@app.get("/users/{user_id}")
|
|
32
|
+
async def get_user(user_id: str, ctx: RequestContext = Depends()):
|
|
33
|
+
# Access repository through UnitOfWork
|
|
34
|
+
user = await ctx.uow.users.get(user_id)
|
|
35
|
+
return user
|
|
36
|
+
|
|
37
|
+
# With explicit repository method
|
|
38
|
+
@app.get("/orders")
|
|
39
|
+
async def list_orders(ctx: RequestContext = Depends()):
|
|
40
|
+
repo = ctx.uow.repository("orders", Order)
|
|
41
|
+
return await repo.find({"status": "pending"})
|
|
42
|
+
|
|
43
|
+
Repository Naming Convention:
|
|
44
|
+
- Attribute access uses collection name: uow.users -> users collection
|
|
45
|
+
- The entity class defaults to Entity if not registered
|
|
46
|
+
- Register entity classes for type-safe repositories
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
db: Any, # ScopedMongoWrapper - avoid import cycle
|
|
52
|
+
entity_registry: Optional[Dict[str, Type[Entity]]] = None,
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Initialize the Unit of Work.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
db: ScopedMongoWrapper for database access
|
|
59
|
+
entity_registry: Optional mapping of collection names to entity classes
|
|
60
|
+
"""
|
|
61
|
+
self._db = db
|
|
62
|
+
self._repositories: Dict[str, Repository] = {}
|
|
63
|
+
self._entity_registry: Dict[str, Type[Entity]] = entity_registry or {}
|
|
64
|
+
|
|
65
|
+
def register_entity(self, collection_name: str, entity_class: Type[Entity]) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Register an entity class for a collection.
|
|
68
|
+
|
|
69
|
+
This enables type-safe repository access.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
collection_name: Name of the collection
|
|
73
|
+
entity_class: Entity subclass for this collection
|
|
74
|
+
"""
|
|
75
|
+
self._entity_registry[collection_name] = entity_class
|
|
76
|
+
|
|
77
|
+
def repository(
|
|
78
|
+
self,
|
|
79
|
+
name: str,
|
|
80
|
+
entity_class: Optional[Type[T]] = None,
|
|
81
|
+
) -> Repository[T]:
|
|
82
|
+
"""
|
|
83
|
+
Get or create a repository for a collection.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
name: Collection name
|
|
87
|
+
entity_class: Optional entity class override
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Repository instance for the collection
|
|
91
|
+
"""
|
|
92
|
+
if name in self._repositories:
|
|
93
|
+
return self._repositories[name]
|
|
94
|
+
|
|
95
|
+
# Determine entity class
|
|
96
|
+
if entity_class is None:
|
|
97
|
+
entity_class = self._entity_registry.get(name, Entity)
|
|
98
|
+
|
|
99
|
+
# Get collection from db wrapper
|
|
100
|
+
collection = getattr(self._db, name)
|
|
101
|
+
|
|
102
|
+
# Create repository
|
|
103
|
+
repo = MongoRepository(collection, entity_class)
|
|
104
|
+
self._repositories[name] = repo
|
|
105
|
+
|
|
106
|
+
logger.debug(f"Created repository for '{name}' with entity {entity_class.__name__}")
|
|
107
|
+
return repo
|
|
108
|
+
|
|
109
|
+
def __getattr__(self, name: str) -> Repository:
|
|
110
|
+
"""
|
|
111
|
+
Access repositories via attribute syntax.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
uow.users # Returns Repository for 'users' collection
|
|
115
|
+
uow.orders # Returns Repository for 'orders' collection
|
|
116
|
+
"""
|
|
117
|
+
# Prevent recursion on private attributes
|
|
118
|
+
if name.startswith("_"):
|
|
119
|
+
raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")
|
|
120
|
+
|
|
121
|
+
return self.repository(name)
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def db(self) -> Any:
|
|
125
|
+
"""
|
|
126
|
+
Direct access to the underlying ScopedMongoWrapper.
|
|
127
|
+
|
|
128
|
+
Use this for operations not covered by the Repository interface,
|
|
129
|
+
like complex aggregations or raw queries.
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
# Complex aggregation
|
|
133
|
+
pipeline = [{"$match": {...}}, {"$group": {...}}]
|
|
134
|
+
results = await ctx.uow.db.users.aggregate(pipeline).to_list(None)
|
|
135
|
+
"""
|
|
136
|
+
return self._db
|
|
137
|
+
|
|
138
|
+
def dispose(self) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Dispose of the UnitOfWork and clear cached repositories.
|
|
141
|
+
|
|
142
|
+
Called automatically at the end of a request scope.
|
|
143
|
+
"""
|
|
144
|
+
self._repositories.clear()
|
|
145
|
+
logger.debug("UnitOfWork disposed")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class TypedUnitOfWork(UnitOfWork, Generic[T]):
|
|
149
|
+
"""
|
|
150
|
+
Generic typed UnitOfWork for better IDE support.
|
|
151
|
+
|
|
152
|
+
This is a convenience class that provides type hints for specific
|
|
153
|
+
repository types.
|
|
154
|
+
|
|
155
|
+
Usage:
|
|
156
|
+
class MyUnitOfWork(TypedUnitOfWork):
|
|
157
|
+
@property
|
|
158
|
+
def users(self) -> Repository[User]:
|
|
159
|
+
return self.repository("users", User)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def orders(self) -> Repository[Order]:
|
|
163
|
+
return self.repository("orders", Order)
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
pass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mdb-engine
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: MongoDB Engine
|
|
5
5
|
Home-page: https://github.com/ranfysvalle02/mdb-engine
|
|
6
6
|
Author: Your Name
|
|
@@ -85,7 +85,9 @@ pip install mdb-engine
|
|
|
85
85
|
|
|
86
86
|
```python
|
|
87
87
|
from pathlib import Path
|
|
88
|
+
from fastapi import Depends
|
|
88
89
|
from mdb_engine import MongoDBEngine
|
|
90
|
+
from mdb_engine.dependencies import get_scoped_db
|
|
89
91
|
|
|
90
92
|
# 1. Initialize the engine
|
|
91
93
|
engine = MongoDBEngine(
|
|
@@ -96,10 +98,9 @@ engine = MongoDBEngine(
|
|
|
96
98
|
# 2. Create a FastAPI app with automatic lifecycle management
|
|
97
99
|
app = engine.create_app(slug="my_app", manifest=Path("manifest.json"))
|
|
98
100
|
|
|
99
|
-
# 3. Use
|
|
101
|
+
# 3. Use request-scoped dependencies - all queries automatically isolated
|
|
100
102
|
@app.post("/tasks")
|
|
101
|
-
async def create_task(task: dict):
|
|
102
|
-
db = engine.get_scoped_db("my_app")
|
|
103
|
+
async def create_task(task: dict, db=Depends(get_scoped_db)):
|
|
103
104
|
result = await db.tasks.insert_one(task)
|
|
104
105
|
return {"id": str(result.inserted_id)}
|
|
105
106
|
```
|
|
@@ -149,22 +150,29 @@ Supported index types: `regular`, `text`, `vector`, `ttl`, `compound`.
|
|
|
149
150
|
|
|
150
151
|
### 2. CRUD Operations (Auto-Scoped)
|
|
151
152
|
|
|
152
|
-
All database operations are automatically scoped to your app:
|
|
153
|
+
All database operations are automatically scoped to your app. Use `Depends(get_scoped_db)` in route handlers:
|
|
153
154
|
|
|
154
155
|
```python
|
|
155
|
-
|
|
156
|
+
from mdb_engine.dependencies import get_scoped_db
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
@app.post("/tasks")
|
|
159
|
+
async def create_task(task: dict, db=Depends(get_scoped_db)):
|
|
160
|
+
result = await db.tasks.insert_one(task)
|
|
161
|
+
return {"id": str(result.inserted_id)}
|
|
159
162
|
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
@app.get("/tasks")
|
|
164
|
+
async def list_tasks(db=Depends(get_scoped_db)):
|
|
165
|
+
return await db.tasks.find({"status": "pending"}).to_list(length=10)
|
|
162
166
|
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
@app.put("/tasks/{task_id}")
|
|
168
|
+
async def update_task(task_id: str, db=Depends(get_scoped_db)):
|
|
169
|
+
await db.tasks.update_one({"_id": task_id}, {"$set": {"status": "done"}})
|
|
170
|
+
return {"updated": True}
|
|
165
171
|
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
@app.delete("/tasks/{task_id}")
|
|
173
|
+
async def delete_task(task_id: str, db=Depends(get_scoped_db)):
|
|
174
|
+
await db.tasks.delete_one({"_id": task_id})
|
|
175
|
+
return {"deleted": True}
|
|
168
176
|
```
|
|
169
177
|
|
|
170
178
|
**What happens under the hood:**
|
|
@@ -210,6 +218,26 @@ async def health():
|
|
|
210
218
|
| **Multi-App** | Secure cross-app data access | [Multi-App Example](https://github.com/ranfysvalle02/mdb-engine/tree/main/examples/advanced/multi_app) |
|
|
211
219
|
| **SSO** | Shared auth across apps | [Shared Auth Example](https://github.com/ranfysvalle02/mdb-engine/tree/main/examples/advanced/multi_app_shared) |
|
|
212
220
|
|
|
221
|
+
### AppContext — All Services in One Place ✨
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from fastapi import Depends
|
|
225
|
+
from mdb_engine.dependencies import AppContext
|
|
226
|
+
|
|
227
|
+
@app.post("/ai-chat")
|
|
228
|
+
async def chat(query: str, ctx: AppContext = Depends()):
|
|
229
|
+
user = ctx.require_user() # 401 if not logged in
|
|
230
|
+
ctx.require_role("user") # 403 if missing role
|
|
231
|
+
|
|
232
|
+
# Everything available: ctx.db, ctx.embedding_service, ctx.memory, ctx.llm
|
|
233
|
+
if ctx.llm:
|
|
234
|
+
response = ctx.llm.chat.completions.create(
|
|
235
|
+
model=ctx.llm_model,
|
|
236
|
+
messages=[{"role": "user", "content": query}]
|
|
237
|
+
)
|
|
238
|
+
return {"response": response.choices[0].message.content}
|
|
239
|
+
```
|
|
240
|
+
|
|
213
241
|
---
|
|
214
242
|
|
|
215
243
|
## Full Examples
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
mdb_engine/README.md,sha256=T3EFGcPopY9LslYW3lxgG3hohWkAOmBNbYG0FDMUJiY,3502
|
|
2
|
-
mdb_engine/__init__.py,sha256
|
|
2
|
+
mdb_engine/__init__.py,sha256=-W3Gdj2GAoptKsR_T4xluYnmnelZTdKrTxCthIytZ48,3008
|
|
3
3
|
mdb_engine/config.py,sha256=EsB0PHrRCCt24db6Ly88s6PQ3sm1esSLMa2G4AHpt5k,7315
|
|
4
4
|
mdb_engine/constants.py,sha256=eaotvW57TVOg7rRbLziGrVNoP7adgw_G9iVByHezc_A,7837
|
|
5
|
+
mdb_engine/dependencies.py,sha256=bkKwYz6CnucDNDcXdxTvRCqmQpsWbAXQFf5w-AcKMH8,14107
|
|
5
6
|
mdb_engine/exceptions.py,sha256=N8WYOgh1fOH0yJeR9I3FTXqpACIL_EQX3zcfUgrFvPI,8521
|
|
6
|
-
mdb_engine/auth/
|
|
7
|
-
mdb_engine/auth/
|
|
7
|
+
mdb_engine/auth/ARCHITECTURE.md,sha256=JXZsjEIpNz4Szk18KaOiEabhEuz1pmYWXQRN-EJEHDM,4076
|
|
8
|
+
mdb_engine/auth/README.md,sha256=STL4PHyMlYpSNEAqo4RBKTEm4kz1GbWpI9c_lS94nV4,36802
|
|
9
|
+
mdb_engine/auth/__init__.py,sha256=QemWqAVvqlxjF-UbzMTGP5AUOeuaAsUEtK4PMxmozmA,5852
|
|
8
10
|
mdb_engine/auth/audit.py,sha256=LtdG9GdwYTSMI30zMygB0K8w4l179GaGvkNbxWgoQ2I,17461
|
|
9
|
-
mdb_engine/auth/
|
|
11
|
+
mdb_engine/auth/base.py,sha256=tIcC30_ViVuhmR8PCpY-MYBzu6Y9Ak1DZkAQishQyMg,7434
|
|
12
|
+
mdb_engine/auth/casbin_factory.py,sha256=66unyPRqNPydi0Bln8NNpsPt8gMwl7Hwc_nEsqfJj9U,16633
|
|
10
13
|
mdb_engine/auth/casbin_models.py,sha256=7XtFmRBhhjw1nKprnluvjyJoTj5fzdPeQwVvo6fI-r0,955
|
|
11
14
|
mdb_engine/auth/config_defaults.py,sha256=7JYX8CFI7OP7GHFIDHTf3Jns09LJcQ7f0_GL9yBnyuA,2018
|
|
12
15
|
mdb_engine/auth/config_helpers.py,sha256=DpQ8QZAO2FVVKFmhTJIa4FYPS8Iy8O7vcVh_tg0INLg,6309
|
|
13
16
|
mdb_engine/auth/cookie_utils.py,sha256=F88p6_aMG0aSmGXCn0KYjweSMPHqfce1lrKHU4P5yQk,4913
|
|
14
17
|
mdb_engine/auth/csrf.py,sha256=Rx9qYt8owSJJwwj7Z_w4nzkaLbaqWoVpNv13qnqosIA,12458
|
|
15
18
|
mdb_engine/auth/decorators.py,sha256=47uyOK_6WmK18J6ELRKwwgaIkcp6kHOILXuARj82Ijw,12288
|
|
16
|
-
mdb_engine/auth/dependencies.py,sha256
|
|
19
|
+
mdb_engine/auth/dependencies.py,sha256=-JooBflCsyndw8oUJ864nX01QOX9Pq09KQhY43oaSJk,27156
|
|
17
20
|
mdb_engine/auth/helpers.py,sha256=BCrid985cYh-3h5ZMUV9TES0q40uJXio4oYKQZta7KA,1970
|
|
18
|
-
mdb_engine/auth/integration.py,sha256=
|
|
21
|
+
mdb_engine/auth/integration.py,sha256=zaTeo4ZXnxnTbw6iZCawZ6dDXDQQCaX6XYc6cbRw6xs,22759
|
|
19
22
|
mdb_engine/auth/jwt.py,sha256=aRXLfgFjsebIARmPqgfcA5zSgSN44HD7BgSyOEFaQCM,7244
|
|
20
23
|
mdb_engine/auth/middleware.py,sha256=uMx1dOO1NaR4K2nDet3k_ASR7rc7ZbKD0HlEkVYGJvU,10869
|
|
21
|
-
mdb_engine/auth/oso_factory.py,sha256=
|
|
22
|
-
mdb_engine/auth/provider.py,sha256=
|
|
24
|
+
mdb_engine/auth/oso_factory.py,sha256=FPDzL5zYrFWd--amBoqKk_1FHET8QzlmiYTLinZeezU,11289
|
|
25
|
+
mdb_engine/auth/provider.py,sha256=m7G1rMgMezalALcV_74Kb0G77exyAIP-eOGyI2XfApc,26595
|
|
23
26
|
mdb_engine/auth/rate_limiter.py,sha256=kYuYIDlgkmYB2jx_UUmM9VrndUq2iTkdP7DZV-Jfu8o,15825
|
|
24
27
|
mdb_engine/auth/restrictions.py,sha256=TXa2lO3lZQeMtokhyVLP19-XbWwq2R8JMTw2KfaiDMg,8852
|
|
25
28
|
mdb_engine/auth/session_manager.py,sha256=XjYeE4i1998tBIefhp8VH3Oe7reyA_CC_qjw6zZ5EVY,15459
|
|
@@ -43,23 +46,27 @@ mdb_engine/core/app_registration.py,sha256=zRK2JTeT0rDW4XdOgFAV-j9Og7xijXdov4u1V
|
|
|
43
46
|
mdb_engine/core/app_secrets.py,sha256=ipLsC_QdnIFhrCLNQ3nXvBAfndSor4WNRJyqPc0-J8Q,10555
|
|
44
47
|
mdb_engine/core/connection.py,sha256=9WRccQ2Zz8K6PS6yC2XXxhZkppALvAWZJhfLmsJBnbI,8383
|
|
45
48
|
mdb_engine/core/encryption.py,sha256=jblhUuu_L_lht0qlbn5vqDsdj9rN80uL9DCFDCD-zxw,7540
|
|
46
|
-
mdb_engine/core/engine.py,sha256=
|
|
49
|
+
mdb_engine/core/engine.py,sha256=YmsoTNqMaLi4kcUFySSpCQZ1O8Fd41YryhwubfMfc0g,69566
|
|
47
50
|
mdb_engine/core/index_management.py,sha256=9-r7MIy3JnjQ35sGqsbj8K_I07vAUWtAVgSWC99lJcE,5555
|
|
48
|
-
mdb_engine/core/manifest.py,sha256=
|
|
51
|
+
mdb_engine/core/manifest.py,sha256=4Dk7iDOoxOrpZ20BA2onaX6cqMhWVSIA0bryeKPsXDQ,133677
|
|
49
52
|
mdb_engine/core/ray_integration.py,sha256=Qe3jAYQhOKeBBlWFAcqzciDWN2zctJh_6uMTIai1tS0,13617
|
|
50
53
|
mdb_engine/core/seeding.py,sha256=I5g43xdoEdC4rNwc_Ih0L6QOZguPcpAWrS6HvJ2a3fE,6433
|
|
51
54
|
mdb_engine/core/service_initialization.py,sha256=gJNIB0As4vg6UXRgt1EDo5p3zzlHfF8NZqjCAlB75mA,12997
|
|
52
55
|
mdb_engine/core/types.py,sha256=v_lSnBTyl2JHX39cPvPsnOxXhu8xaGC92UektvYPOvg,11239
|
|
53
|
-
mdb_engine/database/README.md,sha256
|
|
56
|
+
mdb_engine/database/README.md,sha256=-31mVxBeVQaYsF3AD1-gQbD2NCYVcPjdFoA6sZ6b02Y,19354
|
|
54
57
|
mdb_engine/database/__init__.py,sha256=rrc3eZFli3K2zrvVdDbMBi8YkmoHYzP6JNT0AUBE5VU,981
|
|
55
58
|
mdb_engine/database/abstraction.py,sha256=FLVhFuS4OTZIRQyDkUBZdIlhqRndqNA89EO0cJvNsN0,23462
|
|
56
59
|
mdb_engine/database/connection.py,sha256=PDmwkjb4wYhLTUjlcYHscorces69rIuSxt5wdYixGQw,13777
|
|
57
60
|
mdb_engine/database/query_validator.py,sha256=AIWkQ-c6HAqJ-ixn-hq_0yfC2vdf1-jbEpzYZNmfEqs,13275
|
|
58
61
|
mdb_engine/database/resource_limiter.py,sha256=n54-I6bjEk5jxNGMXyb6_fArDCiyJ69_h9goFW5GzUs,6936
|
|
59
62
|
mdb_engine/database/scoped_wrapper.py,sha256=S_QIqGVyCteKAYlSUHNIqqc0S80jWhzHV-Lfa-ezNG4,94794
|
|
60
|
-
mdb_engine/
|
|
61
|
-
mdb_engine/
|
|
62
|
-
mdb_engine/
|
|
63
|
+
mdb_engine/di/__init__.py,sha256=uy9Q153a0QE678TkJ_AEwLuxPY3OyEum2I42_JsuwrI,912
|
|
64
|
+
mdb_engine/di/container.py,sha256=Wf4-O69Kg_hLC3aHmAf847K7p0sHLh4aYKMGOTmvzok,7441
|
|
65
|
+
mdb_engine/di/providers.py,sha256=jCgez_w1NkfAHCa6O3tsoGHiKcZt-fAbmgvn7KXmTCU,5940
|
|
66
|
+
mdb_engine/di/scopes.py,sha256=0pNzm2I5aRr7H_IBCRhcwd4dW4LzwD4uC2PmWyZI1Ps,4187
|
|
67
|
+
mdb_engine/embeddings/README.md,sha256=XjGWUXSdbSpXCAEEVfYmcJoFKBVmXDoM3eTjenrM5Z0,5926
|
|
68
|
+
mdb_engine/embeddings/__init__.py,sha256=BM8tfcekVZv_iPQj3OuZBQ3hHr0eRQetNLEMysRTZkE,2129
|
|
69
|
+
mdb_engine/embeddings/dependencies.py,sha256=BphKvHYF5iYo6k7v4rYkEOYKZCtlGsPfMdrT6v1J430,2195
|
|
63
70
|
mdb_engine/embeddings/service.py,sha256=v5Ch4VMADDUw-tqNs8XbaG9JJktvO7cqniNJ8Mes56k,25991
|
|
64
71
|
mdb_engine/indexes/README.md,sha256=r7duq-1vtqHhBk1cwoBMYYh_dfTzxiaQaPE3mLB_3JQ,15341
|
|
65
72
|
mdb_engine/indexes/__init__.py,sha256=9QFJ6qo_yD26dZcKyKjj-hhesFpaomBt-mWTtYTQvqc,613
|
|
@@ -73,13 +80,17 @@ mdb_engine/observability/__init__.py,sha256=jjLsrW6Gy2ayrbfLrgHsDB61NxWWkYLHwv0q
|
|
|
73
80
|
mdb_engine/observability/health.py,sha256=kZ9LXcJ3_8tKXRWXNNCz3qsj0PkZ0BSTdzisc8ds2vw,9197
|
|
74
81
|
mdb_engine/observability/logging.py,sha256=yo_KnUtqjPx_KvqNrAxW8ud6HQjI7Lk6T7Lj7IMW1uY,4073
|
|
75
82
|
mdb_engine/observability/metrics.py,sha256=EL9-ZFOaxxIdU8PWIqPRyPZHhP3fc4VyUawRSS2GIY4,10726
|
|
83
|
+
mdb_engine/repositories/__init__.py,sha256=048N4QmFLVhJyLdYrWHTTBqujhaF--B8g0z6fUSMuJs,858
|
|
84
|
+
mdb_engine/repositories/base.py,sha256=-Llwhn5NxEh-5S6CJAzZx1nF6J2Sch0WxIpMaaPQhs4,8575
|
|
85
|
+
mdb_engine/repositories/mongo.py,sha256=aR_JFDYtWIZiZA616tDbOPrLN_Gi8diD6Hc9UfeaT-w,7177
|
|
86
|
+
mdb_engine/repositories/unit_of_work.py,sha256=obTsGCcZgsNhQdFOvNGLtvq-guyykmPZzBo-GM_ROAo,5146
|
|
76
87
|
mdb_engine/routing/README.md,sha256=WVvTQXDq0amryrjkCu0wP_piOEwFjLukjmPz2mroWHY,13658
|
|
77
88
|
mdb_engine/routing/__init__.py,sha256=reupjHi_RTc2ZBA4AH5XzobAmqy4EQIsfSUcTkFknUM,2438
|
|
78
89
|
mdb_engine/routing/websockets.py,sha256=X6-MG0mAN8ZEgdmZD8edaiDx8DqMCN-hV3coIOdgZNc,29346
|
|
79
90
|
mdb_engine/utils/__init__.py,sha256=_xjHB5p6WLWBql1DyDqf5zdjj2xpfMlK25Y6BH9-oFk,145
|
|
80
|
-
mdb_engine-0.1.
|
|
81
|
-
mdb_engine-0.1.
|
|
82
|
-
mdb_engine-0.1.
|
|
83
|
-
mdb_engine-0.1.
|
|
84
|
-
mdb_engine-0.1.
|
|
85
|
-
mdb_engine-0.1.
|
|
91
|
+
mdb_engine-0.2.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
92
|
+
mdb_engine-0.2.1.dist-info/METADATA,sha256=LaeDnv88ajuRpA0YnOwmMNDSF_4BSRmy-AoxsmFC80U,10470
|
|
93
|
+
mdb_engine-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
94
|
+
mdb_engine-0.2.1.dist-info/entry_points.txt,sha256=INCbYdFbBzJalwPwxliEzLmPfR57IvQ7RAXG_pn8cL8,48
|
|
95
|
+
mdb_engine-0.2.1.dist-info/top_level.txt,sha256=PH0UEBwTtgkm2vWvC9He_EOMn7hVn_Wg_Jyc0SmeO8k,11
|
|
96
|
+
mdb_engine-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|