mrkeyoor.com_
Sun 20 Sept 12:45 UTC
PyPIDataupdated 20 Sept 2026

pymongo review

PyMongo 4.17.0 is MongoDB's official Python driver and includes synchronous and asyncio clients, the BSON implementation MongoDB expects, and GridFS. It sends Python dictionaries through collection methods such as find, aggregate, insert_one, bulk_write, and watch without imposing an object model. Version 4.17 improves session-bound operations, BSON encode and decode performance, and server selection under overload, while deprecating old Python 2-style SON methods before 5.0.

Verdict

PyMongo 4.17.0 installed in 0.4 seconds and used 8 MB in our sandbox, with 0 known vulnerabilities and a successful 0.14-second bson import. Use it for direct MongoDB access, but add database validation or an ODM when plain dictionaries make silent field mistakes too easy.

We installed it

Lab card: what happened when we installed pymongoScreenshot of pymongo documentation
Install✓ · 0.4s2 packages on disk · 8 MB
Importimport bson in 0.14s · compiled extensions · py.typed · requires Python >=3.9
Known vulns0(pip-audit)

Answers from our run

Does pymongo install cleanly?

Yes. In a fresh container with an empty cache, pip install pymongo finished in 0.4s, leaving 2 packages and 8 MB on disk. pip-audit reported no known vulnerabilities.

What does pymongo need to run?

Python >=3.9, and a platform wheel with compiled extensions. In our run import bson succeeded in 0.14s, and the package ships py.typed for type checkers.

pymongo or beanie: which should you use?

beanie: Use it when an asyncio service wants Pydantic document models and ODM query helpers. PyMongo 4.17.0 installed in 0.4 seconds and used 8 MB in our sandbox, with 0 known vulnerabilities and a successful 0.14-second bson import.

When should you not use pymongo?

You expect model validation at the driver boundary; a misspelled filter key quietly matches nothing and a misspelled update field can become stored data

API stability4/5PyMongo follows semantic versioning, and version 4.17 adds session helpers and internal performance work without replacing current CRUD or asyncio calls. The next major already has visible cleanup work because SON.has_key, iterkeys, and itervalues are deprecated for 5.0. The earlier 4.0 release removed several collection shortcuts, so major upgrades require the migration guide.
Docs4/5MongoDB's current driver manual covers connection targets, CRUD, aggregation, indexes, timeouts, monitoring, security, and synchronous versus asynchronous use. A separate generated API site and detailed changelog provide exact signatures and release changes. The split can make one operational question span 2 sites, but the README links both and warns against installing the unrelated bson package.
Maintenance5/5MongoDB released PyMongo 4.17.0 on 2026-04-20, and the repository was pushed on 2026-08-26. GitHub showed 4,351 stars and 18 open issues and pull requests; the project was not archived. Release work includes server-spec synchronization, BSON performance, session behavior, authentication testing, and supported-platform maintenance.
Ecosystem5/5The supplied count is 22,303,563 weekly downloads. PyMongo is the base layer for ODMs such as Beanie and MongoEngine and directly supports sync and async clients, GridFS, change streams, authentication options, compression, and encryption extras. The driver covers MongoDB-specific transport well, while schema policy and domain models remain separate choices.

Use it if

  • Python code needs the vendor-maintained driver for MongoDB or Atlas
  • Plain dictionaries and MongoDB operators fit better than ODM model classes
  • One project needs both synchronous jobs and native asyncio access
  • The application uses sessions, change streams, GridFS, field-level encryption, or MongoDB-specific authentication
Skip it if

Setup reality

We installed PyMongo 4.17.0 in 0.4 seconds on Python 3.12. Two packages occupied 8 MB, pip-audit reported 0 known vulnerabilities, and import bson completed in 0.14 seconds. The wheel contains compiled .so extensions and py.typed metadata, requires Python 3.9 or newer, and declares 23 direct dependencies. Its installed metadata did not identify a license.

Never install the separate bson package from PyPI. PyMongo already ships its required bson module, and the unrelated distribution can shadow it. Standard mongodb:// connections need no file-based config. mongodb+srv:// discovery needs dnspython. Store credentials and TLS options outside source, and percent-encode reserved characters in URI usernames or passwords. Extras add AWS, GSSAPI, OCSP, compression, and client-side encryption dependencies.

Constructing MongoClient does not prove the address is reachable. It starts topology monitoring and waits for an operation that needs a server. Use client.admin.command('ping') in readiness checks and choose serverSelectionTimeoutMS so a bad URI does not stall the first request for an unexpected interval. Create 1 client per process after any fork and reuse its connection pool.

Cursors fetch subsequent batches while you iterate. A session cannot be shared across concurrent threads or asyncio tasks, and transactions require a replica set or sharded cluster. Version 4.17 adds session-binding context managers, but the scope still has to cover every intended operation. AsyncMongoClient collection methods are awaited, while find returns an async cursor. Type hints do not replace MongoDB schema validation or unique indexes.

Patterns

Reuse one client and insert a document connect-insert

from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017')
orders = client['shop']['orders']
result = orders.insert_one({'sku': 'ABC', 'qty': 3})
print(result.inserted_id)

Client construction starts background monitoring but does not confirm a usable server. Create it after any process fork.

Fail startup on an unreachable server ping-server

from pymongo import MongoClient
from pymongo.errors import ServerSelectionTimeoutError

client = MongoClient(uri, serverSelectionTimeoutMS=3000)
try:
    client.admin.command('ping')
except ServerSelectionTimeoutError as exc:
    raise SystemExit(f'cannot reach MongoDB: {exc}')

Without an explicit operation, a bad URI can stay hidden until the first request needs server selection.

Filter, project, sort, and limit query-project

from pymongo import DESCENDING

cursor = (
    orders.find({'qty': {'$gte': 2}}, {'sku': 1, 'qty': 1, '_id': 0})
          .sort('qty', DESCENDING)
          .limit(20)
)
for document in cursor:
    print(document)

find returns a lazy cursor. Iteration can trigger additional network batches, and a consumed cursor should not be reused for a second pass.

Update or create one document update-upsert

result = orders.update_one(
    {'sku': 'ABC'},
    {'$set': {'qty': 10}, '$currentDate': {'updated_at': True}},
    upsert=True,
)
print(result.matched_count, result.modified_count, result.upserted_id)

Update documents require operators such as $set. Use replace_one when you mean to replace every field except _id.

Send mixed writes as a batch bulk-operations

from pymongo import InsertOne, UpdateOne, DeleteOne

operations = [
    InsertOne({'sku': 'NEW', 'qty': 1}),
    UpdateOne({'sku': 'ABC'}, {'$inc': {'qty': 1}}),
    DeleteOne({'sku': 'OLD'}),
]
result = orders.bulk_write(operations, ordered=False)

Unordered mode continues after an individual failure and may reorder independent operations. Inspect BulkWriteError details before claiming the batch succeeded.

Group and rank server-side aggregate

pipeline = [
    {'$match': {'status': 'shipped'}},
    {'$group': {'_id': '$sku', 'total': {'$sum': '$qty'}}},
    {'$sort': {'total': -1}},
    {'$limit': 10},
]
for row in orders.aggregate(pipeline):
    print(row['_id'], row['total'])

Put selective match and project stages early. A large blocking stage may require allowDiskUse=True and a supporting index.

Enforce uniqueness and add a TTL create-indexes

from pymongo import ASCENDING, DESCENDING

orders.create_index([('sku', ASCENDING)], unique=True)
orders.create_index([('status', ASCENDING), ('created_at', DESCENDING)])
orders.create_index('expires_at', expireAfterSeconds=0)

A new unique index fails when existing documents contain duplicates. TTL deletion is asynchronous and should not be used as an exact scheduler.

Catch a duplicate key separately handle-duplicate

from pymongo.errors import DuplicateKeyError, OperationFailure

try:
    orders.insert_one({'_id': existing_id, 'sku': 'ABC'})
except DuplicateKeyError:
    orders.update_one({'_id': existing_id}, {'$set': {'sku': 'ABC'}})
except OperationFailure as exc:
    print(exc.code, exc.details)

DuplicateKeyError inherits from broader operation exceptions. Catch the specific error first when it has its own recovery path.

Keep related writes in one session run-transaction

with client.start_session() as session:
    def transfer(active_session):
        accounts.update_one(
            {'_id': 1}, {'$inc': {'balance': -100}}, session=active_session,
        )
        accounts.update_one(
            {'_id': 2}, {'$inc': {'balance': 100}}, session=active_session,
        )
    session.with_transaction(transfer)

Transactions require a replica set or sharded cluster. The helper may retry qualifying failures, so the callback must tolerate another execution.

Query with the built-in asyncio client use-async-client

import asyncio
from pymongo import AsyncMongoClient

async def main():
    client = AsyncMongoClient('mongodb://localhost:27017')
    orders = client['shop']['orders']
    await orders.insert_one({'sku': 'ABC', 'qty': 3})
    async for document in orders.find({'qty': {'$gte': 2}}):
        print(document)
    await client.close()

asyncio.run(main())

Collection operations are awaited, while find returns an async cursor for async for. Do not share one session across concurrent tasks.

Resume a collection change stream watch-changes

pipeline = [{'$match': {'operationType': {'$in': ['insert', 'update']}}}]
with orders.watch(pipeline, full_document='updateLookup') as stream:
    for change in stream:
        save_resume_token(change['_id'])
        print(change['operationType'], change['fullDocument'])

Change streams rely on the replica-set oplog. Persist the resume token, but an outage beyond oplog history can still make it unusable.

Put a large file in GridFS store-gridfs

import gridfs

fs = gridfs.GridFS(db)
with open('report.pdf', 'rb') as handle:
    file_id = fs.put(handle, filename='report.pdf', content_type='application/pdf')

with fs.get(file_id) as stored:
    data = stored.read()

GridFS splits bytes across files and chunks collections to bypass the BSON document limit. Object storage is usually a better HTTP download origin.

Alternatives

PackageRegistryPick it when
beaniePyPIUse it when an asyncio service wants Pydantic document models and ODM query helpers
mongoenginePyPIUse it for synchronous document classes with declared fields, validation, and references
motorPyPIKeep it while migrating existing Motor code; start new asyncio work with PyMongo's AsyncMongoClient

More data guides

numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.