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.
Files changed (105) hide show
  1. {mdb_engine-0.2.1/mdb_engine.egg-info → mdb_engine-0.2.4}/PKG-INFO +88 -13
  2. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/README.md +80 -5
  3. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/__init__.py +7 -1
  4. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/README.md +6 -0
  5. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/audit.py +40 -40
  6. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/base.py +3 -3
  7. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/casbin_factory.py +6 -6
  8. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/config_defaults.py +5 -5
  9. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/config_helpers.py +12 -12
  10. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/cookie_utils.py +9 -9
  11. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/csrf.py +9 -8
  12. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/decorators.py +7 -6
  13. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/dependencies.py +22 -21
  14. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/integration.py +9 -9
  15. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/jwt.py +9 -9
  16. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/middleware.py +4 -3
  17. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/oso_factory.py +6 -6
  18. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/provider.py +4 -4
  19. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/rate_limiter.py +12 -11
  20. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/restrictions.py +16 -15
  21. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/session_manager.py +11 -13
  22. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/shared_middleware.py +344 -132
  23. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/shared_users.py +20 -20
  24. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/token_lifecycle.py +10 -12
  25. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/token_store.py +4 -5
  26. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/users.py +51 -52
  27. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/utils.py +29 -33
  28. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/commands/generate.py +6 -6
  29. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/utils.py +4 -4
  30. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/config.py +6 -7
  31. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/app_registration.py +12 -12
  32. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/app_secrets.py +1 -2
  33. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/connection.py +3 -4
  34. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/encryption.py +1 -2
  35. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/engine.py +43 -44
  36. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/manifest.py +80 -58
  37. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/ray_integration.py +10 -9
  38. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/seeding.py +3 -3
  39. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/service_initialization.py +10 -9
  40. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/types.py +40 -40
  41. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/abstraction.py +15 -16
  42. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/connection.py +40 -12
  43. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/query_validator.py +8 -8
  44. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/resource_limiter.py +7 -7
  45. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/scoped_wrapper.py +51 -58
  46. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/dependencies.py +14 -13
  47. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/di/container.py +12 -13
  48. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/di/providers.py +14 -13
  49. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/di/scopes.py +5 -5
  50. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/embeddings/dependencies.py +2 -2
  51. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/embeddings/service.py +67 -50
  52. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/exceptions.py +20 -20
  53. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/indexes/helpers.py +11 -11
  54. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/indexes/manager.py +9 -9
  55. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/memory/README.md +93 -2
  56. mdb_engine-0.2.4/mdb_engine/memory/service.py +480 -0
  57. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/observability/health.py +10 -9
  58. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/observability/logging.py +10 -10
  59. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/observability/metrics.py +8 -7
  60. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/repositories/base.py +25 -25
  61. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/repositories/mongo.py +17 -17
  62. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/repositories/unit_of_work.py +6 -6
  63. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/routing/websockets.py +19 -18
  64. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/utils/__init__.py +3 -1
  65. mdb_engine-0.2.4/mdb_engine/utils/mongo.py +117 -0
  66. {mdb_engine-0.2.1 → mdb_engine-0.2.4/mdb_engine.egg-info}/PKG-INFO +88 -13
  67. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine.egg-info/SOURCES.txt +2 -1
  68. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine.egg-info/requires.txt +3 -2
  69. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/pyproject.toml +10 -9
  70. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/setup.py +6 -7
  71. mdb_engine-0.2.1/mdb_engine/memory/service.py +0 -1228
  72. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/LICENSE +0 -0
  73. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/MANIFEST.in +0 -0
  74. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/README.md +0 -0
  75. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/ARCHITECTURE.md +0 -0
  76. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/__init__.py +0 -0
  77. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/casbin_models.py +0 -0
  78. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/auth/helpers.py +0 -0
  79. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/__init__.py +0 -0
  80. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/commands/__init__.py +0 -0
  81. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/commands/migrate.py +0 -0
  82. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/commands/show.py +0 -0
  83. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/commands/validate.py +0 -0
  84. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/cli/main.py +0 -0
  85. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/constants.py +0 -0
  86. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/README.md +0 -0
  87. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/__init__.py +0 -0
  88. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/core/index_management.py +0 -0
  89. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/README.md +0 -0
  90. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/database/__init__.py +0 -0
  91. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/di/__init__.py +0 -0
  92. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/embeddings/README.md +0 -0
  93. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/embeddings/__init__.py +0 -0
  94. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/indexes/README.md +0 -0
  95. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/indexes/__init__.py +0 -0
  96. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/memory/__init__.py +0 -0
  97. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/observability/README.md +0 -0
  98. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/observability/__init__.py +0 -0
  99. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/repositories/__init__.py +0 -0
  100. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/routing/README.md +0 -0
  101. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine/routing/__init__.py +0 -0
  102. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine.egg-info/dependency_links.txt +0 -0
  103. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine.egg-info/entry_points.txt +0 -0
  104. {mdb_engine-0.2.1 → mdb_engine-0.2.4}/mdb_engine.egg-info/top_level.txt +0 -0
  105. {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.1
3
+ Version: 0.2.4
4
4
  Summary: MongoDB Engine
5
5
  Home-page: https://github.com/ranfysvalle02/mdb-engine
6
- Author: Your Name
7
- Author-email: Your Name <your.email@example.com>
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.8
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.0.0
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>=0.4.0; extra == "dev"
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
- # 1. Initialize the engine
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
- # 2. Create a FastAPI app with automatic lifecycle management
144
+ # Create app - manifest.json is loaded automatically!
99
145
  app = engine.create_app(slug="my_app", manifest=Path("manifest.json"))
100
146
 
101
- # 3. Use request-scoped dependencies - all queries automatically isolated
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](https://github.com/ranfysvalle02/mdb-engine/blob/main/docs/QUICK_START.md)
309
- - [Contributing](https://github.com/ranfysvalle02/mdb-engine/blob/main/CONTRIBUTING.md)
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
- # 1. Initialize the engine
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
- # 2. Create a FastAPI app with automatic lifecycle management
79
+ # Create app - manifest.json is loaded automatically!
34
80
  app = engine.create_app(slug="my_app", manifest=Path("manifest.json"))
35
81
 
36
- # 3. Use request-scoped dependencies - all queries automatically isolated
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](https://github.com/ranfysvalle02/mdb-engine/blob/main/docs/QUICK_START.md)
244
- - [Contributing](https://github.com/ranfysvalle02/mdb-engine/blob/main/CONTRIBUTING.md)
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
- __version__ = "0.2.1" # Major version bump for new DI system
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, Dict, List, Optional
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: Optional[str] = None,
165
- user_id: Optional[str] = None,
166
- app_slug: Optional[str] = None,
167
- ip_address: Optional[str] = None,
168
- user_agent: Optional[str] = None,
169
- details: Optional[Dict[str, Any]] = None,
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: Optional[str] = None,
221
- user_agent: Optional[str] = None,
222
- app_slug: Optional[str] = None,
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: Optional[str] = None,
239
- user_agent: Optional[str] = None,
240
- app_slug: Optional[str] = None,
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: Optional[str] = None,
257
- app_slug: Optional[str] = None,
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: Optional[str] = None,
272
- user_agent: Optional[str] = None,
273
- app_slug: Optional[str] = None,
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: List[str],
290
- new_roles: List[str],
291
- changed_by: Optional[str] = None,
292
- ip_address: Optional[str] = None,
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: Optional[str] = None,
316
- app_slug: Optional[str] = None,
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: Optional[str] = None,
333
- app_slug: Optional[str] = None,
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: Optional[AuthAction] = None,
353
- success: Optional[bool] = None,
352
+ action: AuthAction | None = None,
353
+ success: bool | None = None,
354
354
  limit: int = 100,
355
- ) -> List[Dict[str, Any]]:
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: Dict[str, Any] = {"timestamp": {"$gte": since}}
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: Optional[str] = None,
388
- ip_address: Optional[str] = None,
387
+ email: str | None = None,
388
+ ip_address: str | None = None,
389
389
  hours: int = 24,
390
390
  limit: int = 100,
391
- ) -> List[Dict[str, Any]]:
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: Dict[str, Any] = {
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
- ) -> List[Dict[str, Any]]:
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
- ) -> List[Dict[str, Any]]:
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: Optional[str] = None,
502
- ip_address: Optional[str] = None,
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: Dict[str, Any] = {
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
- ) -> Dict[str, Any]:
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, Optional
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: Optional[dict[str, Any]] = None,
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: Optional[str] = None,
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, Optional
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, "r") as f:
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: Optional[list] = None,
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
- ) -> Optional[CasbinAdapter]:
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, (list, tuple)) and len(policy) >= 3:
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, (list, tuple)) and len(policy) >= 3:
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