Kerangka dari Bagian 2 belum bisa menyimpan apa pun. Pada bagian ini kita mengisi lima menu pertama dengan operasi CRUD: menambah produk, menampilkan seluruh produk, mengubah harga, mengubah stok, dan menghapus produk. Satu aturan dipegang sejak baris pertama: setiap nilai dari pengguna masuk ke SQL sebagai parameter ?, tidak pernah digabung ke dalam string query.
Lima Operasi di database.py #
Module database.py bertambah enam function operasi data. Berikut isi lengkapnya setelah perubahan; bagian atas (koneksi dan inisialisasi) tidak berubah dari Bagian 2:
"""Lapisan akses data untuk aplikasi inventaris."""
import sqlite3
from pathlib import Path
NAMA_DATABASE = "inventaris.db"
BERKAS_SKEMA = Path(__file__).with_name("schema.sql")
def koneksi(db_path: str = NAMA_DATABASE) -> sqlite3.Connection:
"""Buka koneksi baru ke database inventaris."""
conn = sqlite3.connect(db_path)
conn.execute("PRAGMA foreign_keys = ON")
conn.row_factory = sqlite3.Row
return conn
def inisialisasi(db_path: str = NAMA_DATABASE) -> None:
"""Terapkan schema.sql ke database. Aman dijalankan berulang."""
conn = koneksi(db_path)
try:
conn.executescript(BERKAS_SKEMA.read_text(encoding="utf-8"))
conn.commit()
finally:
conn.close()
def tambah_produk(nama: str, kategori: str, harga: int, stok: int,
db_path: str = NAMA_DATABASE) -> int:
"""Simpan produk baru, kembalikan id-nya."""
sql = "INSERT INTO produk (nama, kategori, harga, stok) VALUES (?, ?, ?, ?)"
conn = koneksi(db_path)
try:
kursor = conn.execute(sql, (nama, kategori, harga, stok))
conn.commit()
return int(kursor.lastrowid)
finally:
conn.close()
def lihat_produk(db_path: str = NAMA_DATABASE) -> list[sqlite3.Row]:
"""Ambil seluruh produk, urut berdasarkan nama."""
conn = koneksi(db_path)
try:
return conn.execute("SELECT * FROM produk ORDER BY nama").fetchall()
finally:
conn.close()
def ambil_produk(produk_id: int,
db_path: str = NAMA_DATABASE) -> sqlite3.Row | None:
"""Ambil satu produk berdasarkan id; None bila tidak ada."""
conn = koneksi(db_path)
try:
kursor = conn.execute("SELECT * FROM produk WHERE id = ?", (produk_id,))
return kursor.fetchone()
finally:
conn.close()
def perbarui_harga(produk_id: int, harga_baru: int,
db_path: str = NAMA_DATABASE) -> bool:
"""Set harga produk menjadi nilai baru. False bila id tidak ada."""
conn = koneksi(db_path)
try:
kursor = conn.execute(
"UPDATE produk SET harga = ? WHERE id = ?", (harga_baru, produk_id)
)
conn.commit()
return kursor.rowcount > 0
finally:
conn.close()
def perbarui_stok(produk_id: int, stok_baru: int,
db_path: str = NAMA_DATABASE) -> bool:
"""Set stok produk menjadi nilai baru. False bila id tidak ada."""
conn = koneksi(db_path)
try:
kursor = conn.execute(
"UPDATE produk SET stok = ? WHERE id = ?", (stok_baru, produk_id)
)
conn.commit()
return kursor.rowcount > 0
finally:
conn.close()
def hapus_produk(produk_id: int, db_path: str = NAMA_DATABASE) -> bool:
"""Hapus produk beserta riwayatnya. False bila id tidak ada."""
conn = koneksi(db_path)
try:
kursor = conn.execute("DELETE FROM produk WHERE id = ?", (produk_id,))
conn.commit()
return kursor.rowcount > 0
finally:
conn.close()Mari kita bedah pola yang berulang pada keenam function tersebut:
conn.execute(sql, (nilai, ...)): query dan data dikirim terpisah. Driversqlite3menyusun statement final setelah string SQL selesai di-parse, sehingga masukan pengguna tidak pernah bisa mengubah makna query — inilah parameterized query yang dibahas pada materi Parameterized Queries.return int(kursor.lastrowid)padatambah_produk(): attributelastrowidberisi id baris yang baru saja di-insert, lalu id tersebut dikembalikan ke pemanggil sebagai konfirmasi.return kursor.rowcount > 0pada ketiga operasi tulisUPDATEdanDELETE: attributerowcountmenghitung baris yang benar-benar terpengaruh. Jika pengguna memasukkan id yang tidak ada,rowcountbernilai0dan function mengembalikanFalse— bukan error, hanya kabar “tidak ada yang berubah”.conn.commit()setelah setiap tulisan: tanpa commit, perubahan hanya hidup di transaksi yang terbuka dan hilang saat koneksi ditutup.finally: conn.close(): setiap function membuka dan menutup koneksinya sendiri. Untuk aplikasi menu kecil ini sederhana dan aman; pola ini akan kita tinjau ulang di Bagian 6..fetchall()dan.fetchone():fetchall()mengembalikan seluruh baris sebagai list, sedangkanfetchone()mengembalikan satu baris atauNonesaat tidak ada hasil.
Perhatikan juga bahwa hapus_produk() tidak menghapus tabel riwayat secara eksplisit. Constraint ON DELETE CASCADE dari Bagian 1 yang mengerjakannya — itulah gunanya mendesain database sebelum menulis kode.
Menyambungkan Menu #
Module main.py kini benar-benar bekerja. Setiap menu punya satu function handler sendiri sehingga isi match tetap pendek:
"""Aplikasi Inventaris SQLite - antarmuka pengguna."""
import database
def format_rupiah(nilai: int) -> str:
"""Format angka menjadi teks rupiah, contoh 15000 menjadi Rp15.000."""
return "Rp" + f"{nilai:,}".replace(",", ".")
def cetak_tabel(rows: list) -> None:
"""Cetak daftar produk sebagai tabel rapi."""
print(f"{'ID':<3} {'Nama':<18} {'Kategori':<10} {'Harga':>10} {'Stok':>4}")
print(f"{'---':<3} {'-' * 18} {'-' * 10} {'-' * 10} {'-' * 4}")
for row in rows:
print(
f"{row['id']:<3} {row['nama']:<18} {row['kategori']:<10} "
f"{format_rupiah(row['harga']):>10} {row['stok']:>4}"
)
def baca_angka(prompt: str) -> int:
"""Minta masukan angka bulat, ulangi sampai masukan sah."""
while True:
teks = input(prompt).strip()
try:
return int(teks)
except ValueError:
print("Masukkan angka bulat, misalnya 15000.")
def menu_tambah() -> None:
nama = input("Nama produk: ").strip()
kategori = input("Kategori: ").strip()
harga = baca_angka("Harga (Rp): ")
stok = baca_angka("Stok awal: ")
id_baru = database.tambah_produk(nama, kategori, harga, stok)
print(f"Produk '{nama}' tersimpan dengan id {id_baru}.")
def menu_lihat() -> None:
rows = database.lihat_produk()
if not rows:
print("Belum ada produk.")
return
cetak_tabel(rows)
def menu_ubah_harga() -> None:
produk_id = baca_angka("ID produk: ")
harga_baru = baca_angka("Harga baru (Rp): ")
if database.perbarui_harga(produk_id, harga_baru):
print("Harga berhasil diperbarui.")
else:
print(f"Tidak ada produk dengan id {produk_id}.")
def menu_ubah_stok() -> None:
produk_id = baca_angka("ID produk: ")
stok_baru = baca_angka("Stok baru: ")
if database.perbarui_stok(produk_id, stok_baru):
print("Stok berhasil diperbarui.")
else:
print(f"Tidak ada produk dengan id {produk_id}.")
def menu_hapus() -> None:
produk_id = baca_angka("ID produk: ")
if database.hapus_produk(produk_id):
print("Produk berhasil dihapus.")
else:
print(f"Tidak ada produk dengan id {produk_id}.")
def tampilkan_menu() -> None:
print()
print("=== Aplikasi Inventaris ===")
print("1. Tambah produk")
print("2. Lihat semua produk")
print("3. Ubah harga")
print("4. Ubah stok")
print("5. Hapus produk")
print("0. Keluar")
def main() -> None:
database.inisialisasi()
print("Database siap. Selamat datang!")
while True:
tampilkan_menu()
try:
pilihan = input("Pilih menu: ").strip()
except EOFError:
print()
break
if pilihan == "0":
print("Sampai jumpa!")
break
match pilihan:
case "1":
menu_tambah()
case "2":
menu_lihat()
case "3":
menu_ubah_harga()
case "4":
menu_ubah_stok()
case "5":
menu_hapus()
case _:
print("Pilihan tidak dikenal.")
if __name__ == "__main__":
main()Tiga helper di bagian atas layak dijelaskan lebih dekat:
format_rupiah():f"{nilai:,}"menambah pemisah ribuan gaya Inggris (15,000), lalu.replace(",", ".")mengubahnya menjadi gaya Indonesia (15.000).cetak_tabel(): setiap kolom diberi lebar tetap — nama rata kiri 18 karakter (:<18), harga dan stok rata kanan (:>10,:>4) — sehingga angka sejajar ke kanan seperti layaknya tabel keuangan.baca_angka(): mengulang prompt sampaiint(teks)berhasil; masukan sepertitujuh ribumenimbulkanValueErroryang ditangani di tempat, bukan meneruskan data rusak ke database.
Mencoba Seluruh Alur CRUD #
Kita mulai dari database bersih berisi enam data awal, menambah satu produk baru, lalu menampilkan seluruh tabel:
printf '1\nSusu Kotak\nMinuman\n6000\n12\n2\n0\n' | python3 main.pyOutput yang Dihasilkan:
Database siap. Selamat datang!
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: Nama produk: Kategori: Harga (Rp): Stok awal: Produk 'Susu Kotak' tersimpan dengan id 7.
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: ID Nama Kategori Harga Stok
--- ------------------ ---------- ---------- ----
5 Buku Tulis Alat Tulis Rp5.000 40
4 Keripik Singkong Makanan Rp10.000 2
1 Kopi Tubruk Minuman Rp15.000 24
6 Pulpen Gel Alat Tulis Rp3.500 4
3 Roti Tawar Makanan Rp12.000 15
7 Susu Kotak Minuman Rp6.000 12
2 Teh Celup Minuman Rp8.000 3
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: Sampai jumpa!Susu Kotak mendapat id 7 karena AUTOINCREMENT tidak memakai ulang nomor yang sudah pernah terpakai. Selanjutnya kita mengubah harga Susu Kotak — sengaja dengan satu masukan salah lebih dulu untuk melihat baca_angka() bekerja — lalu mengubah stoknya:
printf '3\n7\ntujuh ribu\n6500\n4\n7\n20\n2\n0\n' | python3 main.pyOutput yang Dihasilkan:
Database siap. Selamat datang!
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: ID produk: Harga baru (Rp): Masukkan angka bulat, misalnya 15000.
Harga baru (Rp): Harga berhasil diperbarui.
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: ID produk: Stok baru: Stok berhasil diperbarui.
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: ID Nama Kategori Harga Stok
--- ------------------ ---------- ---------- ----
5 Buku Tulis Alat Tulis Rp5.000 40
4 Keripik Singkong Makanan Rp10.000 2
1 Kopi Tubruk Minuman Rp15.000 24
6 Pulpen Gel Alat Tulis Rp3.500 4
3 Roti Tawar Makanan Rp12.000 15
7 Susu Kotak Minuman Rp6.500 20
2 Teh Celup Minuman Rp8.000 3
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: Sampai jumpa!Terakhir, masih melanjutkan kondisi database yang sama (Susu Kotak kini ber-id 7 dengan harga Rp6.500 dan stok 20), kita menghapus Susu Kotak lalu mencoba menghapus id yang tidak ada:
printf '5\n7\n5\n99\n0\n' | python3 main.pyOutput yang Dihasilkan:
Database siap. Selamat datang!
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: ID produk: Produk berhasil dihapus.
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: ID produk: Tidak ada produk dengan id 99.
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: Sampai jumpa!Kedua versi module hasil bagian ini tersedia pada berkas sumber di bawah halaman. Aplikasi sudah bisa menyimpan data permanen, tetapi masih percaya sepenuhnya pada masukan pengguna. Bagian 4 menghadapi kenyataan itu: validasi, nama duplikat, dan transaksi yang gagal di tengah jalan.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
database.py
Unduh berkas"""Lapisan akses data untuk aplikasi inventaris."""
import sqlite3
from pathlib import Path
NAMA_DATABASE = "inventaris.db"
BERKAS_SKEMA = Path(__file__).with_name("schema.sql")
def koneksi(db_path: str = NAMA_DATABASE) -> sqlite3.Connection:
"""Buka koneksi baru ke database inventaris."""
conn = sqlite3.connect(db_path)
conn.execute("PRAGMA foreign_keys = ON")
conn.row_factory = sqlite3.Row
return conn
def inisialisasi(db_path: str = NAMA_DATABASE) -> None:
"""Terapkan schema.sql ke database. Aman dijalankan berulang."""
conn = koneksi(db_path)
try:
conn.executescript(BERKAS_SKEMA.read_text(encoding="utf-8"))
conn.commit()
finally:
conn.close()
def tambah_produk(nama: str, kategori: str, harga: int, stok: int,
db_path: str = NAMA_DATABASE) -> int:
"""Simpan produk baru, kembalikan id-nya."""
sql = "INSERT INTO produk (nama, kategori, harga, stok) VALUES (?, ?, ?, ?)"
conn = koneksi(db_path)
try:
kursor = conn.execute(sql, (nama, kategori, harga, stok))
conn.commit()
return int(kursor.lastrowid)
finally:
conn.close()
def lihat_produk(db_path: str = NAMA_DATABASE) -> list[sqlite3.Row]:
"""Ambil seluruh produk, urut berdasarkan nama."""
conn = koneksi(db_path)
try:
return conn.execute("SELECT * FROM produk ORDER BY nama").fetchall()
finally:
conn.close()
def ambil_produk(produk_id: int,
db_path: str = NAMA_DATABASE) -> sqlite3.Row | None:
"""Ambil satu produk berdasarkan id; None bila tidak ada."""
conn = koneksi(db_path)
try:
kursor = conn.execute("SELECT * FROM produk WHERE id = ?", (produk_id,))
return kursor.fetchone()
finally:
conn.close()
def perbarui_harga(produk_id: int, harga_baru: int,
db_path: str = NAMA_DATABASE) -> bool:
"""Set harga produk menjadi nilai baru. False bila id tidak ada."""
conn = koneksi(db_path)
try:
kursor = conn.execute(
"UPDATE produk SET harga = ? WHERE id = ?", (harga_baru, produk_id)
)
conn.commit()
return kursor.rowcount > 0
finally:
conn.close()
def perbarui_stok(produk_id: int, stok_baru: int,
db_path: str = NAMA_DATABASE) -> bool:
"""Set stok produk menjadi nilai baru. False bila id tidak ada."""
conn = koneksi(db_path)
try:
kursor = conn.execute(
"UPDATE produk SET stok = ? WHERE id = ?", (stok_baru, produk_id)
)
conn.commit()
return kursor.rowcount > 0
finally:
conn.close()
def hapus_produk(produk_id: int, db_path: str = NAMA_DATABASE) -> bool:
"""Hapus produk beserta riwayatnya. False bila id tidak ada."""
conn = koneksi(db_path)
try:
kursor = conn.execute("DELETE FROM produk WHERE id = ?", (produk_id,))
conn.commit()
return kursor.rowcount > 0
finally:
conn.close()
main.py
Unduh berkas"""Aplikasi Inventaris SQLite - antarmuka pengguna."""
import database
def format_rupiah(nilai: int) -> str:
"""Format angka menjadi teks rupiah, contoh 15000 menjadi Rp15.000."""
return "Rp" + f"{nilai:,}".replace(",", ".")
def cetak_tabel(rows: list) -> None:
"""Cetak daftar produk sebagai tabel rapi."""
print(f"{'ID':<3} {'Nama':<18} {'Kategori':<10} {'Harga':>10} {'Stok':>4}")
print(f"{'---':<3} {'-' * 18} {'-' * 10} {'-' * 10} {'-' * 4}")
for row in rows:
print(
f"{row['id']:<3} {row['nama']:<18} {row['kategori']:<10} "
f"{format_rupiah(row['harga']):>10} {row['stok']:>4}"
)
def baca_angka(prompt: str) -> int:
"""Minta masukan angka bulat, ulangi sampai masukan sah."""
while True:
teks = input(prompt).strip()
try:
return int(teks)
except ValueError:
print("Masukkan angka bulat, misalnya 15000.")
def menu_tambah() -> None:
nama = input("Nama produk: ").strip()
kategori = input("Kategori: ").strip()
harga = baca_angka("Harga (Rp): ")
stok = baca_angka("Stok awal: ")
id_baru = database.tambah_produk(nama, kategori, harga, stok)
print(f"Produk '{nama}' tersimpan dengan id {id_baru}.")
def menu_lihat() -> None:
rows = database.lihat_produk()
if not rows:
print("Belum ada produk.")
return
cetak_tabel(rows)
def menu_ubah_harga() -> None:
produk_id = baca_angka("ID produk: ")
harga_baru = baca_angka("Harga baru (Rp): ")
if database.perbarui_harga(produk_id, harga_baru):
print("Harga berhasil diperbarui.")
else:
print(f"Tidak ada produk dengan id {produk_id}.")
def menu_ubah_stok() -> None:
produk_id = baca_angka("ID produk: ")
stok_baru = baca_angka("Stok baru: ")
if database.perbarui_stok(produk_id, stok_baru):
print("Stok berhasil diperbarui.")
else:
print(f"Tidak ada produk dengan id {produk_id}.")
def menu_hapus() -> None:
produk_id = baca_angka("ID produk: ")
if database.hapus_produk(produk_id):
print("Produk berhasil dihapus.")
else:
print(f"Tidak ada produk dengan id {produk_id}.")
def tampilkan_menu() -> None:
print()
print("=== Aplikasi Inventaris ===")
print("1. Tambah produk")
print("2. Lihat semua produk")
print("3. Ubah harga")
print("4. Ubah stok")
print("5. Hapus produk")
print("0. Keluar")
def main() -> None:
database.inisialisasi()
print("Database siap. Selamat datang!")
while True:
tampilkan_menu()
try:
pilihan = input("Pilih menu: ").strip()
except EOFError:
print()
break
if pilihan == "0":
print("Sampai jumpa!")
break
match pilihan:
case "1":
menu_tambah()
case "2":
menu_lihat()
case "3":
menu_ubah_harga()
case "4":
menu_ubah_stok()
case "5":
menu_hapus()
case _:
print("Pilihan tidak dikenal.")
if __name__ == "__main__":
main()