PyMongo Basics
Introduction
PyMongo is the official synchronous Python driver for MongoDB. It maps Python dicts to BSON documents and exposes insert_one, find, update_one, and bulk_write with the same semantics as mongosh. This chapter connects from a virtual environment, performs CRUD, handles ObjectId, errors, and transactions—the foundation for FastAPI integration.
Prerequisites
- Python pip and venv
- BSON and Document Basics
- Query Operators and Update Operators
- MongoDB running locally or Atlas URI
Install
python -m venv .venv
source .venv/bin/activate
pip install "pymongo[srv]>=4.6"[srv] adds dnspython for mongodb+srv:// Atlas URIs.
requirements.txt:
pymongo[srv]==4.10.1Connect With MongoClient
mongo_demo.py:
Code explanation:
- One
MongoClientper process—thread-safe pool inside client["hello_demo"]selects databasedb["posts"]selects collection (same asdb.posts)
Close on shutdown (CLI scripts):
client.close()Long-running apps: create client at startup, close at exit.
Insert
Find
Code explanation:
findreturns cursor—lazy until iteration- Projection:
1include,0exclude sort("createdAt", -1)descending
Update and Delete
bulk_write
from pymongo import InsertOne, UpdateOne, DeleteOne
posts.bulk_write([
InsertOne({"title": "Bulk", "slug": "bulk", "published": True}),
UpdateOne({"slug": "pymongo-intro"}, {"$inc": {"viewCount": 1}}),
DeleteOne({"slug": "bulk"}),
], ordered=False)ObjectId and JSON APIs
ObjectId is not JSON-serializable by default:
from bson import ObjectId
import json
def default_serializer(obj):
if isinstance(obj, ObjectId):
return str(obj)
raise TypeError(f"Type {type(obj)} not serializable")
doc = posts.find_one({"slug": "pymongo-intro"})
print(json.dumps(doc, default=default_serializer))FastAPI/Pydantic use str for id in response models — chapter 20.
Parse string id from URL:
def parse_object_id(value: str) -> ObjectId:
if not ObjectId.is_valid(value):
raise ValueError("Invalid id")
return ObjectId(value)Error Handling
from pymongo.errors import DuplicateKeyError, OperationFailure
try:
posts.insert_one({"_id": "fixed-id", "title": "Once"})
posts.insert_one({"_id": "fixed-id", "title": "Twice"})
except DuplicateKeyError:
print("Duplicate _id or unique index")
try:
client.admin.command("nonexistentCommand")
except OperationFailure as exc:
print(exc.code, exc.details)Retry transient errors with exponential backoff in workers—not blind retry on DuplicateKeyError.
Transactions (Replica Set)
Requires replica set URI — chapter 14.
Indexes From PyMongo
posts.create_index("slug", unique=True)
posts.create_index([("published", 1), ("createdAt", -1)])
print(posts.index_information())Project Layout Hint
app/
├── db/
│ ├── client.py # MongoClient singleton
│ └── collections.py # get_posts_collection()
├── repositories/
│ └── posts.py # CRUD functions
└── main.pyKeeps route handlers thin—similar to SQLAlchemy repositories in FastAPI SQLAlchemy.
Post-Chapter Checklist
-
MongoClientconnects andpingsucceeds - CRUD via
insert_one,find,update_one,delete_one -
ObjectIdconverted for JSON -
DuplicateKeyErrorhandled for unique slug
FAQ
PyMongo vs Motor?
PyMongo is sync; Motor wraps it for async/await — Motor chapter. FastAPI sync routes can use PyMongo directly.
ODM like MongoEngine?
ODM adds schema classes; this track uses PyMongo + Pydantic for clarity.
Django MongoDB?
Use MongoDB extension or separate service; Django ORM is SQL-first — Django track.
Connection string in code?
Use os.environ["MONGODB_URI"] — Security.
Type hints?
from pymongo.collection import Collection for Collection[dict] (3.9+ generic) optional.