Skema di Bagian 1 masih berupa berkas teks; belum ada satu baris pun kode Python yang membacanya. Pada bagian ini kita membangun fondasi aplikasi: module database.py yang memusatkan seluruh urusan koneksi dan penerapan skema, serta main.py berupa kerangka menu yang menyambut pengguna. Sasaran utamanya sederhana namun sering dilupakan: aplikasi harus siap dijalankan berkali-kali tanpa merusak datanya sendiri.
Memisahkan Lapisan Data #
Sebelum menulis kode, kita tetapkan pembagian tanggung jawab dua module ini. Module database.py adalah satu-satunya tempat yang boleh menjalankan SQL; module main.py hanya mengurus interaksi dengan pengguna lewat menu. Pemisahan ini membuat bug data mudah dilacak karena seluruh SQL hidup di satu berkas, dan tampilan bisa berubah tanpa menyentuh logika penyimpanan.
Berikut isi lengkap database.py untuk tahap ini:
"""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()Meskipun pendek, setiap baris function koneksi() membawa satu keputusan penting:
sqlite3.connect(db_path): membuka koneksi ke berkas database; jika berkasnya belum ada, SQLite membuat berkas baru secara otomatis. Inilah alasan kita tidak perlu membuatinventaris.dbsecara manual.conn.execute("PRAGMA foreign_keys = ON"): menyalakan pemeriksaan foreign key untuk koneksi ini. Pengaturan tersebut berlaku per koneksi dan selalu nonaktif di awal, jadi setiap koneksi baru wajib menyalakannya sendiri.conn.row_factory = sqlite3.Row: mengubah bentuk hasil query dari tuple menjadi objeksqlite3.Rowyang bisa diakses berdasarkan nama kolom, misalnyarow["nama"]. Kode pembaca menjadi jauh lebih jelas dibanding mengingat urutan kolom sepertirow[1].return conn: koneksi dikembalikan ke pemanggil; function ini sengaja tidak menutup koneksi karena penutupan adalah tanggung jawab pemanggil.
Sementara itu, inisialisasi() menerapkan skema lewat empat langkah:
koneksi(db_path): membuka koneksi seperti biasa.BERKAS_SKEMA.read_text(encoding="utf-8"): membaca isischema.sql. Path berkas skema dicari di sebelahdatabase.pysendiri memakaiPath(__file__).with_name(...), sehingga aplikasi tetap bekerja terlepas dari folder mana ia dijalankan.conn.executescript(...): menjalankan seluruh isi skrip SQL sekaligus. Methodexecutescript()memangexecutescript(bukanexecute) karena berkas skema berisi banyak statement, bukan satu.finally: conn.close(): koneksi ditutup baik setelah sukses maupun setelah galat. Karenaschema.sqlmemakaiIF NOT EXISTSdanINSERT OR IGNORE, menjalankan function ini berkali-kali tidak pernah merusak data — sifat inilah yang disebut idempoten.
Kerangka Menu di main.py #
Module main.py pada tahap ini hanya menyiapkan alur: panggil inisialisasi(), lalu putar menu sampai pengguna memilih keluar.
"""Aplikasi Inventaris SQLite - antarmuka pengguna."""
import database
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
print("Fitur ini dibangun pada bagian selanjutnya.")
if __name__ == "__main__":
main()Beberapa keputusan pada kerangka tersebut layak dijelaskan:
database.inisialisasi()di awalmain(): setiap kali aplikasi dijalankan, skema diterapkan lebih dulu. Pada menjalankan pertama tabel dan data awal terbentuk; pada menjalankan berikutnya, statement idempoten melewati yang sudah ada tanpa efek samping.try: ... except EOFError: functioninput()mengangkatEOFErrorsaat masukan habis, misalnya ketika program dijalankan dengan pipa dan datanya telanjuri habis. Tanpa penanganan ini, program berakhir dengan traceback yang menakutkan.if __name__ == "__main__":: blokmain()hanya berjalan saat berkas dieksekusi langsung, sehinggamain.pykelak bisa diimpor oleh test tanpa memicu menu interaktif.
Menjalankan Aplikasi Pertama Kali #
Pastikan schema.sql, database.py, dan main.py berada dalam satu folder, lalu jalankan:
python3 main.pyInteraksi berikut kami jalankan dengan mengirim masukan lewat printf agar transkripnya dapat direproduksi; hasil yang sama akan Anda lihat saat mengetik manual:
printf '2\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: Fitur ini dibangun pada bagian selanjutnya.
=== Aplikasi Inventaris ===
1. Tambah produk
2. Lihat semua produk
3. Ubah harga
4. Ubah stok
5. Hapus produk
0. Keluar
Pilih menu: Sampai jumpa!Setelah program berhenti, folder proyek memuat berkas baru inventaris.db berukuran sekitar 28 KB. Mari kita buktikan sifat idempoten dengan menjalankan aplikasi sekali lagi, lalu memeriksa isinya:
python3 -c "
import sqlite3
conn = sqlite3.connect('inventaris.db')
print('Jumlah produk setelah main.py dijalankan dua kali:',
conn.execute('SELECT COUNT(*) FROM produk').fetchone()[0])
print('Data awal:', [r[0] for r in conn.execute('SELECT nama FROM produk ORDER BY id')])
conn.close()
"Output yang Dihasilkan:
Jumlah produk setelah main.py dijalankan dua kali: 6
Data awal: ['Kopi Tubruk', 'Teh Celup', 'Roti Tawar', 'Keripik Singkong', 'Buku Tulis', 'Pulpen Gel']Jumlah produk tetap enam walaupun inisialisasi() sudah dua kali menjalankan INSERT: INSERT OR IGNORE bekerja persis seperti yang dirancang pada Bagian 1. Fondasi telah berdiri dengan satu berkas database, dua tabel, dan data awal yang utuh; kedua module hasil bagian ini tersedia pada berkas sumber di bawah halaman. Pada Bagian 3 kita mengisi menu 1 sampai 5 dengan operasi CRUD sungguhan.
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()
main.py
Unduh berkas"""Aplikasi Inventaris SQLite - antarmuka pengguna."""
import database
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
print("Fitur ini dibangun pada bagian selanjutnya.")
if __name__ == "__main__":
main()