LearningPython TutorialFinal Mini Project
CHAPTER 24 · BUILD A COMPLETE PYTHON APP

Python Final Mini Project — Student Progress Manager

Ab tak ke concepts ko ek real project me combine karte hain. Hum ek command-line Student Progress Manager banayenge jo student records save karega, marks validate karega, SQLite database use karega, reports show karega, JSON export karega aur basic automated tests ke saath reliable rahega.

English + Hinglish95 min buildCourse finale
Project mode: Full project local Python par persistent students.db file use karta hai. BrounStack Playground ke liye neeche separate in-memory demo diya gaya hai, kyunki online runs isolated ho sakte hain.

Project goal

App ka purpose sirf CRUD banana nahi hai. Goal hai clean program structure, input validation, safe SQL, reusable functions, predictable errors, JSON export aur tests ko ek hi practical workflow me use karna.

Hinglish Explanation

Is project ko course ka revision samjho. Variables se lekar functions, exceptions, files, modules, testing aur SQLite tak ka real combination yaha milega.

Features we will build

  • Add a student with name, email and score.
  • List all students in score order.
  • Search one student by ID.
  • Update a student score.
  • Delete a student safely.
  • Show class statistics: count, average, highest and lowest score.
  • Export records to JSON.
  • Validate input and handle expected errors.
  • Use parameterized SQL queries.
  • Test core validation and grading logic.

Course concepts used

  • Variables, strings, conditions, loops: menu and input processing.
  • Lists/dictionaries: reports and JSON-friendly records.
  • Functions: each responsibility separate.
  • Exceptions: invalid input and database failures.
  • Modules: sqlite3, json, pathlib.
  • File handling: JSON export.
  • Comprehensions/built-ins: small transformations and summaries.
  • Testing: validation and grade rules.
  • SQLite: persistent data and parameterized CRUD.

Recommended project structure

project treeText
student-progress-manager/
├── app.py
├── students.db
├── exports/
│   └── students.json
├── test_app.py
└── README.md

Beginner project ke liye one main module enough hai. Jab project grow kare, database, services and CLI ko separate modules me split kar sakte ho.

Database schema

schema.sqlSQL
CREATE TABLE IF NOT EXISTS students (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    score REAL NOT NULL CHECK(score BETWEEN 0 AND 100)
);

Database constraints invalid state ko reduce karte hain. Application validation bhi rakhenge taaki user ko readable error message mile.

Complete app.py

Ye complete local version hai. Isse file me save karke terminal se run kar sakte ho.

app.pyPython
import json
import sqlite3
from pathlib import Path

DB_PATH = Path("students.db")
EXPORT_DIR = Path("exports")


def connect_db():
    connection = sqlite3.connect(DB_PATH)
    connection.row_factory = sqlite3.Row
    return connection


def setup_database():
    with connect_db() as connection:
        connection.execute("""
            CREATE TABLE IF NOT EXISTS students (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL,
                email TEXT NOT NULL UNIQUE,
                score REAL NOT NULL CHECK(score BETWEEN 0 AND 100)
            )
        """)


def clean_name(value):
    name = value.strip()
    if len(name) < 2:
        raise ValueError("Name must contain at least 2 characters")
    return name


def clean_email(value):
    email = value.strip().lower()
    if "@" not in email or email.startswith("@") or email.endswith("@"):
        raise ValueError("Enter a valid email address")
    return email


def clean_score(value):
    try:
        score = float(value)
    except (TypeError, ValueError) as error:
        raise ValueError("Score must be a number") from error

    if not 0 <= score <= 100:
        raise ValueError("Score must be between 0 and 100")
    return score


def grade_for(score):
    if score >= 90:
        return "A"
    if score >= 75:
        return "B"
    if score >= 60:
        return "C"
    if score >= 40:
        return "D"
    return "F"


def add_student(name, email, score):
    name = clean_name(name)
    email = clean_email(email)
    score = clean_score(score)

    with connect_db() as connection:
        cursor = connection.execute(
            "INSERT INTO students (name, email, score) VALUES (?, ?, ?)",
            (name, email, score),
        )
    return cursor.lastrowid


def list_students():
    with connect_db() as connection:
        return connection.execute(
            "SELECT id, name, email, score FROM students ORDER BY score DESC, name"
        ).fetchall()


def get_student(student_id):
    with connect_db() as connection:
        return connection.execute(
            "SELECT id, name, email, score FROM students WHERE id = ?",
            (student_id,),
        ).fetchone()


def update_score(student_id, score):
    score = clean_score(score)
    with connect_db() as connection:
        cursor = connection.execute(
            "UPDATE students SET score = ? WHERE id = ?",
            (score, student_id),
        )
    return cursor.rowcount == 1


def delete_student(student_id):
    with connect_db() as connection:
        cursor = connection.execute(
            "DELETE FROM students WHERE id = ?",
            (student_id,),
        )
    return cursor.rowcount == 1


def class_stats():
    with connect_db() as connection:
        return connection.execute("""
            SELECT
                COUNT(*) AS total,
                AVG(score) AS average,
                MAX(score) AS highest,
                MIN(score) AS lowest
            FROM students
        """).fetchone()


def export_json():
    rows = list_students()
    records = [
        {
            "id": row["id"],
            "name": row["name"],
            "email": row["email"],
            "score": row["score"],
            "grade": grade_for(row["score"]),
        }
        for row in rows
    ]

    EXPORT_DIR.mkdir(exist_ok=True)
    path = EXPORT_DIR / "students.json"
    path.write_text(
        json.dumps(records, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )
    return path


def print_student(row):
    print(
        f'#{row["id"]} | {row["name"]} | {row["email"]} | '
        f'{row["score"]:.1f} | Grade {grade_for(row["score"])}'
    )


def read_student_id():
    try:
        return int(input("Student ID: ").strip())
    except ValueError as error:
        raise ValueError("Student ID must be an integer") from error


def menu():
    print("\nStudent Progress Manager")
    print("1. Add student")
    print("2. List students")
    print("3. Find student")
    print("4. Update score")
    print("5. Delete student")
    print("6. Class statistics")
    print("7. Export JSON")
    print("0. Exit")


def run():
    setup_database()

    while True:
        menu()
        choice = input("Choose: ").strip()

        try:
            if choice == "1":
                student_id = add_student(
                    input("Name: "),
                    input("Email: "),
                    input("Score: "),
                )
                print(f"Student added with ID {student_id}")

            elif choice == "2":
                rows = list_students()
                if not rows:
                    print("No students found")
                for row in rows:
                    print_student(row)

            elif choice == "3":
                row = get_student(read_student_id())
                print_student(row) if row else print("Student not found")

            elif choice == "4":
                updated = update_score(read_student_id(), input("New score: "))
                print("Score updated" if updated else "Student not found")

            elif choice == "5":
                deleted = delete_student(read_student_id())
                print("Student deleted" if deleted else "Student not found")

            elif choice == "6":
                stats = class_stats()
                if stats["total"] == 0:
                    print("No data available")
                else:
                    print("Students:", stats["total"])
                    print("Average:", round(stats["average"], 2))
                    print("Highest:", stats["highest"])
                    print("Lowest:", stats["lowest"])

            elif choice == "7":
                print("Exported to", export_json())

            elif choice == "0":
                print("Goodbye")
                break

            else:
                print("Choose a valid menu option")

        except ValueError as error:
            print("Input error:", error)
        except sqlite3.IntegrityError as error:
            print("Database rule failed:", error)
        except sqlite3.Error as error:
            print("Database error:", error)


if __name__ == "__main__":
    run()

How the app works

  1. setup_database() table ensure karta hai.
  2. run() menu loop start karta hai.
  3. User input validation functions se pass hota hai.
  4. CRUD functions parameterized SQL execute karte hain.
  5. Database rows sqlite3.Row objects ke form me readable field access dete hain.
  6. Report SQL aggregates use karta hai.
  7. Export function rows ko dictionaries me convert karke JSON file likhta hai.
  8. Expected failures user-friendly messages me handle hote hain.

Why SQL parameters are mandatory

safe.pyPython
connection.execute(
    "SELECT * FROM students WHERE email = ?",
    (email,),
)

User data ko SQL f-string me directly insert mat karo. Placeholders data aur SQL structure ko separate rakhte hain and SQL injection risk ko reduce karte hain.

JSON export

Database row JSON directly nahi hota. Hum records ko normal dictionaries me transform karte hain, derived grade add karte hain, then json.dumps() se serialize karte hain.

students.jsonJSON
[
  {
    "id": 1,
    "name": "Aman",
    "email": "aman@example.com",
    "score": 88.0,
    "grade": "B"
  }
]

Add automated tests

Validation and grading deterministic hain, so ye unit tests ke liye perfect starting points hain.

test_app.pyPython
import unittest

from app import clean_email, clean_score, grade_for


class StudentProgressTests(unittest.TestCase):
    def test_grade_boundaries(self):
        cases = [
            (95, "A"),
            (90, "A"),
            (75, "B"),
            (60, "C"),
            (40, "D"),
            (39.9, "F"),
        ]
        for score, expected in cases:
            with self.subTest(score=score):
                self.assertEqual(grade_for(score), expected)

    def test_clean_score(self):
        self.assertEqual(clean_score("88.5"), 88.5)

    def test_invalid_score(self):
        with self.assertRaises(ValueError):
            clean_score("150")

    def test_email_normalization(self):
        self.assertEqual(
            clean_email(" Aman@Example.COM "),
            "aman@example.com",
        )


if __name__ == "__main__":
    unittest.main()
terminalShell
python -m unittest -v

Run the project locally

  1. New folder create karo.
  2. Optional but recommended: python -m venv .venv.
  3. Virtual environment activate karo.
  4. Code ko app.py me save karo.
  5. Terminal me python app.py run karo.
  6. Kuch students add karo and list/report/export test karo.
  7. test_app.py add karke python -m unittest -v run karo.

Playground-friendly in-memory version

Ye short demo persistence ke bina SQLite workflow, functions, report and JSON output demonstrate karta hai.

playground-demo.pyPython
import json
import sqlite3


def grade(score):
    if score >= 90:
        return "A"
    if score >= 75:
        return "B"
    if score >= 60:
        return "C"
    if score >= 40:
        return "D"
    return "F"


db = sqlite3.connect(":memory:")
db.row_factory = sqlite3.Row

db.execute("CREATE TABLE students (name TEXT, score REAL CHECK(score BETWEEN 0 AND 100))")
db.executemany(
    "INSERT INTO students VALUES (?, ?)",
    [("Aman", 88), ("Riya", 94), ("Kabir", 72)],
)

rows = db.execute("SELECT name, score FROM students ORDER BY score DESC").fetchall()
records = [
    {"name": row["name"], "score": row["score"], "grade": grade(row["score"])}
    for row in rows
]

print(json.dumps(records, indent=2))
print("Average:", round(sum(item["score"] for item in records) / len(records), 2))
db.close()

Run final project demo →

Stretch improvements

  • Split code into database.py, services.py and cli.py.
  • Add courses and enrollments with foreign keys.
  • Add pagination and search.
  • Add CSV import/export.
  • Add timestamps for created/updated records.
  • Add transaction tests with temporary databases.
  • Add logging to a file without storing sensitive data.
  • Add a REST API later using a web framework.
  • Add authentication only when you understand secure password hashing and session/token handling.

Common project mistakes

  • One giant function me complete application likhna.
  • Validation only database error aane ke baad karna.
  • SQL values ko f-strings se interpolate karna.
  • Every exception ko broad except Exception se silently hide karna.
  • Database connection close/transaction behavior ignore karna.
  • Tests sirf happy path ke liye likhna.
  • Generated database/export files ko unnecessarily Git me commit karna.
  • Secrets or passwords plain text me store karna.

From mini project to real software

Production app ke liye authentication, authorization, migrations, backups, concurrency, audit logging, stronger email validation, secure configuration, deployment and monitoring jaise concerns separately design karne padte hain. Mini project ka goal in sab ko fake karna nahi, balki clean foundations build karna hai.

Final course checklist

  • Python syntax, variables, conditions and loops confidently use kar sakte ho?
  • Strings, lists, tuples, sets and dictionaries ka correct use clear hai?
  • Functions, scope, modules and exceptions samajh aate hain?
  • Files, JSON and standard library tools use kar sakte ho?
  • Classes, inheritance, iterators, generators and decorators ka purpose clear hai?
  • Comprehensions and useful built-ins readable way me use kar sakte ho?
  • Virtual environment, pip and basic tests ka workflow samajh aaya?
  • SQLite me safe parameterized CRUD aur transactions use kar sakte ho?
  • Ek complete mini project ko requirements → code → test → improve cycle me build kar sakte ho?

Final Challenge — Make It Yours

Project copy karke sirf run mat karo. Isme apne changes add karo.

  1. Student me city field add karo.
  2. Search by email feature add karo.
  3. Pass/fail report add karo.
  4. Top 3 students query add karo.
  5. Duplicate email error ko friendly custom message me map karo.
  6. JSON export filename me current date add karo.
  7. At least 8 unit tests likho.
  8. README me setup, features and sample output document karo.
  9. Fresh environment me project run/test karke verify karo.
  10. Git branch + commit + PR workflow me project publish karo.
Course complete: Aapne BrounStack Python path ke 24 chapters cover kar liye. Ab best next step repeated practice hai: small programs build karo, errors debug karo, tests likho aur gradually larger projects par move karo.