ODMantic: a MongoDB object document mapper built on Pydantic type hints
Sync and Async ODM (Object Document Mapper) for MongoDB based on python type hints
At a glance
- What is it?
- ODMantic maps MongoDB documents to Python classes through Pydantic models, with both a synchronous and an asyncio engine, validation through Field constraints, and a functional query API. It fits teams that want type checking and validation in front of MongoDB, and it asks for Pydantic 2.5 and Python 3.8 as the price.
- Who is it for?
- Adopt ODMantic if you want MongoDB documents to be typed Python objects with validation applied before a write, because the model layer, the Field constraints and the two engine flavours are the entire point of the library. Do not adopt it if your project is still on Pydantic 1 or on Python 3.7, since the stated floors are Pydantic 2.5 and Python 3.8, and treat the Beta development status as a signal for anything long lived.
- Can I use it commercially?
- Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 158 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What ODMantic is and what it borrows from Pydantic
ODMantic is a sync and async object document mapper for MongoDB based on standard Python type hints, and it is built on top of Pydantic for model definition and validation. That second clause is the whole design.
The core features are listed as simple, developer experience, fully typed, AsyncIO support and serialization. Simple means you type your fields with Python types and build queries with Python comparison operators. Developer experience means field and method autocompletion, type hints, data validation and database operations through a functional API. Fully typed means static analysis can catch problems before you run anything. Serialization means JSON serialization and JSON schema generation come built in.
The requirements are concrete. Python 3.8 and later, tested against 3.8, 3.9, 3.10 and 3.11. Pydantic 2.5 and later. MongoDB 4.0 and later. The project metadata describes the package as an AsyncIO MongoDB Object Document Mapper for Python using type hints, and its development status is listed as 4, Beta.
The licence is ISC. The package is on PyPI as odmantic, the documentation is at art049.github.io/odmantic, and the last push to the repository was on 2026-05-19. The version history is uneven: 1.0.2 came in April 2024, and both 1.0.3 and 1.1.0 landed in January 2026.
Defining a model is just a class with type hints
The first example in the documentation is a publisher model, and it shows the three things you can do in a model body: plain typed fields, fields with a constraint, and optional fields.
from typing import Optional
from odmantic import Field, Model
class Publisher(Model):
name: str
founded: int = Field(ge=1440)
location: Optional[str] = NoneThe name field is a required string with no extra validation. The founded field carries a lower bound, and the reason given in the documentation is a piece of domain logic rather than a technical one: since the printing press was invented in 1440, it is handy to allow only values above 1440, and the ge keyword argument to Field does exactly that.
The location field is Optional with a None default, which makes it non required and sets it automatically when unspecified.
One detail is easy to miss and useful to know: the collection name is chosen by the library, not by you. For this model the collection is publisher, and the documentation shows the stored documents sitting in the publisher collection with the fields in alphabetical order plus an _id. The model class name drives the collection, so renaming a class has a data consequence.
Every operation goes through an engine object
In ODMantic, every database operation is performed using the engine object. There is no global connection and no decorator to add to your route handlers. You construct an engine and call methods on it.
from odmantic import AIOEngine
engine = AIOEngine()AIOEngine stands for AsyncIOEngine, and the default behaviour is documented precisely: it automatically tries to connect to a MongoDB instance running locally on port 27017, and since no database name was provided it uses the database named test.
That default is convenient for a first run and a trap in production, because a forgotten database name silently points you at test rather than raising. It is also why the documentation's next step is to start a local MongoDB server, which it suggests doing with Docker, and notes that closing the terminal terminates the instance and removes the container.
docker run --rm -p 27017:27017 mongoMost of the engine I/O methods are asynchronous, which is why the calls in the examples are awaited.
Writing documents and how the id field maps back
Persisting is a single call, and the instances are ordinary Python objects beforehand, which is what makes the model layer useful. You can construct, validate and inspect them without a database in the picture.
instances = [
Publisher(name="HarperCollins", founded=1989, location="US"),
Publisher(name="Hachette Livre", founded=1826, location="FR"),
Publisher(name="Lulu", founded=2002)
]
await engine.save_all(instances)The last publisher has no location, so the field is set to None and stored as null in the database, which the documented mongo CLI output shows as null for the Lulu record.
The id handling is the part worth understanding. MongoDB requires an _id field to act as a primary key, and the documentation says this field is added by ODMantic and can be accessed under the name id on the model. So the attribute is id in Python and _id in the document, and the value is the usual ObjectId. The documented way to read it back is to print the attribute on the instance.
That mapping is a small thing with a real consequence: a field named id in your model and the primary key are different fields, and code that assumes instances have an id before they are saved will find it empty.
Queries are built with Python comparison operators
The query API is where the type hints pay off. Instead of assembling a filter dictionary, you use comparison operators on the model class itself, and the documentation describes this as building queries using Python comparison operators.
early_publishers = await engine.find(Publisher, Publisher.founded <= 2000)
print(early_publishers)The first argument to engine.find is the model, and the second is the filter expression, evaluated against the class attributes rather than against instance values. The documented output is a list of Publisher instances, not a list of raw dictionaries, so the return value is already mapped back through the model.
Because the filter is a Python expression, the type checker sees it. A typo on a field name is a static error rather than a runtime filter that matches nothing, which is the concrete advantage over writing dictionaries by hand.
For inspection, the documentation points at Compass or RoboMongo for a graphical view, and also at the mongo CLI directly, with a command that dumps the collection.
mongo --eval "db.publisher.find({})"Async and sync, and the frameworks it is meant to sit inside
The asyncio support is the headline, and the documentation is careful about what it means. ODMantic works well with ASGI frameworks, naming FastAPI, quart, sanic and Starlette, but the project states that it also works perfectly in synchronous environments.
The implementation shows how the two worlds are bridged. The package dependencies are pydantic at 2.5.2 or later, motor at 3.1.1 or later, pymongo at 4.1.0 or later, and typing-extensions for Python versions below 3.11. Both motor and pymongo are direct dependencies of the package, and the README lists AsyncIO support alongside synchronous use.
That dependency set is also the clearest statement of what the alternative looks like. Using PyMongo or Motor directly gives you dictionaries and a cursor, with no validation layer, no typed attributes and no JSON schema generation. ODMantic adds Pydantic in front of that, and the price is the Pydantic 2.5 floor, the Python 3.8 floor, and a Beta development status.
The repository layout backs the tooling story. Beyond the odmantic package and the tests directory there are docs and a mkdocs.yml for the documentation site, a mypy.ini for static analysis, a tox.ini for test environments, a uv.lock for dependency locking, a.pre-commit-config.yaml, a Taskfile.yml, a mongodb-cluster directory with a.mongodb-cluster-action workflow, and a.devcontainer directory. So the project runs its own MongoDB in CI rather than mocking the driver.
The gaps: a Beta library on a Pydantic 2 migration
Three limits are visible in the facts rather than hidden.
The first is the development status. The project metadata lists 4, Beta, which is a statement about the library rather than about its tests, and it is worth weighing accordingly for anything long lived.
The second is the dependency migration. The requirement is Pydantic 2.5 and later, and the model layer is Pydantic itself, so a project still on Pydantic 1 cannot use this version. Since the release history shows 1.0.2 in April 2024 and then 1.0.3 and 1.1.0 in January 2026, the versions before and after that gap are not interchangeable, and the documentation does not include a migration note covering the change.
The third is the documented surface. The example path in the documentation is built around the asynchronous engine, and the sync engine is present in the project description but is not walked through in the same detail. If your application is synchronous, the number of things you will read about in the documentation is smaller, and the odds of hitting a documented gap are higher.
What the project does not document is worth naming too. There is no statement about schema migrations for changing an existing model, no mention of indexing advice, and no guidance on transactions or sessions. For a library that has been through a Pydantic major version, that is a reasonable set of gaps to check against your own requirements.
Editorial conclusion
Adopt ODMantic if you want MongoDB documents to be typed Python objects with validation applied before a write, because the model layer, the Field constraints and the two engine flavours are the entire point of the library. Do not adopt it if your project is still on Pydantic 1 or on Python 3.7, since the stated floors are Pydantic 2.5 and Python 3.8, and treat the Beta development status as a signal for anything long lived. Before you build on it, confirm that the sync engine covers every call you need, since the documented example path is built around AIOEngine and its await calls.
Frequently asked questions
How do I install ODMantic?
The installation is pip install odmantic. The stated requirements are Python 3.8 or later, Pydantic 2.5 or later and MongoDB 4.0 or later.
How do I query documents with ODMantic?
Queries are built with Python comparison operators on the model class, for example engine.find(Publisher, Publisher.founded <= 2000). The first argument is the model and the second is the filter expression, and the result is a list of model instances rather than raw documents.
Which database does AIOEngine connect to by default?
The AIOEngine automatically tries to connect to a MongoDB instance running locally on port 27017, and since no database name is provided it uses the database named test.
Does ODMantic work with FastAPI and other ASGI frameworks?
The documentation states that ODMantic works well with ASGI frameworks including FastAPI, quart, sanic and Starlette, and that it also works in synchronous environments. The engine methods are asynchronous, so calls are awaited.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/art049-odmantic)Was this useful?