montydb: a MongoDB-shaped database with no server to run
Monty, Mongo tinified. MongoDB implemented in Python !
At a glance
- What is it?
- montydb is a pure Python implementation of a MongoDB-like API that stores documents in memory, a flat file, SQLite or LMDB, so code written against PyMongo runs unchanged without a database server. It is built for tests, prototypes and small applications, and it is not a MongoDB server in any wire-level sense.
- Who is it for?
- Adopt montydb when your tests or prototypes need MongoDB semantics without a running server, especially when you want a fixture that starts in memory and the same code to run against real MongoDB later, and pin your MongoDB compatibility with the mongo_version setting.
- Can I use it commercially?
- Yes. BSD-3-Clause 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 96 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
The same PyMongo calls without a running server
montydb describes itself as a pure Python implemented database that looks and works like MongoDB. The description is blunt about the ambition: MongoDB implemented in Python, Mongo tinified.
The mechanism is an API clone rather than a protocol implementation. You construct a MontyClient, reach a collection through the familiar client.db.collection path, and then use the query syntax you already know:
>>> from montydb import MontyClient
>>> col = MontyClient(":memory:").db.test
>>> col.insert_many( [{"stock": "A", "qty": 6}, {"stock": "A", "qty": 2}] )
>>> cur = col.find( {"stock": "A", "qty": {"$gt": 4}} )
>>> next(cur)
{'_id': ObjectId('5ad34e537e8dd45d9c61a456'), 'stock': 'A', 'qty': 6}That sample runs with the `:memory:` URI, so nothing touches the disk and no daemon is started. The audience is therefore obvious: anyone who writes PyMongo code in a test suite and does not want a MongoDB instance in CI, plus people building a small application that needs document storage and would rather not run a server.
The README says most of the CRUD operators have been implemented, and points to issue #14 for the full list. That wording is the important caveat. The API surface is a subset chosen deliberately, not a complete MongoDB emulation, and the subset is the thing you have to check against your own queries before committing.
The project also documents the versions it is tested against: MongoDB 3.6, 4.0 and 4.2, with 4.4 listed as forthcoming, and Python 3.7 through 3.12. Python 3.7 in that list is a signal about the project's vintage.
One repository, one storage engine, four choices
Storage is where montydb is genuinely different from a real MongoDB, and the configuration model is stricter than the API suggests.
Four storage engines are available: in-memory, flat-file, sqlite and lmdb, described as lightning memory-mapped db. The rule that catches people is stated plainly: the configuration process is only required on repository creation or modification, and one repository, the parent level of databases, can only ever assign one storage engine. A repository is the parent directory that holds the databases, and the engine choice is fixed at that level.
The flat-file engine is the default on-disk option, and the in-memory engine needs no configuration at all. sqlite and lmdb are not defaults and must be configured before you create a client. Once configured, a file named `monty.storage.cfg` is written into the repository path, which makes the engine choice visible on disk rather than implied by the code.
The configuration call takes general settings and then engine specific keyword arguments:
from montydb import set_storage, MontyClient
set_storage(
# general settings
repository="/db/repo", # dir path for database to live on disk, default is {cwd}
storage="flatfile", # storage name, default "flatfile"
mongo_version="4.0", # try matching behavior with this mongodb version
use_bson=False, # default None, and will import pymongo's bson if None or True
# any other kwargs are storage engine settings.
cache_modified=10, # the only setting that flat-file have
)
# ready to goTwo of those settings carry most of the meaning. `mongo_version` tells the library which MongoDB release to imitate, so behaviour can be pinned to a known target rather than drifting. `use_bson` decides where the BSON types come from: with it false or unset the package uses its own fork of `ObjectId` shipped in `montydb.types`, and setting it true imports PyMongo's bson instead.
Installing montydb and running the first query
Installation is a normal pip install, with a uv variant given for people who use it:
pip install montydbuv pip install montydbTwo extras are optional and they pull in the only real dependencies the project has. The `bson` extra installs pymongo so you can use the real bson types in operation, and the `lmdb` extra installs the lmdb package for the memory-mapped storage engine. Both are ordinary extras, written `pip install montydb[bson]` and `pip install montydb[lmdb]`, with the same two commands available as `uv pip install montydb[bson]` and `uv pip install montydb[lmdb]`.
The default install has no required dependencies at all, which is the point of the bundled ObjectId fork: a test fixture can be installed on a machine with nothing else available.
A complete first run with the default flat-file engine is two steps. Call set_storage with the repository path, then open a client on the same path:
from montydb import set_storage, MontyClient
set_storage("/db/repo", cache_modified=5) # optional step
client = MontyClient("/db/repo") # use current working dir if no path given
# ready to goThe optional step is the set_storage call, and the path given to the client is where the repository lives.
If you want the repository path expressed as a URI instead of a plain path, the montydb scheme is supported:
client = MontyClient("montydb:///db/repo")For sqlite the engine has to be selected before the client is created, and the README is explicit that it is not the default:
from montydb import set_storage, MontyClient
set_storage("/db/repo", storage="sqlite") # required, to set sqlite as engine
client = MontyClient("/db/repo")
# ready to goThe same pattern applies to the lmdb engine, where the storage name in the code is `lightning`.
Behaviour pinned to a MongoDB version
The `mongo_version` setting deserves more attention than it gets. The README describes it as trying to match behaviour with a given mongodb version, and the tested versions are 3.6, 4.0 and 4.2.
That is a deliberate compatibility contract rather than a claim of total fidelity. MongoDB has changed semantics between releases, and an embedded clone that silently follows the newest behaviour would break the assumption that a test running against montydb says something about the code's behaviour against a server. Pinning the version makes the emulation deterministic.
The limitation follows directly. If your application uses a feature introduced after 4.2, montydb will not have it. The same goes for the gap between what MongoDB does and what the implemented CRUD operators cover, which the README defers to issue #14 rather than documenting inline.
There is a second compatibility layer in the storage itself, and it is sharper. The README warns that a pre-existing sqlite storage file saved by montydb 1.3.0 or earlier is not readable or writable after montydb 2.0.0. So a repository created with an old version of the library is not just stale, it is broken, and there is no migration path described. For a team that upgraded across that boundary, the data has to be exported and re-imported with the utilities described below.
The bson decision follows from the same concern about determinism. If you install the `bson` extra, you get PyMongo's real bson types and the optional dependency on pymongo; if you do not, montydb uses its own ObjectId fork. Mixing the two across environments is the kind of difference that shows up as an equality failure in a test rather than as an import error.
Dump and restore utilities that speak both dialects
montydb ships four utilities that exist because an embedded clone still has to interoperate with a real MongoDB deployment, and its README describes each one in terms of that interoperation.
`montyimport` imports content from an Extended JSON file into a MontyCollection instance, and the JSON file could have been generated by `montyexport` or by `mongoexport`. `montyexport` produces a JSON export of the data, loadable by `montyimport` or by `mongoimport`. The binary equivalents work the same way: `montyrestore` loads a BSON dump that could come from `montydump` or from `mongodump`, and `montydump` creates a binary export that `montyrestore` or `mongorestore` can read.
That symmetry is the useful part. You can move data between a test fixture and a live database using the standard Mongo tooling, in either direction, without writing conversion code.
The API shape is consistent across the four:
from montydb import open_repo, utils
with open_repo("foo/bar"):
utils.montyimport("db", "col", "/path/dump.json")
and the export side:
from montydb import open_repo, utils
with open_repo("foo/bar"):
utils.montyexport("db", "col", "/data/dump.json")
There is one more utility with a different purpose. `MongoQueryRecorder` records MongoDB query results over a period of time, and the README is explicit that it requires access to the database profiler. It works by filtering the database profile data and reproducing the queries of `find` and `distinct` commands. This is not an embedded feature at all: it points at a real MongoDB deployment, imports `pymongo` and `MongoClient`, and turns profiler output into a replayable query log.
That tool answers a question embedded clones cannot: what queries does the application actually issue in production, as opposed to what the test says it issues.
Where an embedded Mongo clone stops being enough
The first limit is concurrency, and it is structural rather than a missing feature. Every storage engine listed here is embedded and in-process, so a montydb repository belongs to one Python process. A real MongoDB is a server that other processes, and other machines, talk to. If your design needs several workers reading the same data, or a web tier separated from the database tier, montydb is not the shape of the problem.
The second limit is query fidelity, and it is a matter of degree rather than kind. The README promises most CRUD operators, with the list living in an issue tracker. Any application that leans on aggregation pipelines, on transactions, on change streams, or on geospatial queries is asking for features outside the stated scope.
The third is the engine lock-in. Since one repository can only assign one storage engine, choosing flat-file and later needing SQLite means a new repository. And the SQLite path has a compatibility cliff at 2.0.0 that predates the current release line, so the format is not guaranteed stable across the history of the project.
The alternative to montydb is to skip the emulation and use the storage engine directly, writing plain SQLite calls or using a document store with a Python-native API, and to keep MongoDB-specific syntax out of the application layer. The difference in approach is where the compatibility burden sits: montydb lets the application keep MongoDB shaped code and confines the difference to one client object and one configuration file, while using SQLite directly removes the emulation layer but means rewriting queries and losing the PyMongo shaped API.
There is a third option for the test case specifically, which is to run a real MongoDB in the test environment. It costs container startup time and CI memory, and it gives you the real thing, including the operators montydb does not implement.
Licence, packaging and the release cadence
montydb is released under the BSD-3-Clause licence, with the licence text in LICENSE at the top of the repository, and the packaging metadata declares the same identifier along with a LICENSE file entry. BSD-3-Clause is a permissive licence that allows commercial use, modification and redistribution provided the notice and the disclaimer of warranty are retained.
The packaging is modern and unusual in one respect. The build backend is hatchling, and the version in the pyproject is 99.dev.0 rather than a release number, which is a placeholder that the build script sets. The project metadata lists no required dependencies at all, with pymongo and lmdb only appearing as optional extras, and the keywords explicitly include embedded, which tells you the intended position in the ecosystem.
The development tooling is worth a glance for anyone maintaining a fork. The dev group pulls in pytest with coverage, ruff for linting with a line length of 88, bandit for security checks, codespell, mongoengine, and a pinned pymongo 3.11.3. The codespell configuration carries an explicit ignore list for the word nin, which is the MongoDB query operator $nin and the kind of token a spell checker trips over constantly.
On cadence, the release history shows 2.5.6 on 2025-11-01, 2.5.5 on 2025-09-28 and 2.5.4 on 2025-08-04, a monthly cadence through the second half of 2025. The last push was on 2026-07-20 and the repository is not archived. The README lists Python 3.12 as the newest tested version while the metadata classifiers also cover 3.7, so the supported Python range has drifted upward in practice since those classifiers were written.
Editorial conclusion
Adopt montydb when your tests or prototypes need MongoDB semantics without a running server, especially when you want a fixture that starts in memory and the same code to run against real MongoDB later, and pin your MongoDB compatibility with the mongo_version setting. Do not adopt it for concurrency, for query features outside the implemented CRUD operators, or as a drop-in replacement for a live MongoDB deployment, because the storage engines are embedded and single-process, and SQLite files written by montydb 1.3.0 and earlier are not readable after 2.0.0. Before you start, decide which storage engine your repository will use, since one repository can only ever assign one, and set use_bson explicitly if you want PyMongo's bson types rather than the bundled ObjectId fork.
Frequently asked questions
What is montydb and what is it used for?
montydb is a pure Python implementation of a database that looks and works like MongoDB, so PyMongo shaped code can run without a database server. The README describes it as Mongo tinified, with most CRUD operators implemented and the full list tracked in issue #14.
How do I install montydb?
Run `pip install montydb`, or `uv pip install montydb`. Optional extras add features: `pip install montydb[bson]` pulls in pymongo for the real bson types, and `pip install montydb[lmdb]` adds the lmdb storage engine.
Which storage engines does montydb support?
In-memory, flat-file, sqlite and lmdb. Flat-file is the default on-disk engine, while sqlite and lmdb must be selected with set_storage before the client is created, and one repository can only ever assign one storage engine.
How do I make montydb behave like a specific MongoDB version?
Set the `mongo_version` option when you configure the storage. The README lists MongoDB 3.6, 4.0 and 4.2 as the tested versions, with 4.4 on the way.
Can montydb read a SQLite repository created by an older release?
No. The README states that a pre-existing sqlite storage file saved by montydb 1.3.0 or earlier is not readable or writable after montydb 2.0.0, so data has to be exported and re-imported with the montyexport and montyimport utilities.
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/davidlatwe-montydb)Was this useful?