0
# Indexes
1
2
ODMantic provides index management for MongoDB collections through field-level indexing options and compound index definitions.
3
4
## Capabilities
5
6
### Index Class
7
8
Define compound indexes spanning multiple fields with custom options.
9
10
```python { .api }
11
class Index:
12
"""Compound index definition for MongoDB collections."""
13
14
def __init__(self, *fields, **kwargs):
15
"""
16
Create compound index definition.
17
18
Args:
19
*fields: Field names or (field, direction) tuples
20
**kwargs: Index options (unique, sparse, background, etc.)
21
22
Example:
23
Index("field1", ("field2", -1), unique=True)
24
Index(User.name, User.email, background=True)
25
"""
26
```
27
28
### Reference Function
29
30
Define reference fields that link to other documents.
31
32
```python { .api }
33
def Reference(*, key_name=None):
34
"""
35
Define reference field to another document.
36
37
Args:
38
key_name: MongoDB field name for the reference
39
40
Returns:
41
Field configuration for ObjectId reference
42
"""
43
```
44
45
### Field-Level Indexing
46
47
Index options available in Field definitions.
48
49
```python { .api }
50
# In Field() function:
51
# index=True - Create regular index
52
# unique=True - Create unique index
53
# primary_field=True - Use as primary key (_id)
54
```
55
56
## Usage Examples
57
58
### Single Field Indexes
59
60
```python
61
from odmantic import Model, Field
62
63
class User(Model):
64
# Regular index
65
username: str = Field(index=True)
66
67
# Unique index
68
email: str = Field(unique=True)
69
70
# Both indexed
71
phone: str = Field(index=True, unique=True)
72
73
# Non-indexed fields
74
first_name: str
75
last_name: str
76
age: int
77
78
# The engine will create indexes when configure_database is called
79
async def setup_indexes(engine):
80
await engine.configure_database([User])
81
```
82
83
### Compound Indexes
84
85
```python
86
from odmantic import Model, Index
87
88
class BlogPost(Model):
89
title: str
90
author: str = Field(index=True)
91
category: str = Field(index=True)
92
published_date: datetime
93
view_count: int = 0
94
tags: list[str] = []
95
96
# Define compound indexes
97
model_config = {
98
"collection": "blog_posts",
99
"indexes": [
100
# Compound index on author and published_date
101
Index("author", "published_date"),
102
103
# Compound index with sort direction
104
Index("category", ("published_date", -1)),
105
106
# Unique compound index
107
Index("author", "title", unique=True),
108
109
# Text index for search
110
Index(("title", "text"), ("content", "text")),
111
112
# Sparse index (only indexes documents with the field)
113
Index("tags", sparse=True),
114
]
115
}
116
117
# Alternative way to define indexes using class attribute
118
class Product(Model):
119
name: str
120
category: str
121
price: float
122
brand: str
123
sku: str = Field(unique=True)
124
125
# Define indexes as class attribute
126
__indexes__ = [
127
Index("category", "brand"),
128
Index("price", background=True),
129
Index(("name", "text"), name="text_search_index"),
130
]
131
```
132
133
### Reference Fields
134
135
```python
136
from odmantic import Model, Reference, ObjectId
137
138
class Author(Model):
139
name: str = Field(unique=True)
140
email: str = Field(unique=True)
141
bio: str = ""
142
143
class Book(Model):
144
title: str
145
isbn: str = Field(unique=True)
146
147
# Reference to Author document
148
author_id: ObjectId = Reference()
149
150
# Reference with custom key name
151
publisher_id: ObjectId = Reference(key_name="publisher")
152
153
published_date: datetime
154
page_count: int
155
156
class Review(Model):
157
# Multiple references
158
book_id: ObjectId = Reference()
159
reviewer_id: ObjectId = Reference()
160
161
rating: int = Field(ge=1, le=5)
162
comment: str
163
created_at: datetime = Field(default_factory=datetime.utcnow)
164
165
# Usage
166
async def reference_example(engine):
167
# Create author
168
author = Author(name="Jane Doe", email="jane@example.com")
169
await engine.save(author)
170
171
# Create book with reference to author
172
book = Book(
173
title="Python Patterns",
174
isbn="978-1234567890",
175
author_id=author.id, # Reference using ObjectId
176
publisher_id=ObjectId(), # Some publisher ID
177
published_date=datetime.utcnow(),
178
page_count=300
179
)
180
await engine.save(book)
181
182
# Find books by author
183
author_books = await engine.find(Book, Book.author_id == author.id)
184
```
185
186
### Text Indexes
187
188
```python
189
class Article(Model):
190
title: str
191
content: str
192
author: str
193
tags: list[str] = []
194
195
# Text search index
196
__indexes__ = [
197
# Simple text index
198
Index(("title", "text"), ("content", "text")),
199
200
# Text index with weights
201
Index(
202
("title", "text"),
203
("content", "text"),
204
weights={"title": 10, "content": 5},
205
name="article_text_search"
206
),
207
208
# Combined text and regular index
209
Index("author", ("title", "text")),
210
]
211
212
# Text search usage
213
async def text_search_example(engine):
214
# Configure indexes
215
await engine.configure_database([Article])
216
217
# Text search requires MongoDB's $text operator
218
# ODMantic doesn't have built-in text search functions,
219
# but you can use raw MongoDB queries through the collection
220
collection = engine.get_collection(Article)
221
222
# Raw text search
223
cursor = collection.find({"$text": {"$search": "python programming"}})
224
articles = []
225
async for doc in cursor:
226
article = Article.model_validate_doc(doc)
227
articles.append(article)
228
```
229
230
### Geospatial Indexes
231
232
```python
233
class Location(Model):
234
name: str
235
# GeoJSON point format: [longitude, latitude]
236
coordinates: list[float] = Field(min_items=2, max_items=2)
237
type: str = "Point"
238
239
# 2dsphere index for geospatial queries
240
__indexes__ = [
241
Index(("coordinates", "2dsphere")),
242
Index("name", ("coordinates", "2dsphere")),
243
]
244
245
# Geospatial usage
246
async def geospatial_example(engine):
247
# Create location
248
location = Location(
249
name="Central Park",
250
coordinates=[-73.965355, 40.782865] # [longitude, latitude]
251
)
252
await engine.save(location)
253
254
# Geospatial queries require raw MongoDB operations
255
collection = engine.get_collection(Location)
256
257
# Find locations near a point
258
near_query = {
259
"coordinates": {
260
"$near": {
261
"$geometry": {
262
"type": "Point",
263
"coordinates": [-73.970, 40.780]
264
},
265
"$maxDistance": 1000 # meters
266
}
267
}
268
}
269
270
cursor = collection.find(near_query)
271
nearby_locations = []
272
async for doc in cursor:
273
location = Location.model_validate_doc(doc)
274
nearby_locations.append(location)
275
```
276
277
### Index Management
278
279
```python
280
async def index_management_examples(engine):
281
# Configure database indexes for models
282
models = [User, BlogPost, Product, Article, Location]
283
await engine.configure_database(models)
284
285
# Update existing indexes (use with caution in production)
286
await engine.configure_database(models, update_existing_indexes=True)
287
288
# Get raw collection for manual index operations
289
collection = engine.get_collection(User)
290
291
# List all indexes
292
indexes = await collection.list_indexes().to_list(None)
293
for index in indexes:
294
print(f"Index: {index}")
295
296
# Create index manually if needed
297
await collection.create_index([("custom_field", 1)], background=True)
298
299
# Drop index manually if needed
300
await collection.drop_index("custom_field_1")
301
```
302
303
### Performance Considerations
304
305
```python
306
class OptimizedModel(Model):
307
# Frequently queried fields should be indexed
308
user_id: ObjectId = Field(index=True)
309
status: str = Field(index=True)
310
created_at: datetime = Field(index=True, default_factory=datetime.utcnow)
311
312
# Compound index for common query patterns
313
__indexes__ = [
314
# For queries like: status="active" AND created_at > date
315
Index("status", ("created_at", -1)),
316
317
# For queries like: user_id=X AND status="active"
318
Index("user_id", "status"),
319
320
# Background index creation (non-blocking)
321
Index("some_large_field", background=True),
322
323
# Sparse index (only for documents with the field)
324
Index("optional_field", sparse=True),
325
326
# Partial index with filter expression
327
Index(
328
"filtered_field",
329
partialFilterExpression={"filtered_field": {"$exists": True}}
330
),
331
]
332
333
# Index strategy guidelines:
334
def index_strategy_example():
335
"""
336
Index Strategy Guidelines:
337
338
1. Index fields used in WHERE clauses frequently
339
2. Create compound indexes for multi-field queries
340
3. Order compound index fields by selectivity (most selective first)
341
4. Use sparse indexes for optional fields
342
5. Use background=True for large collections
343
6. Monitor index usage and remove unused indexes
344
7. Consider partial indexes for conditional queries
345
"""
346
347
# Good compound index order (high to low selectivity)
348
# Index("user_id", "status", "date") # user_id is most selective
349
350
# Bad compound index order
351
# Index("status", "user_id", "date") # status has few unique values
352
```
353
354
### Custom Index Options
355
356
```python
357
class AdvancedIndexModel(Model):
358
name: str
359
data: dict = {}
360
expires_at: datetime
361
362
__indexes__ = [
363
# TTL index (documents auto-expire)
364
Index("expires_at", expireAfterSeconds=3600),
365
366
# Case-insensitive index
367
Index("name", collation={"locale": "en", "strength": 2}),
368
369
# Partial index with complex filter
370
Index(
371
"data.important_field",
372
partialFilterExpression={
373
"data.important_field": {"$exists": True, "$ne": None}
374
},
375
sparse=True
376
),
377
378
# Named index for reference
379
Index("name", "data.category", name="name_category_idx"),
380
381
# Index with custom options
382
Index(
383
("name", "text"),
384
default_language="english",
385
language_override="lang_field"
386
),
387
]
388
```