mdb-engine 0.2.1__tar.gz → 0.2.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.
- {mdb_engine-0.2.1/mdb_engine.egg-info → mdb_engine-0.2.4}/PKG-INFO +88 -13
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/README.md +80 -5
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/__init__.py +7 -1
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/README.md +6 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/audit.py +40 -40
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/base.py +3 -3
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/casbin_factory.py +6 -6
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/config_defaults.py +5 -5
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/config_helpers.py +12 -12
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/cookie_utils.py +9 -9
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/csrf.py +9 -8
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/decorators.py +7 -6
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/dependencies.py +22 -21
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/integration.py +9 -9
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/jwt.py +9 -9
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/middleware.py +4 -3
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/oso_factory.py +6 -6
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/provider.py +4 -4
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/rate_limiter.py +12 -11
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/restrictions.py +16 -15
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/session_manager.py +11 -13
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/shared_middleware.py +344 -132
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/shared_users.py +20 -20
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/token_lifecycle.py +10 -12
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/token_store.py +4 -5
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/users.py +51 -52
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/utils.py +29 -33
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/commands/generate.py +6 -6
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/utils.py +4 -4
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/config.py +6 -7
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/app_registration.py +12 -12
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/app_secrets.py +1 -2
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/connection.py +3 -4
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/encryption.py +1 -2
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/engine.py +43 -44
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/manifest.py +80 -58
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/ray_integration.py +10 -9
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/seeding.py +3 -3
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/service_initialization.py +10 -9
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/types.py +40 -40
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/abstraction.py +15 -16
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/connection.py +40 -12
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/query_validator.py +8 -8
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/resource_limiter.py +7 -7
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/scoped_wrapper.py +51 -58
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/dependencies.py +14 -13
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/di/container.py +12 -13
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/di/providers.py +14 -13
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/di/scopes.py +5 -5
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/embeddings/dependencies.py +2 -2
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/embeddings/service.py +67 -50
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/exceptions.py +20 -20
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/indexes/helpers.py +11 -11
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/indexes/manager.py +9 -9
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/memory/README.md +93 -2
- mdb_engine-0.2.4/mdb_engine/memory/service.py +480 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/observability/health.py +10 -9
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/observability/logging.py +10 -10
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/observability/metrics.py +8 -7
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/repositories/base.py +25 -25
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/repositories/mongo.py +17 -17
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/repositories/unit_of_work.py +6 -6
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/routing/websockets.py +19 -18
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/utils/__init__.py +3 -1
- mdb_engine-0.2.4/mdb_engine/utils/mongo.py +117 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4/mdb_engine.egg-info}/PKG-INFO +88 -13
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine.egg-info/SOURCES.txt +2 -1
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine.egg-info/requires.txt +3 -2
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/pyproject.toml +10 -9
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/setup.py +6 -7
- mdb_engine-0.2.1/mdb_engine/memory/service.py +0 -1228
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/LICENSE +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/MANIFEST.in +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/README.md +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/ARCHITECTURE.md +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/casbin_models.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/helpers.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/commands/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/commands/migrate.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/commands/show.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/commands/validate.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/main.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/constants.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/README.md +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/index_management.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/README.md +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/di/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/embeddings/README.md +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/embeddings/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/indexes/README.md +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/indexes/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/memory/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/observability/README.md +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/observability/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/repositories/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/routing/README.md +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/routing/__init__.py +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine.egg-info/dependency_links.txt +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine.egg-info/entry_points.txt +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine.egg-info/top_level.txt +0 -0
- {mdb_engine-0.2.1 → mdb_engine-0.2.4}/setup.cfg +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mdb-engine
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: MongoDB Engine
|
|
5
5
|
Home-page: https://github.com/ranfysvalle02/mdb-engine
|
|
6
|
-
Author:
|
|
7
|
-
Author-email:
|
|
6
|
+
Author: Fabian Valle
|
|
7
|
+
Author-email: Fabian Valle <oblivio.company@gmail.com>
|
|
8
8
|
License: AGPL-3.0
|
|
9
9
|
Project-URL: Homepage, https://github.com/ranfysvalle02/mdb-engine
|
|
10
10
|
Project-URL: Documentation, https://github.com/ranfysvalle02/mdb-engine#readme
|
|
@@ -15,13 +15,12 @@ Classifier: Development Status :: 4 - Beta
|
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
21
|
Classifier: Topic :: Database
|
|
23
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.10
|
|
25
24
|
Description-Content-Type: text/markdown
|
|
26
25
|
License-File: LICENSE
|
|
27
26
|
Requires-Dist: motor>=3.0.0
|
|
@@ -32,7 +31,7 @@ Requires-Dist: pyjwt>=2.8.0
|
|
|
32
31
|
Requires-Dist: jsonschema>=4.0.0
|
|
33
32
|
Requires-Dist: bcrypt>=4.0.0
|
|
34
33
|
Requires-Dist: cryptography>=41.0.0
|
|
35
|
-
Requires-Dist: mem0ai>=1.
|
|
34
|
+
Requires-Dist: mem0ai>=0.1.7
|
|
36
35
|
Requires-Dist: semantic-text-splitter>=0.9.0
|
|
37
36
|
Requires-Dist: numpy<2.0.0,>=1.0.0
|
|
38
37
|
Requires-Dist: openai>=1.0.0
|
|
@@ -50,9 +49,10 @@ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
|
|
|
50
49
|
Requires-Dist: pytest-cov>=4.1.0; extra == "test"
|
|
51
50
|
Requires-Dist: pytest-mock>=3.11.0; extra == "test"
|
|
52
51
|
Requires-Dist: pytest-timeout>=2.1.0; extra == "test"
|
|
52
|
+
Requires-Dist: pytest-xdist>=3.3.0; extra == "test"
|
|
53
53
|
Requires-Dist: testcontainers>=3.7.0; extra == "test"
|
|
54
54
|
Provides-Extra: dev
|
|
55
|
-
Requires-Dist: ruff
|
|
55
|
+
Requires-Dist: ruff<0.6.0,>=0.4.0; extra == "dev"
|
|
56
56
|
Requires-Dist: semgrep>=1.50.0; extra == "dev"
|
|
57
57
|
Provides-Extra: all
|
|
58
58
|
Requires-Dist: casbin>=1.0.0; extra == "all"
|
|
@@ -73,6 +73,31 @@ Dynamic: requires-python
|
|
|
73
73
|
|
|
74
74
|
---
|
|
75
75
|
|
|
76
|
+
## 🎯 manifest.json: The Key to Everything
|
|
77
|
+
|
|
78
|
+
**`manifest.json` is the foundation of your application.** It's a single configuration file that defines your app's identity, data structure, authentication, indexes, and services. Everything flows from this file.
|
|
79
|
+
|
|
80
|
+
### Your First manifest.json
|
|
81
|
+
|
|
82
|
+
Create a `manifest.json` file with just 3 fields:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"schema_version": "2.0",
|
|
87
|
+
"slug": "my_app",
|
|
88
|
+
"name": "My App"
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
That's it! This minimal manifest gives you:
|
|
93
|
+
- ✅ Automatic data scoping (all queries filtered by `app_id`)
|
|
94
|
+
- ✅ Collection name prefixing (`db.tasks` → `my_app_tasks`)
|
|
95
|
+
- ✅ App registration and lifecycle management
|
|
96
|
+
|
|
97
|
+
**Learn more**: [Quick Start Guide](docs/QUICK_START.md) | [Manifest Deep Dive](docs/MANIFEST_DEEP_DIVE.md)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
76
101
|
## Installation
|
|
77
102
|
|
|
78
103
|
```bash
|
|
@@ -83,28 +108,55 @@ pip install mdb-engine
|
|
|
83
108
|
|
|
84
109
|
## 30-Second Quick Start
|
|
85
110
|
|
|
111
|
+
**Step 1**: Create your `manifest.json`:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"schema_version": "2.0",
|
|
116
|
+
"slug": "my_app",
|
|
117
|
+
"name": "My App",
|
|
118
|
+
"managed_indexes": {
|
|
119
|
+
"tasks": [
|
|
120
|
+
{
|
|
121
|
+
"type": "regular",
|
|
122
|
+
"keys": {"status": 1, "created_at": -1},
|
|
123
|
+
"name": "status_sort"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Step 2**: Create your FastAPI app:
|
|
131
|
+
|
|
86
132
|
```python
|
|
87
133
|
from pathlib import Path
|
|
88
134
|
from fastapi import Depends
|
|
89
135
|
from mdb_engine import MongoDBEngine
|
|
90
136
|
from mdb_engine.dependencies import get_scoped_db
|
|
91
137
|
|
|
92
|
-
#
|
|
138
|
+
# Initialize the engine
|
|
93
139
|
engine = MongoDBEngine(
|
|
94
140
|
mongo_uri="mongodb://localhost:27017",
|
|
95
141
|
db_name="my_database"
|
|
96
142
|
)
|
|
97
143
|
|
|
98
|
-
#
|
|
144
|
+
# Create app - manifest.json is loaded automatically!
|
|
99
145
|
app = engine.create_app(slug="my_app", manifest=Path("manifest.json"))
|
|
100
146
|
|
|
101
|
-
#
|
|
147
|
+
# Use request-scoped dependencies - all queries automatically isolated
|
|
102
148
|
@app.post("/tasks")
|
|
103
149
|
async def create_task(task: dict, db=Depends(get_scoped_db)):
|
|
104
150
|
result = await db.tasks.insert_one(task)
|
|
105
151
|
return {"id": str(result.inserted_id)}
|
|
106
152
|
```
|
|
107
153
|
|
|
154
|
+
**What just happened?**
|
|
155
|
+
- ✅ Engine loaded your `manifest.json`
|
|
156
|
+
- ✅ Indexes created automatically from `managed_indexes`
|
|
157
|
+
- ✅ Database queries automatically scoped to your app
|
|
158
|
+
- ✅ Lifecycle management handled (startup/shutdown)
|
|
159
|
+
|
|
108
160
|
That's it. Your data is automatically sandboxed, indexes are created, and cleanup is handled.
|
|
109
161
|
|
|
110
162
|
---
|
|
@@ -200,9 +252,11 @@ async def health():
|
|
|
200
252
|
|
|
201
253
|
## Why mdb-engine?
|
|
202
254
|
|
|
255
|
+
- **manifest.json is everything** — Single source of truth for your entire app configuration
|
|
203
256
|
- **Zero boilerplate** — No more connection setup, index creation scripts, or auth handlers
|
|
204
257
|
- **Data isolation** — Multi-tenant ready with automatic app sandboxing
|
|
205
258
|
- **Manifest-driven** — Define your app's "DNA" in JSON, not scattered code
|
|
259
|
+
- **Incremental adoption** — Start minimal, add features as needed
|
|
206
260
|
- **No lock-in** — Standard Motor/PyMongo underneath; export anytime with `mongodump --query='{"app_id":"my_app"}'`
|
|
207
261
|
|
|
208
262
|
---
|
|
@@ -300,13 +354,34 @@ async def get_items():
|
|
|
300
354
|
|
|
301
355
|
---
|
|
302
356
|
|
|
357
|
+
## Understanding manifest.json
|
|
358
|
+
|
|
359
|
+
Your `manifest.json` is the heart of your application. It defines:
|
|
360
|
+
|
|
361
|
+
- **App Identity**: `slug`, `name`, `description`
|
|
362
|
+
- **Data Access**: `data_access.read_scopes`, `data_access.write_scope`
|
|
363
|
+
- **Indexes**: `managed_indexes` (regular, vector, text, TTL, compound)
|
|
364
|
+
- **Authentication**: `auth.policy`, `auth.users` (Casbin/OSO, demo users)
|
|
365
|
+
- **AI Services**: `embedding_config`, `memory_config`
|
|
366
|
+
- **Real-time**: `websockets` endpoints
|
|
367
|
+
- **CORS**: `cors` settings
|
|
368
|
+
|
|
369
|
+
**Start minimal, grow as needed.** You can begin with just `slug`, `name`, and `schema_version`, then add features incrementally.
|
|
370
|
+
|
|
371
|
+
**📖 Learn More:**
|
|
372
|
+
- [Quick Start Guide](docs/QUICK_START.md) - Get started with manifest.json
|
|
373
|
+
- [Manifest Deep Dive](docs/MANIFEST_DEEP_DIVE.md) - Comprehensive manifest.json guide
|
|
374
|
+
- [Examples](examples/) - Real-world manifest.json files
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
303
378
|
## Links
|
|
304
379
|
|
|
305
380
|
- [GitHub Repository](https://github.com/ranfysvalle02/mdb-engine)
|
|
306
381
|
- [Documentation](https://github.com/ranfysvalle02/mdb-engine/tree/main/docs)
|
|
307
382
|
- [All Examples](https://github.com/ranfysvalle02/mdb-engine/tree/main/examples)
|
|
308
|
-
- [Quick Start Guide](
|
|
309
|
-
- [Contributing](
|
|
383
|
+
- [Quick Start Guide](docs/QUICK_START.md) - **Start here!**
|
|
384
|
+
- [Contributing](CONTRIBUTING.md)
|
|
310
385
|
|
|
311
386
|
---
|
|
312
387
|
|
|
@@ -8,6 +8,31 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## 🎯 manifest.json: The Key to Everything
|
|
12
|
+
|
|
13
|
+
**`manifest.json` is the foundation of your application.** It's a single configuration file that defines your app's identity, data structure, authentication, indexes, and services. Everything flows from this file.
|
|
14
|
+
|
|
15
|
+
### Your First manifest.json
|
|
16
|
+
|
|
17
|
+
Create a `manifest.json` file with just 3 fields:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"schema_version": "2.0",
|
|
22
|
+
"slug": "my_app",
|
|
23
|
+
"name": "My App"
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
That's it! This minimal manifest gives you:
|
|
28
|
+
- ✅ Automatic data scoping (all queries filtered by `app_id`)
|
|
29
|
+
- ✅ Collection name prefixing (`db.tasks` → `my_app_tasks`)
|
|
30
|
+
- ✅ App registration and lifecycle management
|
|
31
|
+
|
|
32
|
+
**Learn more**: [Quick Start Guide](docs/QUICK_START.md) | [Manifest Deep Dive](docs/MANIFEST_DEEP_DIVE.md)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
11
36
|
## Installation
|
|
12
37
|
|
|
13
38
|
```bash
|
|
@@ -18,28 +43,55 @@ pip install mdb-engine
|
|
|
18
43
|
|
|
19
44
|
## 30-Second Quick Start
|
|
20
45
|
|
|
46
|
+
**Step 1**: Create your `manifest.json`:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"schema_version": "2.0",
|
|
51
|
+
"slug": "my_app",
|
|
52
|
+
"name": "My App",
|
|
53
|
+
"managed_indexes": {
|
|
54
|
+
"tasks": [
|
|
55
|
+
{
|
|
56
|
+
"type": "regular",
|
|
57
|
+
"keys": {"status": 1, "created_at": -1},
|
|
58
|
+
"name": "status_sort"
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Step 2**: Create your FastAPI app:
|
|
66
|
+
|
|
21
67
|
```python
|
|
22
68
|
from pathlib import Path
|
|
23
69
|
from fastapi import Depends
|
|
24
70
|
from mdb_engine import MongoDBEngine
|
|
25
71
|
from mdb_engine.dependencies import get_scoped_db
|
|
26
72
|
|
|
27
|
-
#
|
|
73
|
+
# Initialize the engine
|
|
28
74
|
engine = MongoDBEngine(
|
|
29
75
|
mongo_uri="mongodb://localhost:27017",
|
|
30
76
|
db_name="my_database"
|
|
31
77
|
)
|
|
32
78
|
|
|
33
|
-
#
|
|
79
|
+
# Create app - manifest.json is loaded automatically!
|
|
34
80
|
app = engine.create_app(slug="my_app", manifest=Path("manifest.json"))
|
|
35
81
|
|
|
36
|
-
#
|
|
82
|
+
# Use request-scoped dependencies - all queries automatically isolated
|
|
37
83
|
@app.post("/tasks")
|
|
38
84
|
async def create_task(task: dict, db=Depends(get_scoped_db)):
|
|
39
85
|
result = await db.tasks.insert_one(task)
|
|
40
86
|
return {"id": str(result.inserted_id)}
|
|
41
87
|
```
|
|
42
88
|
|
|
89
|
+
**What just happened?**
|
|
90
|
+
- ✅ Engine loaded your `manifest.json`
|
|
91
|
+
- ✅ Indexes created automatically from `managed_indexes`
|
|
92
|
+
- ✅ Database queries automatically scoped to your app
|
|
93
|
+
- ✅ Lifecycle management handled (startup/shutdown)
|
|
94
|
+
|
|
43
95
|
That's it. Your data is automatically sandboxed, indexes are created, and cleanup is handled.
|
|
44
96
|
|
|
45
97
|
---
|
|
@@ -135,9 +187,11 @@ async def health():
|
|
|
135
187
|
|
|
136
188
|
## Why mdb-engine?
|
|
137
189
|
|
|
190
|
+
- **manifest.json is everything** — Single source of truth for your entire app configuration
|
|
138
191
|
- **Zero boilerplate** — No more connection setup, index creation scripts, or auth handlers
|
|
139
192
|
- **Data isolation** — Multi-tenant ready with automatic app sandboxing
|
|
140
193
|
- **Manifest-driven** — Define your app's "DNA" in JSON, not scattered code
|
|
194
|
+
- **Incremental adoption** — Start minimal, add features as needed
|
|
141
195
|
- **No lock-in** — Standard Motor/PyMongo underneath; export anytime with `mongodump --query='{"app_id":"my_app"}'`
|
|
142
196
|
|
|
143
197
|
---
|
|
@@ -235,13 +289,34 @@ async def get_items():
|
|
|
235
289
|
|
|
236
290
|
---
|
|
237
291
|
|
|
292
|
+
## Understanding manifest.json
|
|
293
|
+
|
|
294
|
+
Your `manifest.json` is the heart of your application. It defines:
|
|
295
|
+
|
|
296
|
+
- **App Identity**: `slug`, `name`, `description`
|
|
297
|
+
- **Data Access**: `data_access.read_scopes`, `data_access.write_scope`
|
|
298
|
+
- **Indexes**: `managed_indexes` (regular, vector, text, TTL, compound)
|
|
299
|
+
- **Authentication**: `auth.policy`, `auth.users` (Casbin/OSO, demo users)
|
|
300
|
+
- **AI Services**: `embedding_config`, `memory_config`
|
|
301
|
+
- **Real-time**: `websockets` endpoints
|
|
302
|
+
- **CORS**: `cors` settings
|
|
303
|
+
|
|
304
|
+
**Start minimal, grow as needed.** You can begin with just `slug`, `name`, and `schema_version`, then add features incrementally.
|
|
305
|
+
|
|
306
|
+
**📖 Learn More:**
|
|
307
|
+
- [Quick Start Guide](docs/QUICK_START.md) - Get started with manifest.json
|
|
308
|
+
- [Manifest Deep Dive](docs/MANIFEST_DEEP_DIVE.md) - Comprehensive manifest.json guide
|
|
309
|
+
- [Examples](examples/) - Real-world manifest.json files
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
238
313
|
## Links
|
|
239
314
|
|
|
240
315
|
- [GitHub Repository](https://github.com/ranfysvalle02/mdb-engine)
|
|
241
316
|
- [Documentation](https://github.com/ranfysvalle02/mdb-engine/tree/main/docs)
|
|
242
317
|
- [All Examples](https://github.com/ranfysvalle02/mdb-engine/tree/main/examples)
|
|
243
|
-
- [Quick Start Guide](
|
|
244
|
-
- [Contributing](
|
|
318
|
+
- [Quick Start Guide](docs/QUICK_START.md) - **Start here!**
|
|
319
|
+
- [Contributing](CONTRIBUTING.md)
|
|
245
320
|
|
|
246
321
|
---
|
|
247
322
|
|
|
@@ -78,7 +78,10 @@ from .indexes import (
|
|
|
78
78
|
# Repository pattern
|
|
79
79
|
from .repositories import Entity, MongoRepository, Repository, UnitOfWork
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
# Utilities
|
|
82
|
+
from .utils import clean_mongo_doc, clean_mongo_docs
|
|
83
|
+
|
|
84
|
+
__version__ = "0.2.4" # Patch version bump for exception handling improvements
|
|
82
85
|
|
|
83
86
|
__all__ = [
|
|
84
87
|
# Core Engine
|
|
@@ -127,4 +130,7 @@ __all__ = [
|
|
|
127
130
|
"AsyncAtlasIndexManager",
|
|
128
131
|
"AutoIndexManager",
|
|
129
132
|
"run_index_creation_for_collection",
|
|
133
|
+
# Utilities
|
|
134
|
+
"clean_mongo_doc",
|
|
135
|
+
"clean_mongo_docs",
|
|
130
136
|
]
|
|
@@ -42,6 +42,10 @@ All apps share a central user pool. Users authenticate once and can access any a
|
|
|
42
42
|
{
|
|
43
43
|
"auth": {
|
|
44
44
|
"mode": "shared",
|
|
45
|
+
"auth_hub_url": "http://localhost:8000",
|
|
46
|
+
"related_apps": {
|
|
47
|
+
"dashboard": "http://localhost:8001"
|
|
48
|
+
},
|
|
45
49
|
"roles": ["viewer", "editor", "admin"],
|
|
46
50
|
"default_role": "viewer",
|
|
47
51
|
"require_role": "viewer",
|
|
@@ -60,6 +64,8 @@ All apps share a central user pool. Users authenticate once and can access any a
|
|
|
60
64
|
| Field | Description |
|
|
61
65
|
|-------|-------------|
|
|
62
66
|
| `roles` | Available roles for this app |
|
|
67
|
+
| `auth_hub_url` | URL of the authentication hub for SSO apps. Used for redirecting unauthenticated users to login. Can be overridden via `AUTH_HUB_URL` environment variable |
|
|
68
|
+
| `related_apps` | Map of related app slugs to their URLs for cross-app navigation. Keys are app slugs, values are URLs. Can be overridden via `{APP_SLUG_UPPER}_URL` environment variables |
|
|
63
69
|
| `default_role` | Role assigned to new users |
|
|
64
70
|
| `require_role` | Minimum role required to access app |
|
|
65
71
|
| `public_routes` | Routes that don't require authentication |
|
|
@@ -35,7 +35,7 @@ Usage:
|
|
|
35
35
|
import logging
|
|
36
36
|
from datetime import datetime, timedelta
|
|
37
37
|
from enum import Enum
|
|
38
|
-
from typing import Any
|
|
38
|
+
from typing import Any
|
|
39
39
|
|
|
40
40
|
from motor.motor_asyncio import AsyncIOMotorDatabase
|
|
41
41
|
from pymongo.errors import OperationFailure
|
|
@@ -161,12 +161,12 @@ class AuthAuditLog:
|
|
|
161
161
|
self,
|
|
162
162
|
action: AuthAction,
|
|
163
163
|
success: bool,
|
|
164
|
-
user_email:
|
|
165
|
-
user_id:
|
|
166
|
-
app_slug:
|
|
167
|
-
ip_address:
|
|
168
|
-
user_agent:
|
|
169
|
-
details:
|
|
164
|
+
user_email: str | None = None,
|
|
165
|
+
user_id: str | None = None,
|
|
166
|
+
app_slug: str | None = None,
|
|
167
|
+
ip_address: str | None = None,
|
|
168
|
+
user_agent: str | None = None,
|
|
169
|
+
details: dict[str, Any] | None = None,
|
|
170
170
|
) -> str:
|
|
171
171
|
"""
|
|
172
172
|
Log an authentication event.
|
|
@@ -217,9 +217,9 @@ class AuthAuditLog:
|
|
|
217
217
|
async def log_login_success(
|
|
218
218
|
self,
|
|
219
219
|
email: str,
|
|
220
|
-
ip_address:
|
|
221
|
-
user_agent:
|
|
222
|
-
app_slug:
|
|
220
|
+
ip_address: str | None = None,
|
|
221
|
+
user_agent: str | None = None,
|
|
222
|
+
app_slug: str | None = None,
|
|
223
223
|
) -> str:
|
|
224
224
|
"""Convenience method to log successful login."""
|
|
225
225
|
return await self.log_event(
|
|
@@ -235,9 +235,9 @@ class AuthAuditLog:
|
|
|
235
235
|
self,
|
|
236
236
|
email: str,
|
|
237
237
|
reason: str = "invalid_credentials",
|
|
238
|
-
ip_address:
|
|
239
|
-
user_agent:
|
|
240
|
-
app_slug:
|
|
238
|
+
ip_address: str | None = None,
|
|
239
|
+
user_agent: str | None = None,
|
|
240
|
+
app_slug: str | None = None,
|
|
241
241
|
) -> str:
|
|
242
242
|
"""Convenience method to log failed login."""
|
|
243
243
|
return await self.log_event(
|
|
@@ -253,8 +253,8 @@ class AuthAuditLog:
|
|
|
253
253
|
async def log_logout(
|
|
254
254
|
self,
|
|
255
255
|
email: str,
|
|
256
|
-
ip_address:
|
|
257
|
-
app_slug:
|
|
256
|
+
ip_address: str | None = None,
|
|
257
|
+
app_slug: str | None = None,
|
|
258
258
|
) -> str:
|
|
259
259
|
"""Convenience method to log logout."""
|
|
260
260
|
return await self.log_event(
|
|
@@ -268,9 +268,9 @@ class AuthAuditLog:
|
|
|
268
268
|
async def log_register(
|
|
269
269
|
self,
|
|
270
270
|
email: str,
|
|
271
|
-
ip_address:
|
|
272
|
-
user_agent:
|
|
273
|
-
app_slug:
|
|
271
|
+
ip_address: str | None = None,
|
|
272
|
+
user_agent: str | None = None,
|
|
273
|
+
app_slug: str | None = None,
|
|
274
274
|
) -> str:
|
|
275
275
|
"""Convenience method to log new user registration."""
|
|
276
276
|
return await self.log_event(
|
|
@@ -286,10 +286,10 @@ class AuthAuditLog:
|
|
|
286
286
|
self,
|
|
287
287
|
email: str,
|
|
288
288
|
app_slug: str,
|
|
289
|
-
old_roles:
|
|
290
|
-
new_roles:
|
|
291
|
-
changed_by:
|
|
292
|
-
ip_address:
|
|
289
|
+
old_roles: list[str],
|
|
290
|
+
new_roles: list[str],
|
|
291
|
+
changed_by: str | None = None,
|
|
292
|
+
ip_address: str | None = None,
|
|
293
293
|
) -> str:
|
|
294
294
|
"""Log a role change event."""
|
|
295
295
|
action = (
|
|
@@ -312,8 +312,8 @@ class AuthAuditLog:
|
|
|
312
312
|
self,
|
|
313
313
|
email: str,
|
|
314
314
|
reason: str = "logout",
|
|
315
|
-
ip_address:
|
|
316
|
-
app_slug:
|
|
315
|
+
ip_address: str | None = None,
|
|
316
|
+
app_slug: str | None = None,
|
|
317
317
|
) -> str:
|
|
318
318
|
"""Log token revocation."""
|
|
319
319
|
return await self.log_event(
|
|
@@ -329,8 +329,8 @@ class AuthAuditLog:
|
|
|
329
329
|
self,
|
|
330
330
|
ip_address: str,
|
|
331
331
|
endpoint: str,
|
|
332
|
-
email:
|
|
333
|
-
app_slug:
|
|
332
|
+
email: str | None = None,
|
|
333
|
+
app_slug: str | None = None,
|
|
334
334
|
) -> str:
|
|
335
335
|
"""Log rate limit exceeded event."""
|
|
336
336
|
return await self.log_event(
|
|
@@ -349,10 +349,10 @@ class AuthAuditLog:
|
|
|
349
349
|
async def get_recent_events(
|
|
350
350
|
self,
|
|
351
351
|
hours: int = 24,
|
|
352
|
-
action:
|
|
353
|
-
success:
|
|
352
|
+
action: AuthAction | None = None,
|
|
353
|
+
success: bool | None = None,
|
|
354
354
|
limit: int = 100,
|
|
355
|
-
) ->
|
|
355
|
+
) -> list[dict[str, Any]]:
|
|
356
356
|
"""
|
|
357
357
|
Get recent audit events.
|
|
358
358
|
|
|
@@ -367,7 +367,7 @@ class AuthAuditLog:
|
|
|
367
367
|
"""
|
|
368
368
|
since = datetime.utcnow() - timedelta(hours=hours)
|
|
369
369
|
|
|
370
|
-
query:
|
|
370
|
+
query: dict[str, Any] = {"timestamp": {"$gte": since}}
|
|
371
371
|
if action:
|
|
372
372
|
query["action"] = action.value if isinstance(action, AuthAction) else action
|
|
373
373
|
if success is not None:
|
|
@@ -384,11 +384,11 @@ class AuthAuditLog:
|
|
|
384
384
|
|
|
385
385
|
async def get_failed_logins(
|
|
386
386
|
self,
|
|
387
|
-
email:
|
|
388
|
-
ip_address:
|
|
387
|
+
email: str | None = None,
|
|
388
|
+
ip_address: str | None = None,
|
|
389
389
|
hours: int = 24,
|
|
390
390
|
limit: int = 100,
|
|
391
|
-
) ->
|
|
391
|
+
) -> list[dict[str, Any]]:
|
|
392
392
|
"""
|
|
393
393
|
Get failed login attempts.
|
|
394
394
|
|
|
@@ -403,7 +403,7 @@ class AuthAuditLog:
|
|
|
403
403
|
"""
|
|
404
404
|
since = datetime.utcnow() - timedelta(hours=hours)
|
|
405
405
|
|
|
406
|
-
query:
|
|
406
|
+
query: dict[str, Any] = {
|
|
407
407
|
"action": AuthAction.LOGIN_FAILED.value,
|
|
408
408
|
"timestamp": {"$gte": since},
|
|
409
409
|
}
|
|
@@ -425,7 +425,7 @@ class AuthAuditLog:
|
|
|
425
425
|
email: str,
|
|
426
426
|
hours: int = 168, # 7 days
|
|
427
427
|
limit: int = 100,
|
|
428
|
-
) ->
|
|
428
|
+
) -> list[dict[str, Any]]:
|
|
429
429
|
"""
|
|
430
430
|
Get all activity for a specific user.
|
|
431
431
|
|
|
@@ -462,7 +462,7 @@ class AuthAuditLog:
|
|
|
462
462
|
ip_address: str,
|
|
463
463
|
hours: int = 24,
|
|
464
464
|
limit: int = 100,
|
|
465
|
-
) ->
|
|
465
|
+
) -> list[dict[str, Any]]:
|
|
466
466
|
"""
|
|
467
467
|
Get all activity from a specific IP address.
|
|
468
468
|
|
|
@@ -498,8 +498,8 @@ class AuthAuditLog:
|
|
|
498
498
|
|
|
499
499
|
async def count_failed_logins(
|
|
500
500
|
self,
|
|
501
|
-
email:
|
|
502
|
-
ip_address:
|
|
501
|
+
email: str | None = None,
|
|
502
|
+
ip_address: str | None = None,
|
|
503
503
|
hours: int = 1,
|
|
504
504
|
) -> int:
|
|
505
505
|
"""
|
|
@@ -517,7 +517,7 @@ class AuthAuditLog:
|
|
|
517
517
|
"""
|
|
518
518
|
since = datetime.utcnow() - timedelta(hours=hours)
|
|
519
519
|
|
|
520
|
-
query:
|
|
520
|
+
query: dict[str, Any] = {
|
|
521
521
|
"action": AuthAction.LOGIN_FAILED.value,
|
|
522
522
|
"timestamp": {"$gte": since},
|
|
523
523
|
}
|
|
@@ -531,7 +531,7 @@ class AuthAuditLog:
|
|
|
531
531
|
async def get_security_summary(
|
|
532
532
|
self,
|
|
533
533
|
hours: int = 24,
|
|
534
|
-
) ->
|
|
534
|
+
) -> dict[str, Any]:
|
|
535
535
|
"""
|
|
536
536
|
Get security summary statistics.
|
|
537
537
|
|
|
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import abc
|
|
13
13
|
import logging
|
|
14
|
-
from typing import Any
|
|
14
|
+
from typing import Any
|
|
15
15
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
@@ -71,7 +71,7 @@ class BaseAuthorizationProvider(abc.ABC):
|
|
|
71
71
|
subject: str,
|
|
72
72
|
resource: str,
|
|
73
73
|
action: str,
|
|
74
|
-
user_object:
|
|
74
|
+
user_object: dict[str, Any] | None = None,
|
|
75
75
|
) -> bool:
|
|
76
76
|
"""
|
|
77
77
|
Check if a subject is allowed to perform an action on a resource.
|
|
@@ -195,7 +195,7 @@ class BaseAuthorizationProvider(abc.ABC):
|
|
|
195
195
|
resource: str,
|
|
196
196
|
action: str,
|
|
197
197
|
error: Exception,
|
|
198
|
-
context:
|
|
198
|
+
context: str | None = None,
|
|
199
199
|
) -> bool:
|
|
200
200
|
"""
|
|
201
201
|
Handle authorization evaluation errors with fail-closed security.
|
|
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import logging
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import TYPE_CHECKING, Any
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
15
|
|
|
16
16
|
from .casbin_models import DEFAULT_RBAC_MODEL, SIMPLE_ACL_MODEL
|
|
17
17
|
|
|
@@ -46,7 +46,7 @@ async def get_casbin_model(model_type: str = "rbac") -> str:
|
|
|
46
46
|
# Try async file reading (non-blocking)
|
|
47
47
|
import aiofiles
|
|
48
48
|
|
|
49
|
-
async with aiofiles.open(model_path
|
|
49
|
+
async with aiofiles.open(model_path) as f:
|
|
50
50
|
content = await f.read()
|
|
51
51
|
logger.debug(f"Read model file asynchronously: {model_path}")
|
|
52
52
|
return content
|
|
@@ -67,7 +67,7 @@ async def create_casbin_enforcer(
|
|
|
67
67
|
db_name: str,
|
|
68
68
|
model: str = "rbac",
|
|
69
69
|
policies_collection: str = "casbin_policies",
|
|
70
|
-
default_roles:
|
|
70
|
+
default_roles: list | None = None,
|
|
71
71
|
) -> casbin.AsyncEnforcer:
|
|
72
72
|
"""
|
|
73
73
|
Create a Casbin AsyncEnforcer with MongoDB adapter.
|
|
@@ -189,7 +189,7 @@ async def create_casbin_enforcer(
|
|
|
189
189
|
|
|
190
190
|
async def initialize_casbin_from_manifest(
|
|
191
191
|
engine, app_slug: str, auth_config: dict[str, Any]
|
|
192
|
-
) ->
|
|
192
|
+
) -> CasbinAdapter | None:
|
|
193
193
|
"""
|
|
194
194
|
Initialize Casbin provider from manifest configuration.
|
|
195
195
|
|
|
@@ -261,7 +261,7 @@ async def initialize_casbin_from_manifest(
|
|
|
261
261
|
if initial_policies:
|
|
262
262
|
logger.info(f"Setting up {len(initial_policies)} initial policies...")
|
|
263
263
|
for policy in initial_policies:
|
|
264
|
-
if isinstance(policy,
|
|
264
|
+
if isinstance(policy, list | tuple) and len(policy) >= 3:
|
|
265
265
|
role, resource, action = policy[0], policy[1], policy[2]
|
|
266
266
|
try:
|
|
267
267
|
# Check if policy already exists
|
|
@@ -331,7 +331,7 @@ async def initialize_casbin_from_manifest(
|
|
|
331
331
|
if initial_policies:
|
|
332
332
|
verified = 0
|
|
333
333
|
for policy in initial_policies:
|
|
334
|
-
if isinstance(policy,
|
|
334
|
+
if isinstance(policy, list | tuple) and len(policy) >= 3:
|
|
335
335
|
role, resource, action = policy[0], policy[1], policy[2]
|
|
336
336
|
if await adapter.has_policy(role, resource, action):
|
|
337
337
|
verified += 1
|