mindtrace-database 0.2.0__tar.gz → 0.3.0__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.
- mindtrace_database-0.3.0/PKG-INFO +622 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace/database/__init__.py +1 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace/database/backends/mongo_odm_backend.py +1 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace/database/backends/redis_odm_backend.py +4 -3
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace/database/backends/unified_odm_backend.py +1 -0
- mindtrace_database-0.3.0/mindtrace_database.egg-info/PKG-INFO +622 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/pyproject.toml +2 -1
- mindtrace_database-0.2.0/PKG-INFO +0 -17
- mindtrace_database-0.2.0/mindtrace_database.egg-info/PKG-INFO +0 -17
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/LICENSE +0 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/README.md +0 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace/database/backends/local_odm_backend.py +0 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace/database/backends/mindtrace_odm_backend.py +0 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace/database/core/exceptions.py +0 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace_database.egg-info/SOURCES.txt +0 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace_database.egg-info/dependency_links.txt +0 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace_database.egg-info/requires.txt +0 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace_database.egg-info/top_level.txt +0 -0
- {mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mindtrace-database
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Database functionality for Mindtrace
|
|
5
|
+
Author: Mindtrace Team
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://mindtrace.ai
|
|
8
|
+
Project-URL: Repository, https://github.com/mindtrace/mindtrace/blob/main/mindtrace/database
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: beanie<2,>=1.29.0
|
|
14
|
+
Requires-Dist: mindtrace-core
|
|
15
|
+
Requires-Dist: redis>=4.0.0
|
|
16
|
+
Requires-Dist: redis-om>=0.3.5
|
|
17
|
+
Requires-Dist: motor>=3.3.0
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/mindtrace-database/)
|
|
21
|
+
[](https://github.com/mindtrace/mindtrace/blob/main/mindtrace/database/LICENSE)
|
|
22
|
+
|
|
23
|
+
# Mindtrace Database Module
|
|
24
|
+
|
|
25
|
+
A powerful, flexible Object-Document Mapping (ODM) system that provides a **unified interface** for working with multiple database backends in the Mindtrace project. Write once, run on MongoDB, Redis, or both!
|
|
26
|
+
|
|
27
|
+
## ✨ Key Features
|
|
28
|
+
|
|
29
|
+
- **Unified Backend System** - One interface for multiple databases
|
|
30
|
+
- **Dynamic Backend Switching** - Switch between MongoDB and Redis at runtime
|
|
31
|
+
- **Simplified Document Models** - Define once, use everywhere
|
|
32
|
+
- **Async/Sync Support** - Choose your preferred programming style
|
|
33
|
+
- **Advanced Querying** - Rich query capabilities across all backends
|
|
34
|
+
- **Comprehensive Error Handling** - Clear, actionable error messages
|
|
35
|
+
- **Full Test Coverage** - Thoroughly tested with unit and integration tests
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### The Simple Way: Unified Documents
|
|
40
|
+
|
|
41
|
+
Define your document model once and use it with any backend:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from mindtrace.database import UnifiedMindtraceDocument, UnifiedMindtraceODMBackend, BackendType
|
|
45
|
+
from pydantic import Field
|
|
46
|
+
|
|
47
|
+
# 1. Define your document model (works with both MongoDB and Redis!)
|
|
48
|
+
class User(UnifiedMindtraceDocument):
|
|
49
|
+
name: str = Field(description="User's full name")
|
|
50
|
+
age: int = Field(ge=0, description="User's age")
|
|
51
|
+
email: str = Field(description="User's email address")
|
|
52
|
+
skills: list[str] = Field(default_factory=list)
|
|
53
|
+
|
|
54
|
+
class Meta:
|
|
55
|
+
collection_name = "users"
|
|
56
|
+
global_key_prefix = "myapp"
|
|
57
|
+
indexed_fields = ["email", "name"]
|
|
58
|
+
unique_fields = ["email"]
|
|
59
|
+
|
|
60
|
+
# 2. Create backend (supports both MongoDB and Redis)
|
|
61
|
+
backend = UnifiedMindtraceODMBackend(
|
|
62
|
+
unified_model_cls=User,
|
|
63
|
+
mongo_db_uri="mongodb://localhost:27017",
|
|
64
|
+
mongo_db_name="myapp",
|
|
65
|
+
redis_url="redis://localhost:6379",
|
|
66
|
+
preferred_backend=BackendType.MONGO # Start with MongoDB
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# 3. Initialize
|
|
70
|
+
await backend.initialize_async() # For async operations
|
|
71
|
+
# or
|
|
72
|
+
backend.initialize_sync() # For sync operations
|
|
73
|
+
|
|
74
|
+
# 4. Use it! (Same API regardless of backend)
|
|
75
|
+
user = User(name="Alice", age=30, email="alice@example.com", skills=["Python"])
|
|
76
|
+
|
|
77
|
+
# Insert
|
|
78
|
+
inserted_user = await backend.insert_async(user)
|
|
79
|
+
# or: inserted_user = backend.insert(user)
|
|
80
|
+
|
|
81
|
+
# Get by ID
|
|
82
|
+
retrieved_user = await backend.get_async(inserted_user.id)
|
|
83
|
+
|
|
84
|
+
# Find with filters
|
|
85
|
+
python_users = await backend.find_async({"skills": "Python"})
|
|
86
|
+
|
|
87
|
+
# Switch backends on the fly!
|
|
88
|
+
backend.switch_backend(BackendType.REDIS)
|
|
89
|
+
redis_user = backend.insert(user) # Now using Redis
|
|
90
|
+
|
|
91
|
+
# Get all users
|
|
92
|
+
all_users = await backend.all_async()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Traditional Way: Backend-Specific Models
|
|
96
|
+
|
|
97
|
+
If you prefer more control, you can still define backend-specific models:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from mindtrace.database import (
|
|
101
|
+
MongoMindtraceODMBackend,
|
|
102
|
+
RedisMindtraceODMBackend,
|
|
103
|
+
MindtraceDocument,
|
|
104
|
+
MindtraceRedisDocument
|
|
105
|
+
)
|
|
106
|
+
from beanie import Indexed
|
|
107
|
+
from redis_om import Field as RedisField
|
|
108
|
+
from typing import Annotated
|
|
109
|
+
|
|
110
|
+
# MongoDB model
|
|
111
|
+
class MongoUser(MindtraceDocument):
|
|
112
|
+
name: str
|
|
113
|
+
email: Annotated[str, Indexed(unique=True)]
|
|
114
|
+
age: int
|
|
115
|
+
|
|
116
|
+
class Settings:
|
|
117
|
+
name = "users"
|
|
118
|
+
|
|
119
|
+
# Redis model
|
|
120
|
+
class RedisUser(MindtraceRedisDocument):
|
|
121
|
+
name: str = RedisField(index=True)
|
|
122
|
+
email: str = RedisField(index=True)
|
|
123
|
+
age: int = RedisField(index=True)
|
|
124
|
+
|
|
125
|
+
class Meta:
|
|
126
|
+
global_key_prefix = "myapp"
|
|
127
|
+
|
|
128
|
+
# Use them separately
|
|
129
|
+
mongo_backend = MongoMindtraceODMBackend(
|
|
130
|
+
model_cls=MongoUser,
|
|
131
|
+
db_uri="mongodb://localhost:27017",
|
|
132
|
+
db_name="myapp"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
redis_backend = RedisMindtraceODMBackend(
|
|
136
|
+
model_cls=RedisUser,
|
|
137
|
+
redis_url="redis://localhost:6379"
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Available Backends
|
|
142
|
+
|
|
143
|
+
### 1. UnifiedMindtraceODMBackend (Recommended)
|
|
144
|
+
|
|
145
|
+
The flagship backend that provides a unified interface for multiple databases:
|
|
146
|
+
|
|
147
|
+
**Key Features:**
|
|
148
|
+
- **Single Interface**: One API for all backends
|
|
149
|
+
- **Runtime Switching**: Change backends without code changes
|
|
150
|
+
- **Automatic Model Generation**: Converts unified models to backend-specific formats
|
|
151
|
+
- **Flexible Configuration**: Use one or multiple backends
|
|
152
|
+
|
|
153
|
+
**Configuration Options:**
|
|
154
|
+
```python
|
|
155
|
+
# Option 1: Unified model (recommended)
|
|
156
|
+
backend = UnifiedMindtraceODMBackend(
|
|
157
|
+
unified_model_cls=MyUnifiedDoc,
|
|
158
|
+
mongo_db_uri="mongodb://localhost:27017",
|
|
159
|
+
mongo_db_name="mydb",
|
|
160
|
+
redis_url="redis://localhost:6379",
|
|
161
|
+
preferred_backend=BackendType.MONGO
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Option 2: Separate models
|
|
165
|
+
backend = UnifiedMindtraceODMBackend(
|
|
166
|
+
mongo_model_cls=MyMongoDoc,
|
|
167
|
+
redis_model_cls=MyRedisDoc,
|
|
168
|
+
mongo_db_uri="mongodb://localhost:27017",
|
|
169
|
+
mongo_db_name="mydb",
|
|
170
|
+
redis_url="redis://localhost:6379",
|
|
171
|
+
preferred_backend=BackendType.REDIS
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Option 3: Single backend
|
|
175
|
+
backend = UnifiedMindtraceODMBackend(
|
|
176
|
+
unified_model_cls=MyUnifiedDoc,
|
|
177
|
+
mongo_db_uri="mongodb://localhost:27017",
|
|
178
|
+
mongo_db_name="mydb",
|
|
179
|
+
preferred_backend=BackendType.MONGO
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 2. MongoMindtraceODMBackend
|
|
184
|
+
|
|
185
|
+
Specialized MongoDB backend using Beanie ODM:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from mindtrace.database import MongoMindtraceODMBackend, MindtraceDocument
|
|
189
|
+
|
|
190
|
+
class User(MindtraceDocument):
|
|
191
|
+
name: str
|
|
192
|
+
email: str
|
|
193
|
+
|
|
194
|
+
class Settings:
|
|
195
|
+
name = "users"
|
|
196
|
+
use_cache = False
|
|
197
|
+
|
|
198
|
+
backend = MongoMindtraceODMBackend(
|
|
199
|
+
model_cls=User,
|
|
200
|
+
db_uri="mongodb://localhost:27017",
|
|
201
|
+
db_name="myapp"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Supports MongoDB-specific features
|
|
205
|
+
pipeline = [{"$match": {"age": {"$gte": 18}}}]
|
|
206
|
+
results = await backend.aggregate(pipeline)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 3. RedisMindtraceODMBackend
|
|
210
|
+
|
|
211
|
+
High-performance Redis backend with JSON support:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from mindtrace.database import RedisMindtraceODMBackend, MindtraceRedisDocument
|
|
215
|
+
from redis_om import Field
|
|
216
|
+
|
|
217
|
+
class User(MindtraceRedisDocument):
|
|
218
|
+
name: str = Field(index=True)
|
|
219
|
+
email: str = Field(index=True)
|
|
220
|
+
age: int = Field(index=True)
|
|
221
|
+
|
|
222
|
+
class Meta:
|
|
223
|
+
global_key_prefix = "myapp"
|
|
224
|
+
|
|
225
|
+
backend = RedisMindtraceODMBackend(
|
|
226
|
+
model_cls=User,
|
|
227
|
+
redis_url="redis://localhost:6379"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Initialize Redis OM
|
|
231
|
+
await backend.initialize()
|
|
232
|
+
|
|
233
|
+
# Supports Redis-specific queries
|
|
234
|
+
users = backend.find(User.age >= 18)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 4. LocalMindtraceODMBackend
|
|
238
|
+
|
|
239
|
+
In-memory backend for testing and development:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from mindtrace.database import LocalMindtraceODMBackend
|
|
243
|
+
from pydantic import BaseModel
|
|
244
|
+
|
|
245
|
+
class User(BaseModel):
|
|
246
|
+
name: str
|
|
247
|
+
email: str
|
|
248
|
+
|
|
249
|
+
backend = LocalMindtraceODMBackend(model_cls=User)
|
|
250
|
+
# No initialization needed - works immediately!
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## API Reference
|
|
254
|
+
|
|
255
|
+
### Core Operations
|
|
256
|
+
|
|
257
|
+
All backends support these essential operations:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
# Insert a document
|
|
261
|
+
inserted_doc = await backend.insert_async(doc)
|
|
262
|
+
# or: inserted_doc = backend.insert(doc)
|
|
263
|
+
|
|
264
|
+
# Get document by ID
|
|
265
|
+
doc = await backend.get_async("doc_id")
|
|
266
|
+
# or: doc = backend.get("doc_id")
|
|
267
|
+
|
|
268
|
+
# Delete document
|
|
269
|
+
await backend.delete_async("doc_id")
|
|
270
|
+
# or: backend.delete("doc_id")
|
|
271
|
+
|
|
272
|
+
# Get all documents
|
|
273
|
+
all_docs = await backend.all_async()
|
|
274
|
+
# or: all_docs = backend.all()
|
|
275
|
+
|
|
276
|
+
# Find documents with filters
|
|
277
|
+
results = await backend.find_async({"name": "Alice"})
|
|
278
|
+
# or: results = backend.find({"name": "Alice"})
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Unified Backend Specific
|
|
282
|
+
|
|
283
|
+
Additional methods for the unified backend:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
# Backend management
|
|
287
|
+
backend.switch_backend(BackendType.REDIS)
|
|
288
|
+
current_type = backend.get_current_backend_type()
|
|
289
|
+
is_async = backend.is_async()
|
|
290
|
+
|
|
291
|
+
# Backend availability
|
|
292
|
+
has_mongo = backend.has_mongo_backend()
|
|
293
|
+
has_redis = backend.has_redis_backend()
|
|
294
|
+
|
|
295
|
+
# Direct backend access
|
|
296
|
+
mongo_backend = backend.get_mongo_backend()
|
|
297
|
+
redis_backend = backend.get_redis_backend()
|
|
298
|
+
|
|
299
|
+
# Model access
|
|
300
|
+
raw_model = backend.get_raw_model()
|
|
301
|
+
unified_model = backend.get_unified_model()
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Advanced Querying
|
|
305
|
+
|
|
306
|
+
#### MongoDB (through Unified Backend)
|
|
307
|
+
```python
|
|
308
|
+
# MongoDB-style queries
|
|
309
|
+
users = await backend.find_async({"age": {"$gte": 18}})
|
|
310
|
+
users = await backend.find_async({"skills": {"$in": ["Python", "JavaScript"]}})
|
|
311
|
+
|
|
312
|
+
# Aggregation pipelines (when using MongoDB)
|
|
313
|
+
if backend.get_current_backend_type() == BackendType.MONGO:
|
|
314
|
+
pipeline = [
|
|
315
|
+
{"$match": {"age": {"$gte": 18}}},
|
|
316
|
+
{"$group": {"_id": "$department", "count": {"$sum": 1}}}
|
|
317
|
+
]
|
|
318
|
+
results = await backend.get_mongo_backend().aggregate(pipeline)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### Redis (through Unified Backend)
|
|
322
|
+
```python
|
|
323
|
+
# Switch to Redis for these queries
|
|
324
|
+
backend.switch_backend(BackendType.REDIS)
|
|
325
|
+
|
|
326
|
+
# Redis OM expressions
|
|
327
|
+
Model = backend.get_raw_model()
|
|
328
|
+
users = backend.find(Model.age >= 18)
|
|
329
|
+
users = backend.find(Model.name == "Alice")
|
|
330
|
+
users = backend.find(Model.skills << "Python") # Contains
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Error Handling
|
|
334
|
+
|
|
335
|
+
The module provides comprehensive error handling:
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
from mindtrace.database import DocumentNotFoundError, DuplicateInsertError
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
user = await backend.get_async("non_existent_id")
|
|
342
|
+
except DocumentNotFoundError as e:
|
|
343
|
+
print(f"User not found: {e}")
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
await backend.insert_async(duplicate_user)
|
|
347
|
+
except DuplicateInsertError as e:
|
|
348
|
+
print(f"User already exists: {e}")
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Testing
|
|
352
|
+
|
|
353
|
+
The database module includes comprehensive test coverage with both unit and integration tests.
|
|
354
|
+
|
|
355
|
+
### Test Structure
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
tests/
|
|
359
|
+
├── unit/mindtrace/database/ # Unit tests (no DB required)
|
|
360
|
+
│ ├── test_mongo_unit.py
|
|
361
|
+
│ ├── test_redis_unit.py
|
|
362
|
+
│ └── test_unified_unit.py
|
|
363
|
+
└── integration/mindtrace/database/ # Integration tests (DB required)
|
|
364
|
+
├── test_mongo.py
|
|
365
|
+
├── test_redis_odm.py
|
|
366
|
+
└── test_unified.py
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Running Tests
|
|
370
|
+
|
|
371
|
+
#### Quick Start - All Tests
|
|
372
|
+
```bash
|
|
373
|
+
# Use the test script (handles everything automatically)
|
|
374
|
+
./scripts/run_tests.sh tests/unit/mindtrace/database tests/integration/mindtrace/database
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### Unit Tests Only (No Database Required)
|
|
378
|
+
```bash
|
|
379
|
+
# From project root
|
|
380
|
+
PYTHONPATH=mindtrace/core:mindtrace/database:$PYTHONPATH \
|
|
381
|
+
python -m pytest tests/unit/mindtrace/database/ -v
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### Integration Tests (Requires Databases)
|
|
385
|
+
```bash
|
|
386
|
+
# Start test databases
|
|
387
|
+
docker compose -f tests/docker-compose.yml up -d
|
|
388
|
+
|
|
389
|
+
# Run integration tests
|
|
390
|
+
PYTHONPATH=mindtrace/core:mindtrace/database:$PYTHONPATH \
|
|
391
|
+
python -m pytest tests/integration/mindtrace/database/ -v
|
|
392
|
+
|
|
393
|
+
# Stop test databases
|
|
394
|
+
docker compose -f tests/docker-compose.yml down
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### Targeted Testing
|
|
398
|
+
```bash
|
|
399
|
+
# Test only unified backend
|
|
400
|
+
./scripts/run_tests.sh --integration tests/integration/mindtrace/database/test_unified.py
|
|
401
|
+
|
|
402
|
+
# Test only MongoDB
|
|
403
|
+
./scripts/run_tests.sh --integration tests/integration/mindtrace/database/test_mongo.py
|
|
404
|
+
|
|
405
|
+
# Test only Redis
|
|
406
|
+
./scripts/run_tests.sh --integration tests/integration/mindtrace/database/test_redis_odm.py
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Test Coverage
|
|
410
|
+
|
|
411
|
+
The test suite covers:
|
|
412
|
+
|
|
413
|
+
- ✅ **CRUD Operations** - Create, Read, Update, Delete
|
|
414
|
+
- ✅ **Query Operations** - Find, filter, search
|
|
415
|
+
- ✅ **Error Handling** - All exception scenarios
|
|
416
|
+
- ✅ **Backend Switching** - Dynamic backend changes
|
|
417
|
+
- ✅ **Async/Sync Compatibility** - Both programming styles
|
|
418
|
+
- ✅ **Model Conversion** - Unified to backend-specific models
|
|
419
|
+
- ✅ **Edge Cases** - Duplicate keys, missing documents, invalid queries
|
|
420
|
+
|
|
421
|
+
## Examples
|
|
422
|
+
|
|
423
|
+
### Complete Example: User Management System
|
|
424
|
+
|
|
425
|
+
```python
|
|
426
|
+
import asyncio
|
|
427
|
+
from mindtrace.database import (
|
|
428
|
+
UnifiedMindtraceODMBackend,
|
|
429
|
+
UnifiedMindtraceDocument,
|
|
430
|
+
BackendType,
|
|
431
|
+
DocumentNotFoundError
|
|
432
|
+
)
|
|
433
|
+
from pydantic import Field
|
|
434
|
+
from typing import List
|
|
435
|
+
|
|
436
|
+
class User(UnifiedMindtraceDocument):
|
|
437
|
+
name: str = Field(description="Full name")
|
|
438
|
+
email: str = Field(description="Email address")
|
|
439
|
+
age: int = Field(ge=0, le=150, description="Age")
|
|
440
|
+
department: str = Field(description="Department")
|
|
441
|
+
skills: List[str] = Field(default_factory=list)
|
|
442
|
+
|
|
443
|
+
class Meta:
|
|
444
|
+
collection_name = "employees"
|
|
445
|
+
global_key_prefix = "company"
|
|
446
|
+
indexed_fields = ["email", "department", "skills"]
|
|
447
|
+
unique_fields = ["email"]
|
|
448
|
+
|
|
449
|
+
async def main():
|
|
450
|
+
# Setup backend with both MongoDB and Redis
|
|
451
|
+
backend = UnifiedMindtraceODMBackend(
|
|
452
|
+
unified_model_cls=User,
|
|
453
|
+
mongo_db_uri="mongodb://localhost:27017",
|
|
454
|
+
mongo_db_name="company",
|
|
455
|
+
redis_url="redis://localhost:6379",
|
|
456
|
+
preferred_backend=BackendType.MONGO
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
await backend.initialize_async()
|
|
460
|
+
|
|
461
|
+
# Create some users
|
|
462
|
+
users = [
|
|
463
|
+
User(
|
|
464
|
+
name="Alice Johnson",
|
|
465
|
+
email="alice@company.com",
|
|
466
|
+
age=30,
|
|
467
|
+
department="Engineering",
|
|
468
|
+
skills=["Python", "MongoDB", "Docker"]
|
|
469
|
+
),
|
|
470
|
+
User(
|
|
471
|
+
name="Bob Smith",
|
|
472
|
+
email="bob@company.com",
|
|
473
|
+
age=25,
|
|
474
|
+
department="Engineering",
|
|
475
|
+
skills=["JavaScript", "Redis", "React"]
|
|
476
|
+
),
|
|
477
|
+
User(
|
|
478
|
+
name="Carol Davis",
|
|
479
|
+
email="carol@company.com",
|
|
480
|
+
age=35,
|
|
481
|
+
department="Marketing",
|
|
482
|
+
skills=["Analytics", "SQL"]
|
|
483
|
+
)
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
# Insert users
|
|
487
|
+
print("Creating users...")
|
|
488
|
+
for user in users:
|
|
489
|
+
try:
|
|
490
|
+
inserted = await backend.insert_async(user)
|
|
491
|
+
print(f"✅ Created: {inserted.name} (ID: {inserted.id})")
|
|
492
|
+
except Exception as e:
|
|
493
|
+
print(f"❌ Failed to create {user.name}: {e}")
|
|
494
|
+
|
|
495
|
+
# Find engineers
|
|
496
|
+
print("\n🔍 Finding engineers...")
|
|
497
|
+
engineers = await backend.find_async({"department": "Engineering"})
|
|
498
|
+
for eng in engineers:
|
|
499
|
+
print(f"👨💻 {eng.name} - Skills: {', '.join(eng.skills)}")
|
|
500
|
+
|
|
501
|
+
# Switch to Redis for fast lookups
|
|
502
|
+
print("\n Switching to Redis for fast operations...")
|
|
503
|
+
backend.switch_backend(BackendType.REDIS)
|
|
504
|
+
|
|
505
|
+
# Insert more users in Redis
|
|
506
|
+
redis_user = User(
|
|
507
|
+
name="Dave Wilson",
|
|
508
|
+
email="dave@company.com",
|
|
509
|
+
age=28,
|
|
510
|
+
department="DevOps",
|
|
511
|
+
skills=["Kubernetes", "Redis", "Monitoring"]
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
redis_inserted = backend.insert(redis_user)
|
|
515
|
+
print(f"Redis user created: {redis_inserted.name}")
|
|
516
|
+
|
|
517
|
+
# Demonstrate backend isolation
|
|
518
|
+
print(f"\n MongoDB users: {len(await backend.get_mongo_backend().all())}")
|
|
519
|
+
print(f"Redis users: {len(backend.get_redis_backend().all())}")
|
|
520
|
+
|
|
521
|
+
# Switch back to MongoDB
|
|
522
|
+
backend.switch_backend(BackendType.MONGO)
|
|
523
|
+
print(f"Back to MongoDB - Users: {len(await backend.all_async())}")
|
|
524
|
+
|
|
525
|
+
if __name__ == "__main__":
|
|
526
|
+
asyncio.run(main())
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### More Examples
|
|
530
|
+
|
|
531
|
+
Check out the `samples/database/` directory for additional examples:
|
|
532
|
+
|
|
533
|
+
- **`using_unified_backend.py`** - Comprehensive unified backend usage
|
|
534
|
+
- **Advanced querying patterns**
|
|
535
|
+
- **Backend switching strategies**
|
|
536
|
+
- **Error handling best practices**
|
|
537
|
+
|
|
538
|
+
## Best Practices
|
|
539
|
+
|
|
540
|
+
### 1. Model Design
|
|
541
|
+
```python
|
|
542
|
+
# ✅ Good: Clear, descriptive models
|
|
543
|
+
class Product(UnifiedMindtraceDocument):
|
|
544
|
+
name: str = Field(description="Product name", min_length=1)
|
|
545
|
+
price: float = Field(ge=0, description="Price in USD")
|
|
546
|
+
category: str = Field(description="Product category")
|
|
547
|
+
|
|
548
|
+
class Meta:
|
|
549
|
+
collection_name = "products"
|
|
550
|
+
indexed_fields = ["category", "name"]
|
|
551
|
+
unique_fields = ["name"]
|
|
552
|
+
|
|
553
|
+
# ❌ Avoid: Unclear models without validation
|
|
554
|
+
class Product(UnifiedMindtraceDocument):
|
|
555
|
+
n: str
|
|
556
|
+
p: float
|
|
557
|
+
c: str
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### 2. Error Handling
|
|
561
|
+
```python
|
|
562
|
+
# ✅ Always handle database exceptions
|
|
563
|
+
try:
|
|
564
|
+
user = await backend.get_async(user_id)
|
|
565
|
+
print(f"Found user: {user.name}")
|
|
566
|
+
except DocumentNotFoundError:
|
|
567
|
+
print("User not found - creating new user")
|
|
568
|
+
user = await backend.insert_async(User(name="New User", email="new@example.com"))
|
|
569
|
+
except Exception as e:
|
|
570
|
+
logger.error(f"Database error: {e}")
|
|
571
|
+
# Handle appropriately
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### 3. Backend Selection
|
|
575
|
+
```python
|
|
576
|
+
# ✅ Choose backends based on use case
|
|
577
|
+
if high_frequency_reads:
|
|
578
|
+
backend.switch_backend(BackendType.REDIS) # Fast reads
|
|
579
|
+
else:
|
|
580
|
+
backend.switch_backend(BackendType.MONGO) # Complex queries
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### 4. Initialization
|
|
584
|
+
```python
|
|
585
|
+
# ✅ Initialize once at application startup
|
|
586
|
+
class DatabaseService:
|
|
587
|
+
def __init__(self):
|
|
588
|
+
self.backend = UnifiedMindtraceODMBackend(...)
|
|
589
|
+
|
|
590
|
+
async def initialize(self):
|
|
591
|
+
await self.backend.initialize_async()
|
|
592
|
+
self.backend.initialize_sync() # If you need both
|
|
593
|
+
|
|
594
|
+
async def cleanup(self):
|
|
595
|
+
# Cleanup if needed
|
|
596
|
+
pass
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
## Contributing
|
|
600
|
+
|
|
601
|
+
When adding new features:
|
|
602
|
+
|
|
603
|
+
1. **Add tests** - Both unit and integration tests
|
|
604
|
+
2. **Update documentation** - Keep README and docstrings current
|
|
605
|
+
3. **Follow patterns** - Use existing code style and patterns
|
|
606
|
+
4. **Test thoroughly** - Run the full test suite
|
|
607
|
+
|
|
608
|
+
## Requirements
|
|
609
|
+
|
|
610
|
+
- **Python 3.9+**
|
|
611
|
+
- **MongoDB 4.4+** (for MongoDB backend)
|
|
612
|
+
- **Redis 6.0+** (for Redis backend)
|
|
613
|
+
- **Core dependencies**: `pydantic`, `beanie`, `redis-om-python`
|
|
614
|
+
|
|
615
|
+
## Need Help?
|
|
616
|
+
|
|
617
|
+
- Check the `samples/database/` directory for working examples
|
|
618
|
+
- Look at the test files for usage patterns
|
|
619
|
+
- Review the docstrings in the source code for detailed API documentation
|
|
620
|
+
|
|
621
|
+
The Mindtrace Database Module makes it easy to work with multiple databases through a single, powerful interface. Start simple with the unified backend, then customize as your needs grow!
|
|
622
|
+
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from mindtrace.database.backends.local_odm_backend import LocalMindtraceODMBackend
|
|
2
|
+
from mindtrace.database.backends.mindtrace_odm_backend import MindtraceODMBackend
|
|
2
3
|
from mindtrace.database.backends.mongo_odm_backend import MindtraceDocument, MongoMindtraceODMBackend
|
|
3
4
|
from mindtrace.database.backends.redis_odm_backend import MindtraceRedisDocument, RedisMindtraceODMBackend
|
|
4
5
|
from mindtrace.database.backends.unified_odm_backend import (
|
|
@@ -86,6 +86,7 @@ class MongoMindtraceODMBackend(MindtraceODMBackend):
|
|
|
86
86
|
db_uri (str): MongoDB connection URI string.
|
|
87
87
|
db_name (str): Name of the MongoDB database to use.
|
|
88
88
|
"""
|
|
89
|
+
super().__init__()
|
|
89
90
|
self.model_cls = model_cls
|
|
90
91
|
self.client = AsyncIOMotorClient(db_uri)
|
|
91
92
|
self.db_name = db_name
|
|
@@ -82,6 +82,7 @@ class RedisMindtraceODMBackend(MindtraceODMBackend):
|
|
|
82
82
|
model_cls (Type[ModelType]): The document model class to use for operations.
|
|
83
83
|
redis_url (str): Redis connection URL string.
|
|
84
84
|
"""
|
|
85
|
+
super().__init__()
|
|
85
86
|
self.model_cls = model_cls
|
|
86
87
|
self.redis = get_redis_connection(url=redis_url)
|
|
87
88
|
self._is_initialized = False
|
|
@@ -113,7 +114,7 @@ class RedisMindtraceODMBackend(MindtraceODMBackend):
|
|
|
113
114
|
|
|
114
115
|
self._is_initialized = True
|
|
115
116
|
except Exception as e:
|
|
116
|
-
|
|
117
|
+
self.logger.warning(f"Redis migration failed: {e}")
|
|
117
118
|
self._is_initialized = True # Continue anyway
|
|
118
119
|
|
|
119
120
|
def is_async(self) -> bool:
|
|
@@ -181,7 +182,7 @@ class RedisMindtraceODMBackend(MindtraceODMBackend):
|
|
|
181
182
|
raise
|
|
182
183
|
except Exception:
|
|
183
184
|
# If all fails, continue without duplicate check but log warning
|
|
184
|
-
|
|
185
|
+
self.logger.warning(f"Could not check for duplicates: {e}")
|
|
185
186
|
|
|
186
187
|
doc = self.model_cls(**obj_data)
|
|
187
188
|
doc.save()
|
|
@@ -302,7 +303,7 @@ class RedisMindtraceODMBackend(MindtraceODMBackend):
|
|
|
302
303
|
return self.model_cls.find().all()
|
|
303
304
|
except Exception as e:
|
|
304
305
|
# If query fails, log the error and return empty list
|
|
305
|
-
|
|
306
|
+
self.logger.warning(f"Redis query failed: {e}")
|
|
306
307
|
# Try to return all documents if specific query fails
|
|
307
308
|
try:
|
|
308
309
|
return self.model_cls.find().all()
|
|
@@ -375,6 +375,7 @@ class UnifiedMindtraceODMBackend(MindtraceODMBackend):
|
|
|
375
375
|
redis_url: Redis connection URL
|
|
376
376
|
preferred_backend: Which backend to prefer when both are available
|
|
377
377
|
"""
|
|
378
|
+
super().__init__()
|
|
378
379
|
self.mongo_backend = None
|
|
379
380
|
self.redis_backend = None
|
|
380
381
|
self.preferred_backend = preferred_backend
|
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mindtrace-database
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Database functionality for Mindtrace
|
|
5
|
+
Author: Mindtrace Team
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://mindtrace.ai
|
|
8
|
+
Project-URL: Repository, https://github.com/mindtrace/mindtrace/blob/main/mindtrace/database
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: beanie<2,>=1.29.0
|
|
14
|
+
Requires-Dist: mindtrace-core
|
|
15
|
+
Requires-Dist: redis>=4.0.0
|
|
16
|
+
Requires-Dist: redis-om>=0.3.5
|
|
17
|
+
Requires-Dist: motor>=3.3.0
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/mindtrace-database/)
|
|
21
|
+
[](https://github.com/mindtrace/mindtrace/blob/main/mindtrace/database/LICENSE)
|
|
22
|
+
|
|
23
|
+
# Mindtrace Database Module
|
|
24
|
+
|
|
25
|
+
A powerful, flexible Object-Document Mapping (ODM) system that provides a **unified interface** for working with multiple database backends in the Mindtrace project. Write once, run on MongoDB, Redis, or both!
|
|
26
|
+
|
|
27
|
+
## ✨ Key Features
|
|
28
|
+
|
|
29
|
+
- **Unified Backend System** - One interface for multiple databases
|
|
30
|
+
- **Dynamic Backend Switching** - Switch between MongoDB and Redis at runtime
|
|
31
|
+
- **Simplified Document Models** - Define once, use everywhere
|
|
32
|
+
- **Async/Sync Support** - Choose your preferred programming style
|
|
33
|
+
- **Advanced Querying** - Rich query capabilities across all backends
|
|
34
|
+
- **Comprehensive Error Handling** - Clear, actionable error messages
|
|
35
|
+
- **Full Test Coverage** - Thoroughly tested with unit and integration tests
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### The Simple Way: Unified Documents
|
|
40
|
+
|
|
41
|
+
Define your document model once and use it with any backend:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from mindtrace.database import UnifiedMindtraceDocument, UnifiedMindtraceODMBackend, BackendType
|
|
45
|
+
from pydantic import Field
|
|
46
|
+
|
|
47
|
+
# 1. Define your document model (works with both MongoDB and Redis!)
|
|
48
|
+
class User(UnifiedMindtraceDocument):
|
|
49
|
+
name: str = Field(description="User's full name")
|
|
50
|
+
age: int = Field(ge=0, description="User's age")
|
|
51
|
+
email: str = Field(description="User's email address")
|
|
52
|
+
skills: list[str] = Field(default_factory=list)
|
|
53
|
+
|
|
54
|
+
class Meta:
|
|
55
|
+
collection_name = "users"
|
|
56
|
+
global_key_prefix = "myapp"
|
|
57
|
+
indexed_fields = ["email", "name"]
|
|
58
|
+
unique_fields = ["email"]
|
|
59
|
+
|
|
60
|
+
# 2. Create backend (supports both MongoDB and Redis)
|
|
61
|
+
backend = UnifiedMindtraceODMBackend(
|
|
62
|
+
unified_model_cls=User,
|
|
63
|
+
mongo_db_uri="mongodb://localhost:27017",
|
|
64
|
+
mongo_db_name="myapp",
|
|
65
|
+
redis_url="redis://localhost:6379",
|
|
66
|
+
preferred_backend=BackendType.MONGO # Start with MongoDB
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# 3. Initialize
|
|
70
|
+
await backend.initialize_async() # For async operations
|
|
71
|
+
# or
|
|
72
|
+
backend.initialize_sync() # For sync operations
|
|
73
|
+
|
|
74
|
+
# 4. Use it! (Same API regardless of backend)
|
|
75
|
+
user = User(name="Alice", age=30, email="alice@example.com", skills=["Python"])
|
|
76
|
+
|
|
77
|
+
# Insert
|
|
78
|
+
inserted_user = await backend.insert_async(user)
|
|
79
|
+
# or: inserted_user = backend.insert(user)
|
|
80
|
+
|
|
81
|
+
# Get by ID
|
|
82
|
+
retrieved_user = await backend.get_async(inserted_user.id)
|
|
83
|
+
|
|
84
|
+
# Find with filters
|
|
85
|
+
python_users = await backend.find_async({"skills": "Python"})
|
|
86
|
+
|
|
87
|
+
# Switch backends on the fly!
|
|
88
|
+
backend.switch_backend(BackendType.REDIS)
|
|
89
|
+
redis_user = backend.insert(user) # Now using Redis
|
|
90
|
+
|
|
91
|
+
# Get all users
|
|
92
|
+
all_users = await backend.all_async()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Traditional Way: Backend-Specific Models
|
|
96
|
+
|
|
97
|
+
If you prefer more control, you can still define backend-specific models:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from mindtrace.database import (
|
|
101
|
+
MongoMindtraceODMBackend,
|
|
102
|
+
RedisMindtraceODMBackend,
|
|
103
|
+
MindtraceDocument,
|
|
104
|
+
MindtraceRedisDocument
|
|
105
|
+
)
|
|
106
|
+
from beanie import Indexed
|
|
107
|
+
from redis_om import Field as RedisField
|
|
108
|
+
from typing import Annotated
|
|
109
|
+
|
|
110
|
+
# MongoDB model
|
|
111
|
+
class MongoUser(MindtraceDocument):
|
|
112
|
+
name: str
|
|
113
|
+
email: Annotated[str, Indexed(unique=True)]
|
|
114
|
+
age: int
|
|
115
|
+
|
|
116
|
+
class Settings:
|
|
117
|
+
name = "users"
|
|
118
|
+
|
|
119
|
+
# Redis model
|
|
120
|
+
class RedisUser(MindtraceRedisDocument):
|
|
121
|
+
name: str = RedisField(index=True)
|
|
122
|
+
email: str = RedisField(index=True)
|
|
123
|
+
age: int = RedisField(index=True)
|
|
124
|
+
|
|
125
|
+
class Meta:
|
|
126
|
+
global_key_prefix = "myapp"
|
|
127
|
+
|
|
128
|
+
# Use them separately
|
|
129
|
+
mongo_backend = MongoMindtraceODMBackend(
|
|
130
|
+
model_cls=MongoUser,
|
|
131
|
+
db_uri="mongodb://localhost:27017",
|
|
132
|
+
db_name="myapp"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
redis_backend = RedisMindtraceODMBackend(
|
|
136
|
+
model_cls=RedisUser,
|
|
137
|
+
redis_url="redis://localhost:6379"
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Available Backends
|
|
142
|
+
|
|
143
|
+
### 1. UnifiedMindtraceODMBackend (Recommended)
|
|
144
|
+
|
|
145
|
+
The flagship backend that provides a unified interface for multiple databases:
|
|
146
|
+
|
|
147
|
+
**Key Features:**
|
|
148
|
+
- **Single Interface**: One API for all backends
|
|
149
|
+
- **Runtime Switching**: Change backends without code changes
|
|
150
|
+
- **Automatic Model Generation**: Converts unified models to backend-specific formats
|
|
151
|
+
- **Flexible Configuration**: Use one or multiple backends
|
|
152
|
+
|
|
153
|
+
**Configuration Options:**
|
|
154
|
+
```python
|
|
155
|
+
# Option 1: Unified model (recommended)
|
|
156
|
+
backend = UnifiedMindtraceODMBackend(
|
|
157
|
+
unified_model_cls=MyUnifiedDoc,
|
|
158
|
+
mongo_db_uri="mongodb://localhost:27017",
|
|
159
|
+
mongo_db_name="mydb",
|
|
160
|
+
redis_url="redis://localhost:6379",
|
|
161
|
+
preferred_backend=BackendType.MONGO
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Option 2: Separate models
|
|
165
|
+
backend = UnifiedMindtraceODMBackend(
|
|
166
|
+
mongo_model_cls=MyMongoDoc,
|
|
167
|
+
redis_model_cls=MyRedisDoc,
|
|
168
|
+
mongo_db_uri="mongodb://localhost:27017",
|
|
169
|
+
mongo_db_name="mydb",
|
|
170
|
+
redis_url="redis://localhost:6379",
|
|
171
|
+
preferred_backend=BackendType.REDIS
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Option 3: Single backend
|
|
175
|
+
backend = UnifiedMindtraceODMBackend(
|
|
176
|
+
unified_model_cls=MyUnifiedDoc,
|
|
177
|
+
mongo_db_uri="mongodb://localhost:27017",
|
|
178
|
+
mongo_db_name="mydb",
|
|
179
|
+
preferred_backend=BackendType.MONGO
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 2. MongoMindtraceODMBackend
|
|
184
|
+
|
|
185
|
+
Specialized MongoDB backend using Beanie ODM:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from mindtrace.database import MongoMindtraceODMBackend, MindtraceDocument
|
|
189
|
+
|
|
190
|
+
class User(MindtraceDocument):
|
|
191
|
+
name: str
|
|
192
|
+
email: str
|
|
193
|
+
|
|
194
|
+
class Settings:
|
|
195
|
+
name = "users"
|
|
196
|
+
use_cache = False
|
|
197
|
+
|
|
198
|
+
backend = MongoMindtraceODMBackend(
|
|
199
|
+
model_cls=User,
|
|
200
|
+
db_uri="mongodb://localhost:27017",
|
|
201
|
+
db_name="myapp"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Supports MongoDB-specific features
|
|
205
|
+
pipeline = [{"$match": {"age": {"$gte": 18}}}]
|
|
206
|
+
results = await backend.aggregate(pipeline)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 3. RedisMindtraceODMBackend
|
|
210
|
+
|
|
211
|
+
High-performance Redis backend with JSON support:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from mindtrace.database import RedisMindtraceODMBackend, MindtraceRedisDocument
|
|
215
|
+
from redis_om import Field
|
|
216
|
+
|
|
217
|
+
class User(MindtraceRedisDocument):
|
|
218
|
+
name: str = Field(index=True)
|
|
219
|
+
email: str = Field(index=True)
|
|
220
|
+
age: int = Field(index=True)
|
|
221
|
+
|
|
222
|
+
class Meta:
|
|
223
|
+
global_key_prefix = "myapp"
|
|
224
|
+
|
|
225
|
+
backend = RedisMindtraceODMBackend(
|
|
226
|
+
model_cls=User,
|
|
227
|
+
redis_url="redis://localhost:6379"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Initialize Redis OM
|
|
231
|
+
await backend.initialize()
|
|
232
|
+
|
|
233
|
+
# Supports Redis-specific queries
|
|
234
|
+
users = backend.find(User.age >= 18)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 4. LocalMindtraceODMBackend
|
|
238
|
+
|
|
239
|
+
In-memory backend for testing and development:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from mindtrace.database import LocalMindtraceODMBackend
|
|
243
|
+
from pydantic import BaseModel
|
|
244
|
+
|
|
245
|
+
class User(BaseModel):
|
|
246
|
+
name: str
|
|
247
|
+
email: str
|
|
248
|
+
|
|
249
|
+
backend = LocalMindtraceODMBackend(model_cls=User)
|
|
250
|
+
# No initialization needed - works immediately!
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## API Reference
|
|
254
|
+
|
|
255
|
+
### Core Operations
|
|
256
|
+
|
|
257
|
+
All backends support these essential operations:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
# Insert a document
|
|
261
|
+
inserted_doc = await backend.insert_async(doc)
|
|
262
|
+
# or: inserted_doc = backend.insert(doc)
|
|
263
|
+
|
|
264
|
+
# Get document by ID
|
|
265
|
+
doc = await backend.get_async("doc_id")
|
|
266
|
+
# or: doc = backend.get("doc_id")
|
|
267
|
+
|
|
268
|
+
# Delete document
|
|
269
|
+
await backend.delete_async("doc_id")
|
|
270
|
+
# or: backend.delete("doc_id")
|
|
271
|
+
|
|
272
|
+
# Get all documents
|
|
273
|
+
all_docs = await backend.all_async()
|
|
274
|
+
# or: all_docs = backend.all()
|
|
275
|
+
|
|
276
|
+
# Find documents with filters
|
|
277
|
+
results = await backend.find_async({"name": "Alice"})
|
|
278
|
+
# or: results = backend.find({"name": "Alice"})
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Unified Backend Specific
|
|
282
|
+
|
|
283
|
+
Additional methods for the unified backend:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
# Backend management
|
|
287
|
+
backend.switch_backend(BackendType.REDIS)
|
|
288
|
+
current_type = backend.get_current_backend_type()
|
|
289
|
+
is_async = backend.is_async()
|
|
290
|
+
|
|
291
|
+
# Backend availability
|
|
292
|
+
has_mongo = backend.has_mongo_backend()
|
|
293
|
+
has_redis = backend.has_redis_backend()
|
|
294
|
+
|
|
295
|
+
# Direct backend access
|
|
296
|
+
mongo_backend = backend.get_mongo_backend()
|
|
297
|
+
redis_backend = backend.get_redis_backend()
|
|
298
|
+
|
|
299
|
+
# Model access
|
|
300
|
+
raw_model = backend.get_raw_model()
|
|
301
|
+
unified_model = backend.get_unified_model()
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Advanced Querying
|
|
305
|
+
|
|
306
|
+
#### MongoDB (through Unified Backend)
|
|
307
|
+
```python
|
|
308
|
+
# MongoDB-style queries
|
|
309
|
+
users = await backend.find_async({"age": {"$gte": 18}})
|
|
310
|
+
users = await backend.find_async({"skills": {"$in": ["Python", "JavaScript"]}})
|
|
311
|
+
|
|
312
|
+
# Aggregation pipelines (when using MongoDB)
|
|
313
|
+
if backend.get_current_backend_type() == BackendType.MONGO:
|
|
314
|
+
pipeline = [
|
|
315
|
+
{"$match": {"age": {"$gte": 18}}},
|
|
316
|
+
{"$group": {"_id": "$department", "count": {"$sum": 1}}}
|
|
317
|
+
]
|
|
318
|
+
results = await backend.get_mongo_backend().aggregate(pipeline)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### Redis (through Unified Backend)
|
|
322
|
+
```python
|
|
323
|
+
# Switch to Redis for these queries
|
|
324
|
+
backend.switch_backend(BackendType.REDIS)
|
|
325
|
+
|
|
326
|
+
# Redis OM expressions
|
|
327
|
+
Model = backend.get_raw_model()
|
|
328
|
+
users = backend.find(Model.age >= 18)
|
|
329
|
+
users = backend.find(Model.name == "Alice")
|
|
330
|
+
users = backend.find(Model.skills << "Python") # Contains
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Error Handling
|
|
334
|
+
|
|
335
|
+
The module provides comprehensive error handling:
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
from mindtrace.database import DocumentNotFoundError, DuplicateInsertError
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
user = await backend.get_async("non_existent_id")
|
|
342
|
+
except DocumentNotFoundError as e:
|
|
343
|
+
print(f"User not found: {e}")
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
await backend.insert_async(duplicate_user)
|
|
347
|
+
except DuplicateInsertError as e:
|
|
348
|
+
print(f"User already exists: {e}")
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Testing
|
|
352
|
+
|
|
353
|
+
The database module includes comprehensive test coverage with both unit and integration tests.
|
|
354
|
+
|
|
355
|
+
### Test Structure
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
tests/
|
|
359
|
+
├── unit/mindtrace/database/ # Unit tests (no DB required)
|
|
360
|
+
│ ├── test_mongo_unit.py
|
|
361
|
+
│ ├── test_redis_unit.py
|
|
362
|
+
│ └── test_unified_unit.py
|
|
363
|
+
└── integration/mindtrace/database/ # Integration tests (DB required)
|
|
364
|
+
├── test_mongo.py
|
|
365
|
+
├── test_redis_odm.py
|
|
366
|
+
└── test_unified.py
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Running Tests
|
|
370
|
+
|
|
371
|
+
#### Quick Start - All Tests
|
|
372
|
+
```bash
|
|
373
|
+
# Use the test script (handles everything automatically)
|
|
374
|
+
./scripts/run_tests.sh tests/unit/mindtrace/database tests/integration/mindtrace/database
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### Unit Tests Only (No Database Required)
|
|
378
|
+
```bash
|
|
379
|
+
# From project root
|
|
380
|
+
PYTHONPATH=mindtrace/core:mindtrace/database:$PYTHONPATH \
|
|
381
|
+
python -m pytest tests/unit/mindtrace/database/ -v
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### Integration Tests (Requires Databases)
|
|
385
|
+
```bash
|
|
386
|
+
# Start test databases
|
|
387
|
+
docker compose -f tests/docker-compose.yml up -d
|
|
388
|
+
|
|
389
|
+
# Run integration tests
|
|
390
|
+
PYTHONPATH=mindtrace/core:mindtrace/database:$PYTHONPATH \
|
|
391
|
+
python -m pytest tests/integration/mindtrace/database/ -v
|
|
392
|
+
|
|
393
|
+
# Stop test databases
|
|
394
|
+
docker compose -f tests/docker-compose.yml down
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### Targeted Testing
|
|
398
|
+
```bash
|
|
399
|
+
# Test only unified backend
|
|
400
|
+
./scripts/run_tests.sh --integration tests/integration/mindtrace/database/test_unified.py
|
|
401
|
+
|
|
402
|
+
# Test only MongoDB
|
|
403
|
+
./scripts/run_tests.sh --integration tests/integration/mindtrace/database/test_mongo.py
|
|
404
|
+
|
|
405
|
+
# Test only Redis
|
|
406
|
+
./scripts/run_tests.sh --integration tests/integration/mindtrace/database/test_redis_odm.py
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Test Coverage
|
|
410
|
+
|
|
411
|
+
The test suite covers:
|
|
412
|
+
|
|
413
|
+
- ✅ **CRUD Operations** - Create, Read, Update, Delete
|
|
414
|
+
- ✅ **Query Operations** - Find, filter, search
|
|
415
|
+
- ✅ **Error Handling** - All exception scenarios
|
|
416
|
+
- ✅ **Backend Switching** - Dynamic backend changes
|
|
417
|
+
- ✅ **Async/Sync Compatibility** - Both programming styles
|
|
418
|
+
- ✅ **Model Conversion** - Unified to backend-specific models
|
|
419
|
+
- ✅ **Edge Cases** - Duplicate keys, missing documents, invalid queries
|
|
420
|
+
|
|
421
|
+
## Examples
|
|
422
|
+
|
|
423
|
+
### Complete Example: User Management System
|
|
424
|
+
|
|
425
|
+
```python
|
|
426
|
+
import asyncio
|
|
427
|
+
from mindtrace.database import (
|
|
428
|
+
UnifiedMindtraceODMBackend,
|
|
429
|
+
UnifiedMindtraceDocument,
|
|
430
|
+
BackendType,
|
|
431
|
+
DocumentNotFoundError
|
|
432
|
+
)
|
|
433
|
+
from pydantic import Field
|
|
434
|
+
from typing import List
|
|
435
|
+
|
|
436
|
+
class User(UnifiedMindtraceDocument):
|
|
437
|
+
name: str = Field(description="Full name")
|
|
438
|
+
email: str = Field(description="Email address")
|
|
439
|
+
age: int = Field(ge=0, le=150, description="Age")
|
|
440
|
+
department: str = Field(description="Department")
|
|
441
|
+
skills: List[str] = Field(default_factory=list)
|
|
442
|
+
|
|
443
|
+
class Meta:
|
|
444
|
+
collection_name = "employees"
|
|
445
|
+
global_key_prefix = "company"
|
|
446
|
+
indexed_fields = ["email", "department", "skills"]
|
|
447
|
+
unique_fields = ["email"]
|
|
448
|
+
|
|
449
|
+
async def main():
|
|
450
|
+
# Setup backend with both MongoDB and Redis
|
|
451
|
+
backend = UnifiedMindtraceODMBackend(
|
|
452
|
+
unified_model_cls=User,
|
|
453
|
+
mongo_db_uri="mongodb://localhost:27017",
|
|
454
|
+
mongo_db_name="company",
|
|
455
|
+
redis_url="redis://localhost:6379",
|
|
456
|
+
preferred_backend=BackendType.MONGO
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
await backend.initialize_async()
|
|
460
|
+
|
|
461
|
+
# Create some users
|
|
462
|
+
users = [
|
|
463
|
+
User(
|
|
464
|
+
name="Alice Johnson",
|
|
465
|
+
email="alice@company.com",
|
|
466
|
+
age=30,
|
|
467
|
+
department="Engineering",
|
|
468
|
+
skills=["Python", "MongoDB", "Docker"]
|
|
469
|
+
),
|
|
470
|
+
User(
|
|
471
|
+
name="Bob Smith",
|
|
472
|
+
email="bob@company.com",
|
|
473
|
+
age=25,
|
|
474
|
+
department="Engineering",
|
|
475
|
+
skills=["JavaScript", "Redis", "React"]
|
|
476
|
+
),
|
|
477
|
+
User(
|
|
478
|
+
name="Carol Davis",
|
|
479
|
+
email="carol@company.com",
|
|
480
|
+
age=35,
|
|
481
|
+
department="Marketing",
|
|
482
|
+
skills=["Analytics", "SQL"]
|
|
483
|
+
)
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
# Insert users
|
|
487
|
+
print("Creating users...")
|
|
488
|
+
for user in users:
|
|
489
|
+
try:
|
|
490
|
+
inserted = await backend.insert_async(user)
|
|
491
|
+
print(f"✅ Created: {inserted.name} (ID: {inserted.id})")
|
|
492
|
+
except Exception as e:
|
|
493
|
+
print(f"❌ Failed to create {user.name}: {e}")
|
|
494
|
+
|
|
495
|
+
# Find engineers
|
|
496
|
+
print("\n🔍 Finding engineers...")
|
|
497
|
+
engineers = await backend.find_async({"department": "Engineering"})
|
|
498
|
+
for eng in engineers:
|
|
499
|
+
print(f"👨💻 {eng.name} - Skills: {', '.join(eng.skills)}")
|
|
500
|
+
|
|
501
|
+
# Switch to Redis for fast lookups
|
|
502
|
+
print("\n Switching to Redis for fast operations...")
|
|
503
|
+
backend.switch_backend(BackendType.REDIS)
|
|
504
|
+
|
|
505
|
+
# Insert more users in Redis
|
|
506
|
+
redis_user = User(
|
|
507
|
+
name="Dave Wilson",
|
|
508
|
+
email="dave@company.com",
|
|
509
|
+
age=28,
|
|
510
|
+
department="DevOps",
|
|
511
|
+
skills=["Kubernetes", "Redis", "Monitoring"]
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
redis_inserted = backend.insert(redis_user)
|
|
515
|
+
print(f"Redis user created: {redis_inserted.name}")
|
|
516
|
+
|
|
517
|
+
# Demonstrate backend isolation
|
|
518
|
+
print(f"\n MongoDB users: {len(await backend.get_mongo_backend().all())}")
|
|
519
|
+
print(f"Redis users: {len(backend.get_redis_backend().all())}")
|
|
520
|
+
|
|
521
|
+
# Switch back to MongoDB
|
|
522
|
+
backend.switch_backend(BackendType.MONGO)
|
|
523
|
+
print(f"Back to MongoDB - Users: {len(await backend.all_async())}")
|
|
524
|
+
|
|
525
|
+
if __name__ == "__main__":
|
|
526
|
+
asyncio.run(main())
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### More Examples
|
|
530
|
+
|
|
531
|
+
Check out the `samples/database/` directory for additional examples:
|
|
532
|
+
|
|
533
|
+
- **`using_unified_backend.py`** - Comprehensive unified backend usage
|
|
534
|
+
- **Advanced querying patterns**
|
|
535
|
+
- **Backend switching strategies**
|
|
536
|
+
- **Error handling best practices**
|
|
537
|
+
|
|
538
|
+
## Best Practices
|
|
539
|
+
|
|
540
|
+
### 1. Model Design
|
|
541
|
+
```python
|
|
542
|
+
# ✅ Good: Clear, descriptive models
|
|
543
|
+
class Product(UnifiedMindtraceDocument):
|
|
544
|
+
name: str = Field(description="Product name", min_length=1)
|
|
545
|
+
price: float = Field(ge=0, description="Price in USD")
|
|
546
|
+
category: str = Field(description="Product category")
|
|
547
|
+
|
|
548
|
+
class Meta:
|
|
549
|
+
collection_name = "products"
|
|
550
|
+
indexed_fields = ["category", "name"]
|
|
551
|
+
unique_fields = ["name"]
|
|
552
|
+
|
|
553
|
+
# ❌ Avoid: Unclear models without validation
|
|
554
|
+
class Product(UnifiedMindtraceDocument):
|
|
555
|
+
n: str
|
|
556
|
+
p: float
|
|
557
|
+
c: str
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### 2. Error Handling
|
|
561
|
+
```python
|
|
562
|
+
# ✅ Always handle database exceptions
|
|
563
|
+
try:
|
|
564
|
+
user = await backend.get_async(user_id)
|
|
565
|
+
print(f"Found user: {user.name}")
|
|
566
|
+
except DocumentNotFoundError:
|
|
567
|
+
print("User not found - creating new user")
|
|
568
|
+
user = await backend.insert_async(User(name="New User", email="new@example.com"))
|
|
569
|
+
except Exception as e:
|
|
570
|
+
logger.error(f"Database error: {e}")
|
|
571
|
+
# Handle appropriately
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### 3. Backend Selection
|
|
575
|
+
```python
|
|
576
|
+
# ✅ Choose backends based on use case
|
|
577
|
+
if high_frequency_reads:
|
|
578
|
+
backend.switch_backend(BackendType.REDIS) # Fast reads
|
|
579
|
+
else:
|
|
580
|
+
backend.switch_backend(BackendType.MONGO) # Complex queries
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### 4. Initialization
|
|
584
|
+
```python
|
|
585
|
+
# ✅ Initialize once at application startup
|
|
586
|
+
class DatabaseService:
|
|
587
|
+
def __init__(self):
|
|
588
|
+
self.backend = UnifiedMindtraceODMBackend(...)
|
|
589
|
+
|
|
590
|
+
async def initialize(self):
|
|
591
|
+
await self.backend.initialize_async()
|
|
592
|
+
self.backend.initialize_sync() # If you need both
|
|
593
|
+
|
|
594
|
+
async def cleanup(self):
|
|
595
|
+
# Cleanup if needed
|
|
596
|
+
pass
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
## Contributing
|
|
600
|
+
|
|
601
|
+
When adding new features:
|
|
602
|
+
|
|
603
|
+
1. **Add tests** - Both unit and integration tests
|
|
604
|
+
2. **Update documentation** - Keep README and docstrings current
|
|
605
|
+
3. **Follow patterns** - Use existing code style and patterns
|
|
606
|
+
4. **Test thoroughly** - Run the full test suite
|
|
607
|
+
|
|
608
|
+
## Requirements
|
|
609
|
+
|
|
610
|
+
- **Python 3.9+**
|
|
611
|
+
- **MongoDB 4.4+** (for MongoDB backend)
|
|
612
|
+
- **Redis 6.0+** (for Redis backend)
|
|
613
|
+
- **Core dependencies**: `pydantic`, `beanie`, `redis-om-python`
|
|
614
|
+
|
|
615
|
+
## Need Help?
|
|
616
|
+
|
|
617
|
+
- Check the `samples/database/` directory for working examples
|
|
618
|
+
- Look at the test files for usage patterns
|
|
619
|
+
- Review the docstrings in the source code for detailed API documentation
|
|
620
|
+
|
|
621
|
+
The Mindtrace Database Module makes it easy to work with multiple databases through a single, powerful interface. Start simple with the unified backend, then customize as your needs grow!
|
|
622
|
+
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: mindtrace-database
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Database functionality for Mindtrace
|
|
5
|
-
Author: Mindtrace Team
|
|
6
|
-
License-Expression: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://mindtrace.ai
|
|
8
|
-
Project-URL: Repository, https://github.com/mindtrace/mindtrace/blob/main/mindtrace/database
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: beanie<2,>=1.29.0
|
|
13
|
-
Requires-Dist: mindtrace-core
|
|
14
|
-
Requires-Dist: redis>=4.0.0
|
|
15
|
-
Requires-Dist: redis-om>=0.3.5
|
|
16
|
-
Requires-Dist: motor>=3.3.0
|
|
17
|
-
Dynamic: license-file
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: mindtrace-database
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Database functionality for Mindtrace
|
|
5
|
-
Author: Mindtrace Team
|
|
6
|
-
License-Expression: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://mindtrace.ai
|
|
8
|
-
Project-URL: Repository, https://github.com/mindtrace/mindtrace/blob/main/mindtrace/database
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: beanie<2,>=1.29.0
|
|
13
|
-
Requires-Dist: mindtrace-core
|
|
14
|
-
Requires-Dist: redis>=4.0.0
|
|
15
|
-
Requires-Dist: redis-om>=0.3.5
|
|
16
|
-
Requires-Dist: motor>=3.3.0
|
|
17
|
-
Dynamic: license-file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace_database.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace_database.egg-info/requires.txt
RENAMED
|
File without changes
|
{mindtrace_database-0.2.0 → mindtrace_database-0.3.0}/mindtrace_database.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|