Katalog toko buku online kita hidup dari empat aksi dasar: menambah judul baru, mencari buku, memperbarui harga dan stok, serta membuang data yang tidak lagi dijual. Di mongosh kita memakai insertOne dan find; di PyMongo nama method-nya sama persis hanya bergaya Python: insert_one dan find. Yang berbeda adalah cara membaca hasil operasi — setiap method mengembalikan objek result dengan atribut yang informatif, dan tipe data Python diterjemahkan otomatis ke BSON.

Peta materi Chapter ini:

  • insert_one / insert_many beserta inserted_id dan inserted_ids.
  • Pemetaan tipe Python ↔ BSON, termasuk datetime untuk field Date.
  • find_one, iterasi find, dan pencarian berdasarkan _id.
  • update_one, update_many, delete_one, delete_many beserta hitungan hasilnya.

Kita kerjakan katalog dari nol. Database latihan dijatuhkan lebih dulu agar skrip ini selalu menghasilkan output yang sama betapa pun sering dijalankan ulang:

python
import os
from datetime import datetime, timezone

from pymongo import MongoClient

URI = os.environ.get(
    "MONGODB_URI", "mongodb://127.0.0.1:27017/?directConnection=true"
)

client = MongoClient(URI, serverSelectionTimeoutMS=5000)
client.drop_database("latihan_crud_py")
db = client["latihan_crud_py"]

# --- CREATE ---
hasil = db.buku.insert_one({
    "judul": "Laut Bercerita",
    "penulis": "Leila S. Chudori",
    "harga": 98000,
    "stok": 12,
    "tanggal_terbit": datetime.now(timezone.utc),
})
print("insert_one  -> acknowledged:", hasil.acknowledged)
print("inserted_id :", hasil.inserted_id)
print("sebagai str :", str(hasil.inserted_id))

hasil_banyak = db.buku.insert_many([
    {"judul": "Bumi", "penulis": "Tere Liye",
     "harga": 89000, "stok": 25, "tanggal_terbit": datetime.now(timezone.utc)},
    {"judul": "Filosofi Teras", "penulis": "Henry Manampiring",
     "harga": 89000, "stok": 18, "tanggal_terbit": datetime.now(timezone.utc)},
])
print("insert_many -> jumlah    :", len(hasil_banyak.inserted_ids))

# --- READ ---
dok = db.buku.find_one({"penulis": "Tere Liye"})
print("find_one    ->", dok["judul"])

print("tersimpan sebagai BSON Date:",
      db.buku.count_documents({"tanggal_terbit": {"$type": "date"}}), "document")
print("terbaca di Python sebagai:", type(dok["tanggal_terbit"]).__name__)

dok_id = db.buku.find_one({"_id": hasil.inserted_id})
print("cari by _id ->", dok_id["judul"])

for buku in db.buku.find():
    print(f"  {buku['judul']} — Rp{buku['harga']} (stok {buku['stok']})")

client.close()

Output yang Dihasilkan:

text
insert_one  -> acknowledged: True
inserted_id : 6ad3ef8e9e27b466502cb65f
sebagai str : 6ad3ef8e9e27b466502cb65f
insert_many -> jumlah    : 2
find_one    -> Bumi
tersimpan sebagai BSON Date: 3 document
terbaca di Python sebagai: datetime
cari by _id -> Laut Bercerita
  Laut Bercerita — Rp98000 (stok 12)
  Bumi — Rp89000 (stok 25)
  Filosofi Teras — Rp89000 (stok 18)

Mari kita bedah baris-baris pentingnya:

  1. db.buku.insert_one({...}) — document adalah dict Python biasa; hasilnya objek InsertOneResult. Atribut inserted_id memuat ObjectId yang dibuat di sisi klien — di layar ia tampil sebagai string heksadesimal, dan str(...) mengubahnya ke str Python saat kita butuh bentuk itu untuk respons API atau log.
  2. datetime.now(timezone.utc) — objek datetime Python diterjemahkan PyMongo menjadi tipe BSON Date. Query $type: "date" di sisi server membuktikan ketiga document tersimpan dengan tipe itu — tipe yang sama yang tampil sebagai ISODate(...) di mongosh.
  3. insert_many([...]) — menerima list of dict; inserted_ids adalah list berisi seluruh _id yang tersimpan, sehingga len() langsung memberi jumlahnya.

Pemetaan tipe lainnya mengikuti logika yang sama:

PythonBSON
dictdocument
listarray
strstring
int, floatint32/int64, double
datetime (aware)Date
ObjectId (dari bson.objectid)ObjectId

Membaca Data #

find_one mengembalikan satu dict — atau None bila tidak ada yang cocok, jadi selalu periksa hasilnya sebelum mengakses field (dok["judul"] pada None memicu TypeError di Python). Adapun find mengembalikan cursor yang bisa di-loop langsung seperti list; mekanisme lazimya kita bedah tuntas di Chapter Cursor dan Pagination.

Pencarian berdasarkan _id adalah pola yang paling sering dipakai di API: data datang sebagai string dari URL, lalu dikonversi menjadi ObjectId sebelum dikirim sebagai filter. Bentuk konversinya:

python Ilustrasi — _id dari string menjadi ObjectId
from bson.objectid import ObjectId

id_dari_url = "6ad3ef8e9e27b466502cb65f"
dok = db.buku.find_one({"_id": ObjectId(id_dari_url)})

Menyaring dengan string mentah ({"_id": id_dari_url}) tidak pernah cocok — BSON ObjectId dan str adalah dua tipe yang berbeda, dan filter MongoDB selalu memperhatikan tipe.

Mengubah dan Menghapus Data #

Operasi tulis berikutnya memakai pola yang sudah dikenal dari mongosh: filter di argumen pertama, perubahan di argumen kedua. File kedua menuntaskan sisa katalog — dan sekalian membersihkan database latihan:

python
import os

from pymongo import MongoClient

URI = os.environ.get(
    "MONGODB_URI", "mongodb://127.0.0.1:27017/?directConnection=true"
)

client = MongoClient(URI, serverSelectionTimeoutMS=5000)
client.drop_database("latihan_crud_py")
db = client["latihan_crud_py"]

db.buku.insert_many([
    {"judul": "Laut Bercerita", "penulis": "Leila S. Chudori",
     "harga": 98000, "stok": 12},
    {"judul": "Bumi", "penulis": "Tere Liye",
     "harga": 89000, "stok": 25},
    {"judul": "Filosofi Teras", "penulis": "Henry Manampiring",
     "harga": 89000, "stok": 18},
    {"judul": "Sapiens", "penulis": "Yuval Noah Harari",
     "harga": 135000, "stok": 7},
])

# --- UPDATE ---
u1 = db.buku.update_one(
    {"judul": "Bumi"},
    {"$set": {"harga": 95000}, "$inc": {"stok": 5}},
)
print("update_one  -> matched:", u1.matched_count, "| modified:", u1.modified_count)

u2 = db.buku.update_many(
    {"harga": {"$gte": 95000}},
    {"$set": {"status": "premium"}},
)
print("update_many -> matched:", u2.matched_count, "| modified:", u2.modified_count)

# --- DELETE ---
d1 = db.buku.delete_one({"judul": "Sapiens"})
print("delete_one  -> deleted:", d1.deleted_count)

d2 = db.buku.delete_many({"stok": {"$lt": 20}})
print("delete_many -> deleted:", d2.deleted_count)

print("sisa document:", db.buku.count_documents({}))

# bersih-bersih database latihan
client.drop_database("latihan_crud_py")
client.close()

Output yang Dihasilkan:

text
update_one  -> matched: 1 | modified: 1
update_many -> matched: 3 | modified: 3
delete_one  -> deleted: 1
delete_many -> deleted: 2
sisa document: 1

Empat baris output itu merangkum cara membaca hasil operasi:

  1. matched_count vs modified_count — berapa document yang cocok dengan filter, dan berapa yang benar-benar berubah. Keduanya bisa berbeda: bila nilai baru sama dengan nilai lama, document cocok tetapi tidak dimodifikasi.
  2. $set dan $inc bisa digabung dalam satu update; operator lain seperti $push dan $unset mengikuti sintaks yang sama seperti di mongosh.
  3. deleted_count — jumlah document yang dibuang oleh delete_one / delete_many; update dan delete memakai bentuk filter yang sama persis dengan find.

Rangkuman #

  • insert_one mengembalikan InsertOneResult (inserted_id, acknowledged); insert_many mengembalikan list inserted_ids.
  • datetime aware dikonversi ke BSON Date; verifikasi lewat $type: "date"; tz_aware=True membuat hasil baca membawa timezone.
  • find_one mengembalikan dict atau None; find mengembalikan cursor yang bisa di-loop.
  • Filter _id wajib memakai ObjectId(...), bukan string mentah.
  • update_one/update_many melaporkan matched_count dan modified_count; delete melaporkan deleted_count; upsert=True menggabungkan update dan insert.

CRUD lengkap sudah berjalan dari Python. Chapter berikutnya memperdalam sisi pembacaan: filter operator, projection, dan pengurutan dalam bentuk dictionary Python.