mdb-engine 0.2.4__tar.gz → 0.3.1__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.
- {mdb_engine-0.2.4/mdb_engine.egg-info → mdb_engine-0.3.1}/PKG-INFO +127 -23
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/README.md +126 -22
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/__init__.py +1 -1
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/shared_middleware.py +18 -2
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/engine.py +417 -5
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/manifest.py +93 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1/mdb_engine.egg-info}/PKG-INFO +127 -23
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/pyproject.toml +1 -1
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/setup.py +1 -1
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/LICENSE +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/MANIFEST.in +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/README.md +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/ARCHITECTURE.md +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/README.md +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/audit.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/base.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/casbin_factory.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/casbin_models.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/config_defaults.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/config_helpers.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/cookie_utils.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/csrf.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/decorators.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/dependencies.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/helpers.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/integration.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/jwt.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/middleware.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/oso_factory.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/provider.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/rate_limiter.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/restrictions.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/session_manager.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/shared_users.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/token_lifecycle.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/token_store.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/users.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/auth/utils.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/cli/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/cli/commands/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/cli/commands/generate.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/cli/commands/migrate.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/cli/commands/show.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/cli/commands/validate.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/cli/main.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/cli/utils.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/config.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/constants.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/README.md +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/app_registration.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/app_secrets.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/connection.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/encryption.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/index_management.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/ray_integration.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/seeding.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/service_initialization.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/core/types.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/database/README.md +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/database/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/database/abstraction.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/database/connection.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/database/query_validator.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/database/resource_limiter.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/database/scoped_wrapper.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/dependencies.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/di/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/di/container.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/di/providers.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/di/scopes.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/embeddings/README.md +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/embeddings/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/embeddings/dependencies.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/embeddings/service.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/exceptions.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/indexes/README.md +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/indexes/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/indexes/helpers.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/indexes/manager.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/memory/README.md +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/memory/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/memory/service.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/observability/README.md +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/observability/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/observability/health.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/observability/logging.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/observability/metrics.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/repositories/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/repositories/base.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/repositories/mongo.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/repositories/unit_of_work.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/routing/README.md +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/routing/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/routing/websockets.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/utils/__init__.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine/utils/mongo.py +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine.egg-info/SOURCES.txt +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine.egg-info/dependency_links.txt +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine.egg-info/entry_points.txt +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine.egg-info/requires.txt +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/mdb_engine.egg-info/top_level.txt +0 -0
- {mdb_engine-0.2.4 → mdb_engine-0.3.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mdb-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: MongoDB Engine
|
|
5
5
|
Home-page: https://github.com/ranfysvalle02/mdb-engine
|
|
6
6
|
Author: Fabian Valle
|
|
@@ -106,58 +106,162 @@ pip install mdb-engine
|
|
|
106
106
|
|
|
107
107
|
---
|
|
108
108
|
|
|
109
|
-
## 30-Second Quick Start
|
|
109
|
+
## 30-Second Quick Start: Build a Todo List API
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
Let's build a complete CRUD todo list app in 3 steps!
|
|
112
|
+
|
|
113
|
+
### Step 1: Create `manifest.json`
|
|
112
114
|
|
|
113
115
|
```json
|
|
114
116
|
{
|
|
115
117
|
"schema_version": "2.0",
|
|
116
|
-
"slug": "
|
|
117
|
-
"name": "
|
|
118
|
+
"slug": "todo_app",
|
|
119
|
+
"name": "Todo List App",
|
|
118
120
|
"managed_indexes": {
|
|
119
|
-
"
|
|
121
|
+
"todos": [
|
|
120
122
|
{
|
|
121
123
|
"type": "regular",
|
|
122
|
-
"keys": {"
|
|
123
|
-
"name": "
|
|
124
|
+
"keys": {"completed": 1, "created_at": -1},
|
|
125
|
+
"name": "completed_sort"
|
|
124
126
|
}
|
|
125
127
|
]
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
```
|
|
129
131
|
|
|
130
|
-
|
|
132
|
+
### Step 2: Create `app.py` with Full CRUD
|
|
131
133
|
|
|
132
134
|
```python
|
|
135
|
+
from datetime import datetime
|
|
133
136
|
from pathlib import Path
|
|
134
|
-
from
|
|
137
|
+
from typing import Optional
|
|
138
|
+
|
|
139
|
+
from bson import ObjectId
|
|
140
|
+
from fastapi import Depends, HTTPException
|
|
141
|
+
from pydantic import BaseModel
|
|
142
|
+
|
|
135
143
|
from mdb_engine import MongoDBEngine
|
|
136
144
|
from mdb_engine.dependencies import get_scoped_db
|
|
137
145
|
|
|
138
|
-
# Initialize
|
|
146
|
+
# Initialize engine
|
|
139
147
|
engine = MongoDBEngine(
|
|
140
148
|
mongo_uri="mongodb://localhost:27017",
|
|
141
149
|
db_name="my_database"
|
|
142
150
|
)
|
|
143
151
|
|
|
144
|
-
# Create app - manifest.json
|
|
145
|
-
app = engine.create_app(
|
|
152
|
+
# Create app - manifest.json loaded automatically!
|
|
153
|
+
app = engine.create_app(
|
|
154
|
+
slug="todo_app",
|
|
155
|
+
manifest=Path("manifest.json")
|
|
156
|
+
)
|
|
146
157
|
|
|
147
|
-
#
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
158
|
+
# Pydantic models
|
|
159
|
+
class TodoCreate(BaseModel):
|
|
160
|
+
title: str
|
|
161
|
+
description: Optional[str] = None
|
|
162
|
+
|
|
163
|
+
class TodoUpdate(BaseModel):
|
|
164
|
+
title: Optional[str] = None
|
|
165
|
+
description: Optional[str] = None
|
|
166
|
+
completed: Optional[bool] = None
|
|
167
|
+
|
|
168
|
+
# CREATE - Add a new todo
|
|
169
|
+
@app.post("/todos")
|
|
170
|
+
async def create_todo(todo: TodoCreate, db=Depends(get_scoped_db)):
|
|
171
|
+
doc = {
|
|
172
|
+
**todo.dict(),
|
|
173
|
+
"completed": False,
|
|
174
|
+
"created_at": datetime.utcnow()
|
|
175
|
+
}
|
|
176
|
+
result = await db.todos.insert_one(doc)
|
|
177
|
+
return {"id": str(result.inserted_id), "message": "Todo created"}
|
|
178
|
+
|
|
179
|
+
# READ - List all todos
|
|
180
|
+
@app.get("/todos")
|
|
181
|
+
async def list_todos(completed: Optional[bool] = None, db=Depends(get_scoped_db)):
|
|
182
|
+
query = {}
|
|
183
|
+
if completed is not None:
|
|
184
|
+
query["completed"] = completed
|
|
185
|
+
|
|
186
|
+
todos = await db.todos.find(query).sort("created_at", -1).to_list(length=100)
|
|
187
|
+
for todo in todos:
|
|
188
|
+
todo["_id"] = str(todo["_id"])
|
|
189
|
+
return {"todos": todos, "count": len(todos)}
|
|
190
|
+
|
|
191
|
+
# READ - Get single todo
|
|
192
|
+
@app.get("/todos/{todo_id}")
|
|
193
|
+
async def get_todo(todo_id: str, db=Depends(get_scoped_db)):
|
|
194
|
+
todo = await db.todos.find_one({"_id": ObjectId(todo_id)})
|
|
195
|
+
if not todo:
|
|
196
|
+
raise HTTPException(status_code=404, detail="Todo not found")
|
|
197
|
+
todo["_id"] = str(todo["_id"])
|
|
198
|
+
return todo
|
|
199
|
+
|
|
200
|
+
# UPDATE - Update a todo
|
|
201
|
+
@app.put("/todos/{todo_id}")
|
|
202
|
+
async def update_todo(todo_id: str, todo: TodoUpdate, db=Depends(get_scoped_db)):
|
|
203
|
+
updates = {k: v for k, v in todo.dict(exclude_unset=True).items() if v is not None}
|
|
204
|
+
if not updates:
|
|
205
|
+
raise HTTPException(status_code=400, detail="No fields to update")
|
|
206
|
+
|
|
207
|
+
updates["updated_at"] = datetime.utcnow()
|
|
208
|
+
result = await db.todos.update_one(
|
|
209
|
+
{"_id": ObjectId(todo_id)},
|
|
210
|
+
{"$set": updates}
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if result.matched_count == 0:
|
|
214
|
+
raise HTTPException(status_code=404, detail="Todo not found")
|
|
215
|
+
return {"message": "Todo updated"}
|
|
216
|
+
|
|
217
|
+
# DELETE - Delete a todo
|
|
218
|
+
@app.delete("/todos/{todo_id}")
|
|
219
|
+
async def delete_todo(todo_id: str, db=Depends(get_scoped_db)):
|
|
220
|
+
result = await db.todos.delete_one({"_id": ObjectId(todo_id)})
|
|
221
|
+
if result.deleted_count == 0:
|
|
222
|
+
raise HTTPException(status_code=404, detail="Todo not found")
|
|
223
|
+
return {"message": "Todo deleted"}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Step 3: Run It!
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Start MongoDB (if not running)
|
|
230
|
+
mongod
|
|
231
|
+
|
|
232
|
+
# Install dependencies
|
|
233
|
+
pip install mdb-engine fastapi uvicorn
|
|
234
|
+
|
|
235
|
+
# Run the app
|
|
236
|
+
uvicorn app:app --reload
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Test your API:**
|
|
240
|
+
```bash
|
|
241
|
+
# Create a todo
|
|
242
|
+
curl -X POST http://localhost:8000/todos \
|
|
243
|
+
-H "Content-Type: application/json" \
|
|
244
|
+
-d '{"title": "Buy groceries", "description": "Milk and eggs"}'
|
|
245
|
+
|
|
246
|
+
# List todos
|
|
247
|
+
curl http://localhost:8000/todos
|
|
248
|
+
|
|
249
|
+
# Update a todo (replace {id} with actual ID)
|
|
250
|
+
curl -X PUT http://localhost:8000/todos/{id} \
|
|
251
|
+
-H "Content-Type: application/json" \
|
|
252
|
+
-d '{"completed": true}'
|
|
253
|
+
|
|
254
|
+
# Delete a todo
|
|
255
|
+
curl -X DELETE http://localhost:8000/todos/{id}
|
|
152
256
|
```
|
|
153
257
|
|
|
154
258
|
**What just happened?**
|
|
155
|
-
- ✅
|
|
156
|
-
- ✅ Indexes created
|
|
157
|
-
- ✅
|
|
158
|
-
- ✅
|
|
259
|
+
- ✅ **Automatic scoping**: All queries filtered by `app_id` — your data is isolated
|
|
260
|
+
- ✅ **Indexes created**: The `completed_sort` index was created automatically
|
|
261
|
+
- ✅ **Lifecycle managed**: Startup/shutdown handled automatically
|
|
262
|
+
- ✅ **Zero boilerplate**: No connection setup, no index scripts, no auth handlers
|
|
159
263
|
|
|
160
|
-
That's it
|
|
264
|
+
**That's it!** You now have a fully functional, production-ready todo API with automatic data sandboxing, index management, and lifecycle handling.
|
|
161
265
|
|
|
162
266
|
---
|
|
163
267
|
|
|
@@ -41,58 +41,162 @@ pip install mdb-engine
|
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
|
-
## 30-Second Quick Start
|
|
44
|
+
## 30-Second Quick Start: Build a Todo List API
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Let's build a complete CRUD todo list app in 3 steps!
|
|
47
|
+
|
|
48
|
+
### Step 1: Create `manifest.json`
|
|
47
49
|
|
|
48
50
|
```json
|
|
49
51
|
{
|
|
50
52
|
"schema_version": "2.0",
|
|
51
|
-
"slug": "
|
|
52
|
-
"name": "
|
|
53
|
+
"slug": "todo_app",
|
|
54
|
+
"name": "Todo List App",
|
|
53
55
|
"managed_indexes": {
|
|
54
|
-
"
|
|
56
|
+
"todos": [
|
|
55
57
|
{
|
|
56
58
|
"type": "regular",
|
|
57
|
-
"keys": {"
|
|
58
|
-
"name": "
|
|
59
|
+
"keys": {"completed": 1, "created_at": -1},
|
|
60
|
+
"name": "completed_sort"
|
|
59
61
|
}
|
|
60
62
|
]
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
### Step 2: Create `app.py` with Full CRUD
|
|
66
68
|
|
|
67
69
|
```python
|
|
70
|
+
from datetime import datetime
|
|
68
71
|
from pathlib import Path
|
|
69
|
-
from
|
|
72
|
+
from typing import Optional
|
|
73
|
+
|
|
74
|
+
from bson import ObjectId
|
|
75
|
+
from fastapi import Depends, HTTPException
|
|
76
|
+
from pydantic import BaseModel
|
|
77
|
+
|
|
70
78
|
from mdb_engine import MongoDBEngine
|
|
71
79
|
from mdb_engine.dependencies import get_scoped_db
|
|
72
80
|
|
|
73
|
-
# Initialize
|
|
81
|
+
# Initialize engine
|
|
74
82
|
engine = MongoDBEngine(
|
|
75
83
|
mongo_uri="mongodb://localhost:27017",
|
|
76
84
|
db_name="my_database"
|
|
77
85
|
)
|
|
78
86
|
|
|
79
|
-
# Create app - manifest.json
|
|
80
|
-
app = engine.create_app(
|
|
87
|
+
# Create app - manifest.json loaded automatically!
|
|
88
|
+
app = engine.create_app(
|
|
89
|
+
slug="todo_app",
|
|
90
|
+
manifest=Path("manifest.json")
|
|
91
|
+
)
|
|
81
92
|
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
# Pydantic models
|
|
94
|
+
class TodoCreate(BaseModel):
|
|
95
|
+
title: str
|
|
96
|
+
description: Optional[str] = None
|
|
97
|
+
|
|
98
|
+
class TodoUpdate(BaseModel):
|
|
99
|
+
title: Optional[str] = None
|
|
100
|
+
description: Optional[str] = None
|
|
101
|
+
completed: Optional[bool] = None
|
|
102
|
+
|
|
103
|
+
# CREATE - Add a new todo
|
|
104
|
+
@app.post("/todos")
|
|
105
|
+
async def create_todo(todo: TodoCreate, db=Depends(get_scoped_db)):
|
|
106
|
+
doc = {
|
|
107
|
+
**todo.dict(),
|
|
108
|
+
"completed": False,
|
|
109
|
+
"created_at": datetime.utcnow()
|
|
110
|
+
}
|
|
111
|
+
result = await db.todos.insert_one(doc)
|
|
112
|
+
return {"id": str(result.inserted_id), "message": "Todo created"}
|
|
113
|
+
|
|
114
|
+
# READ - List all todos
|
|
115
|
+
@app.get("/todos")
|
|
116
|
+
async def list_todos(completed: Optional[bool] = None, db=Depends(get_scoped_db)):
|
|
117
|
+
query = {}
|
|
118
|
+
if completed is not None:
|
|
119
|
+
query["completed"] = completed
|
|
120
|
+
|
|
121
|
+
todos = await db.todos.find(query).sort("created_at", -1).to_list(length=100)
|
|
122
|
+
for todo in todos:
|
|
123
|
+
todo["_id"] = str(todo["_id"])
|
|
124
|
+
return {"todos": todos, "count": len(todos)}
|
|
125
|
+
|
|
126
|
+
# READ - Get single todo
|
|
127
|
+
@app.get("/todos/{todo_id}")
|
|
128
|
+
async def get_todo(todo_id: str, db=Depends(get_scoped_db)):
|
|
129
|
+
todo = await db.todos.find_one({"_id": ObjectId(todo_id)})
|
|
130
|
+
if not todo:
|
|
131
|
+
raise HTTPException(status_code=404, detail="Todo not found")
|
|
132
|
+
todo["_id"] = str(todo["_id"])
|
|
133
|
+
return todo
|
|
134
|
+
|
|
135
|
+
# UPDATE - Update a todo
|
|
136
|
+
@app.put("/todos/{todo_id}")
|
|
137
|
+
async def update_todo(todo_id: str, todo: TodoUpdate, db=Depends(get_scoped_db)):
|
|
138
|
+
updates = {k: v for k, v in todo.dict(exclude_unset=True).items() if v is not None}
|
|
139
|
+
if not updates:
|
|
140
|
+
raise HTTPException(status_code=400, detail="No fields to update")
|
|
141
|
+
|
|
142
|
+
updates["updated_at"] = datetime.utcnow()
|
|
143
|
+
result = await db.todos.update_one(
|
|
144
|
+
{"_id": ObjectId(todo_id)},
|
|
145
|
+
{"$set": updates}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if result.matched_count == 0:
|
|
149
|
+
raise HTTPException(status_code=404, detail="Todo not found")
|
|
150
|
+
return {"message": "Todo updated"}
|
|
151
|
+
|
|
152
|
+
# DELETE - Delete a todo
|
|
153
|
+
@app.delete("/todos/{todo_id}")
|
|
154
|
+
async def delete_todo(todo_id: str, db=Depends(get_scoped_db)):
|
|
155
|
+
result = await db.todos.delete_one({"_id": ObjectId(todo_id)})
|
|
156
|
+
if result.deleted_count == 0:
|
|
157
|
+
raise HTTPException(status_code=404, detail="Todo not found")
|
|
158
|
+
return {"message": "Todo deleted"}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Step 3: Run It!
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Start MongoDB (if not running)
|
|
165
|
+
mongod
|
|
166
|
+
|
|
167
|
+
# Install dependencies
|
|
168
|
+
pip install mdb-engine fastapi uvicorn
|
|
169
|
+
|
|
170
|
+
# Run the app
|
|
171
|
+
uvicorn app:app --reload
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Test your API:**
|
|
175
|
+
```bash
|
|
176
|
+
# Create a todo
|
|
177
|
+
curl -X POST http://localhost:8000/todos \
|
|
178
|
+
-H "Content-Type: application/json" \
|
|
179
|
+
-d '{"title": "Buy groceries", "description": "Milk and eggs"}'
|
|
180
|
+
|
|
181
|
+
# List todos
|
|
182
|
+
curl http://localhost:8000/todos
|
|
183
|
+
|
|
184
|
+
# Update a todo (replace {id} with actual ID)
|
|
185
|
+
curl -X PUT http://localhost:8000/todos/{id} \
|
|
186
|
+
-H "Content-Type: application/json" \
|
|
187
|
+
-d '{"completed": true}'
|
|
188
|
+
|
|
189
|
+
# Delete a todo
|
|
190
|
+
curl -X DELETE http://localhost:8000/todos/{id}
|
|
87
191
|
```
|
|
88
192
|
|
|
89
193
|
**What just happened?**
|
|
90
|
-
- ✅
|
|
91
|
-
- ✅ Indexes created
|
|
92
|
-
- ✅
|
|
93
|
-
- ✅
|
|
194
|
+
- ✅ **Automatic scoping**: All queries filtered by `app_id` — your data is isolated
|
|
195
|
+
- ✅ **Indexes created**: The `completed_sort` index was created automatically
|
|
196
|
+
- ✅ **Lifecycle managed**: Startup/shutdown handled automatically
|
|
197
|
+
- ✅ **Zero boilerplate**: No connection setup, no index scripts, no auth handlers
|
|
94
198
|
|
|
95
|
-
That's it
|
|
199
|
+
**That's it!** You now have a fully functional, production-ready todo API with automatic data sandboxing, index management, and lifecycle handling.
|
|
96
200
|
|
|
97
201
|
---
|
|
98
202
|
|
|
@@ -81,7 +81,7 @@ from .repositories import Entity, MongoRepository, Repository, UnitOfWork
|
|
|
81
81
|
# Utilities
|
|
82
82
|
from .utils import clean_mongo_doc, clean_mongo_docs
|
|
83
83
|
|
|
84
|
-
__version__ = "0.
|
|
84
|
+
__version__ = "0.3.1" # Patch version bump: Fix public route matching for mounted apps
|
|
85
85
|
|
|
86
86
|
__all__ = [
|
|
87
87
|
# Core Engine
|
|
@@ -82,6 +82,22 @@ def _compute_fingerprint(request: Request) -> str:
|
|
|
82
82
|
return hashlib.sha256(fingerprint_string.encode()).hexdigest()
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
def _get_request_path(request: Request) -> str:
|
|
86
|
+
"""
|
|
87
|
+
Get the request path relative to the mount point.
|
|
88
|
+
|
|
89
|
+
For mounted apps (via create_multi_app), use request.scope["path"] which
|
|
90
|
+
contains the path relative to the mount point. For non-mounted apps,
|
|
91
|
+
fall back to request.url.path.
|
|
92
|
+
|
|
93
|
+
This ensures public routes in manifests (which are relative paths like "/")
|
|
94
|
+
match correctly when apps are mounted at prefixes like "/auth-hub".
|
|
95
|
+
"""
|
|
96
|
+
# Use scope["path"] which is relative to mount point for mounted apps
|
|
97
|
+
# Fall back to url.path for non-mounted apps
|
|
98
|
+
return request.scope.get("path", request.url.path)
|
|
99
|
+
|
|
100
|
+
|
|
85
101
|
class SharedAuthMiddleware(BaseHTTPMiddleware):
|
|
86
102
|
"""
|
|
87
103
|
Middleware for shared authentication across multi-app deployments.
|
|
@@ -169,7 +185,7 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
|
|
|
169
185
|
# However, for Lazy middleware, we want to skip if not initialized yet
|
|
170
186
|
return await call_next(request)
|
|
171
187
|
|
|
172
|
-
is_public = self._is_public_route(request
|
|
188
|
+
is_public = self._is_public_route(_get_request_path(request))
|
|
173
189
|
|
|
174
190
|
# Extract token from cookie or header
|
|
175
191
|
token = self._extract_token(request)
|
|
@@ -522,7 +538,7 @@ def _create_lazy_middleware_class(
|
|
|
522
538
|
)
|
|
523
539
|
return await call_next(request)
|
|
524
540
|
|
|
525
|
-
is_public = _is_public_route_helper(request
|
|
541
|
+
is_public = _is_public_route_helper(_get_request_path(request), self._public_routes)
|
|
526
542
|
token = _extract_token_helper(
|
|
527
543
|
request, self._cookie_name, self._header_name, self._header_prefix
|
|
528
544
|
)
|