Skip to content

State Management

Beanie can keep the document state synced with the database in order to find local changes and save only them.

This feature must be explicitly turned on in the Settings inner class:

class Sample(Document):
    num: int
    name: str

    class Settings:
        use_state_management = True

Beanie keeps the current changes (not yet saved in the database) by default (with use_state_management = True), AND the previous changes (saved to the database) with state_management_save_previous = True.

class Sample(Document):
    num: int
    name: str

    class Settings:
        use_state_management = True
        state_management_save_previous = True

Every new save override the previous changes and clears the current changes.

Saving changes

To save only changed values, the save_changes() method should be used.

s = await Sample.find_one(Sample.name == "Test")
s.num = 100
await s.save_changes()

The save_changes() method can only be used with already inserted documents.

Interacting with changes

Beanie exposes several methods that can be used to interact with the saved changes:

s = await Sample.find_one(Sample.name == "Test")

s.is_changed == False
s.get_changes == {}

s.num = 200

s.is_changed == True
s.get_changes() == {"num": 200}

s.rollback()

s.is_changed == False
s.get_changes() == {}

And similar methods can be used with the previous changes that have been saved in the database if state_management_save_previous is set to True:

s = await Sample.find_one(Sample.name == "Test")

s.num = 200
await s.save_changes()

s.has_changed == True
s.get_previous_changes() == {"num": 200}
s.get_changes() == {}

Options

By default, state management will merge the changes made to nested objects, which is fine for most cases as it is non-destructive and does not re-assign the whole object if only one of its attributes changed:

from typing import Dict


class Item(Document):
    name: str
    attributes: Dict[str, float]

    class Settings:
        use_state_management = True
i = Item(name="Test", attributes={"attribute_1": 1.0, "attribute_2": 2.0})
await i.insert()
i.attributes = {"attribute_1": 1.0}
await i.save_changes()
# Changes will consist of: {"attributes.attribute_1": 1.0}
# Keeping attribute_2

However, there are some cases where you would want to replace the whole object when one of its attributes changed. You can enable the state_management_replace_objects attribute in your model's Settings inner class:

from typing import Dict


class Item(Document):
    name: str
    attributes: Dict[str, float]

    class Settings:
        use_state_management = True
        state_management_replace_objects = True

With this setting activated, the whole object will be overridden when one attribute of the nested object is changed:

i = Item(name="Test", attributes={"attribute_1": 1.0, "attribute_2": 2.0})
await i.insert()
i.attributes.attribute_1 = 1.0
await i.save_changes()
# Changes will consist of: {"attributes.attribute_1": 1.0, "attributes.attribute_2": 2.0}
# Keeping attribute_2

When the whole object is assigned, the whole nested object will be overridden:

i = Item(name="Test", attributes={"attribute_1": 1.0, "attribute_2": 2.0})
await i.insert()
i.attributes = {"attribute_1": 1.0}
await i.save_changes()
# Changes will consist of: {"attributes": {"attribute_1": 1.0}}
# Removing attribute_2