Python Fundamentals

INFO 153B/253B: Backend Web Architecture

Week 2

 

Kay Ashaolu - Instructor

Suk Min Hwang - GSI

Today's Agenda

  • Part 1: Prep Work Recap - Quick Python refresher check-in
  • Part 2: Python for Web Development - Why Python for backends?
  • Part 3: Data Structures for APIs - Dicts and lists as JSON
  • Part 4: Functions as Request Handlers - Preview of Flask patterns
  • Part 5: OOP Basics for Models - Classes as data structures
  • Demo: Python Dict to JSON API Response (10 min)
  • In-Class Exploration: Student Records API (45 min)

Part 1: Prep Work Recap

Quick check-in on O'Reilly Chapter 2

What You Learned in Prep

  • Variables: Names for values (x = 15, price = 9.99)
  • String formatting: f-strings and .format()
  • Data structures: Lists, tuples, sets, dictionaries
  • Control flow: If statements, loops, list comprehensions
  • Functions: Definition, parameters, return values
Key insight: All of these will be used to build API responses.

Quick Check: Variables & Types

# What types are these?
user_id = 42           # int
price = 19.99          # float
username = "alice"     # str
is_active = True       # bool
  • Python is dynamically typed - no need to declare types
  • Variables are references to objects in memory
  • Right side is evaluated first, then assigned to left side

Quick Check: Collections

# Lists - ordered, mutable
users = ["alice", "bob", "charlie"]

# Dictionaries - key-value pairs
user = {"id": 1, "name": "Alice", "email": "alice@example.com"}

# Accessing data
first_user = users[0]           # "alice"
user_email = user["email"]      # "alice@example.com"
  • Lists: Collections of items (like arrays)
  • Dictionaries: Key-value mappings (like objects/hashes)
  • Both are essential for API responses

Quick Check: Functions

def greet(name):
    """Return a greeting message."""
    return f"Hello, {name}!"

# Calling the function
message = greet("Alice")  # "Hello, Alice!"
  • def defines a function
  • Parameters receive input values
  • return sends a value back to the caller
  • Functions that don't return explicitly return None

Prep Recap: Key Takeaways

  • Dictionaries will become your JSON responses
  • Lists will hold collections of resources
  • Functions will handle HTTP requests
  • String formatting will build URLs and messages
The connection: Python data structures map directly to JSON, which is how APIs communicate.

Part 2: Python for Web Development

Why Python dominates backend development

Why Python for Backends?

  • Readability: Clean syntax, easy to maintain
  • Rich ecosystem: Flask, Django, FastAPI, SQLAlchemy
  • Rapid development: Less boilerplate than Java/C#
  • Industry adoption: Instagram, Spotify, Dropbox, Netflix
  • Data science integration: Same language for APIs and ML

The Request-Response Mental Model

# This is how you should think about APIs
def handle_request(request_data):
    # 1. Receive data from client
    user_id = request_data["user_id"]

    # 2. Process the request
    user = find_user(user_id)

    # 3. Return data to client
    return {"id": user.id, "name": user.name}
  • Every API endpoint follows this pattern
  • Input: Data from the client (usually JSON)
  • Process: Business logic, database queries
  • Output: Data back to client (usually JSON)

Preview: From Function to Flask Route

Plain Python

def get_user(user_id):
    user = find_user(user_id)
    return {
        "id": user.id,
        "name": user.name
    }

Flask (Week 3)

@app.route("/users/<id>")
def get_user(id):
    user = find_user(id)
    return {
        "id": user.id,
        "name": user.name
    }
  • Flask adds the @app.route decorator
  • The function body is almost identical
  • Today we focus on the Python part; Flask comes next week

Python's Role in the Backend Stack

LayerTechnologyPython's Role
HTTP ServerGunicorn, uWSGIRuns your Flask app
Web FrameworkFlask, DjangoRoutes requests to functions
Business LogicYour Python codeProcesses data
DatabasePostgreSQL, MySQLSQLAlchemy for queries
Task QueueRedis, CeleryBackground processing
  • Python connects all these layers together
  • This course covers each layer progressively

Part 3: Data Structures for APIs

Dictionaries and lists become JSON

What is JSON?

  • JSON = JavaScript Object Notation
  • A lightweight text format for exchanging data between systems
  • Human-readable AND machine-readable
  • Language-agnostic: Python, JavaScript, Java, Go all understand it
{
    "name": "Alice",
    "age": 25,
    "enrolled": true,
    "courses": ["CS61A", "INFO253B"]
}
  • This is how your browser talks to servers
  • This is how microservices communicate with each other

Why JSON Became the Standard

  • Before JSON: XML was dominant (verbose, complex)
  • JSON advantages:
    • Simpler syntax - less boilerplate
    • Smaller file sizes - faster transmission
    • Native to JavaScript - easy for web browsers
    • Maps naturally to programming data structures
  • Today: ~90% of REST APIs use JSON
Key insight: JSON looks almost identical to Python dictionaries. This is not a coincidence - both represent key-value data structures.

Dictionaries ARE JSON Objects

Python Dict

user = {
    "id": 1,
    "name": "Alice",
    "email": "alice@test.com",
    "is_active": True
}

JSON (identical!)

{
    "id": 1,
    "name": "Alice",
    "email": "alice@test.com",
    "is_active": true
}
  • Python dicts and JSON objects have nearly identical syntax
  • Key difference: JSON uses true/false/null, Python uses True/False/None
  • This is why Python is so natural for APIs

The json Module

import json

# Python dict to JSON string
user = {"id": 1, "name": "Alice"}
json_string = json.dumps(user)
# '{"id": 1, "name": "Alice"}'

# JSON string to Python dict
json_data = '{"id": 2, "name": "Bob"}'
user = json.loads(json_data)
# {"id": 2, "name": "Bob"}
  • json.dumps() - Python to JSON string (dump string)
  • json.loads() - JSON string to Python (load string)
  • Flask does this automatically, but you should understand it

Pretty Printing JSON

import json

user = {
    "id": 1,
    "name": "Alice",
    "roles": ["admin", "user"],
    "profile": {"bio": "Hello!", "avatar": None}
}

# Pretty print with indentation
print(json.dumps(user, indent=2))
{
  "id": 1,
  "name": "Alice",
  "roles": ["admin", "user"],
  "profile": {
    "bio": "Hello!",
    "avatar": null
  }
}
  • indent=2 adds readable formatting
  • Useful for debugging and logging

Nested Structures

# A typical API response structure
response = {
    "status": "success",
    "data": {
        "users": [
            {"id": 1, "name": "Alice"},
            {"id": 2, "name": "Bob"}
        ],
        "total": 2
    },
    "meta": {
        "page": 1,
        "per_page": 10
    }
}
  • Real API responses have nested dicts and lists
  • Access nested data: response["data"]["users"][0]["name"]
  • This structure is common in paginated APIs

Building Responses with List Comprehensions

# Database records (simulated)
users_db = [
    {"id": 1, "name": "Alice", "password": "secret123"},
    {"id": 2, "name": "Bob", "password": "hunter2"},
]

# Build API response - exclude sensitive data!
def get_users():
    return {
        "users": [
            {"id": u["id"], "name": u["name"]}  # No password!
            for u in users_db
        ]
    }
  • List comprehensions transform data efficiently
  • Never expose sensitive data like passwords
  • Shape the response to what clients need

Type Mapping: Python to JSON

Python TypeJSON TypeExample
dictobject{"key": "value"}
list, tuplearray[1, 2, 3]
strstring"hello"
int, floatnumber42, 3.14
True, Falsetrue, falsetrue
Nonenullnull
Warning: datetime, set, and custom objects don't serialize to JSON automatically!

Part 4: Functions as Request Handlers

Building the pattern for Flask routes

The Handler Pattern

def get_user(user_id):
    """Handle a GET request for a single user."""
    # Validate input
    if not isinstance(user_id, int) or user_id < 1:
        return {"error": "Invalid user ID"}, 400

    # Find the user
    user = find_user_by_id(user_id)
    if user is None:
        return {"error": "User not found"}, 404

    # Return success response
    return {"id": user["id"], "name": user["name"]}, 200
  • Validate input first
  • Return error responses with appropriate status codes
  • Return success response with data

CRUD Operations as Functions

# In-memory "database" for now
users = {}
next_id = 1

def create_user(data):
    global next_id
    user = {"id": next_id, "name": data["name"]}
    users[next_id] = user
    next_id += 1
    return user

def get_user(user_id):
    return users.get(user_id)

def update_user(user_id, data):
    if user_id in users:
        users[user_id].update(data)
        return users[user_id]
    return None

def delete_user(user_id):
    return users.pop(user_id, None)

CRUD Maps to HTTP Methods

OperationHTTP MethodFunctionURL Pattern
CreatePOSTcreate_user(data)/users
Read (one)GETget_user(id)/users/{id}
Read (all)GETlist_users()/users
UpdatePUT/PATCHupdate_user(id, data)/users/{id}
DeleteDELETEdelete_user(id)/users/{id}
  • Each function becomes a Flask route handler
  • HTTP methods indicate the operation type
  • RESTful APIs follow this convention

Function Parameters in APIs

def search_users(name=None, limit=10, offset=0):
    """Search users with optional filters."""
    results = list(users.values())

    # Filter by name if provided
    if name:
        results = [u for u in results if name.lower() in u["name"].lower()]

    # Apply pagination
    total = len(results)
    results = results[offset:offset + limit]

    return {
        "users": results,
        "total": total,
        "limit": limit,
        "offset": offset
    }
  • Default parameters handle optional query strings
  • Pagination prevents returning too much data
  • Filtering lets clients request specific data

Error Handling Pattern

def get_user(user_id):
    # Validate input type
    try:
        user_id = int(user_id)
    except ValueError:
        return {"error": "User ID must be an integer"}, 400

    # Check if user exists
    user = users.get(user_id)
    if user is None:
        return {"error": f"User {user_id} not found"}, 404

    return user, 200
  • 400: Bad Request - client sent invalid data
  • 404: Not Found - resource doesn't exist
  • 200: OK - successful response
  • Always return helpful error messages

Part 5: OOP Basics for Models

Classes as structured data containers

Why Classes for Backend Data?

  • Structure: Define what fields an object has
  • Validation: Ensure data is correct on creation
  • Methods: Add behavior to your data
  • Reusability: Create many instances from one template
Preview: SQLAlchemy models are classes that map to database tables.

A Simple User Class

class User:
    def __init__(self, id, name, email):
        self.id = id
        self.name = name
        self.email = email

    def to_dict(self):
        """Convert to dictionary for JSON serialization."""
        return {
            "id": self.id,
            "name": self.name,
            "email": self.email
        }

# Creating a user
user = User(1, "Alice", "alice@example.com")
print(user.to_dict())  # {"id": 1, "name": "Alice", "email": "alice@example.com"}
  • __init__ is the constructor - called when creating an instance
  • self refers to the instance being created
  • to_dict() converts the object for JSON serialization

Adding Validation

class User:
    def __init__(self, id, name, email):
        if not name or len(name) < 2:
            raise ValueError("Name must be at least 2 characters")
        if "@" not in email:
            raise ValueError("Invalid email address")

        self.id = id
        self.name = name
        self.email = email
# This works
user = User(1, "Alice", "alice@example.com")

# This raises an error
user = User(2, "A", "not-an-email")  # ValueError!
  • Validation in __init__ ensures data integrity
  • Invalid data fails fast with clear error messages

Class Methods for Business Logic

class User:
    def __init__(self, id, name, email, password_hash=None):
        self.id = id
        self.name = name
        self.email = email
        self.password_hash = password_hash

    def to_dict(self):
        # Note: Never include password_hash!
        return {"id": self.id, "name": self.name, "email": self.email}

    def to_public_dict(self):
        # Even more limited for public APIs
        return {"id": self.id, "name": self.name}

    def check_password(self, password):
        # Verify password (simplified example)
        return hash(password) == self.password_hash
  • Different methods for different serialization needs
  • Business logic lives in the model class

Using Classes with Our Handler Pattern

class User:
    # ... (as before)
    pass

# Our "database"
users = {}

def create_user(data):
    try:
        user = User(
            id=len(users) + 1,
            name=data["name"],
            email=data["email"]
        )
        users[user.id] = user
        return user.to_dict(), 201
    except ValueError as e:
        return {"error": str(e)}, 400
    except KeyError as e:
        return {"error": f"Missing required field: {e}"}, 400
  • Classes provide structure, functions provide API logic
  • Validation errors become 400 responses
  • 201 status code means "Created"

Preview: SQLAlchemy Models

# Week 7: This is what database models look like
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    def to_dict(self):
        return {"id": self.id, "name": self.name, "email": self.email}
  • Same to_dict() pattern you learned today
  • SQLAlchemy adds database persistence
  • The class structure is nearly identical

Live Demo

From Python Dict to JSON API Response

Demo: What We'll Build

  • Start with a simple Python dictionary
  • Convert it to JSON with pretty printing
  • Write a function that returns user data
  • Create a User class with to_dict()
  • See how it all connects to API responses
Watch along: I'll type everything live. You'll practice in the in-class exploration.

Summary

Key takeaways from today

What You Learned Today

  • Python for backends: Clean, productive, industry-standard
  • Dictionaries = JSON: Python data structures map directly to API responses
  • json module: dumps() and loads() for conversion
  • Functions as handlers: Input, process, output pattern
  • CRUD operations: Create, Read, Update, Delete
  • Classes for models: Structure with to_dict() for serialization

Looking Ahead

WeekTopicBuilding On
3REST APIs with FlaskFunctions become routes
4DockerContainerize your Flask app
6ValidationMarshmallow schemas
7DatabasesClasses become SQLAlchemy models
Lab 1 (Friday): Python practice exercises. Prep for next week: O'Reilly Chapter 3 - Your First REST API

In-Class Exploration

Student Records API (Python Only)

Project Overview

  • Build Python functions for a student records system
  • Implement CRUD operations: list, get, add, update, delete
  • Use dictionaries to store student data
  • Return JSON-serializable responses
  • No Flask yet - pure Python functions
Goal: These exact functions will become Flask routes next week!

Getting Started

  1. Click the GitHub Classroom link (in bCourses) - this creates a repo just for you
  2. Clone your new repo:
    git clone <your-repo-url>
    cd in-class-exploration-week-2-<your-username>
  3. Open and start coding:
    code .
    python test_records.py
  • Follow the TODOs in student_records.py
  • Run tests frequently to check your progress

Submitting (at 10:45 AM)

  1. Commit everything you have (even if incomplete):
    git add .
    git commit -m "In-class exploration submission"
  2. Push to your repository:
    git push
  3. Submit on bCourses: Add your GitHub repo URL to the assignment
    https://github.com/UCB-INFO-BACKEND-WEBARCH/in-class-exploration-week-2-yourusername
Grading: Pass/No Pass based on engagement. Just show you worked on it for 30+ minutes!

Want to keep working? Feel free to continue after submitting - just commit and push your additional changes later.

Questions?

 

Website: groups.ischool.berkeley.edu/i253/sp26

Email: kay@ischool.berkeley.edu