Python is a high-level, interpreted programming language created by Guido van Rossum in 1991. Its design philosophy emphasises code readability, making it ideal for beginners and powerful enough for professionals.
Simple syntax · Dynamically typed · Garbage collected · Vast standard library · Cross-platform
Web development · Data science · AI/ML · Automation · Scripting · Scientific computing
Python runs line-by-line. No compilation step needed — great for rapid development and testing.
330,000+ packages on PyPI. One of the world's most popular languages with massive community support.
Before writing code, you need Python installed and a good editor configured.
- 1Download Python: Visit python.org/downloads and install Python 3.11+. Always check "Add Python to PATH" on Windows.
- 2Install VS Code: Download from code.visualstudio.com and install the Python extension by Microsoft.
- 3Verify installation: Open a terminal and run
python --version. You should see Python 3.x.x. - 4Run your first script: Create
hello.py, typeprint("Hello, World!"), and run it withpython hello.py.
# Your very first Python program # The print() function outputs text to the terminal print("Hello, World!") # Python uses indentation instead of curly braces # This is valid Python — clean and readable name = "Alice" print(f"Hello, {name}!") # Output: Hello, Alice!
Variables are named containers that store data. Python is dynamically typed — you don't declare types explicitly; Python figures it out automatically.
# ── INTEGER: whole numbers ────────────────────────── age = 25 year = 2024 print(type(age)) # <class 'int'> # ── FLOAT: decimal numbers ────────────────────────── price = 19.99 pi = 3.14159 print(type(price)) # <class 'float'> # ── STRING: text (single or double quotes) ────────── name = "Alice Mugisha" city = 'Kigali' message = f"{name} lives in {city}" # f-string print(message) # Alice Mugisha lives in Kigali # String methods print(name.upper()) # ALICE MUGISHA print(name.split(" ")) # ['Alice', 'Mugisha'] print(len(name)) # 13 # ── BOOLEAN: True or False ────────────────────────── is_student = True is_employed = False print(type(is_student)) # <class 'bool'> # ── NONE: absence of a value ──────────────────────── result = None print(result is None) # True # ── TYPE CONVERSION ───────────────────────────────── num_str = "42" num_int = int(num_str) # string → int num_flt = float(num_str) # string → float back_str = str(num_int) # int → string
| Type | Example | Use Case |
|---|---|---|
| int | age = 25 | Counting, indexing, IDs |
| float | price = 9.99 | Measurements, money, decimals |
| str | name = "Alice" | Text, names, messages |
| bool | active = True | Flags, conditions, switches |
| NoneType | result = None | Missing/optional values |
Operators perform operations on values. Python has arithmetic, comparison, and logical operators — the building blocks of all logic.
# ── ARITHMETIC OPERATORS ──────────────────────────── print(10 + 3) # 13 — addition print(10 - 3) # 7 — subtraction print(10 * 3) # 30 — multiplication print(10 / 3) # 3.33 — division (always float) print(10 // 3) # 3 — floor division (int) print(10 % 3) # 1 — modulus (remainder) print(2 ** 8) # 256 — exponentiation # ── COMPARISON OPERATORS ──────────────────────────── x, y = 10, 20 print(x == y) # False — equal to print(x != y) # True — not equal print(x < y) # True — less than print(x > y) # False — greater than print(x <= 10) # True — less or equal # ── LOGICAL OPERATORS ─────────────────────────────── a, b = True, False print(a and b) # False — both must be True print(a or b) # True — at least one True print(not a) # False — inverts the value # Real-world example: check eligibility age = 20 has_id = True can_vote = age >= 18 and has_id print(f"Can vote: {can_vote}") # Can vote: True
Control flow directs which code runs and when. Use if/elif/else for decisions and loops for repetition.
# ── IF / ELIF / ELSE ──────────────────────────────── score = 75 if score >= 90: grade = "A" elif score >= 80: grade = "B" elif score >= 70: grade = "C" else: grade = "F" print(f"Grade: {grade}") # Grade: C # ── FOR LOOP: iterate over a sequence ─────────────── students = ["Alice", "Bob", "Claire"] for student in students: print(f"Welcome, {student}!") # range() generates a sequence of numbers for i in range(1, 6): # 1 to 5 print(f"{i} x 3 = {i * 3}") # enumerate() gives index + value for idx, name in enumerate(students, start=1): print(f"{idx}. {name}") # ── WHILE LOOP: repeat while condition is True ────── countdown = 5 while countdown > 0: print(f"T-minus {countdown}...") countdown -= 1 print("Liftoff! 🚀") # ── BREAK & CONTINUE ──────────────────────────────── for num in range(10): if num == 3: continue # skip 3 if num == 7: break # stop at 7 print(num)
Functions are reusable blocks of code that accept inputs (parameters), do something, and optionally return a result. Good functions do one thing well.
# ── BASIC FUNCTION ────────────────────────────────── def greet(name): """Return a greeting string for the given name.""" return f"Hello, {name}! Welcome aboard." message = greet("Alice") print(message) # Hello, Alice! Welcome aboard. # ── DEFAULT PARAMETERS ────────────────────────────── def calculate_tax(amount, rate=0.18): """Calculate tax. Default rate is 18% (VAT).""" return amount * rate print(calculate_tax(10000)) # 1800.0 print(calculate_tax(10000, 0.15)) # 1500.0 # ── MULTIPLE RETURN VALUES ────────────────────────── def get_min_max(numbers): """Return the smallest and largest values.""" return min(numbers), max(numbers) low, high = get_min_max([3, 1, 9, 4, 7]) print(f"Min: {low}, Max: {high}") # Min: 1, Max: 9 # ── *ARGS: variable number of arguments ───────────── def add_all(*numbers): """Sum any number of values passed in.""" return sum(numbers) print(add_all(1, 2, 3, 4, 5)) # 15 # ── **KWARGS: keyword arguments ───────────────────── def create_profile(**info): """Build a profile dict from keyword arguments.""" return info profile = create_profile(name="Alice", age=20, city="Kigali") print(profile) # {'name': 'Alice', 'age': 20, 'city': 'Kigali'} # ── LAMBDA: anonymous one-line function ───────────── square = lambda x: x ** 2 print(square(9)) # 81 numbers = [5, 2, 8, 1] numbers.sort(key=lambda x: x) print(numbers) # [1, 2, 5, 8]
Python has four built-in collection types. Choosing the right one matters for performance and correctness.
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # LIST — ordered, mutable, allows duplicates # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ courses = ["Python", "Web Dev", "Databases"] courses.append("DevOps") # add to end courses.insert(1, "OOP") # insert at index courses.remove("Web Dev") # remove by value last = courses.pop() # remove & return last print(courses[0]) # Python (index access) print(courses[1:3]) # slicing # List comprehension — concise and Pythonic squares = [x ** 2 for x in range(1, 6)] print(squares) # [1, 4, 9, 16, 25] # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # TUPLE — ordered, IMMUTABLE, allows duplicates # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ coordinates = (1.9441, 30.0619) # Kigali lat/lng lat, lng = coordinates # unpacking print(f"Lat: {lat}, Lng: {lng}") # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # SET — unordered, NO duplicates, mutable # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ tags = {"python", "flask", "python", "api"} print(tags) # {'python', 'flask', 'api'} — no duplicate set_a = {1, 2, 3} set_b = {2, 3, 4} print(set_a & set_b) # {2, 3} — intersection print(set_a | set_b) # {1,2,3,4}— union print(set_a - set_b) # {1} — difference # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # DICTIONARY — key-value pairs, ordered (3.7+) # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ student = { "name": "Alice Mugisha", "age": 20, "grade": "A", "active": True } print(student["name"]) # Alice Mugisha print(student.get("phone", "N/A")) # N/A (safe access) student["email"] = "alice@school.rw" # add new key for key, value in student.items(): print(f" {key}: {value}")
| Type | Ordered | Mutable | Duplicates | Best For |
|---|---|---|---|---|
| list [] | ✓ | ✓ | ✓ | Sequences, collections that change |
| tuple () | ✓ | ✗ | ✓ | Fixed data, coordinates, records |
| set {} | ✗ | ✓ | ✗ | Unique items, membership testing |
| dict {k:v} | ✓ | ✓ | keys: ✗ | Key-value lookups, JSON-like data |
Errors happen. Good code handles them gracefully instead of crashing. Python uses try/except blocks to catch and respond to errors.
# ── BASIC TRY / EXCEPT ────────────────────────────── try: age = int(input("Enter your age: ")) print(f"In 10 years you'll be {age + 10}") except ValueError: print("Please enter a valid number!") # ── MULTIPLE EXCEPT BLOCKS ────────────────────────── def divide(a, b): try: result = a / b except ZeroDivisionError: print("Error: Cannot divide by zero!") return None except TypeError: print("Error: Both values must be numbers!") return None else: print(f"Result: {result}") # runs if no error return result finally: print("Division attempt complete.") # always runs divide(10, 2) # Result: 5.0 divide(10, 0) # Error: Cannot divide by zero! # ── CUSTOM EXCEPTIONS ─────────────────────────────── class InsufficientFundsError(Exception): """Raised when account balance is too low.""" pass def withdraw(balance, amount): if amount > balance: raise InsufficientFundsError( f"Need {amount} but only have {balance}" ) return balance - amount try: withdraw(500, 1000) except InsufficientFundsError as e: print(f"Transaction failed: {e}")
A module is a Python file. A package is a directory of modules. They let you organise code and reuse it across projects.
# ── IMPORTING STANDARD LIBRARY MODULES ────────────── import math print(math.sqrt(144)) # 12.0 print(math.pi) # 3.141592653589793 import datetime today = datetime.date.today() print(f"Today: {today}") import random print(random.randint(1, 100)) # random int 1–100 # ── IMPORT SPECIFIC ITEMS ─────────────────────────── from math import pi, ceil, floor print(ceil(4.2)) # 5 print(floor(4.9)) # 4 # ── YOUR OWN MODULE ───────────────────────────────── # File: utils.py def format_currency(amount, currency="RWF"): return f"{currency} {amount:,.0f}" # File: main.py from utils import format_currency print(format_currency(15000)) # RWF 15,000
OOP models real-world entities as objects with data (attributes) and behaviour (methods). It makes code modular, reusable, and easier to maintain.
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # CLASS & OBJECTS # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ class Student: """Represents a student in the management system.""" school_name = "Tech Academy" # class attribute (shared) def __init__(self, name, age, grade): # instance attributes (unique per object) self.name = name self.age = age self.grade = grade def introduce(self): """Return a brief introduction string.""" return f"Hi! I'm {self.name}, age {self.age}, grade {self.grade}." def is_passing(self): """Check if the student has a passing grade.""" return self.grade not in ["F", "D"] def __repr__(self): return f"Student({self.name!r}, {self.grade!r})" # Creating objects (instances) alice = Student("Alice", 20, "A") bob = Student("Bob", 22, "C") print(alice.introduce()) # Hi! I'm Alice, age 20, grade A. print(bob.is_passing()) # True # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # INHERITANCE — reuse and extend existing classes # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ class GraduateStudent(Student): # inherits from Student """A student who is also doing research.""" def __init__(self, name, age, grade, thesis_topic): super().__init__(name, age, grade) # call parent self.thesis_topic = thesis_topic def introduce(self): # POLYMORPHISM — override parent method base = super().introduce() return f"{base} Thesis: {self.thesis_topic}" grad = GraduateStudent("Claire", 25, "A", "AI in Healthcare") print(grad.introduce()) print(grad.is_passing()) # inherited method still works # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # ENCAPSULATION — hide internal details # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ class BankAccount: def __init__(self, owner, balance=0): self.owner = owner self.__balance = balance # __ makes it private def deposit(self, amount): if amount > 0: self.__balance += amount def get_balance(self): # controlled access return self.__balance account = BankAccount("Alice", 5000) account.deposit(2000) print(account.get_balance()) # 7000
Python makes reading and writing files simple. Always use the with statement — it automatically closes the file even if an error occurs.
import json # ── WRITE a text file ─────────────────────────────── with open("students.txt", "w") as f: f.write("Alice Mugisha, A\n") f.write("Bob Nkurunziza, C\n") # ── READ a text file ──────────────────────────────── with open("students.txt", "r") as f: for line in f: print(line.strip()) # ── APPEND to a file ──────────────────────────────── with open("students.txt", "a") as f: f.write("Claire Uwimana, A\n") # ── JSON files (most common for web data) ─────────── data = { "students": [ {"name": "Alice", "grade": "A"}, {"name": "Bob", "grade": "C"} ] } # Write JSON with open("data.json", "w") as f: json.dump(data, f, indent=2) # Read JSON with open("data.json", "r") as f: loaded = json.load(f) print(loaded["students"][0]["name"]) # Alice
A virtual environment is an isolated Python installation for each project. This prevents package version conflicts between projects.
# Create a virtual environment named 'venv' python -m venv venv # Activate it source venv/bin/activate # macOS / Linux venv\Scripts\activate # Windows # Install packages pip install flask pip install requests pip install sqlalchemy # Save dependencies to a file pip freeze > requirements.txt # Install from requirements.txt (for teammates) pip install -r requirements.txt # Deactivate when done deactivate
venv/ to your .gitignore so it's not committed to version control.
Before writing web code, understand the client-server model and HTTP — the language of the web.
GET (read) · POST (create) · PUT (update) · DELETE (remove) — the four actions of REST APIs
Browser (client) sends requests → Server processes and returns responses (HTML, JSON, etc.)
200 OK · 201 Created · 400 Bad Request · 404 Not Found · 500 Server Error
JavaScript Object Notation — the standard format for API data exchange. Python dicts convert directly to JSON.
Flask is a lightweight Python web framework. It gives you routing, templating, and request handling without imposing a strict structure.
pip install flask flask-sqlalchemy
from flask import Flask, render_template, request, jsonify app = Flask(__name__) # __name__ tells Flask where to look for files # ── HOME ROUTE ────────────────────────────────────── @app.route("/") def home(): """Render the home page.""" return render_template("index.html") # ── ROUTE WITH URL PARAMETER ──────────────────────── @app.route("/student/<int:student_id>") def get_student(student_id): """Return student info as JSON.""" # In a real app, query the database here student = {"id": student_id, "name": "Alice", "grade": "A"} return jsonify(student) if __name__ == "__main__": app.run(debug=True) # debug=True for auto-reload
Routes map URLs to Python functions. Each function is called a view and returns a response.
from flask import Flask, request, redirect, url_for, flash app = Flask(__name__) app.secret_key = "your-secret-key" # needed for flash messages # ── MULTIPLE HTTP METHODS on one route ────────────── @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": username = request.form.get("username") password = request.form.get("password") if username == "admin" and password == "pass123": flash("Login successful!", "success") return redirect(url_for("dashboard")) else: flash("Invalid credentials.", "error") return render_template("login.html") @app.route("/dashboard") def dashboard(): return render_template("dashboard.html") # ── QUERY PARAMETERS: /search?name=alice ──────────── @app.route("/search") def search(): query = request.args.get("name", "") # Filter students whose name contains query results = [s for s in students if query.lower() in s["name"].lower()] return jsonify(results)
Flask uses Jinja2 templates — HTML files with special tags that let you inject Python data, loop, and apply conditions.
<!-- Base template structure -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ title }}</title> <!-- {{ }} for variables -->
</head>
<body>
<h1>Student List</h1>
<!-- {% %} for logic -->
{% if students %}
<ul>
{% for student in students %}
<li>
<strong>{{ student.name }}</strong> —
Grade: {{ student.grade }}
{% if student.grade == "A" %}
<span>⭐ Top Performer</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>No students found.</p>
{% endif %}
</body>
</html>
@app.route("/students") def student_list(): students = [ {"name": "Alice", "grade": "A"}, {"name": "Bob", "grade": "C"}, ] return render_template( "students.html", title="All Students", students=students )
A REST API is a web service that returns JSON data instead of HTML. It powers mobile apps, frontends, and third-party integrations.
from flask import Flask, jsonify, request, abort app = Flask(__name__) # In-memory storage (replace with DB in production) students = [ {"id": 1, "name": "Alice", "grade": "A"}, {"id": 2, "name": "Bob", "grade": "C"}, ] # ── GET /api/students — list all ──────────────────── @app.route("/api/students", methods=["GET"]) def get_students(): return jsonify({"students": students, "count": len(students)}) # ── GET /api/students/1 — get one ─────────────────── @app.route("/api/students/<int:sid>", methods=["GET"]) def get_student(sid): student = next((s for s in students if s["id"] == sid), None) if not student: abort(404) # triggers 404 response return jsonify(student) # ── POST /api/students — create ───────────────────── @app.route("/api/students", methods=["POST"]) def create_student(): data = request.get_json() if not data or "name" not in data: return jsonify({"error": "name is required"}), 400 new_student = { "id": len(students) + 1, "name": data["name"], "grade": data.get("grade", "N/A") } students.append(new_student) return jsonify(new_student), 201 # 201 = Created # ── PUT /api/students/1 — update ──────────────────── @app.route("/api/students/<int:sid>", methods=["PUT"]) def update_student(sid): student = next((s for s in students if s["id"] == sid), None) if not student: abort(404) data = request.get_json() student.update(data) return jsonify(student) # ── DELETE /api/students/1 — delete ───────────────── @app.route("/api/students/<int:sid>", methods=["DELETE"]) def delete_student(sid): global students students = [s for s in students if s["id"] != sid] return jsonify({"message": "Student deleted"}), 200 # ── Error handlers ────────────────────────────────── @app.errorhandler(404) def not_found(e): return jsonify({"error": "Resource not found"}), 404
A database is an organised system for storing, retrieving, and managing data. Relational databases (SQL) store data in structured tables with relationships.
Serverless, file-based DB. Built into Python. Perfect for development, small apps, and prototyping.
Full client-server databases. Production-ready, handles millions of records and concurrent connections.
Python's built-in module to work with SQLite databases. No installation required.
ORM (Object-Relational Mapper) — write Python code instead of raw SQL. Works with all major DBs.
The sqlite3 module is built into Python. No installation required — perfect for learning and small projects.
import sqlite3 from contextlib import contextmanager # ── CONNECTION HELPER ─────────────────────────────── @contextmanager def get_db(): """Context manager for safe DB connections.""" conn = sqlite3.connect("school.db") conn.row_factory = sqlite3.Row # rows as dicts try: yield conn conn.commit() except Exception: conn.rollback() raise finally: conn.close() # ── CREATE TABLE ──────────────────────────────────── def init_db(): """Create tables if they don't exist.""" with get_db() as conn: conn.execute(""" CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, grade TEXT DEFAULT 'N/A', created TEXT DEFAULT CURRENT_TIMESTAMP ) """) # ── INSERT ────────────────────────────────────────── def add_student(name, email, grade="N/A"): """Add a new student. Uses parameterised query to prevent SQL injection.""" with get_db() as conn: cursor = conn.execute( "INSERT INTO students (name, email, grade) VALUES (?, ?, ?)", (name, email, grade) # ← NEVER use string formatting here ) return cursor.lastrowid # return new ID # ── SELECT ALL ────────────────────────────────────── def get_all_students(): """Return all students as a list of dicts.""" with get_db() as conn: rows = conn.execute("SELECT * FROM students ORDER BY name").fetchall() return [dict(row) for row in rows] # ── SELECT ONE ────────────────────────────────────── def get_student(student_id): """Return a single student by ID.""" with get_db() as conn: row = conn.execute( "SELECT * FROM students WHERE id = ?", (student_id,) ).fetchone() return dict(row) if row else None # ── UPDATE ────────────────────────────────────────── def update_student(student_id, grade): """Update a student's grade.""" with get_db() as conn: conn.execute( "UPDATE students SET grade = ? WHERE id = ?", (grade, student_id) ) # ── DELETE ────────────────────────────────────────── def delete_student(student_id): """Remove a student from the database.""" with get_db() as conn: conn.execute( "DELETE FROM students WHERE id = ?", (student_id,) ) # ── USAGE EXAMPLE ─────────────────────────────────── if __name__ == "__main__": init_db() add_student("Alice Mugisha", "alice@school.rw", "A") add_student("Bob Nkurunziza", "bob@school.rw", "C") students = get_all_students() for s in students: print(s)
For production applications, MySQL offers better performance, concurrency, and scalability than SQLite.
pip install mysql-connector-python
import mysql.connector from mysql.connector import Error # ── CONNECTION CONFIG ──────────────────────────────── DB_CONFIG = { "host": "localhost", "user": "root", "password": "yourpassword", "database": "school_db" } def get_connection(): """Create and return a MySQL connection.""" return mysql.connector.connect(**DB_CONFIG) # ── INSERT with parameterised query ───────────────── def add_student(name, email, grade): sql = "INSERT INTO students (name, email, grade) VALUES (%s, %s, %s)" try: conn = get_connection() cursor = conn.cursor() cursor.execute(sql, (name, email, grade)) # %s = placeholder conn.commit() print(f"Student added with ID: {cursor.lastrowid}") except Error as e: print(f"Database error: {e}") finally: if conn.is_connected(): cursor.close() conn.close() # ── SELECT with filtering ──────────────────────────── def search_students(grade): sql = "SELECT * FROM students WHERE grade = %s" try: conn = get_connection() cursor = conn.cursor(dictionary=True) cursor.execute(sql, (grade,)) return cursor.fetchall() except Error as e: print(f"Error: {e}") return [] finally: conn.close()
SQLAlchemy lets you define database tables as Python classes. No raw SQL required — it generates queries for you.
from flask import Flask from flask_sqlalchemy import SQLAlchemy from datetime import datetime app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///school.db" # For MySQL: "mysql+mysqlconnector://user:pass@localhost/school_db" db = SQLAlchemy(app) # ── MODEL = TABLE DEFINITION ───────────────────────── class Student(db.Model): """ORM model — each attribute maps to a DB column.""" __tablename__ = "students" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) email = db.Column(db.String(150), unique=True, nullable=False) grade = db.Column(db.String(5), default="N/A") created_at = db.Column(db.DateTime, default=datetime.utcnow) def to_dict(self): """Serialise for JSON responses.""" return { "id": self.id, "name": self.name, "email": self.email, "grade": self.grade } # ── CRUD with SQLAlchemy ──────────────────────────── # CREATE with app.app_context(): db.create_all() # create tables from models student = Student(name="Alice", email="alice@school.rw", grade="A") db.session.add(student) db.session.commit() # READ all_students = Student.query.all() top_students = Student.query.filter_by(grade="A").all() one_student = Student.query.get(1) # UPDATE student = Student.query.get(1) student.grade = "A+" db.session.commit() # DELETE student = Student.query.get(1) db.session.delete(student) db.session.commit()
SQL injection is one of the most dangerous web vulnerabilities. Always use parameterised queries — never build SQL strings with user input.
# ❌ DANGEROUS — never do this! # An attacker could input: ' OR '1'='1 user_input = request.form.get("name") sql = f"SELECT * FROM students WHERE name = '{user_input}'" # This could become: SELECT * FROM students WHERE name = '' OR '1'='1' # Which returns ALL rows — a major security breach! # ✅ SAFE — use parameterised queries always cursor.execute( "SELECT * FROM students WHERE name = ?", (user_input,) # DB driver escapes the value safely ) # ✅ SAFE with SQLAlchemy ORM (automatically parameterised) Student.query.filter_by(name=user_input).first() # ✅ SAFE with SQLAlchemy text() for raw SQL from sqlalchemy import text db.session.execute( text("SELECT * FROM students WHERE name = :name"), {"name": user_input} )
? for SQLite, %s for MySQL) or the ORM. This is non-negotiable.
student_manager/ ├── app.py # Flask application & routes ├── database.py # DB connection & CRUD functions ├── requirements.txt # flask, flask-sqlalchemy ├── templates/ │ ├── base.html # shared layout │ ├── index.html # student list │ ├── add.html # add student form │ └── edit.html # edit student form └── static/ └── style.css # stylesheet
import sqlite3 from contextlib import contextmanager DATABASE = "students.db" @contextmanager def get_db(): conn = sqlite3.connect(DATABASE) conn.row_factory = sqlite3.Row try: yield conn conn.commit() except: conn.rollback() raise finally: conn.close() def init_db(): with get_db() as db: db.execute(""" CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, age INTEGER, grade TEXT DEFAULT 'N/A' ) """) def get_all_students(): with get_db() as db: rows = db.execute("SELECT * FROM students ORDER BY name").fetchall() return [dict(r) for r in rows] def get_student_by_id(sid): with get_db() as db: row = db.execute("SELECT * FROM students WHERE id=?", (sid,)).fetchone() return dict(row) if row else None def add_student(name, email, age, grade): with get_db() as db: db.execute( "INSERT INTO students (name, email, age, grade) VALUES (?,?,?,?)", (name, email, age, grade) ) def update_student(sid, name, email, age, grade): with get_db() as db: db.execute( "UPDATE students SET name=?, email=?, age=?, grade=? WHERE id=?", (name, email, age, grade, sid) ) def delete_student(sid): with get_db() as db: db.execute("DELETE FROM students WHERE id=?", (sid,))
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify from database import init_db, get_all_students, get_student_by_id from database import add_student, update_student, delete_student app = Flask(__name__) app.secret_key = "sms-secret-2024" # Initialise the database when app starts with app.app_context(): init_db() # ── WEB ROUTES (HTML pages) ───────────────────────── @app.route("/") def index(): """Home page — list all students.""" students = get_all_students() return render_template("index.html", students=students) @app.route("/add", methods=["GET", "POST"]) def add(): """Show add form (GET) or process submission (POST).""" if request.method == "POST": name = request.form["name"].strip() email = request.form["email"].strip() age = request.form.get("age", None) grade = request.form.get("grade", "N/A") if not name or not email: flash("Name and email are required.", "error") return redirect(url_for("add")) add_student(name, email, age, grade) flash(f"{name} added successfully!", "success") return redirect(url_for("index")) return render_template("add.html") @app.route("/edit/<int:sid>", methods=["GET", "POST"]) def edit(sid): """Edit an existing student record.""" student = get_student_by_id(sid) if not student: flash("Student not found.", "error") return redirect(url_for("index")) if request.method == "POST": update_student( sid, request.form["name"], request.form["email"], request.form.get("age"), request.form.get("grade", "N/A") ) flash("Student updated.", "success") return redirect(url_for("index")) return render_template("edit.html", student=student) @app.route("/delete/<int:sid>", methods=["POST"]) def delete(sid): """Delete a student (POST only for safety).""" delete_student(sid) flash("Student deleted.", "info") return redirect(url_for("index")) # ── API ROUTES (JSON responses) ───────────────────── @app.route("/api/students") def api_students(): return jsonify(get_all_students()) if __name__ == "__main__": app.run(debug=True, port=5000)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Student Manager</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="container">
<header>
<h1>🎓 Student Manager</h1>
<a href="/add" class="btn btn-primary">+ Add Student</a>
</header>
<!-- Flash messages -->
{% for category, message in get_flashed_messages(with_categories=true) %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
<!-- Student table -->
{% if students %}
<table class="table">
<thead>
<tr>
<th>#</th><th>Name</th><th>Email</th><th>Age</th><th>Grade</th><th>Actions</th>
</tr>
</thead>
<tbody>
{% for s in students %}
<tr>
<td>{{ s.id }}</td>
<td>{{ s.name }}</td>
<td>{{ s.email }}</td>
<td>{{ s.age or '—' }}</td>
<td>
<span class="badge {% if s.grade == 'A' %}badge-green{% endif %}">
{{ s.grade }}
</span>
</td>
<td>
<a href="/edit/{{ s.id }}" class="btn btn-sm">Edit</a>
<form method="POST" action="/delete/{{ s.id }}" style="display:inline">
<button type="submit" class="btn btn-danger btn-sm"
onclick="return confirm('Delete {{ s.name }}?')">
Delete
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="empty">No students yet. <a href="/add">Add one!</a></p>
{% endif %}
</div>
</body>
</html>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, sans-serif;
background: #f8f9fa;
color: #1a1a2e;
}
.container {
max-width: 960px;
margin: 40px auto;
padding: 0 20px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 28px;
}
.btn {
padding: 8px 18px;
border-radius: 6px;
border: none;
cursor: pointer;
font-size: 14px;
text-decoration: none;
display: inline-block;
background: #e5e7eb;
color: #374151;
transition: background .2s;
}
.btn-primary { background: #1d6b44; color: white; }
.btn-danger { background: #dc2626; color: white; }
.btn-sm { padding: 4px 12px; font-size: 12px; }
.table {
width: 100%; border-collapse: collapse;
background: white; border-radius: 12px;
overflow: hidden; box-shadow: 0 1px 6px rgba(0,0,0,.08);
}
.table th {
background: #1a1a2e; color: white;
padding: 12px 16px; text-align: left; font-size: 13px;
}
.table td { padding: 12px 16px; border-bottom: 1px solid #f0f0f0; }
.table tr:last-child td { border-bottom: none; }
.table tr:hover td { background: #f9fafb; }
.badge { padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; }
.badge-green { background: #d1fae5; color: #065f46; }
.alert {
padding: 12px 18px; border-radius: 8px; margin-bottom: 16px;
}
.alert-success { background: #d1fae5; color: #065f46; }
.alert-error { background: #fee2e2; color: #991b1b; }
.alert-info { background: #dbeafe; color: #1e3a5f; }
.empty { text-align: center; padding: 40px; color: #6b7280; }
cd student_manager → python -m venv venv → activate → pip install flask → python app.py → open http://localhost:5000