Skip to main content

Typed Mongo

A production-ready modern Python MongoDB ODM

In addition to synchronous mode, you can use asynchronous mode, just export from typedmongo.asyncio.

Install

pip install typedmongo

Usage

Usage examples trump all usage documentation. So please look at the Example below first.

Example
import datetime
from typing import Literal

from pymongo import AsyncMongoClient as MongoClient

import typedmongo.asyncio as mongo


class Wallet(mongo.Document):
    balance: mongo.DecimalField


class User(mongo.MongoDocument):
    name: mongo.StringField
    gender: mongo.LiteralField[Literal["m", "f"]]
    age: mongo.IntegerField
    tags: mongo.ListField[str]
    wallet: mongo.EmbeddedField[Wallet]
    created_at: mongo.DateTimeField = mongo.DateTimeField(
        default=lambda: datetime.datetime.now(datetime.timezone.utc)
    )
    children: mongo.ListField[User]
    extra: mongo.DictField = mongo.DictField(default=dict)
    # Optional fields
    nickname: mongo.OptionalField[str]
    bio: mongo.OptionalField[str]


async def main():
    await mongo.initial_collections(
        MongoClient().mongo,
        User,
    )

    # Insert one document
    document_id = await User.objects.insert_one(
        User.load(
            {
                "name": "Aber",
                "gender": "m",
                "age": 18,
                "tags": ["a", "b"],
                "wallet": {"balance": 100},
                "children": [],
            },
        )
    )

    # Find one document
    user = await User.objects.find_one(User._id == document_id, sort=[+User.age])

    # Update one document
    update_result = await User.objects.update_one(
        User._id == document_id, {"$set": {"tags": ["a", "b", "e", "r"]}}
    )

    # Delete one document
    delete_result = await User.objects.delete_one(User._id == document_id)

    # Find one and update
    user = await User.objects.find_one_and_update(
        User._id == document_id, {"$set": {"tags": ["a", "b", "e"]}}
    )

    # Find one and replace
    user = await User.objects.find_one_and_replace(
        User._id == document_id,
        User.load({"name": "Aber", "age": 0}),
        after_document=True,
    )

    # Find one and delete
    user = await User.objects.find_one_and_delete(User._id == document_id)

    # Find many documents and sort
    users = [user async for user in User.objects.find(User.age == 18, sort=[-User.age])]

    # Update many documents
    update_result = await User.objects.update_many(
        User.wallet._.balance == Decimal("100"), {"$inc": {"wallet.balance": 10}}
    )

    # Count documents
    await User.objects.count_documents(User.age >= 0)

    # Bulk write operations
    await User.objects.bulk_write(
        mongo.DeleteOne(User._id == 0),
        mongo.DeleteMany(User.age < 18),
        mongo.InsertOne(User.load({"name": "InsertOne"}, partial=True)),
        mongo.ReplaceOne(User.name == "Aber", User.load({}, partial=True)),
        mongo.UpdateMany({}, {"$set": {"age": 25}}),
        mongo.UpdateMany(User.name == "Yue", {"$set": {"name": "yue"}}),
    )

Document

Note: The Document must be initialized with initial_collections before it can be used.

  • Document.load: Load data from dict to instance, and validate the data.
  • Document.dump: Dump the instance to jsonable dict.

Collection Name

Document.__collection_name__: Normally, subclasses of Document will generate a collection_name based on the Class Name, but if you want to customize it, you can set __collection_name__ when defining it.

class APIKey(mongo.Document):
    __collection_name__ = "api_key"

Raw Collection

If you want to use functions such as aggregate, you can access pymongo's original collection object through Document.objects.collection.

Document.objects.collection.aggregate([
    {"$group": {"_id": "$field", "count": {"$sum": 1}}}
])

Field

  • ObjectIdField
  • StringField
  • IntegerField
  • FloatField
  • BooleanField
  • DecimalField
  • DateTimeField
  • DictField
  • EmbeddedField
  • ListField
  • LiteralField
  • UnionField
  • EnumField
  • OptionalField[T] - Optional field that defaults to None

Optional Fields

Use mongo.OptionalField[T] to declare fields that:

  • Accept None values
  • Default to None when missing, in both partial and non-partial loads
class User(mongo.Document):
    name: mongo.StringField  # Required
    nickname: mongo.OptionalField[str]  # Optional, defaults to None
    age: mongo.OptionalField[int]
    bio: mongo.OptionalField[str]
    wallet: mongo.OptionalField[Wallet]  # Optional embedded document
    tags: mongo.OptionalField[list[str]]  # Optional list

# Create with only required fields
user = User(name="Alice")
assert user.nickname is None

# Load without optional fields (non-partial)
user = User.load({"name": "Bob"})
assert user.nickname is None

# Load without optional fields (partial)
user = User.load({"name": "Charlie"}, partial=True)
assert user.nickname is None

Difference from allow_none=True:

  • Regular fields with allow_none=True still require a value (or explicit default)
  • OptionalField[T] fields automatically default to None if missing, in both partial and non-partial loads

Conditional expressions

If you want to use conditional expressions with methods like aggregate, you can call expression.compile() to get a mongo expression.

Document.objects.collection.aggregate([
    {"$match": (Document.age >= 18).compile()},
    {"$group": {"_id": "$field", "count": {"$sum": 1}}},
])

Comparison expressions

  • Document.field == value
  • Document.field != value
  • Document.field > value
  • Document.field >= value
  • Document.field < value
  • Document.field <= value

Logical expressions

  • (Document.field == value) & (Document.field == value)
  • (Document.field == value) | (Document.field == value)
  • ~(Document.field == value)
  • ~((Document.field == value) & (Document.field == value))
  • ~((Document.field == value) | (Document.field == value))

String matching shortcuts

  • Contains(value, case_sensitive=True) - Check if field contains substring
  • StartsWith(value, case_sensitive=True) - Check if field starts with prefix
  • EndsWith(value, case_sensitive=True) - Check if field ends with suffix
from typedmongo.asyncio import Contains, StartsWith, EndsWith

# Find users whose name contains "John"
users = User.objects.find(User.name == Contains("John"))

# Case-insensitive search
users = User.objects.find(User.name == Contains("john", case_sensitive=False))

# Starts with / Ends with
users = User.objects.find(User.name == StartsWith("A"))
users = User.objects.find(User.email == EndsWith("@example.com"))

RawExpression

Sometime, you maybe need use raw query, you can use RawExpression to do that.

from typedmongo.asyncio import RawExpression
# Or `from typedmongo import RawExpression`

User.objects.find(RawExpression({"field_name": {"$mongo_command": value}}) & User.age > 18)

Sort expressions

  • +Document.field: Ascending
  • -Document.field: Descending
User.objects.find(..., sort=[+User.age, -User.name])

Objects

  • Document.objects: The object manager of the Document.
    • collection: The collection of the Document.
    • use_session: Use session for the operations. (Use contextvars, so you don't need to pass the session to the function parameters)
    • use_transaction: Use transaction for the operations.
    • insert_one: Insert one document.
    • insert_many: Insert many documents.
    • find: Find many documents.
    • find_one: Find one document.
    • find_one_and_update: Find one and update.
    • find_one_and_replace: Find one and replace.
    • find_one_and_delete: Find one and delete.
    • delete_one: Delete one document.
    • delete_many: Delete many documents.
    • update_one: Update one document.
    • update_many: Update many documents.
    • count_documents: Count documents.
    • bulk_write: Bulk write operations.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typedmongo-1.20.0.tar.gz (49.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

typedmongo-1.20.0-py3-none-any.whl (34.0 kB view details)

Uploaded Python 3

File details

Details for the file typedmongo-1.20.0.tar.gz.

File metadata

  • Download URL: typedmongo-1.20.0.tar.gz
  • Upload date:
  • Size: 49.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for typedmongo-1.20.0.tar.gz
Algorithm Hash digest
SHA256 bb43f24eaf742a6c2d8ddeea7d30da052dd55e63edaf365f86c09708ce2b59ba
MD5 615b8160f29f00946faf1fcd617e21de
BLAKE2b-256 4b37c315f7a3b34302905a40a1f195c1178572521bbc74a4ce1fcf388dcd2937

See more details on using hashes here.

Provenance

The following attestation bundles were made for typedmongo-1.20.0.tar.gz:

Publisher: ci.yml on abersheeran/typedmongo

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file typedmongo-1.20.0-py3-none-any.whl.

File metadata

  • Download URL: typedmongo-1.20.0-py3-none-any.whl
  • Upload date:
  • Size: 34.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for typedmongo-1.20.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5054d82cc9503b98e744a6888148f77d89cd8c650d506545eaa663032240be3a
MD5 b3de4a8926bc6f986568aea29cb80ee1
BLAKE2b-256 7dfa3041fee1dc275f977a3cb87c4fbd54a226c49a16354d150d9cba81d3df8a

See more details on using hashes here.

Provenance

The following attestation bundles were made for typedmongo-1.20.0-py3-none-any.whl:

Publisher: ci.yml on abersheeran/typedmongo

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page