Skip to content

# Migration Guide

## Migrating to v1

Before migrating ODMantic, have a look at the Pydantic v2 migration guide.

### Upgrading to ODMantic v1

    pip install -U pydantic

### Handling `Optional` with non-implicit default `None` values

Since this new version, the default value of an `Optional` field is not implicit anymore. Thus, if you want to keep the same behavior, you have to add the `default` parameter to your `Optional` fields.

Before:

    class MyModel(Model):
        my_field: Optional[str]

    assert MyModel().my_field is None

Now:

    class MyModel(Model):
        my_field: Optional[str] = None

    assert MyModel().my_field is None

### Upgrading models configuration

Instead of the old `Config` class, you have to use the new `model_config` typed dict.

Before:

    class Event(Model):
        date: datetime

        class Config:
            collection = "event_collection"
            parse_doc_with_default_factories = True
            indexes = [
                Index(Event.date, unique=True),
                pymongo.IndexModel([("date", pymongo.DESCENDING)]),
            ]

Now:

    class Event(Model):
        date: datetime

        model_config = {
            "collection": "event_collection",
            "parse_doc_with_default_factories": True,
            "indexes": lambda: [
                Index(Event.date, unique=True),
                pymongo.IndexModel([("date", pymongo.DESCENDING)]),
            ],
        }

### Defining custom BSON serializers

Instead of using the `__bson__` class method, you have to use the new WithBsonSerializer annotation.

Note

We will probably bring back the `__bson__` class method in a future version but using the new annotation is the recommended way to define custom BSON serializers.

Here is an example of serializing an integer as a string in BSON:

Before:

    class IntBSONStr(int):
        @classmethod
        def __bson__(cls, v) -> str:
            return str(v)

Now:

    from typing import Annotated
    from odmantic import WithBsonSerializer

    IntBSONStr = Annotated[int, WithBsonSerializer(lambda v: str(v))]

### Building a Pydantic model from an ODMantic model

If you want to build a Pydantic model from an ODMantic model, you now have to enable the `from_attributes` configuration option.

For example, with a `UserModel` that is used internally and a `ResponseSchema` that could be exposed through an API:

    from pydantic import BaseModel, EmailStr
    from odmantic import Model

    class UserModel(Model):
        email: EmailStr
        password_hash: str

    class UserSchema(BaseModel):
        email: EmailStr

    class ResponseSchema(BaseModel):
        user: UserSchema

        model_config = {"from_attributes": True}

    user = UserModel(email="john@doe.com", password_hash="...")
    response = ResponseSchema(user=user)

### Replacing the `Model` and `EmbeddedModel` deprecated methods

- Replace `Model.dict` with the new `Model.model_dump` method

- Replace `Model.doc` with the new `Model.model_dump_doc` method

- Replace `Model.parse_doc` with the new `Model.model_validate_doc` method

- Replace `Model.update` with the new `Model.model_update` method

- Replace `Model.copy` with the new `Model.model_copy` method

### Custom JSON encoders on `odmantic.bson` types

Custom JSON encoders (defined with the `json_encoders` config option) are no longer effective on `odmantic.bson` types since the builtin encoders cannot be overridden in that way anymore.

The solution is to use the PlainSerializer annotation provided by Pydantic. For example, if we want to serialize ObjectId as a `id_` prefixed string:

    from typing import Annotated
    from pydantic import BaseModel, PlainSerializer
    from odmantic import ObjectId

    MyObjectId = Annotated[ObjectId, PlainSerializer(lambda v: "id_" + str(v))]

    class MyModel(BaseModel):
        id: MyObjectId

    instance = MyModel(id=ObjectId("ffffffffffffffffffffffff"))
    print(instance.model_dump_json())
    #> {"id": "id_ffffffffffffffffffffffff"}

And ... that's it, congrats! 🚀⚒️

If you have any questions or if you need help to migrate something that is not covered by this guide, feel free to open an issue on GitHub.