
বইয়ের তাক যত বড় হয়, কোন বইটি আছে এবং কোন লেখকের বইটি আগে পড়েছি—এই ছোট প্রশ্নগুলোর উত্তর কাগজে রাখা কঠিন হয়ে যায়। আবার এমন কাজের জন্য আলাদা সার্ভার, লগইন ব্যবস্থা বা ভারী ওয়েব ফ্রেমওয়ার্কও দরকার নেই। এই টিউটোরিয়ালে Python-এর standard library এবং SQLite ব্যবহার করে একটি বাস্তব, চালানো যায় এমন ব্যক্তিগত বই লাইব্রেরি কমান্ড-লাইন অ্যাপ বানাব। অ্যাপটি বই যোগ করবে, সব বই সাজিয়ে দেখাবে, শিরোনাম বা লেখক দিয়ে খুঁজবে, একই বই দ্বিতীয়বার যোগ করা ঠেকাবে এবং SQLite-এর backup API দিয়ে নিরাপদ কপি বানাবে।
এটি নতুনদের জন্য হলেও উদাহরণটি ইচ্ছাকৃতভাবে খেলনা পর্যায়ে থামবে না। ইনপুট খালি কি না ও অতিরিক্ত বড় কি না পরীক্ষা হবে, SQL-এ Python-এর string জোড়া না দিয়ে parameter ব্যবহার হবে, লেখা-পরিবর্তন transaction-এর মধ্যে থাকবে, এবং database connection প্রত্যেক পথেই বন্ধ হবে। ফলে কোডটি পড়তে পড়তে আপনি শুধু কমান্ড মুখস্থ করবেন না; কেন সেই নকশা বেছে নেওয়া হয়েছে, ভুল হলে কী দেখা যায়, এবং পরের প্রকল্পে কী বদলাতে হবে—তাও বুঝবেন।
শেখার লক্ষ্য ও সীমা
শেষে add, list, search এবং backup—এই চারটি subcommand থাকবে। ডেটা books.db নামে একটি ফাইলে থাকবে; SQLite আলাদা database server process চায় না, তাই এক জনের স্থানীয় বইয়ের তালিকার জন্য এটি স্বাভাবিক পছন্দ। Python-এর sqlite3 module DB-API 2.0-সদৃশ interface দেয়। এতে database file নতুন হলে connect() সেটি তৈরি করতে পারে। তবে এই উদাহরণকে production web service বলা হচ্ছে না: এখানে user authentication, অনুমতি-ভিত্তিক access, HTTPS, multi-user conflict বা remote backup নেই। ওয়েবে প্রকাশ করার আগে সেই নিরাপত্তা ও deployment সমস্যাগুলো আলাদাভাবে সমাধান করতে হবে।
আরও একটি নকশা-সিদ্ধান্ত আগে পরিষ্কার করি। একই title এবং author (case না মেনে) দ্বিতীয়বার ঢুকলে আমরা duplicate policy হিসেবে insertion প্রত্যাখ্যান করব। আলাদা সংস্করণ, অনুবাদক বা একই নামে ভিন্ন লেখার দরকার হলে schema-তে edition, ISBN বা publication year যোগ করে policy বদলাতে হবে। নীরবে duplicate বাদ না দিয়ে বার্তা দেখানো হয়েছে, যাতে ব্যবহারকারী বুঝতে পারেন কেন row তৈরি হয়নি।
প্রস্তুতি ও ফাইল চালানো
Python 3.9 বা তার পরের সংস্করণ যথেষ্ট, কারণ স্ক্রিপ্টে standard library-এর argparse, pathlib এবং sqlite3 ছাড়া কিছু নেই। একটি ফোল্ডারে book_library.py নামে নিচের সম্পূর্ণ executable script সংরক্ষণ করুন। Linux বা macOS-এ চাইলে chmod +x book_library.py দিয়ে সরাসরি চালাতে পারবেন; Windows-এ python book_library.py ব্যবহার করুন।
#!/usr/bin/env python3
"""A small personal book library CLI backed by SQLite."""
from __future__ import annotations
import argparse
import sqlite3
SCHEMA = """
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL CHECK(length(trim(title)) > 0),
author TEXT NOT NULL CHECK(length(trim(author)) > 0),
added_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(title COLLATE NOCASE, author COLLATE NOCASE)
)
"""
def connect(db_path: str) -> sqlite3.Connection:
con = sqlite3.connect(db_path)
con.row_factory = sqlite3.Row
con.execute("PRAGMA foreign_keys = ON")
with con:
con.execute(SCHEMA)
return con
def clean_text(value: str, field: str) -> str:
value = value.strip()
if not value:
raise ValueError(f"{field} cannot be empty")
if len(value) > 200:
raise ValueError(f"{field} must be at most 200 characters")
return value
def add_book(con: sqlite3.Connection, title: str, author: str) -> bool:
title = clean_text(title, "Title")
author = clean_text(author, "Author")
try:
with con:
con.execute(
"INSERT INTO books(title, author) VALUES (?, ?)",
(title, author),
)
except sqlite3.IntegrityError:
return False
return True
def list_books(con: sqlite3.Connection) -> list[sqlite3.Row]:
return con.execute(
"SELECT id, title, author, added_at FROM books ORDER BY title COLLATE NOCASE, id"
).fetchall()
def search_books(con: sqlite3.Connection, term: str) -> list[sqlite3.Row]:
pattern = f"%{term.strip()}%"
return con.execute(
"""SELECT id, title, author, added_at FROM books
WHERE title LIKE ? OR author LIKE ?
ORDER BY title COLLATE NOCASE, id""",
(pattern, pattern),
).fetchall()
def print_books(rows: list[sqlite3.Row]) -> None:
if not rows:
print("No books found.")
return
for row in rows:
print(f"{row['id']}: {row['title']} — {row['author']} (added {row['added_at']})")
def backup_database(source_path: str, destination_path: str) -> None:
source = sqlite3.connect(source_path)
destination = sqlite3.connect(destination_path)
try:
with destination:
source.backup(destination)
finally:
destination.close()
source.close()
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Personal book library")
parser.add_argument("--db", default="books.db", help="SQLite database path")
sub = parser.add_subparsers(dest="command", required=True)
add = sub.add_parser("add", help="add a book")
add.add_argument("title")
add.add_argument("author")
sub.add_parser("list", help="list all books")
find = sub.add_parser("search", help="search title or author")
find.add_argument("term")
backup = sub.add_parser("backup", help="make a consistent SQLite backup")
backup.add_argument("destination")
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
if args.command == "backup":
backup_database(args.db, args.destination)
print(f"Backup written to {args.destination}")
return 0
con = connect(args.db)
try:
if args.command == "add":
try:
added = add_book(con, args.title, args.author)
except ValueError as exc:
print(f"Error: {exc}")
return 2
if added:
print("Book added.")
return 0
print("Book already exists (same title and author).")
return 1
if args.command == "list":
print_books(list_books(con))
elif args.command == "search":
print_books(search_books(con, args.term))
return 0
finally:
con.close()
if __name__ == "__main__":
raise SystemExit(main())
উপরের code block-এর script-টি একই ফোল্ডারের আলাদা book_library.py ফাইল হিসেবেও দেওয়া হয়েছে। তাই article কপি করার সময় indentation নষ্ট হলেও সেই ফাইলটি সরাসরি চালানো যাবে। প্রথমে syntax পরীক্ষা করুন: python -m py_compile book_library.py। তারপর একটি আলাদা demo database ব্যবহার করে ধাপে ধাপে চালান:
python book_library.py --db demo.db add "পথের পাঁচালী" "বিভূতিভূষণ বন্দ্যোপাধ্যায়"
python book_library.py --db demo.db add "The Pragmatic Programmer" "Andrew Hunt"
python book_library.py --db demo.db list
python book_library.py --db demo.db search "প্রা"
python book_library.py --db demo.db backup demo-backup.db
--db global option হওয়ায় সেটি subcommand-এর আগে লিখতে হবে। list-এ title অনুযায়ী case-insensitive ordering দেখা যাবে। search-এ title এবং author দুই কলামই দেখা হয়। ফাঁকা ফলাফল এলে error নয়, No books found. বার্তা আসে—CLI-তে এটি ব্যবহারবান্ধব আচরণ।
Schema, validation এবং duplicate policy
books table-এ id নিজে থেকে বাড়া primary key। title ও author-এ NOT NULL থাকলেও SQL-এ NULL না পাঠালেই খালি string আটকায় না; তাই Python-এর clean_text() trim করে blank পরীক্ষা করে। database-এর CHECK(length(trim(...)) > 0) দ্বিতীয় প্রতিরক্ষা। ২০০ অক্ষরের সীমা command line ভুল, অতি দীর্ঘ paste এবং ভবিষ্যৎ UI-র layout সমস্যা কমায়। এটি security boundary নয়; domain অনুযায়ী সীমা বাড়াতে পারেন।
added_at-এর default SQLite-এর current timestamp, তাই add command-এ সময় নিজে লিখতে হয় না। UNIQUE(title COLLATE NOCASE, author COLLATE NOCASE)-এ Python এবং python একই title হিসেবে ধরা হয়, লেখকের ক্ষেত্রেও একই আচরণ। INSERT ব্যর্থ হলে sqlite3.IntegrityError ধরা হয়েছে। গুরুত্বপূর্ণ কথা হলো, অন্য integrity error-ও এই ছোট অ্যাপ duplicate-এর মতো report করবে; বড় প্রকল্পে exception-এর কারণ আলাদা করে log করা ভালো।
Parameterized SQL কেন জরুরি
SQL string-এ user input জুড়ে লেখা, যেমন "... WHERE title LIKE '%" + term + "%'", দেখতে সহজ কিন্তু বিপজ্জনক। quote, comment এবং injection payload query-র অর্থ বদলে দিতে পারে। এখানে ? placeholder এবং দ্বিতীয় argument tuple ব্যবহার করা হয়েছে। Python driver value-টিকে SQL code না ধরে data হিসেবে bind করে। একই নিয়ম INSERT, search এবং ভবিষ্যতের UPDATE বা DELETE-এও রাখুন; শুধু “এটি local app” ভেবে অভ্যাস ভাঙবেন না।
Search-এ pattern = f"%{term.strip()}%" করা হয়েছে, কিন্তু SQL string বানানো হয়নি—শুধু parameter value বানানো হয়েছে। ফলে সাধারণভাবে term-টি title বা author-এর যেকোনো অংশে খোঁজা হয়। SQLite LIKE-এ % শূন্য বা বহু অক্ষর এবং _ একটি অক্ষরের wildcard। এই script ইচ্ছাকৃতভাবে wildcard অনুমতি দেয়: ব্যবহারকারী Py% দিলে pattern-এর wildcard কার্যকর হবে। এটি চাই না হলে % ও _ escape করে LIKE ? ESCAPE '\\' লিখতে হবে; তখন backslash-ও সঠিকভাবে escape করতে হবে। এই সিদ্ধান্ত README বা help text-এ স্পষ্ট রাখুন, নইলে “exact text খুঁজলাম, বেশি ফল কেন?” ধরনের বিভ্রান্তি হবে।
Transaction ও connection lifecycle
with con: context manager transaction commit বা rollback করে, কিন্তু connection বন্ধ করে না। এই দুই আচরণ আলাদা—Python-এর official sqlite3 documentation-ও connection context manager সম্পর্কে এই সতর্কতা দেয়। তাই connect()-এ schema তৈরি করার সময় with con: ব্যবহার করেছি, এবং main()-এ finally: con.close() রেখেছি। add_book()-এর INSERT-ও with con: ব্লকে; সফল হলে commit, exception হলে rollback। একাধিক লেখা পরিবর্তনকারী operation একসঙ্গে যোগ করলে একই outer transaction-এ রাখা উচিত, যাতে অর্ধেক কাজ স্থায়ী না হয়।
শুধু SELECT-এ সাধারণত pending write transaction খুলছে না, তাই list_books() ও search_books() সরাসরি query করে। কিন্তু connection সর্বদা শেষে বন্ধ হচ্ছে। connection ফেলে রাখলে file lock, descriptor leak অথবা test-এ “database is locked” দেখা দিতে পারে। schema-র DDL-ও transaction-এর ভিতর রাখা হয়েছে, যাতে initialization ব্যর্থ হলে অসম্পূর্ণ setup না থেকে rollback হতে পারে। Production version-এ Python সংস্করণ অনুযায়ী autocommit সংক্রান্ত নতুন API নীতিও পরীক্ষা করবেন; এই beginner script প্রচলিত with con আচরণ দেখায়।
Backup: raw file copy নয়
cp books.db books-copy.db শান্ত সময়ে হয়তো কাজ করবে, কিন্তু live database-এ অন্য connection লিখতে থাকলে শুধু file copy consistent snapshot নাও হতে পারে। এখানে আলাদা source ও destination connection খুলে source.backup(destination) ব্যবহার করা হয়েছে। SQLite-এর backup interface source database-এর snapshot destination-এ কপি করার জন্য তৈরি। with destination: সফল হলে destination commit করবে, আর finally দুই connection বন্ধ করবে। destination আগে থেকে থাকলে SQLite তা overwrite করতে পারে; বাস্তবে backup filename-এ তারিখ, আলাদা directory এবং retention policy রাখুন। Backup পরীক্ষা করতে python book_library.py --db demo.db list এবং কপির উপর python book_library.py --db demo-backup.db list চালিয়ে ফল মিলিয়ে নিন। Backup মানেই encryption নয়; sensitive reading notes যোগ করলে file permissions ও encryption আলাদাভাবে ভাবতে হবে।
Temporary database দিয়ে পরীক্ষা
Test-এ আসল বইয়ের database স্পর্শ করা উচিত নয়। tempfile.TemporaryDirectory()-এর মধ্যে database path বানিয়ে connection খুলুন, add_book() দিয়ে একটি row যোগ করুন, duplicate যোগে False পান, list-এ একটিই row আছে নিশ্চিত করুন এবং search-এ title ও author দুই দিক পরীক্ষা করুন। খালি title-এ ValueError, wildcard term-এ প্রত্যাশিত ফল, এবং backup-এর পরে destination-এ একই row আছে—এসবও যাচাই করা যায়। এইভাবে test প্রতিবার পরিষ্কার file পায় এবং developer-এর ব্যক্তিগত data নষ্ট হওয়ার ঝুঁকি থাকে না।
নিচের command-গুলো smoke test ও unit-style test চালায়। মূল script-এর import side effect নেই, তাই test-এ functions ব্যবহার করা নিরাপদ:
python -m py_compile book_library.py
python - <<'PY'
import tempfile
from pathlib import Path
from book_library import connect, add_book, list_books, search_books, backup_database
with tempfile.TemporaryDirectory() as d:
src = Path(d) / "a.db"
dst = Path(d) / "b.db"
con = connect(str(src))
assert add_book(con, "Dune", "Frank Herbert")
assert not add_book(con, "dune", "frank herbert")
assert len(list_books(con)) == 1
assert len(search_books(con, "Herbert")) == 1
try:
add_book(con, " ", "Nobody")
except ValueError:
pass
else:
raise AssertionError("blank title accepted")
con.close()
backup_database(str(src), str(dst))
copy = connect(str(dst))
assert len(list_books(copy)) == 1
copy.close()
print("all tests passed")
PY
একটি বাস্তব troubleshooting তালিকা কাজে লাগবে। no such table হলে সম্ভবত schema initialization এড়িয়ে সরাসরি query করেছেন; সব entry point-এ connect() ব্যবহার করুন। “database is locked” হলে অন্য process বা পুরনো connection বন্ধ আছে কি না দেখুন এবং long-running transaction ছোট করুন। বাংলা অক্ষর ভাঙলে terminal encoding ও source file UTF-8 কিনা যাচাই করুন—SQLite text নিজে Unicode রাখতে পারে। duplicate বার্তা এলে title-author জোড়াটি case-insensitive unique policy-তে ইতিমধ্যেই আছে। search-এ %-এর কারণে বেশি row এলে মনে রাখুন wildcard policy ইচ্ছাকৃত; literal search দরকার হলে escaping যোগ করুন। backup file শূন্য বা অসম্পূর্ণ মনে হলে destination connection-এর commit এবং source/destination path আলাদা কি না পরীক্ষা করুন।
এরপর কী উন্নত করবেন
অ্যাপটি ছোট রাখলেও extension-এর পথ পরিষ্কার। বই edit বা delete করার আগে id-ভিত্তিক parameterized query ও confirmation যোগ করুন। category, ISBN, reading status এবং notes-এর জন্য migration পরিকল্পনা করুন; শুধু ALTER TABLE চালিয়ে schema version না রাখলে ভবিষ্যৎ update জটিল হবে। Unicode case-insensitive search-এর সীমাবদ্ধতা থাকায় বহু ভাষার শক্তিশালী search চাইলে normalized column বা full-text search বিবেচনা করুন। Pagination যোগ করলে হাজার row একসঙ্গে terminal-এ ছাপাতে হবে না।
একাধিক ব্যবহারকারী বা remote access প্রয়োজন হলে authentication, authorization, audit log, rate limit, encrypted transport এবং server-side database নিন। এই local CLI-তে password রাখার ব্যবস্থা নেই, তাই এটিকে web login solution বলা ভুল হবে। SQLite-ও খারাপ নয়—সঠিক workload-এ চমৎকার—কিন্তু concurrent writes ও operational backup-এর প্রয়োজন বাড়লে PostgreSQL-এর মতো server database উপযুক্ত হতে পারে। সিদ্ধান্তটি data size নয়, access pattern এবং reliability requirement দেখে নিন।
সংক্ষিপ্ত পুনরালোচনা
এই project-এর মূল শিক্ষা চারটি। প্রথমত, schema constraint ও Python validation পাশাপাশি রাখলে ভুল data-এর দুই স্তরের বাধা হয়। দ্বিতীয়ত, সব user value placeholder দিয়ে bind করলে query code এবং data আলাদা থাকে। তৃতীয়ত, with connection transaction commit/rollback সামলায়, কিন্তু explicit close() আপনার দায়িত্ব। চতুর্থত, live SQLite-এর backup-এর জন্য Connection.backup() raw file copy-এর চেয়ে সচেতন পদ্ধতি। এই চার অভ্যাস নিয়ে ছোট personal tool-ও নির্ভরযোগ্য হয় এবং পরের database প্রকল্পে একই চিন্তা কাজে লাগে।
তথ্যসূত্র: Python sqlite3 official documentation। সেখানে connect(), placeholder binding, connection context manager, transaction আচরণ এবং close()-এর lifecycle ব্যাখ্যা আছে; SQLite-এর নিজস্ব syntax ও backup ধারণার জন্য SQLite documentation দেখুন।
প্রতিটি কমান্ডের ভেতরের যাত্রা
একবার একটি command চালালে ভেতরে কী ঘটে সেটি অনুসরণ করা শেখার জন্য খুব মূল্যবান। argparse প্রথমে --db এবং command-এর argument আলাদা করে। main() যদি backup দেখে, সাধারণ connection খোলার বদলে backup-এর দুইটি connection তৈরি করে; কারণ backup-এ source-কে read করার এবং destination-কে write করার আলাদা ভূমিকা দরকার। অন্য তিনটি command-এর ক্ষেত্রে connect() schema নিশ্চিত করে, row factory বসায় এবং connection ফেরত দেয়। এই এক জায়গায় initialization রাখার ফলে list নতুন database-এও no such table দিয়ে ভাঙে না।
add-এর সময় command-line argument আগে Python validation পেরোয়। trim-এর ফলে ব্যবহারকারীর অজান্তে শুরু বা শেষের space database-এ জমে না। তারপর INSERT-এর দুইটি value tuple হিসেবে driver-এ যায়। database constraint duplicate ধরলে exception ওঠে, function False ফেরায় এবং main পরিষ্কার বাংলা-ইংরেজি মিশ্র বার্তা দেখায়। সফল INSERT-এর পরে context manager commit করে। এই return value-ভিত্তিক নকশা test-এ CLI output parse না করেও business rule পরীক্ষা করতে সাহায্য করে।
list ও search query-র result sqlite3.Row হিসেবে পায়। সাধারণ tuple-এর মতো index দিয়ে পড়া গেলেও নাম দিয়ে row['title'] লেখা যায়, তাই column-এর ক্রম পরে বদলালেও print code বেশি পাঠযোগ্য থাকে। ORDER BY title COLLATE NOCASE, id একই title-এর ক্ষেত্রে id দিয়ে স্থির ordering দেয়; pagination যোগ করার সময় এই deterministic ordering বিশেষ কাজে আসবে। Search-এর pattern দুই placeholder-এ আলাদা করে পাঠানো হয়েছে। একই Python string দুইবার ব্যবহার করা হলেও SQL statement-এ input-এর কোনো অংশ জোড়া লাগছে না।
নিরাপদ পরিবর্তন করার অভ্যাস
নিজের প্রয়োজন অনুযায়ী field যোগ করতে গিয়ে schema ও input validation দুটোই একসঙ্গে বদলান। ধরুন বইয়ের ভাষা যোগ করবেন। শুধু table-এ language TEXT যোগ করলে পুরনো row-এ NULL, নতুন row-এ blank এবং spelling-এর অমিল তৈরি হতে পারে। আগে গ্রহণযোগ্য ভাষার তালিকা, case policy এবং পুরনো data migration ঠিক করুন। ছোট project-এ নতুন database দিয়ে শুরু করা সহজ; গুরুত্বপূর্ণ existing data থাকলে migration script, backup এবং rollback plan ছাড়া schema বদলাবেন না।
Delete command যোগ করতে হলে DELETE FROM books WHERE id = ?-এর মতো id placeholder ব্যবহার করুন এবং delete-এর আগে user confirmation নিন। title দিয়ে delete করলে একই title-এর বই বা case-insensitive policy-তে একাধিক সম্ভাবনা তৈরি হতে পারে। Update-এও with con: ব্যবহার করুন। দুইটি table একসঙ্গে বদলাতে হলে প্রথম পরিবর্তন সফল হয়ে দ্বিতীয়টি ব্যর্থ অবস্থায় commit করবেন না; একই context block-এ দুটো রাখুন। বিপরীতে দীর্ঘ search বা export-এর মধ্যে user input লেখা transaction খুলে রাখবেন না, কারণ তা অন্য writer-কে অপ্রয়োজনে আটকে দিতে পারে।
Encoding, path এবং চালানোর বাস্তব পরামর্শ
Database path আপেক্ষিক হলে এটি current working directory-র ওপর নির্ভর করে। IDE থেকে চালালে books.db এক ফোল্ডারে, terminal থেকে চালালে আরেক ফোল্ডারে তৈরি হতে পারে। তাই নিয়মিত ব্যবহারকারীর জন্য Path.home()-এর অধীনে নির্দিষ্ট application directory বেছে নিতে পারেন, তবে directory তৈরি, permission এবং backup location স্পষ্টভাবে পরিচালনা করতে হবে। এই শিক্ষামূলক script ইচ্ছাকৃতভাবে path-টি argument হিসেবে রাখে, যাতে test ও demo-তে অস্থায়ী file ব্যবহার করা যায়।
বাংলা title command line-এ পাঠানোর সময় shell-এর quote ব্যবহার করুন; title-এ space থাকলে quote না দিলে argparse সেটিকে আলাদা argument ভাববে। Windows PowerShell এবং POSIX shell-এর quote আচরণ পুরোপুরি এক নয়, তাই সবচেয়ে নির্ভরযোগ্য উপায় হলো ছোট title দিয়ে শুরু করে --help দেখা। Output-এ Unicode dash এবং বাংলা অক্ষর আছে; পুরনো terminal-এ সমস্যা হলে ASCII hyphen বা UTF-8 terminal বেছে নিন। Source file-এ encoding declaration দরকার নেই, কারণ আধুনিক Python 3 default UTF-8 অনুসরণ করে।
পরীক্ষাকে ভবিষ্যৎ বৈশিষ্ট্যের নকশা বানানো
আজকের test শুধু row count দেখে থামতে পারে, কিন্তু ভালো test প্রত্যাশিত চুক্তি ধরে। Duplicate policy বদলালে test-ও বদলাবে—এটাই স্বাভাবিক, কারণ test পুরনো accidental behavior নয়, নির্ধারিত policy রক্ষা করে। Search-এর ক্ষেত্রে title match, author match, case variation, no-result এবং wildcard আচরণ আলাদা assertion হিসেবে রাখা যায়। Literal % search চাইলে escape function লিখে তার input-output উদাহরণ test করুন। Backup test-এ source-এ একটি row, backup, তারপর destination-এ query—এই ক্রমটি snapshot সত্যিই বহন করছে কি না দেখায়।
Temporary database test-এর সুবিধা আরও আছে। Test ব্যর্থ হলেও developer-এর স্থায়ী books.db নষ্ট হয় না, parallel test-এ file name সংঘর্ষ কমে, এবং CI runner-এ write permission-এর সমস্যা সাধারণত থাকে না। তবে temporary directory বন্ধ হওয়ার আগে connection বন্ধ করতে ভুললে Windows-এ file delete ব্যর্থ হতে পারে; এখানে copy.close() এবং source connection close করা হয়েছে। দীর্ঘমেয়াদি test suite-এ unittest বা pytest ব্যবহার করতে পারেন, কিন্তু এই tutorial ইচ্ছাকৃতভাবে third-party dependency ছাড়াই একই ধারণা দেখিয়েছে।
এই ছোট অ্যাপ তাই শুধু বই রাখার utility নয়, database programming-এর একটি অনুশীলন। Input boundary, schema rule, parameter binding, transaction boundary, resource cleanup, backup consistency এবং reproducible test—প্রতিটি স্তর আলাদা দায়িত্ব পালন করে। স্তরগুলোর কাজ আলাদা রাখলে পরের পরিবর্তন, যেমন GUI বা web API যোগ করা, কম ঝুঁকিতে করা যায়। কিন্তু web API যোগ করলেই authentication, authorization ও network security স্বয়ংক্রিয়ভাবে পাওয়া যায় না; local CLI-র সরলতাকে remote production-এর নিশ্চয়তা হিসেবে কখনো প্রচার করবেন না।
No comments:
Post a Comment