Lightweight MongoDB ODM - Simple async/sync Object-Document Mapper for MongoDB
Project description
LightODM
Lightweight MongoDB ODM - A simple, fast Object-Document Mapper for MongoDB with full async/sync support.
LightODM is a minimal alternative to Beanie that provides essential ODM functionality without the complexity. Built on Pydantic v2, PyMongo, and Motor.
Features
- Dual Mode: Full support for both sync (PyMongo) and async (Motor) operations
- Lightweight: Zero dependencies beyond Pydantic, PyMongo, and Motor
- Type Safe: Full type hints with py.typed marker
- Pydantic v2: Built on Pydantic v2 for robust validation
- Simple API: Clean, intuitive interface similar to Beanie
- Flexible: Optional connection - override
get_collection()for custom setups - Thread Safe: Singleton connection manager with automatic cleanup
Installation
pip install lightodm
Quick Start
Define Your Models
from lightodm import MongoBaseModel
from typing import Optional
class User(MongoBaseModel):
class Settings:
name = "users" # MongoDB collection name
name: str
email: str
age: Optional[int] = None
Environment Setup
Set your MongoDB connection via environment variables:
export MONGO_URL="mongodb://localhost:27017"
export MONGO_USER="your_user"
export MONGO_PASSWORD="your_password"
export MONGO_DB_NAME="your_database"
Sync Usage
# Create and save
user = User(name="John Doe", email="john@example.com", age=30)
user.save()
# Retrieve by ID
user = User.get("some_id")
# Find documents
users = User.find({"age": {"$gte": 18}})
adult_user = User.find_one({"age": {"$gte": 18}})
# Update
User.update_one({"name": "John Doe"}, {"$set": {"age": 31}})
# Delete
user.delete()
User.delete_many({"age": {"$lt": 18}})
# Count
count = User.count({"age": {"$gte": 18}})
# Aggregation
pipeline = [{"$group": {"_id": "$age", "count": {"$sum": 1}}}]
results = User.aggregate(pipeline)
Async Usage
# Create and save
user = User(name="Jane Doe", email="jane@example.com", age=25)
await user.asave()
# Retrieve by ID
user = await User.aget("some_id")
# Find documents
users = await User.afind({"age": {"$gte": 18}})
adult_user = await User.afind_one({"age": {"$gte": 18}})
# Iterate over large result sets
async for user in User.afind_iter({"age": {"$gte": 18}}):
print(user.name)
# Update
await User.aupdate_one({"name": "Jane Doe"}, {"$set": {"age": 26}})
# Delete
await user.adelete()
await User.adelete_many({"age": {"$lt": 18}})
# Count
count = await User.acount({"age": {"$gte": 18}})
# Aggregation
pipeline = [{"$group": {"_id": "$age", "count": {"$sum": 1}}}]
results = await User.aaggregate(pipeline)
Advanced Usage
Custom ID Generation
from lightodm import MongoBaseModel, generate_id
class Product(MongoBaseModel):
class Settings:
name = "products"
# Custom ID (default uses ObjectId)
id: str = None
name: str
sku: str
# Default uses ObjectId
product = Product(name="Widget", sku="WDG-001")
print(product.id) # Generated ObjectId string
Custom Connection
Override get_collection() or get_async_collection() for custom connection logic:
class CustomUser(MongoBaseModel):
class Settings:
name = "custom_users"
name: str
@classmethod
def get_collection(cls):
# Your custom connection logic
from pymongo import MongoClient
client = MongoClient("mongodb://custom-host:27017")
return client["custom_db"]["custom_users"]
Extra Fields
LightODM supports Pydantic's extra='allow' for dynamic fields:
user = User(name="John", email="john@example.com", custom_field="value")
user.save() # custom_field is preserved in MongoDB
Bulk Operations
# Sync
users = [
User(name="User 1", email="user1@example.com"),
User(name="User 2", email="user2@example.com"),
]
ids = User.insert_many(users)
# Async
ids = await User.ainsert_many(users)
LightODM vs Beanie
| Feature | LightODM | Beanie |
|---|---|---|
| Dependencies | Pydantic, PyMongo, Motor | Pydantic, PyMongo, Motor, lazy-model, toml |
| Initialization | Environment variables | init_beanie() required |
| Sync Support | ✅ Full | ❌ Async only |
| Async Support | ✅ Full | ✅ Full |
| Connection | Optional singleton | Required initialization |
| Learning Curve | Low | Medium |
| Code Size | ~500 lines | ~5000+ lines |
| Use Case | Simple projects, microservices | Complex applications |
| Relations | Manual (MongoDB refs) | Built-in with Link |
| Migrations | Manual | Manual |
| Indexes | Manual | Automatic |
| Validation | Pydantic v2 | Pydantic v2 |
| Type Safety | ✅ Full | ✅ Full |
Choose LightODM if:
- You want a simple, lightweight ODM
- You need both sync and async support
- You prefer minimal dependencies
- You want control over connection management
- You're building microservices or simple applications
Choose Beanie if:
- You need built-in relations and document links
- You want automatic index management
- You prefer async-first design
- You're building complex applications with many models
API Reference
MongoBaseModel
Base class for MongoDB document models.
Sync Methods
save(exclude_none=False) -> str- Save/upsert documentdelete() -> bool- Delete documentget(id) -> Optional[T]- Get by IDfind_one(filter, **kwargs) -> Optional[T]- Find single documentfind(filter, **kwargs) -> List[T]- Find multiple documentsfind_iter(filter, **kwargs) -> Iterator[T]- Iterate over resultscount(filter=None) -> int- Count documentsupdate_one(filter, update, upsert=False) -> bool- Update single documentupdate_many(filter, update) -> int- Update multiple documentsdelete_one(filter) -> bool- Delete single documentdelete_many(filter) -> int- Delete multiple documentsaggregate(pipeline, **kwargs) -> List[dict]- Run aggregation pipelineinsert_many(documents) -> List[str]- Insert multiple documents
Async Methods
All sync methods have async equivalents prefixed with a:
asave(),adelete(),aget(),afind_one(),afind(),afind_iter(),acount(),aupdate_one(),aupdate_many(),adelete_one(),adelete_many(),aaggregate(),ainsert_many()
Connection Functions
from lightodm import (
get_mongo_connection,
get_collection,
get_database,
get_client,
get_async_database,
get_async_client,
)
# Get singleton connection
conn = get_mongo_connection()
# Sync helpers
collection = get_collection("users")
db = get_database()
client = get_client()
# Async helpers
db = await get_async_database()
client = await get_async_client()
Development
# Clone repository
git clone https://github.com/Aprova-GmbH/lightodm.git
cd lightodm
# Install with dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Format code
black src tests
ruff check src tests --fix
# Type checking
mypy src
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
Apache License 2.0 - see LICENSE file for details.
Author
Andrey Vykhodtsev - vya@aprova.ch
Links
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lightodm-0.1.0.tar.gz.
File metadata
- Download URL: lightodm-0.1.0.tar.gz
- Upload date:
- Size: 18.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7d72836a4980523085930ee551a1ea8f9e8e39f00798235a7bde61d3d56ab085
|
|
| MD5 |
a566a5984d91117205561da6c97b3d47
|
|
| BLAKE2b-256 |
463a5c659b23ce10bc8ea04f782f0aaa2b86d39a08c007131953889168cfe948
|
File details
Details for the file lightodm-0.1.0-py3-none-any.whl.
File metadata
- Download URL: lightodm-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07328d9eb6b11d1ac9ef352e8b9c32f22ec451d72fe58867b9ba34c0bff5134d
|
|
| MD5 |
5e210f61b9ec9e13e6f04079bd6ab7b1
|
|
| BLAKE2b-256 |
b73a2f51d80783ed869e18119f3c368bbad6b754215014666199d815b59dcb7a
|