Aplikasi Python kita kini bisa ber-CRUD, men-aggregate, bahkan bertransaksi — tetapi dunia nyata tidak sebersih kode latihan. Jaringan terputus sesaat, dua pengguna meregistrasi email yang sama bersamaan, validator menolak input tak lengkap: semuanya tiba di aplikasi sebagai exception PyMongo. Kualitas program ditentukan bukan oleh absennya error, melainkan oleh respons yang tepat — error permanen tidak boleh dicoba ulang, error transien tidak boleh langsung menyerah. Chapter terakhir Section ini memetakan exception yang paling sering dijumpai, menampilkan bentuk aslinya dari eksekusi sungguhan, lalu menyusun pola try/except/finally yang rapi.

Peta Exception PyMongo #

Semua exception driver tinggal di modul pymongo.errors, dan semuanya berketurunan dari satu akar: PyMongoError. Posisi dalam silsilah itu menentukan cara menangkapnya:

Dua cabang besar itu punya watak berbeda. Cabang OperationFailure berarti server menjawab lalu menolak — misalnya duplicate key atau validasi gagal — umumnya permanen. Cabang ConnectionFailure berarti dialog dengan server bermasalah — umumnya transien dan layak dicoba ulang. Tabel ini merangkum pemicu dan tindakannya:

ExceptionPemicuAksi yang tepat
DuplicateKeyError (code 11000)unique index dilanggarpermanen: tolak input, laporkan ke pengguna
WriteError (code 121 dll.)validator atau aturan server menolak documentpermanen: perbaiki document
BulkWriteError (code 65)sebagian operasi massal gagalbaca details["writeErrors"], putuskan lanjut/ulang
AutoReconnect / ConnectionFailurekoneksi terputas sesaattransien: coba ulang dengan backoff
ServerSelectionTimeoutErrortidak ada server tersedia dalam batas waktutransien: periksa URI/server; gagal cepat di CLI
NetworkTimeoutoperasi tak selesai dalam socketTimeoutMStransien: coba ulang bila idempoten
PyMongoErrorpayung semua error driverpenampung terakhir: log dan propagasi

Empat Error dalam Praktik #

Satu berkas menimbulkan empat error secara nyata lalu menangkap satu per satu. Perhatikan bahwa setiap exception membawa kode numerik dan atribut details berisi dokumen error mentah dari server:

python
import os
import time

from pymongo import MongoClient
from pymongo.errors import (
    BulkWriteError,
    ConnectionFailure,
    DuplicateKeyError,
    PyMongoError,
    ServerSelectionTimeoutError,
    WriteError,
)

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

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

db.pengguna.create_index("email", unique=True)

# 1) DuplicateKeyError — unique index dilanggar
try:
    db.pengguna.insert_one({"nama": "Budi", "email": "[email protected]"})
    db.pengguna.insert_one({"nama": "Budi Kembar", "email": "[email protected]"})
except DuplicateKeyError as err:
    print("[DuplicateKeyError] code:", err.code)
    print("  kunci bentrok:", err.details["keyValue"])

# 2) WriteError — schema validator menolak document
db.create_collection("produk", validator={"$jsonSchema": {
    "bsonType": "object",
    "required": ["nama", "harga"],
    "properties": {
        "nama": {"bsonType": "string"},
        "harga": {"bsonType": "int", "minimum": 0},
    },
}})
try:
    db.produk.insert_one({"nama": "Kopi Gayo", "harga": -5000})
except WriteError as err:
    print("[WriteError] code:", err.code)
    print("  errmsg:", err.details["errmsg"])

# 3) BulkWriteError — satu anggota insert_many bentrok
try:
    db.pengguna.insert_many([
        {"nama": "Siti", "email": "[email protected]"},
        {"nama": "Budi Kembar", "email": "[email protected]"},
        {"nama": "Dewi", "email": "[email protected]"},
    ])
except BulkWriteError as err:
    print("[BulkWriteError] code:", err.code)
    print("  tersimpan sebelum gagal:", err.details["nInserted"])
    for we in err.details["writeErrors"]:
        print("  gagal di index", we["index"], "kode", we["code"])

# 4) ServerSelectionTimeoutError — try/except/finally ke port mati
client_mati = MongoClient(
    "mongodb://127.0.0.1:9999/?directConnection=true",
    serverSelectionTimeoutMS=1500,
)
mulai = time.monotonic()
try:
    client_mati.admin.command("ping")
except ServerSelectionTimeoutError as err:
    print("[ServerSelectionTimeoutError] setelah",
          round(time.monotonic() - mulai, 1), "detik")
    print("  turunan ConnectionFailure:", isinstance(err, ConnectionFailure))
finally:
    client_mati.close()

# 5) Urutan except: subclass dulu, payung PyMongoError terakhir
try:
    db.pengguna.insert_one({"nama": "Budi Tiga", "email": "[email protected]"})
except DuplicateKeyError:
    print("[payung] duplikat ditangani cabang khusus")
except PyMongoError as err:
    print("[payung] error PyMongo lain:", type(err).__name__)

client.drop_database("latihan_error_py")
client.close()

Output yang Dihasilkan:

text
[DuplicateKeyError] code: 11000
  kunci bentrok: {'email': '[email protected]'}
[WriteError] code: 121
  errmsg: Document failed validation
[BulkWriteError] code: 65
  tersimpan sebelum gagal: 1
  gagal di index 1 kode 11000
[ServerSelectionTimeoutError] setelah 1.5 detik
  turunan ConnectionFailure: True
[payung] duplikat ditangani cabang khusus

Mari kita bedah empat errornya satu per satu:

  1. DuplicateKeyError — muncul karena create_index("email", unique=True) yang kita pasang sendiri. err.code bernilai 11000, dan err.details["keyValue"] menyebut persis field beserta nilai yang bentrok — bahan langsung untuk pesan “email sudah terdaftar” di antarmuka.
  2. WriteError — schema validator menolak harga: -5000; kode 121. err.details["errInfo"] membawa rincian aturan mana yang dilanggar (struktur yang sama dengan yang kita bedah di Chapter Error Handling Section integrasi aplikasi) — cukup untuk menerjemahkan penolakan menjadi pesan pengguna yang jelas.
  3. BulkWriteError — insert_many bersifat ordered: begitu satu document gagal, sisanya tidak dikirim. details["nInserted"] mencatat satu document Siti sempat tersimpan, dan details["writeErrors"] adalah list error per document beserta posisi index-nya. Bila kita ingin document yang sehat tetap diproses, tambahkan ordered=False — lalu evaluasi daftar error setelahnya.
  4. ServerSelectionTimeoutError — client ke port 9999 dibuat tanpa error (perilaku lazy dari Chapter Membuat Koneksi Database), lalu ping menunggu pemilihan server hingga batas serverSelectionTimeoutMS=1500 terlampaui. Eksekusi berhenti tepat setelah 1,5 detik, dan isinstance membuktikan error ini turunan ConnectionFailure — satu except ConnectionFailure cukup untuk menangkap seluruh keluarga masalah koneksi.

Pola try, except, finally #

Blok keempat di berkas tadi adalah cetak biru pola lengkap. try memuat operasi yang berisiko; except menangkap tipe yang paling spesifik lebih dulu; finally menutup sumber daya apa pun yang terjadi — client_mati.close() tetap berjalan meski tidak ada error. Untuk aplikasi nyata, urutan cabangnya mengikuti aturan Python biasa: subclass sebelum superclass, karena except PyMongoError yang ditulis lebih dulu akan menelan semua error driver dan cabang di bawahnya tak pernah tercapai — persis yang dibuktikan blok kelima.

Dua kebiasaan penutup yang menjaga kualitas. Pertama, log err.code dan konteks operasi, bukan hanya str(err) — teks pesan bisa berubah antar versi, kode stabil. Kedua, jangan pernah menelan exception dengan except kosong; error yang ditelan membuat data hilang tanpa jejak.

Rangkuman #

  • Semua error PyMongo berakar pada PyMongoError; posisi dalam hierarki (OperationFailure vs ConnectionFailure) menandai sifat permanen atau transien.
  • DuplicateKeyError (11000) dan WriteError (121) bersifat permanen — perbaiki inputnya; ServerSelectionTimeoutError, AutoReconnect, dan NetworkTimeout transien — layak dicoba ulang.
  • BulkWriteError (65) merangkum hasil insert_many: nInserted dan writeErrors per document; ordered=False melanjutkan document yang sehat.
  • Atribut err.details memuat dokumen error mentah — keyValue, errInfo — bahan pesan pengguna yang presisi.
  • Susun try/except/finally dengan subclass lebih dulu, tutup sumber daya di finally, dan catat err.code alih-alih hanya teks pesan.

Dengan error handling, perjalanan Python kita lengkap: dari instalasi, koneksi, CRUD, query, aggregation, cursor, hingga transaksi — semuanya teruji di server sungguhan. Selanjutnya, Section berikutnya membawa pola yang sama ke ekosistem JavaScript.