AsyncIO MongoDB Object Document Mapper for Python using type hints
—
ODMantic provides index management for MongoDB collections through field-level indexing options and compound index definitions.
Define compound indexes spanning multiple fields with custom options.
class Index:
"""Compound index definition for MongoDB collections."""
def __init__(self, *fields, **kwargs):
"""
Create compound index definition.
Args:
*fields: Field names or (field, direction) tuples
**kwargs: Index options (unique, sparse, background, etc.)
Example:
Index("field1", ("field2", -1), unique=True)
Index(User.name, User.email, background=True)
"""Define reference fields that link to other documents.
def Reference(*, key_name=None):
"""
Define reference field to another document.
Args:
key_name: MongoDB field name for the reference
Returns:
Field configuration for ObjectId reference
"""Index options available in Field definitions.
# In Field() function:
# index=True - Create regular index
# unique=True - Create unique index
# primary_field=True - Use as primary key (_id)from odmantic import Model, Field
class User(Model):
# Regular index
username: str = Field(index=True)
# Unique index
email: str = Field(unique=True)
# Both indexed
phone: str = Field(index=True, unique=True)
# Non-indexed fields
first_name: str
last_name: str
age: int
# The engine will create indexes when configure_database is called
async def setup_indexes(engine):
await engine.configure_database([User])from odmantic import Model, Index
class BlogPost(Model):
title: str
author: str = Field(index=True)
category: str = Field(index=True)
published_date: datetime
view_count: int = 0
tags: list[str] = []
# Define compound indexes
model_config = {
"collection": "blog_posts",
"indexes": [
# Compound index on author and published_date
Index("author", "published_date"),
# Compound index with sort direction
Index("category", ("published_date", -1)),
# Unique compound index
Index("author", "title", unique=True),
# Text index for search
Index(("title", "text"), ("content", "text")),
# Sparse index (only indexes documents with the field)
Index("tags", sparse=True),
]
}
# Alternative way to define indexes using class attribute
class Product(Model):
name: str
category: str
price: float
brand: str
sku: str = Field(unique=True)
# Define indexes as class attribute
__indexes__ = [
Index("category", "brand"),
Index("price", background=True),
Index(("name", "text"), name="text_search_index"),
]from odmantic import Model, Reference, ObjectId
class Author(Model):
name: str = Field(unique=True)
email: str = Field(unique=True)
bio: str = ""
class Book(Model):
title: str
isbn: str = Field(unique=True)
# Reference to Author document
author_id: ObjectId = Reference()
# Reference with custom key name
publisher_id: ObjectId = Reference(key_name="publisher")
published_date: datetime
page_count: int
class Review(Model):
# Multiple references
book_id: ObjectId = Reference()
reviewer_id: ObjectId = Reference()
rating: int = Field(ge=1, le=5)
comment: str
created_at: datetime = Field(default_factory=datetime.utcnow)
# Usage
async def reference_example(engine):
# Create author
author = Author(name="Jane Doe", email="jane@example.com")
await engine.save(author)
# Create book with reference to author
book = Book(
title="Python Patterns",
isbn="978-1234567890",
author_id=author.id, # Reference using ObjectId
publisher_id=ObjectId(), # Some publisher ID
published_date=datetime.utcnow(),
page_count=300
)
await engine.save(book)
# Find books by author
author_books = await engine.find(Book, Book.author_id == author.id)class Article(Model):
title: str
content: str
author: str
tags: list[str] = []
# Text search index
__indexes__ = [
# Simple text index
Index(("title", "text"), ("content", "text")),
# Text index with weights
Index(
("title", "text"),
("content", "text"),
weights={"title": 10, "content": 5},
name="article_text_search"
),
# Combined text and regular index
Index("author", ("title", "text")),
]
# Text search usage
async def text_search_example(engine):
# Configure indexes
await engine.configure_database([Article])
# Text search requires MongoDB's $text operator
# ODMantic doesn't have built-in text search functions,
# but you can use raw MongoDB queries through the collection
collection = engine.get_collection(Article)
# Raw text search
cursor = collection.find({"$text": {"$search": "python programming"}})
articles = []
async for doc in cursor:
article = Article.model_validate_doc(doc)
articles.append(article)class Location(Model):
name: str
# GeoJSON point format: [longitude, latitude]
coordinates: list[float] = Field(min_items=2, max_items=2)
type: str = "Point"
# 2dsphere index for geospatial queries
__indexes__ = [
Index(("coordinates", "2dsphere")),
Index("name", ("coordinates", "2dsphere")),
]
# Geospatial usage
async def geospatial_example(engine):
# Create location
location = Location(
name="Central Park",
coordinates=[-73.965355, 40.782865] # [longitude, latitude]
)
await engine.save(location)
# Geospatial queries require raw MongoDB operations
collection = engine.get_collection(Location)
# Find locations near a point
near_query = {
"coordinates": {
"$near": {
"$geometry": {
"type": "Point",
"coordinates": [-73.970, 40.780]
},
"$maxDistance": 1000 # meters
}
}
}
cursor = collection.find(near_query)
nearby_locations = []
async for doc in cursor:
location = Location.model_validate_doc(doc)
nearby_locations.append(location)async def index_management_examples(engine):
# Configure database indexes for models
models = [User, BlogPost, Product, Article, Location]
await engine.configure_database(models)
# Update existing indexes (use with caution in production)
await engine.configure_database(models, update_existing_indexes=True)
# Get raw collection for manual index operations
collection = engine.get_collection(User)
# List all indexes
indexes = await collection.list_indexes().to_list(None)
for index in indexes:
print(f"Index: {index}")
# Create index manually if needed
await collection.create_index([("custom_field", 1)], background=True)
# Drop index manually if needed
await collection.drop_index("custom_field_1")class OptimizedModel(Model):
# Frequently queried fields should be indexed
user_id: ObjectId = Field(index=True)
status: str = Field(index=True)
created_at: datetime = Field(index=True, default_factory=datetime.utcnow)
# Compound index for common query patterns
__indexes__ = [
# For queries like: status="active" AND created_at > date
Index("status", ("created_at", -1)),
# For queries like: user_id=X AND status="active"
Index("user_id", "status"),
# Background index creation (non-blocking)
Index("some_large_field", background=True),
# Sparse index (only for documents with the field)
Index("optional_field", sparse=True),
# Partial index with filter expression
Index(
"filtered_field",
partialFilterExpression={"filtered_field": {"$exists": True}}
),
]
# Index strategy guidelines:
def index_strategy_example():
"""
Index Strategy Guidelines:
1. Index fields used in WHERE clauses frequently
2. Create compound indexes for multi-field queries
3. Order compound index fields by selectivity (most selective first)
4. Use sparse indexes for optional fields
5. Use background=True for large collections
6. Monitor index usage and remove unused indexes
7. Consider partial indexes for conditional queries
"""
# Good compound index order (high to low selectivity)
# Index("user_id", "status", "date") # user_id is most selective
# Bad compound index order
# Index("status", "user_id", "date") # status has few unique valuesclass AdvancedIndexModel(Model):
name: str
data: dict = {}
expires_at: datetime
__indexes__ = [
# TTL index (documents auto-expire)
Index("expires_at", expireAfterSeconds=3600),
# Case-insensitive index
Index("name", collation={"locale": "en", "strength": 2}),
# Partial index with complex filter
Index(
"data.important_field",
partialFilterExpression={
"data.important_field": {"$exists": True, "$ne": None}
},
sparse=True
),
# Named index for reference
Index("name", "data.category", name="name_category_idx"),
# Index with custom options
Index(
("name", "text"),
default_language="english",
language_override="lang_field"
),
]Install with Tessl CLI
npx tessl i tessl/pypi-odmantic