Skip to content

Pydongo Mutation System Guide

Updates from Mutations

Pydongo's mutation system allows you to perform atomic MongoDB updates on large numbers of documents without loading them into memory.

Instead of fetching documents, modifying them in Python, and saving them one by one (which becomes slow and memory-intensive for thousands or millions of records), you can compose MongoDB update operations directly using familiar Python syntax.

This is the primary intended use case of the Mutation DSL.

Core Idea

  • Write Python-like syntax -> Generate efficient MongoDB bulk/atomic updates
  • No documents are loaded into memory when using collection-level mutations

Example

# Efficient: updates 10,000+ documents in one MongoDB operation
query = users.find(users.followers < 50)
query.followers += 10
query.posts.add_to_set(Post(content="Thanks for being active!"))
query.mutate()               # Single updateMany() call

How to Create and Apply Mutations

Mutations are collected automatically inside a context created when you call .find().

query = collection.find(collection.age > 30)

# These statements build mutations in the shared context
query.status = "active"                    # $set
query.score += 100                         # $inc
query.tags.add_to_set("vip")               # $addToSet
query.profile.views.setmax(1000)           # $max
query.bio.unset()                          # $unset

# Execute the bulk update (updateMany)
result = query.mutate()                    # or await query.amutate()

You can always inspect the generated update document:

print(query.get_mutations())
# {
#   "$set": {"status": "active"},
#   "$inc": {"score": 100},
#   "$addToSet": {"tags": "vip"},
#   "$max": {"profile.views": 1000},
#   "$unset": {"bio": ""}
# }

Important Warning: Shared Mutation Context

The mutation context is thread-local / event-loop-local and shared across all .find() calls in the same execution context.

Danger Example

# DANGER - same context is reused!
q1 = users.find(users.country == "FR")
q1.followers += 5

q2 = users.find(users.country == "DE")     # ← same mutation context!
q2.followers += 10

q1.mutate()   # Will apply BOTH +5 and +10 to French users!

Pattern 1: Short-lived query builder

with users.find(users.age > 65) as elderly:
    elderly.status = "retired"
    elderly.pension += 200
    elderly.mutate()

Pattern 2: Explicit reset

query = users.find(users.active == True)
query.last_seen = datetime.utcnow()
query.mutate()
MutationExpressionContext.clear()          # Reset for next query

In most real applications, prefer short-lived query builders.

Supported Mutation Operations

Python Syntax MongoDB Operator Example / Notes
field = value $set user.name = "Alice"
field.unset() $unset Removes field completely
numeric += n / numeric -= n $inc followers += 10, balance -= 50
numeric *= n $mul score *= 1.5
numeric /= n $mul (1/n) price /= 2
field.setmax(value) $max Keeps greater value
field.setmin(value) $min Keeps smaller value
array.push(value) $push Append value
array.add_to_set(value) $addToSet Add only if not present
array.pull(value) $pull Remove matching values
array.popleft() $pop: 1 Remove first element
array.popright() $pop: -1 Remove last element
nested.field = value dot notation user.address.city = "Paris"

Single Document Updates (less common use case)

When you already have a document loaded (find_one / afind_one), you can still use mutation syntax, but the traditional approach is usually simpler:

user = await users.afind_one(users.username == "alice")

user.bio = "Loves async Python"
user.followers += 1
user.posts.push(Post(content="New post!"))

await user.save()  # Applies mutations + full document save

For single documents, modifying the Python object and calling .save() is usually more readable.

The real power of the Mutation DSL appears when performing bulk updates without fetching documents.

When to Use Which Approach

Goal Recommended Approach Memory Usage Speed (large N)
Update 1 document (already loaded) Modify object -> .save() Low -
Update 1 document (without loading) .find_one(...).mutate() None Fast
Update hundreds / thousands / millions .find(...).mutate() None Very fast
Complex per-document logic Load -> process -> save (loop) High Slow

Pydongo's Mutation DSL lets you harness MongoDB's atomic updateMany power using clean, type-safe Python syntax.

Perfect for bulk maintenance tasks, counter updates, user rewards, data cleanups, and migrations.

See the social_media_app.py example for a complete demonstration of rewarding many users in a single operation.