Skip to main content

LightODM

PyPI version Python versions Build Status Coverage License Code style: black

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

Composite Keys

For multi-tenant applications or when you need deterministic IDs based on multiple fields, use composite keys:

from lightodm import MongoBaseModel

class TenantUser(MongoBaseModel):
    class Settings:
        name = "tenant_users"
        composite_key = ["tenant_id", "user_id"]  # Order matters

    tenant_id: str
    user_id: str
    data: str

# ID is computed as MD5 hash of concatenated field values
user = TenantUser(tenant_id="tenant1", user_id="user1", data="test")
print(user.id)  # e.g., "a1b2c3..." - always the same for same tenant_id + user_id

# Same values always produce the same ID (idempotent)
user2 = TenantUser(tenant_id="tenant1", user_id="user1", data="different")
assert user.id == user2.id  # True - composite key only uses specified fields

Composite key behavior:

  • Field order in composite_key list determines concatenation order
  • All composite key fields must have non-None values
  • Composite key takes precedence over explicitly provided IDs
  • Produces a 32-character hexadecimal MD5 hash

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 document
  • delete() -> bool - Delete document
  • get(id) -> Optional[T] - Get by ID
  • find_one(filter, **kwargs) -> Optional[T] - Find single document
  • find(filter, **kwargs) -> List[T] - Find multiple documents
  • find_iter(filter, **kwargs) -> Iterator[T] - Iterate over results
  • count(filter=None) -> int - Count documents
  • update_one(filter, update, upsert=False) -> bool - Update single document
  • update_many(filter, update) -> int - Update multiple documents
  • delete_one(filter) -> bool - Delete single document
  • delete_many(filter) -> int - Delete multiple documents
  • aggregate(pipeline, **kwargs) -> List[dict] - Run aggregation pipeline
  • insert_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

Metadata

Release files for lightodm 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lightodm 0.2.0
File Size Uploaded Provenance Actions
lightodm-0.2.0.tar.gz 27.5 kB Without provenance Details for lightodm-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for lightodm 0.2.0
File Interpreter ABI Platform Actions
lightodm-0.2.0-py3-none-any.whl Python 3 none any Details for lightodm-0.2.0-py3-none-any.whl

Total release size: 43.3 kB

Release files / lightodm-0.2.0.tar.gz

Download URL lightodm-0.2.0.tar.gz
Size 27.5 kB
Tags Source
SHA-256 checksum
How to use checksums
9c0caaacb79286bf533a0264b777995c34a856d47d95c19e05e98c8dea3dd554
BLAKE2b-256 checksum
How to use checksums
8c57dde28d98431df9b91495697bbc49d24b86bfc2f2145aa088d53610346062
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / lightodm-0.2.0-py3-none-any.whl

Download URL lightodm-0.2.0-py3-none-any.whl
Size 15.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
83e18d78fa664de6bf4910a67d1a3ae3121b6df52d1b15e05afe5f816dc31449
BLAKE2b-256 checksum
How to use checksums
54f1421726e0fa87b6267e31e7de7e23e57a8171fbaf81763a1f7be700aca757
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Security for lightodm 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page