Lesson 1 of 0
In Progress

Unit 8 – Teachers Guide

Unit 8 · Teacher's Guide | Knowledge Pillars · Code Quest
📚 Teacher's Guide Code Quest · Coding Fundamentals

Unit 8 · APIs & Web Data

Your day-to-day companion for delivering Unit 8 — pacing, standards, lesson plans, misconceptions, differentiation, and a Python error appendix. Unit 8 is the unit where students' code connects to the real internet. They learn HTTP, the requests library, how to read API documentation, how to handle network failures gracefully, and how to be a polite citizen of the web through caching and rate-limit awareness. The capstone — Weather Dashboard — chains two real public APIs, wraps responses in classes from Unit 7, caches everything to disk, and produces a working app students would actually use.

🌐 5 Lessons + Capstone
⏱️ 9–11 class periods
🎓 Florida Course #9009200

👋 Welcome — How to Use This Guide

This Teacher's Guide is your day-to-day companion for delivering Unit 8 of the Code Quest curriculum. Unit 8 is the unit where students' code connects to the real internet. They learn HTTP, the requests library, status codes, error handling, query parameters, how to read API documentation, and how to be a polite citizen of the web through caching and rate-limit awareness. The capstone — Weather Dashboard — chains two real public APIs (Open-Meteo geocoding + forecast), wraps responses in classes from Unit 7, caches everything to disk, persists favorites, and produces a working app students would actually use. It picks up after Unit 7 and assumes students are fluent with classes, methods, dunders, inheritance, composition, file I/O, JSON, try/except, and multi-file architectures.

What this guide contains

  • A pacing guide showing how Unit 8 maps to roughly 9–11 class periods. All five lessons run 50 minutes; Lesson 5 is a 90–120 minute capstone split across 2–3 days. Unit 8 is the first unit where every student does something REAL — making live HTTP calls to public servers.
  • A standards & exam crosswalk. Unit 8 introduces 03.03 (binary file types) at light depth via API responses, deepens 06.02 (troubleshooting in the SDLC) through network error handling, and extends 07.07 (debugging) to cover network-specific failure modes.
  • A unit vocabulary glossary with all 22 key terms students will encounter — most are concrete networking and API terminology (HTTP, URL, request, response, status code, JSON, query parameter, header, rate limit, cache, TTL, endpoint).
  • Five detailed lesson plans using the same 7-section format from prior guides.
  • An appendix covering API-specific Python errors: ModuleNotFoundError on requests, ConnectionError, Timeout, HTTPError from raise_for_status, the silent bug of forgetting response.json(), and the rate-limit ban that follows from skipping caches.

How each lesson plan is structured

Same 7-section format used in prior units:

  • At-a-Glance — objective, time, materials, vocabulary, standards box
  • Lesson Flow — a 50-minute breakdown: warm-up, instruction, guided practice, lab, exit ticket
  • Watch For — the 3–5 most common student misconceptions and how to address each
  • Differentiation — early finishers and struggling students
  • Discussion Prompts — questions to spark whole-class conversation
  • Real-World Connections — answering "why does this matter?"
  • Lesson-Specific Vocabulary — terms introduced or reinforced

How to read this in conjunction with the student lessons

  • Before class: read this lesson plan (5–10 min) and skim the student HTML, paying special attention to the unit's marquee interactive widgets — the Request/Response Inspector in L1, the URL Builder in L2, the Defensive Request Simulator in L3, the Cache Inspector in L4
  • During class: follow the lesson flow; the student HTML is the textbook + lab
  • After class: grade Knowledge Checks and labs against this guide's expectations
💡 Unit 8 is the first "real internet" unit. Up till now students have built apps that run on their own machine with their own data. Unit 8 is when they realize their code can fetch live data from servers around the world. It's the unit students often describe as "where it got real." The Open-Meteo APIs used throughout the unit are free and don't require an API key — chosen specifically so every student can run live calls without an account or rate-limit anxiety. If your school's network blocks outbound HTTPS to geocoding-api.open-meteo.com or api.open-meteo.com, work with IT before Unit 8 starts; otherwise the entire unit can't run as designed.

🗺️ Unit 8 Overview

The big picture

Unit 8 is "APIs & Web Data." Lesson 1 introduces HTTP, the request/response cycle, and the requests library — students make their first live API call within the first 30 minutes. Lesson 2 covers query parameters and how to read API docs (a genuine professional skill). Lesson 3 covers status codes and the network-failure family of errors — the unit's first deep dive into "things go wrong." Lesson 4 introduces caching and rate-limit awareness — the difference between a script that works once and a script that scales. Lesson 5 culminates in Weather Dashboard — a four-file app with Location and WeatherReport classes, two API chains, caching, favorites persistence, and graceful degradation when the network fails.

What students learn

  • Explain what HTTP is and how the request/response cycle works
  • Install and import the requests library (pip install requests)
  • Make GET requests with requests.get(url) and inspect the Response object
  • Read the four most-used Response attributes: .status_code, .json(), .text, .headers
  • Pass query parameters via the params= argument (cleaner than string concatenation)
  • Read API documentation — endpoints, parameters, sample requests, response shapes
  • Recognize the five status code families (1xx info, 2xx success, 3xx redirect, 4xx client error, 5xx server error)
  • Use response.raise_for_status() to convert bad statuses into exceptions
  • Catch the four most-important requests exceptions: ConnectionError, Timeout, HTTPError, and the umbrella RequestException
  • Set timeout= on every request — never block forever
  • Cache API responses to local JSON files with timestamps
  • Implement TTL (time-to-live) — cache freshness checks before reuse
  • Recognize the three cache outcomes: hit (fresh), miss (no entry), stale (expired)
  • Send a User-Agent header to identify your app to servers
  • Respect rate limits and the unwritten rules of polite API consumption
  • Wrap raw API JSON in custom classes with friendly attributes and methods
  • Use @property for computed read-only attributes
  • Chain APIs — output of one becomes input of the next (geocode → forecast)
  • Build a multi-file app where each file has one concern (models / API1 / API2 / UI)
  • Implement stale-cache fallback — degrade gracefully when the network fails

Prerequisites

Units 1–7. Students must be fluent with: variables, control flow, all four data structures, functions, the SDLC, file I/O, JSON serialization (json.dump/json.load), try/except with multiple exception types, modules and the standard library, classes with __init__ and methods, dunder methods (especially __str__), and the multi-file architecture pattern from Unit 6 & 7. Unit 8 layers networking on top of these — the new ideas are HTTP, status codes, query parameters, caching strategy, and rate-limit awareness.

What comes next

Unit 9 introduces algorithm analysis — students will measure how fast their code actually runs, learn the four pillars of computational thinking, and compare algorithms (linear vs binary search, bubble vs selection sort) with real timing data. The Algorithm Playground capstone reuses Unit 7's class skills and Unit 8's multi-file architecture but pivots to performance measurement. Unit 8's HTTP and caching skills carry into Unit 10's database work, where the same "talk to a server" mental model applies to local SQLite.

Materials you'll need

  • Computers with web browser access AND outbound HTTPS to geocoding-api.open-meteo.com and api.open-meteo.com — verify with IT before starting
  • Knowledge Pillars CodeSkills Lab access (with the requests library available — should be by default)
  • Projector or large screen — Unit 8's interactive widgets (Request/Response Inspector, Cache Inspector, Defensive Request Simulator) work best in projection
  • Whiteboard with multiple colors — used heavily in Lesson 1 (request/response sequence diagrams), Lesson 3 (status code families), Lesson 4 (cache flow diagrams), and Lesson 5 (four-file architecture)
  • Optional: printed Open-Meteo API quick-reference card with the geocoding + forecast endpoints — students reference these constantly
  • Optional: printed status-code cheat sheet (200 OK, 301 Redirect, 401 Unauthorized, 404 Not Found, 429 Rate Limit, 500 Server Error)
🎯 Course alignment. Unit 8 lightly addresses Florida CTE standard 03.03 (binary file types) via the API response binary content type, deepens 06.02 (troubleshooting in the SDLC) through systematic network-failure handling, and extends 07.07 (debugging) to cover request-level errors. The unit applies 05.07 (implementing algorithms) heavily through the cache flow and API-chain orchestration. Exam Objective 1 (core programming concepts) and Objective 4 (write & debug programs) are both well-served. The capstone is the broadest single-unit performance task in the course so far. See the Standards Crosswalk below for full detail.

⏱️ Pacing Guide

Unit 8 is designed for 9–11 class periods. All five lessons run 50 minutes; Lesson 5 (Weather Dashboard) is a 90–120 minute capstone split across 2–3 days. Unit 8 is conceptually broad rather than deep — five distinct skills (HTTP, parameters, errors, caching, integration). The Lesson 5 capstone is the largest project of the course so far in terms of file count (4 files) and skill integration.

Recommended pacing — 10-day plan

DayLessonFocusHomework
Day 1L1: HTTP & First RequestWhat HTTP is. Installing requests. Anatomy of a URL. Response attributes. Request/Response Inspector. Walking JSON. Lab 8.1A.Read L2 Sections 1–3
Day 2L1 finish + L2 startKnowledge check on requests basics. Preview query params motivation.Read L2 Sections 4–6 (docs)
Day 3L2: Query Params & DocsQuery strings. The params= argument. Build a request live. Reading API docs. Doc conventions. Building complex requests. Lab 8.2A.Read L3 Sections 1–3 (status codes)
Day 4L3: Status Codes & ErrorsWhy defensive code matters. Five status families. Checking status_code. Network failures. Defensive Request Simulator. Design decisions. Lab 8.3A.Read L4 Sections 1–4 (caching)
Day 5L4: Caching (pt 1)Why cache. Three cache outcomes. Building a cache from scratch. Wrapping fetch with caching. Lab 8.4A.Read L4 Sections 5–7
Day 6L4: Caching (pt 2)Cache Inspector widget. Choosing TTL. Rate limits and User-Agent headers. Knowledge check.Plan Weather Dashboard architecture
Day 7L5: Weather Dashboard (pt 1)API chaining intro. Wrapping API data in classes. Plan stage. Code models.py first.Continue building models
Day 8L5: Capstone (pt 2)Code geocoding.py + weather.py. Test each individually. Then code main.py and wire it all up.Continue building, prepare to test
Day 9L5: Capstone (pt 3)Test (14 cases) + Fix + Maintain. Especially: stale-cache fallback test. Showcase. Submit.—

Compressed 9-day pacing

If you need to compress: combine Day 5 and Day 6 (cover all of Lesson 4 in one period). This works for stronger groups — but caching is the hardest single skill in Unit 8 and rushing it usually shows up as Day 9 capstone bugs. The Cache Inspector widget needs time. Recommended only for classes that breezed through Lessons 1–3.

Extended 11-day pacing

If your class needs more time: split Lesson 5 across four days. Day 7 = models.py only (pure Python, no network). Day 8 = both API modules + test each in isolation. Day 9 = main.py + integration. Day 10 = full test/fix/maintain/showcase. The four-file architecture is a real professional skill; giving each file its own day pays off in code quality.

⏱️ Where the time usually goes. Lesson 1 (HTTP) often runs FAST — students are energized by the live API call. Lesson 2 (query params + docs) takes longer than expected because reading docs is a genuinely new skill. Lesson 3 (errors) is the unit's first hard lesson — branched try/except with three or four exception types is dense. Lesson 4 (caching) is the unit's HARDEST lesson — TTL logic + JSON I/O + dict-of-dicts data shape is a lot. Lesson 5 always benefits from the full 2–3 day window. Stale-cache fallback testing alone usually consumes 30 minutes.

Suggested grading weights for Unit 8

ComponentWeightNotes
Lesson Knowledge Checks (×4)15%3 questions each, completed in-platform
Lab Exercises (×8)30%CodeSkills Lab — every lab makes live API calls
Weather Dashboard Final Project45%Capstone — 100-pt rubric in Lesson 5 plan
Class participation10%Whiteboard work, Cache Inspector demos, peer code reviews

Note: Weather Dashboard is the fifth 45%-weighted capstone in a row. It integrates EVERY Unit 8 skill plus Unit 7's class skills plus Unit 6's persistence patterns plus Unit 4's modular architecture. The four-file structure (models / geocoding / weather / main) is the largest student deliverable of the year. A student who does well on Weather Dashboard has demonstrated full-stack thinking — they've designed a layered app that talks to two real services, caches strategically, and degrades gracefully under failure. That's a real software engineering skill set.

🎓 Standards & Exam Crosswalk

This crosswalk shows exactly which Florida CTE standards and Coding Fundamentals exam objectives each lesson addresses. Use this for admin reviews, IEP documentation, or to prove alignment when adopting the curriculum. Unit 8's primary "new" coverage is in 06.02 (troubleshooting in the SDLC — network errors) and 07.07 (debugging — request-level failures). The capstone (Weather Dashboard) covers the broadest set of standards in any unit so far — it integrates Units 1–7 plus all five new Unit 8 skills.

Florida CTE Standards — Course #9009200

Standards from the Florida Department of Education Coding Fundamentals curriculum framework that Unit 8 addresses:

StandardDescriptionLessons
01.01Use specialized computer coding software to solve problems.All (CodeSkills Lab)
01.02Demonstrate proficiency using specialized computer software (e.g., Python).All
02.02Explain the components of programming languages.L1, L2, L4, L5 (libraries, modules, classes)
03.01Identify and describe text-based file types (txt, csv, json).L4, L5 (cache JSON files)
03.02Demonstrate basic file operations (open, read, write, close).L4, L5 (cache I/O)
03.03Identify and describe binary file types.L1 (response.content), L3
04.03Demonstrate sorting and filtering of data sets.L5 (favorites filtering)
05.06Recognize that more than one algorithm can solve a given problem.L3 (error strategies), L4 (cache strategies)
05.07Create a program that implements an algorithm to achieve a given goal.L5 (Weather Dashboard)
06.02Apply troubleshooting techniques within the SDLC.L3, L5 — primary unit standard (network failures)
06.03Apply iterative debugging within the SDLC.L3, L5 (capstone testing)
07.01Describe the structure of a simple program; explain why sequencing is important.L5 (four-file architecture)
07.05Explain the types and use of variables in programming.L1 (Response objects), L5 (custom classes)
07.07Troubleshoot and debug errors in code.L3, L5 — full depth on network errors

Coding Fundamentals Exam Objectives

Exam objectives that Unit 8 prepares students for. Objective 4 (write & debug simple programs) is the unit's flagship; the capstone is its culminating performance task. Objective 7 (debugging) gets full depth on network-specific failures.

ObjTitleCoverage in Unit 8Lessons
1Core programming conceptsContinued — HTTP/APIs are core internet conceptsL1, L2, L5
3Algorithm understanding & designCache strategies, API chainingL4, L5
4Write and debug simple programsFull coverage — primary objective. Live API code throughout.All
5Data manipulationJSON parsing, response transformationL1, L4, L5
7Debugging techniquesFull coverage — network failures, status codes, branched try/exceptL3, L5

Lesson-by-lesson alignment

LessonFlorida StandardsExam Objectives
L1: HTTP, APIs & Your First Request01.01, 01.02, 02.02, 03.03, 07.051, 4 (primary), 5
L2: Query Parameters & Reading Docs01.01, 01.02, 02.02, 05.061, 4 (primary)
L3: Status Codes & Error Handling01.01, 01.02, 03.03, 05.06, 06.02, 06.03, 07.074, 7 (primary)
L4: Caching & Being a Good Citizen01.01, 01.02, 02.02, 03.01, 03.02, 05.06, 05.071, 3, 4, 5 (primary)
L5: Weather Dashboard Capstone01.01, 01.02, 02.02, 03.01, 03.02, 04.03, 05.06, 05.07, 06.02, 06.03, 07.01, 07.05, 07.071, 3, 4 (primary), 5, 7
📋 What's not in Unit 8. These standards are NOT addressed in Unit 8 and appear in later units: 04.01 (data types — primitives) — already covered in Unit 1 · 04.02 (data types — collections) — already covered in Unit 5 · 07.02 (algorithms, deeper) — already covered in Unit 3, expanded in Unit 9 · 07.03 (loops, deeper) — already covered in Unit 2 · 07.04 (selection structures, deeper) — already covered in Units 1–2 · 07.06 (pseudocode/flowcharts) — already covered in Unit 3 · 07.08 (databases) — Unit 10. Note: students APPLY 07.02–07.05 throughout Unit 8 (the capstone has algorithms, loops, selection, and data types throughout) even though those standards are not its focus.

📖 Unit 8 Vocabulary Glossary

All 22 key terms students will encounter in Unit 8. Most of Unit 8's vocabulary is concrete networking and API terminology students will use repeatedly. The "Intro" column shows the lesson where each term first appears. Each lesson plan also includes its own focused vocabulary list.

TermDefinitionExampleIntro
HTTPHyperText Transfer Protocol. The set of rules computers use to talk to web servers. Request goes out; response comes back.The "talk to a server" protocolL1
APIApplication Programming Interface. A server's published "menu" of things programs can ask it to do. Most modern APIs use HTTP + JSON.Open-Meteo's geocoding APIL1
requests libraryThe Python library for making HTTP calls. Not in the standard library; install with pip install requests.import requestsL1
GET requestThe HTTP verb for "fetch data without changing anything." The most common request type.requests.get(url)L1
URLThe address of a resource on the web. Has a scheme (https), host (api.example.com), path (/v1/cities), and optional query string.https://api.example.com/cities?name=TokyoL1
EndpointA specific URL path on an API that does one thing. Often called by name in docs./v1/searchL1
Response ObjectWhat requests.get returns. Has .status_code, .json(), .text, .headers.r.status_code → 200L1
Query ParameterA key=value pair in the URL query string. Used to customize what the API returns.?name=Tokyo&count=1L2
params= argumentThe clean way to pass query parameters in requests.get. Pass a dict; library URL-encodes for you.params={"name": "Tokyo"}L2
API DocumentationThe official spec for an API: endpoints, parameters, response shapes, examples. The professional skill of reading docs.open-meteo.com/en/docsL2
Status CodeA 3-digit number in every response indicating what happened. 200=OK, 404=Not Found, 500=Server Error.r.status_code == 200L3
Five Status Families1xx info, 2xx success, 3xx redirect, 4xx client error (your fault), 5xx server error (their fault).200, 301, 404, 500L3
raise_for_status()Method on Response that raises HTTPError on 4xx/5xx. Lets try/except handle bad statuses cleanly.r.raise_for_status()L3
ConnectionErrorRaised when the request couldn't reach the server (DNS failure, no internet).except ConnectionError:L3
TimeoutRaised when the server didn't respond within the time budget. Always set timeout=.timeout=10L3
RequestExceptionThe umbrella exception for ALL requests-library errors. Catches ConnectionError, Timeout, HTTPError, others.except requests.RequestException:L3
CacheA local copy of a previous response. Lets repeated lookups skip the network.geocode_cache.jsonL4
TTL (Time To Live)How long cached data stays valid. After TTL, treat as stale and re-fetch.30 days for geocodingL4
Cache Hit / Miss / StaleThree outcomes when checking the cache. Hit = fresh entry exists. Miss = no entry. Stale = entry exists but expired.[hit] for TokyoL4
User-AgentA header your code sends identifying your app. Polite practice; some APIs require it."WeatherDashboard/1.0"L4
Rate LimitMaximum requests per time window an API allows. Exceed = HTTP 429. Caching avoids this.1000 requests/hourL4
@propertyDecorator that turns a method into a read-only attribute. r.temperature not r.temperature().@property\ndef temperature(self):L5
📖 Teaching tip. 22 terms is heavy — drill the five most exam-critical: HTTP (the protocol concept), status code (the five families), raise_for_status() (the cleanest pattern), cache TTL (the freshness concept), and RequestException (the umbrella catch). Most students learn the others through use in labs. The capstone integrates ALL of them — that's by design.

🌐 Lesson 1 · HTTP, APIs & Your First Request

Lesson 1 · HTTP, APIs & Your First Request

Lesson Plan

At-a-Glance

Objective
Students will explain what HTTP is, install and import the requests library, make their first GET request to a real public API, inspect the four key Response attributes (.status_code, .json(), .text, .headers), and walk a JSON response to extract specific values.
Time
50 minutes
Materials
Computer with internet, CodeSkills Lab, projector, whiteboard for request/response sequence diagrams
Vocabulary
HTTP, API, requests library, GET request, URL, Endpoint, Response Object
FL Standards
01.01, 01.02, 02.02, 03.03, 07.05
Exam Obj.
Objectives 1, 4 (primary), 5

Lesson Flow (50 minutes)

🔥 Warm-Up (5 min)

  • Project a phone weather app on screen. Ask: "Where is the temperature actually coming from?" Take answers — students will say "the internet" or "a server."
  • Reveal: today is the day Python's programs stop being islands. Their code is going to talk to actual servers around the world and pull live data back. The first call: getting Tokyo's coordinates from a real geocoding API in about 5 lines of code.

🎤 Direct Instruction (18 min)

  • Open Lesson 8.1 on the projector. Walk through Section 1 ("What Actually Happens When an App Loads Data"). On the whiteboard, draw the request/response cycle:
    1. Your code builds a URL
    2. Sends a REQUEST over the internet to a server
    3. Server processes, builds a RESPONSE
    4. Response arrives back at your code
    5. Your code reads the response (typically JSON)
  • The protocol that defines all this: HTTP. The library Python uses: requests.
  • Walk through Section 2 ("Installing requests"). Live-run: pip install requests. Most CodeSkills environments already have it. Verify with import requests; print(requests.__version__).
  • Walk through Section 3 ("Anatomy of a Request URL"). Project the URL https://geocoding-api.open-meteo.com/v1/search?name=Tokyo. Walk it part by part:
    • https:// = scheme (encrypted)
    • geocoding-api.open-meteo.com = host (the server's name)
    • /v1/search = path (which endpoint on that server)
    • ?name=Tokyo = query string (parameters — Lesson 2)
  • Walk through Section 4 ("What's Inside a Response Object?"). Live-code:
    import requests
     
    r = requests.get("https://geocoding-api.open-meteo.com/v1/search?name=Tokyo")
    print(r.status_code)   # 200 (success!)
    print(r.headers)       # dict of metadata
    data = r.json()        # parses response as Python data
    print(data["results"][0]["latitude"])   # 35.6895
  • Drill the four key Response attributes:
    • .status_code — int, like 200 or 404
    • .json() — METHOD (parens!), parses body as JSON
    • .text — raw response body as string
    • .headers — dict of metadata about the response
  • Critical: .json() has parens; .status_code doesn't. Drill this.
  • Walk through Section 5 ("Try the Request/Response Inspector"). Use the widget. Project. Try 3–4 different URLs; show the response.
  • Walk through Section 6 ("Walking the JSON — Just Like Unit 6"). Same nested-data navigation from Lesson 5.3. data["results"][0]["latitude"] reads left to right. The skill transfers directly.

🤝 Guided Practice (8 min)

  • On the projector, walk through the Open-Meteo geocoding response together. Project the JSON. Have students predict each access path:
    • "Country?" → data["results"][0]["country"]
    • "How many results?" → len(data["results"])
    • "All city names?" → [r["name"] for r in data["results"]] or a loop
  • Drill: every API response is just a nested dict/list. Same skill as Unit 5 + 6.

💻 Independent Lab (15 min)

  • Lab 8.1A — "Make Your First Live Call": students write a complete program that fetches geocoding data for "Paris", prints the latitude and longitude. Required: import requests, GET request, .json(), walk to the lat/lon.
  • Lab 8.1B — "Multi-City Lookup": students fetch geocoding for 3 cities of their choice using a loop. Print each city's name and coordinates. Demonstrates the request/response cycle is reusable.
  • Circulate. Common bugs: forgetting the .json() parens (gets the method object instead of parsed data); typing requests.GET instead of requests.get (Python is case-sensitive); not printing the URL when something fails (always print URL when debugging — copy/paste into browser).

🚪 Exit Ticket (5 min)

  • On paper: "Given r = requests.get(some_url), what's the difference between r.status_code and r.json()? When would you use each?"
  • Looking for: .status_code is an int (no parens) telling you if the request succeeded (200) or not. .json() is a method (with parens) that parses the response body into Python data. Use status_code first to check success; use .json() to get the actual data once you know success.

⚠️ Watch For — Common Misconceptions

1. "Forget the parens on .json()."
What you'll see: data = r.json. data is now the method itself, not the parsed JSON. data["results"] crashes with a confusing TypeError.
How to address: .json() is a METHOD (it does work). .status_code is an ATTRIBUTE (it just sits there). Drill: parens for json, no parens for status_code. The double standard is confusing but you have to know it.
2. "requests is in the standard library."
What you'll see: import requests on a clean Python install crashes with ModuleNotFoundError.
How to address: requests is the most popular Python library that ISN'T in the standard library. Install with pip install requests. CodeSkills Lab usually has it pre-installed; verify in Day 1 before depending on it.
3. "Status code 200 means I have data."
What you'll see: Student does r = requests.get(url), sees status 200, then r.json()["results"][0] crashes because the API returned {"results": []} (empty list — valid JSON, valid 200, no actual results).
How to address: 200 means "request succeeded." It does NOT mean "there's data shaped the way you want." Always check the response shape before drilling in. if "results" in data and data["results"]: before data["results"][0].
4. "URLs need to be hand-built with f-strings."
What you'll see: url = f"https://api.com/search?name={city}". Works for "Tokyo" but breaks on "São Paulo" (the special character needs URL encoding).
How to address: Preview Lesson 2: pass query parameters via params=: requests.get(base_url, params={"name": city}). The library URL-encodes for you. Hand-built URLs work for Tokyo but break in production. (For today, hand-built is OK; tomorrow we fix it.)
5. "If a request 'just hangs', the code is broken."
What you'll see: A request hangs forever. Student forces-quits, thinks Python crashed.
How to address: The default behavior of requests.get is to wait FOREVER. If the server is slow or unreachable, your code blocks indefinitely. The fix: ALWAYS pass timeout=10. Preview Lesson 3 — this is why timeouts exist.

🎯 Differentiation

For early finishers
Challenge: try the GitHub API https://api.github.com/users/torvalds. Print the user's name, public repo count, and bio. Stretch: research response.headers["Content-Type"] and explain why it matters. Bonus: try requests.get with a wrong URL and inspect what status code you get back.
For struggling students
Stay on Lab 8.1A. Skip the multi-city lab. The win: a working program that fetches Paris's coordinates from the live API and prints them. The loop pattern can be revisited.

💬 Discussion Prompts

  • Why is HTTP called a "protocol"? (A protocol is a set of rules for communication. HTTP defines: how messages are structured (request line + headers + body), what verbs exist (GET, POST, PUT, DELETE), how status codes work. Both client and server must agree on the protocol or nothing works.)
  • What's the relationship between APIs and websites? (A website is for humans — HTML, CSS, images. An API is for programs — usually JSON. Many websites have an API behind them; the website is one consumer of that API. Apps on your phone are other consumers.)
  • If you wanted to build a weather app from scratch, why use someone else's API instead of having your own weather sensor? (Cost. Coverage. Reliability. The Open-Meteo team runs servers, processes radar data, maintains forecast models. You couldn't replicate that. Modern apps are MOSTLY plumbing on top of other people's APIs.)

🌍 Real-World Connections

  • Every app on your phone. Instagram, TikTok, Discord, Uber, weather, banking — all backed by APIs. The app is mostly UI; the data lives on servers.
  • Public APIs. Spotify, GitHub, Reddit, NASA, Google Maps, Twitter (X), Stripe — all have public APIs. Master HTTP and you've unlocked the developer ecosystem.
  • Internal APIs. Inside companies, every microservice talks to others via HTTP APIs. Netflix has thousands of internal services calling each other.
  • Government open data. US Census, weather, transit. Many local governments expose city data — bus arrival times, 311 complaints, building permits. Open APIs democratize public data.

📖 Lesson-Specific Vocabulary

HTTP — HyperText Transfer Protocol. The "talk to a server" rules.
API — Application Programming Interface. A server's published menu.
requests library — Python's HTTP library. Install with pip.
GET request — The "fetch" verb. Most common.
URL — Address of a resource. scheme + host + path + query.
Endpoint — A specific path on an API.
Response Object — What requests.get returns. .status_code, .json(), .text, .headers.
↑ Back to top

🧩 Lesson 2 · Query Parameters & Reading Docs

Lesson 2 · Query Parameters & Reading Docs

Lesson Plan

At-a-Glance

Objective
Students will pass query parameters using the params= argument (cleaner than f-strings), read API documentation to find endpoints/parameters/response shapes, decode common doc conventions (required vs optional, types, defaults), and build complex requests with multiple parameters.
Time
50 minutes
Materials
Computer, CodeSkills Lab, projector, optional printed Open-Meteo API quick-reference card
Vocabulary
Query Parameter, params= argument, API Documentation
FL Standards
01.01, 01.02, 02.02, 05.06
Exam Obj.
Objectives 1, 4 (primary)

Lesson Flow (50 minutes)

🔥 Warm-Up (5 min)

  • Project a Lesson 1 hand-built URL: f"https://api.com/search?name={city}". Now show what happens when city = "São Paulo" (or any city with a space). The URL breaks; the API returns 400 or empty.
  • Reveal: today's tool URL-encodes for you, makes URLs readable, and lets you pass multiple parameters cleanly. Plus: today we learn to read API documentation — a real professional skill.

🎤 Direct Instruction (18 min)

  • Open Lesson 8.2 on the projector. Walk through Section 1 ("The Query String — Inputs Inside the URL"). The query string is everything after ?. Format: key=value&key=value. Spaces become + or %20. Special characters get encoded.
  • Walk through Section 2 ("The params= Argument"). Live-code:
    import requests
     
    # OLD WAY (Lesson 1) — fragile:
    url = f"https://geocoding-api.open-meteo.com/v1/search?name={city}&count=5"
     
    # NEW WAY — clean:
    r = requests.get(
        "https://geocoding-api.open-meteo.com/v1/search",
        params={"name": city, "count": 5}
    )
  • The library URL-encodes the dict for you. Spaces, accents, quotes all handled. Multiple parameters — just add keys. Clean, readable, robust.
  • Walk through Section 3 ("Build a Request — Live"). Use the URL Builder widget. Project. Build a request together with 4 parameters. Show the URL as it gets constructed.
  • Walk through Section 4 ("How to Read API Documentation"). Project the Open-Meteo docs page. Walk through:
    • The endpoint URL
    • The list of parameters (required vs optional)
    • Each parameter's TYPE and DEFAULT
    • The response shape (sample JSON)
  • Reading docs IS the job. Pros do this constantly. Drill the habit.
  • Walk through Section 5 ("Decoding Doc Conventions"). Common patterns:
    • name (string, required) — must provide
    • count (integer, default: 10) — optional
    • language (string, default: en) — optional with default
    • Brackets like [item, item] mean comma-separated list
  • Walk through Section 6 ("Building Up Complex Requests"). The Open-Meteo forecast endpoint takes 5+ parameters. Live-code:
    params = {
        "latitude": 35.6895,
        "longitude": 139.6917,
        "current": "temperature_2m,wind_speed_10m,relative_humidity_2m",
        "temperature_unit": "celsius",
        "timezone": "auto"
    }
    r = requests.get("https://api.open-meteo.com/v1/forecast", params=params)

🤝 Guided Practice (8 min)

  • Project the Open-Meteo geocoding docs. As a class, identify:
    • The endpoint URL (/v1/search)
    • Required parameters (just name)
    • Useful optional parameters (count, language, format)
    • The response shape ({"results": [{"name": ..., "latitude": ..., ...}]})
  • Then class predicts the URL for "find 3 results for São Paulo, in Spanish": params={"name": "São Paulo", "count": 3, "language": "es"}.

💻 Independent Lab (14 min)

  • Lab 8.2A — "Use the params Pattern": students rewrite Lesson 1's hand-built URL using params=. Test with "São Paulo" and "New York" to verify special characters now work.
  • Lab 8.2B — "Read & Use Docs": students get a real API doc snippet (Open-Meteo forecast docs). Tasks: identify which parameters are required, name 3 useful optional params, write a Python call passing 4 parameters. Demonstrates reading-docs skill.
  • Circulate. Common bugs: passing values with wrong type ("count": "5" as string when API wants integer — works for some APIs, breaks for others); forgetting that params is a DICT (passing a list); not noticing the difference between path parameters (in the URL itself) and query parameters (in params=).

🚪 Exit Ticket (5 min)

  • On paper: "Why is params={"name": "São Paulo"} better than f-string URL building?"
  • Looking for: it URL-encodes special characters automatically. Multiple parameters are clean. Easier to read. Easier to add or remove parameters. The library handles encoding correctly even for edge cases. Students should mention at least two of these.

⚠️ Watch For — Common Misconceptions

1. "Mix params with hand-built URL."
What you'll see: url = f"https://api.com/search?name={city}" AND params={"count": 5}. The library appends params to a URL that ALREADY has a query string — usually works, but it's confusing.
How to address: Pick ONE pattern. The professional choice: pass the BASE url (no ?) and put ALL parameters in params=. Cleaner. Easier to debug. Drill consistency.
2. "Required vs optional confusion."
What you'll see: Student doesn't pass a required parameter. API returns 400 with a helpful error message. Student doesn't read the message and is confused.
How to address: ALWAYS print r.text on a failed request. Most APIs return JSON describing what was missing. Reading docs is the long-term fix; reading error messages is the short-term fix.
3. "Skip reading the docs."
What you'll see: Student tries to guess parameter names. They guess "city"; the actual parameter is "name". API returns nothing useful.
How to address: Drill the habit: open the docs FIRST. Find the endpoint. Identify the parameters. Then write code. Pros spend more time reading docs than writing code.
4. "Path parameters vs query parameters."
What you'll see: Student tries to use params= for what's actually a PATH parameter (e.g., /users/{user_id}). The library puts it in the query string instead — wrong.
How to address: Path parameters are PART OF THE URL: https://api.com/users/12345. Build with f-strings or .format. Query parameters come AFTER ?: use params=. The Open-Meteo API uses query parameters for everything; some APIs mix.
5. "Treat params as a string."
What you'll see: params="name=Tokyo&count=5" — passing a string instead of a dict. requests accepts this, but you've defeated the URL-encoding benefit.
How to address: Always pass a DICT. params={"name": "Tokyo", "count": 5}. The dict is what gets URL-encoded. The string form just becomes part of the URL as-is.

🎯 Differentiation

For early finishers
Challenge: explore the Open-Meteo Air Quality API (different endpoint, similar params). Make one call to fetch your city's PM2.5 reading. Stretch: research what HTTP DELETE and POST verbs do. Try a public API that supports POST (httpbin.org/post is good for testing).
For struggling students
Stay on Lab 8.2A. Get the params= pattern working with one parameter. The win: replacing one f-string URL with the params dict and seeing the same result. Reading docs deeply can be revisited.

💬 Discussion Prompts

  • Why does the params= pattern exist if you can build URLs with f-strings? (Safety. Special characters, accents, ampersands, equals signs in values would BREAK a hand-built URL. The library URL-encodes everything correctly. It also reads cleaner — multiple parameters as dict keys vs concatenated string is way easier to maintain.)
  • Why don't APIs just have ONE giant parameter named "all_options" with a JSON value? (Some APIs do! POST endpoints with JSON bodies do this. But query strings have a long history (web URLs are bookmarkable, cacheable, loggable). For simple GETs, query parameters are the standard. POSTs handle complex inputs. Different shapes for different jobs.)
  • Are docs always reliable? (No — docs lag behind code, sometimes by years. Real pros also TEST what the API actually returns, especially for edge cases. Trust but verify. print(r.text) when something doesn't match docs.)

🌍 Real-World Connections

  • Search engines. Every Google search builds a URL with ?q=your+query. The same pattern as API query parameters.
  • YouTube videos. Notice the URL youtube.com/watch?v=ABCdef. The v parameter is a query parameter — same pattern as APIs.
  • Documentation as a job. Technical writers at companies like Stripe, Twilio, Plaid spend FULL TIME making API docs excellent. The quality of an API's docs is often a hiring signal — bad docs = avoid the company.
  • Postman / Insomnia / curl. Tools every backend developer uses to "ping" APIs. They're query-parameter builders with extra features. After Lesson 2, students could use them.

📖 Lesson-Specific Vocabulary

Query Parameter — Key=value pair in the URL after ?.
params= argument — Pass a dict to requests.get; library URL-encodes for you.
API Documentation — The official spec for an API. The skill of reading docs is professional-grade.
↑ Back to top

🚦 Lesson 3 · Status Codes & Error Handling

Lesson 3 · Status Codes & Error Handling

Lesson Plan

At-a-Glance

Objective
Students will recognize the five status code families (1xx info, 2xx success, 3xx redirect, 4xx client error, 5xx server error), use response.raise_for_status() for clean status checking, catch the four most-important requests exceptions (ConnectionError, Timeout, HTTPError, RequestException), and always pass timeout= to prevent infinite blocking.
Time
50 minutes
Materials
Computer, CodeSkills Lab, projector, optional printed status-code cheat sheet
Vocabulary
Status Code, Five Status Families, raise_for_status(), ConnectionError, Timeout, RequestException
FL Standards
01.01, 01.02, 03.03, 05.06, 06.02, 06.03, 07.07
Exam Obj.
Objectives 4, 7 (primary)
⚠️ The unit's first hard lesson. Branched try/except with three or four exception types is dense — and students need to actually SEE the failures (DNS error, timeout, 404) to internalize the pattern. The Defensive Request Simulator widget is built specifically for this. Use it. Disconnecting from the network during a live demo (briefly!) is high-impact teaching — students see the actual exception fire.

Lesson Flow (50 minutes)

🔥 Warm-Up (5 min)

  • Project a working Lesson 1 program. Now run it with the WiFi off (or paste a wrong URL like https://nope.invalid). Show the crash. Big traceback. Ugly.
  • Reveal: real apps don't crash. They handle this gracefully. Today is about the FOUR things that can go wrong with an HTTP request — and how to handle each.

🎤 Direct Instruction (20 min)

  • Open Lesson 8.3 on the projector. Walk through Section 1 ("Why Defensive Code Matters"). Mars rovers. Banking software. Even your phone — every API call is wrapped in error handling. The number of things that can go wrong is: many.
  • Walk through Section 2 ("The Five Status Code Families"). On the whiteboard, draw the five rows:
    • 1xx — Information (rarely seen)
    • 2xx — SUCCESS. 200 OK. 201 Created. 204 No Content.
    • 3xx — REDIRECT. 301 Moved Permanently. requests follows by default.
    • 4xx — CLIENT ERROR. YOUR fault. 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Rate Limit.
    • 5xx — SERVER ERROR. THEIR fault. 500 Internal Error, 503 Service Unavailable.
  • Mnemonic: 4xx = "you broke it." 5xx = "they broke it." Keep this distinction.
  • Walk through Section 3 ("Checking the Status Code in Code"). Two patterns:
    • Manual: if r.status_code == 200: ... else: .... Verbose.
    • raise_for_status(): Raises HTTPError on 4xx/5xx; does nothing on 2xx/3xx. Pair with try/except. The professional pattern.
  • Walk through Section 4 ("When the Request Itself Fails"). The request might never even get a response. Three sources:
    • ConnectionError — DNS failure, no internet, server unreachable
    • Timeout — server too slow. Always set timeout=10
    • HTTPError — fired by raise_for_status on 4xx/5xx
  • The umbrella: RequestException. Catches all three. Drill: except requests.RequestException as e: handles every requests-library error.
  • Walk through Section 5 ("Defensive Request Simulator"). Use the widget. Project. Click through scenarios: success, 404, 500, network down, slow response. Watch the code react.
  • Walk through Section 6 ("Design Decisions"). Two patterns to know:
    • Branched: except (ConnectionError, Timeout) as e: # network → use cache; except HTTPError as e: # server response → re-raise or log
    • Catch-all: except RequestException as e: when you don't need to differentiate. Simpler; less granular.
  • Drill: ALWAYS pass timeout=10 in production code. Without it, your code can block FOREVER.

🤝 Guided Practice (8 min)

  • Live-code a "robust fetch" function together:
    def safe_fetch(url, params=None):
        try:
            r = requests.get(url, params=params, timeout=10)
            r.raise_for_status()
            return r.json()
        except requests.Timeout:
            print("⚠️  Server too slow")
            return None
        except requests.ConnectionError:
            print("⚠️  No connection")
            return None
        except requests.HTTPError as e:
            print(f"⚠️  HTTP {e.response.status_code}: {url}")
            return None
  • This pattern goes straight into the capstone. Students copy it.

💻 Independent Lab (12 min)

  • Lab 8.3A — "Robust Fetcher": students wrap Lesson 1's geocoding call in try/except. Required: raise_for_status, timeout=10, branched except for at least two specific exceptions. Test with: a working URL (success), a typo URL (ConnectionError), a fake city name (200 with empty results — handle separately).
  • Lab 8.3B — "Status Code Inspector": students write a function describe_status(code) that takes a status code and returns one of "success", "redirect", "client error", "server error", "unknown". Use code // 100 to find the family.
  • Circulate. Common bugs: bare except: (carryover from Unit 6 — drill it again); forgetting timeout= (request hangs); catching ConnectionError but not Timeout (most network failures are timeouts, not connection errors); not handling the 200-with-empty-data case (200 doesn't mean "data shaped how you want").

🚪 Exit Ticket (5 min)

  • On paper: "What does response.raise_for_status() do? When does it raise an exception?"
  • Looking for: it checks the response's status code. If it's 4xx or 5xx, it raises an HTTPError. If it's 2xx or 3xx, it does nothing. Use it inside a try block to convert bad-status responses into exceptions you can catch.

⚠️ Watch For — Common Misconceptions

1. "Forget timeout=."
What you'll see: Slow API. Code blocks for 60+ seconds. Student thinks code is broken.
How to address: Default is FOREVER. Always pass timeout=10. Drill it. Mark deductions on rubric for missing timeouts.
2. "Catch HTTPError before raising it."
What you'll see: except HTTPError with no raise_for_status(). The except block never fires because no HTTPError gets raised.
How to address: Status checks are NOT automatic. Without raise_for_status(), a 404 looks like a successful response — your code happily reads JSON of an error message. Add raise_for_status() AFTER the GET, BEFORE the json call.
3. "Bare except: on requests calls."
What you'll see: Carryover from Unit 6. except: with no exception type. Catches ALL exceptions including KeyboardInterrupt.
How to address: NEVER bare except. The umbrella except RequestException: catches all requests errors and is the right scope. Drill: name the exception. Mark deductions.
4. "Status 200 with empty results is treated as failure."
What you'll see: Searching for "Asdfgh" → 200 OK → empty results array. Student treats as exception. But it's not — it's valid "no results found."
How to address: Differentiate: HTTP error (4xx/5xx) = raise. Empty results in valid response = check shape. if "results" not in data or not data["results"]: handles "API returned nothing useful." Different from network error.
5. "Catch in the wrong order."
What you'll see: except RequestException BEFORE except Timeout. The umbrella catches first; specific never fires.
How to address: Order specific to general. except Timeout: first. except ConnectionError: next. except RequestException: as catch-all last. Same rule as Unit 6.

🎯 Differentiation

For early finishers
Challenge: implement an exponential-backoff retry. On Timeout or ConnectionError, retry with a delay of 1s, then 2s, then 4s. Give up after 3 tries. Stretch: research response.raise_for_status() source code on GitHub and explain what edge cases it handles.
For struggling students
Stay on Lab 8.3A with ONE exception type — except RequestException as e:. The win: their fetch function doesn't crash on bad URLs. Branched exception handling can be revisited.

💬 Discussion Prompts

  • If status codes are a "standard," why do APIs use them differently? (They mostly don't — 200/404/500 are universal. But some APIs return 200 with an "error" field in the JSON body even when something went wrong; others use 422 vs 400 differently. The standard is loose enough that good API design matters. Read the docs.)
  • Why is 4xx "your fault" and 5xx "their fault"? (4xx means the request was malformed or unauthorized — something YOU did. 5xx means the server crashed or is overloaded — something THEY did. The retry logic differs: 4xx, fix your request. 5xx, retry with backoff (the issue might be transient).)
  • What's the difference between catching a specific exception vs catching RequestException? (Specific lets you handle errors DIFFERENTLY — show "no internet" UI on ConnectionError, show "server down" on HTTPError 5xx, show "city not found" on HTTPError 4xx. Catch-all treats them all the same. Use specific when behavior should differ; catch-all when "any failure → log and skip" suffices.)

🌍 Real-World Connections

  • That Twitter/X "fail whale" page. When a website crashes, 500 errors are flying. The error page IS the 500 response. Behind the scenes, monitoring systems alert engineers based on 5xx rate spikes.
  • Banking API contracts. Financial APIs have STRICT error handling — every transfer failure must be logged with the specific error code. Bare except is a regulatory violation.
  • Mars rovers. NASA's Curiosity has elaborate retry-and-failover logic. Network round-trip is 24 minutes; you can't just retry quickly. Each request has explicit error-recovery paths.
  • Mobile app reviews. "Crashed when offline" = bad review. Apps that handle network failures gracefully get better reviews even if features overlap. Defensive coding = better business.

📖 Lesson-Specific Vocabulary

Status Code — 3-digit number indicating what happened.
Five Status Families — 1xx info, 2xx success, 3xx redirect, 4xx client error, 5xx server error.
raise_for_status() — Raises HTTPError on 4xx/5xx. Used inside try.
ConnectionError — Couldn't reach server (DNS, no internet).
Timeout — Server too slow. Always pair with timeout=10.
RequestException — Umbrella catching all requests errors.
↑ Back to top

💾 Lesson 4 · Caching & Being a Good Citizen

Lesson 4 · Caching & Being a Good Citizen

Lesson Plan

At-a-Glance

Objective
Students will explain why caching matters (speed, cost, kindness), build a JSON-file cache from scratch with timestamp-based TTL freshness checks, identify the three cache outcomes (hit / miss / stale), choose appropriate TTLs for different data shapes (long for static, short for live), set User-Agent headers, and respect rate limits.
Time
50 minutes (often runs 60 — caching is the unit's hardest single skill)
Materials
Computer, CodeSkills Lab, projector, whiteboard for cache flow diagrams
Vocabulary
Cache, TTL (Time To Live), Cache Hit / Miss / Stale, User-Agent, Rate Limit
FL Standards
01.01, 01.02, 02.02, 03.01, 03.02, 05.06, 05.07
Exam Obj.
Objectives 1, 3, 4, 5 (primary)
⚠️ The unit's hardest single lesson. Caching combines: JSON I/O (Unit 6), dict-of-dicts data shape (Unit 5), TTL math with timestamps (Unit 6 datetime), conditional logic for the three outcomes (hit/miss/stale), and the wrap-existing-function pattern. Don't rush. The Cache Inspector widget is built precisely for the visual learner — use it. If your class finds Lesson 4 hard, the capstone WILL be hard; consider extending to a 2-day Lesson 4.

Lesson Flow (50 minutes)

🔥 Warm-Up (5 min)

  • Project a Lesson 1 program. Run it. Time it. Maybe 1.5 seconds. Run it AGAIN. 1.5 seconds. AGAIN. 1.5 seconds. Class observation: every run hits the API even though the data hasn't changed.
  • Reveal: Tokyo's coordinates haven't moved in 400 years. Why are we asking the API every time? Today: caching. Save the answer locally; don't ask twice.

🎤 Direct Instruction (22 min)

  • Open Lesson 8.4 on the projector. Walk through Section 1 ("Why Cache at All?"). Three reasons: SPEED (disk <<< network), COST (free APIs have rate limits), KINDNESS (don't hammer servers for repeat data).
  • Walk through Section 2 ("The Cache Flow — Three Outcomes"). On the whiteboard, draw the flow:
    1. Check cache for the key
    2. If found AND fresh (within TTL) → HIT (return cached, skip API)
    3. If found BUT stale (past TTL) → MISS (re-fetch, update cache)
    4. If not found → MISS (fetch, store, return)
  • The cache is a dict of dicts. Outer key: query identifier. Inner: {"fetched_at": timestamp, "data": cached_response}. Persist as JSON.
  • Walk through Section 3 ("Building a JSON Cache from Scratch"). Live-code:
    import json, time, os
     
    CACHE_FILE = "cache.json"
    TTL = 30 * 24 * 3600  # 30 days
     
    def _load_cache():
        try:
            with open(CACHE_FILE) as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            return {}
     
    def _save_cache(cache):
        with open(CACHE_FILE, "w") as f:
            json.dump(cache, f, indent=2)
  • Familiar pattern from Unit 6. The cache is just persistent state.
  • Walk through Section 4 ("Wrapping a Fetch Function with Caching"). Live-code:
    def get_data_cached(key):
        cache = _load_cache()
        # Hit?
        if key in cache and time.time() - cache[key]["fetched_at"] < TTL:
            print(f"  [hit]")
            return cache[key]["data"]
        # Miss — fetch fresh
        print(f"  [miss]")
        data = fetch_from_api(key)
        cache[key] = {"fetched_at": time.time(), "data": data}
        _save_cache(cache)
        return data
  • Walk through every line. The TTL math: time.time() - cache[key]["fetched_at"] < TTL. If still within budget, fresh.
  • Walk through Section 5 ("Cache Inspector"). Use the widget. Project. Watch hit/miss play out across multiple lookups.
  • Walk through Section 6 ("Choosing the Right TTL"):
    • City coordinates → 30 DAYS. They don't move.
    • Weather forecast → 10 MINUTES. Changes constantly.
    • Movie info → 1 day. Sometimes updated.
    • Stock price → 0 seconds (don't cache!). Real-time.
  • Walk through Section 7 ("Rate Limits & Being a Good Citizen"). Two practices:
    • Send a User-Agent header: headers={"User-Agent": "MyApp/1.0"}. Identifies your app.
    • Respect rate limits. 429 status = "slow down." Caching avoids hitting limits in the first place.

🤝 Guided Practice (5 min)

  • Walk through how the cache fills up:
    1. First lookup of "Tokyo": cache empty → MISS → fetch → store
    2. Second lookup of "Tokyo" (1 sec later): cache has it, 1 sec < 30 days → HIT
    3. Lookup of "Paris" (next): not in cache → MISS → fetch → store
    4. Lookup of "Tokyo" 31 days later: cache has it but stale → MISS → re-fetch → update
  • The cache file grows over time as new keys are added. Old entries get refreshed when re-queried.

💻 Independent Lab (15 min)

  • Lab 8.4A — "Add a Cache": students take Lesson 1's geocoding call and wrap it with a cache. Required: JSON file with timestamp+data per key, TTL of 30 days, hit/miss print statement, User-Agent header. Test by running twice — first call prints [miss], second prints [hit] and is instantaneous.
  • Lab 8.4B — "Cache Inspector": students write a function describe_cache() that reads the cache file and prints: total entries, oldest entry's age, freshest entry's age. Lets them see the cache state visually. Demonstrates dict-of-dicts navigation skills from Unit 5.
  • Circulate. Common bugs: TTL math wrong (treating seconds as days); cache file not being saved (forgetting _save_cache after a miss); cache check order wrong (hitting API first, then storing — defeats the purpose); not handling first-run when cache file doesn't exist (FileNotFoundError — Unit 6 callback).

🚪 Exit Ticket (3 min)

  • On paper: "Why is geocoding cached for 30 DAYS but weather forecasts for 10 MINUTES?"
  • Looking for: city coordinates don't change (Tokyo's lat/lon is the same year over year). Weather changes constantly. The TTL should match the data's volatility. Static data = long TTL. Live data = short TTL. The same caching mechanism works for both — just different numbers.

⚠️ Watch For — Common Misconceptions

1. "TTL of 0 means 'never cache.'"
What you'll see: Student sets TTL=0. Now every check fails the freshness test → always MISS. Cache works but never saves time.
How to address: TTL is in SECONDS. 0 = expired immediately. To skip caching entirely, don't write the wrapper. To cache for 30 days, use 30 * 24 * 3600 seconds. Drill the units.
2. "Cache check after fetch."
What you'll see: Student fetches first, then checks cache. The order is wrong; you've already paid the network cost.
How to address: ALWAYS check cache FIRST. Only fetch on miss. The whole point is to skip the fetch when cache has fresh data. Drill the order: load cache → check key → check freshness → fetch only if miss.
3. "Forget to save after fetch."
What you'll see: Student fetches data on miss, returns it, but never updates the cache. Next call still misses. Cache grows: nothing.
How to address: After a miss-and-fetch, you MUST update the cache and save it: cache[key] = {...}; _save_cache(cache). Without that line, your cache never grows.
4. "Cache is shared between unrelated data."
What you'll see: Student stores geocoding AND weather in one cache file. TTL applies to both — but they need different TTLs. Confusion.
How to address: Two caches. Two files. Two TTLs. geocode_cache.json with 30-day TTL. weather_cache.json with 10-minute TTL. The capstone uses exactly this pattern.
5. "Skip the User-Agent."
What you'll see: Some APIs (Wikimedia, OpenStreetMap) reject requests without a User-Agent. Open-Meteo doesn't require one — but it's good citizenship.
How to address: Default to ALWAYS sending a User-Agent. headers={"User-Agent": "AppName/1.0"}. The capstone rubric checks for this.

🎯 Differentiation

For early finishers
Challenge: add a force_refresh parameter to the cached function. get_data_cached(key, force_refresh=True) bypasses the cache and re-fetches. Stretch: research functools.lru_cache — Python's in-memory cache decorator. How does it differ from a JSON file cache?
For struggling students
Stay on Lab 8.4A. Skip TTL — make the cache "load forever" (no freshness check). The win: a working hit/miss cycle, even if data never expires. TTL math can be revisited.

💬 Discussion Prompts

  • Why don't all APIs do caching for you? Why is it the client's job? (Some APIs DO — they set Cache-Control headers, and HTTP-aware clients honor them. But control is split: the client knows its access patterns; the server doesn't. Client-side caching gives you control over how long things live, even when the server doesn't tell you.)
  • What's the cost of caching being WRONG? (Stale data — users see old prices, old scores, old weather. The TTL is a tradeoff: too long → wrong data; too short → wasted bandwidth. Get it wrong, your users complain. Get it right, your app feels fast and accurate.)
  • If caching is so good, why don't we cache EVERYTHING? (Storage cost. Cache invalidation complexity (the "two hard things in CS" joke: cache invalidation, naming, off-by-one). Privacy — caching personal data on disk creates a leak risk. Consistency — when one user updates data, other users with stale cache don't see it. Caching is a trade.)

🌍 Real-World Connections

  • Browsers cache everything. Your browser is a giant HTTP cache. Visit a site twice — second visit is instant because images, CSS, JS are cached locally. Same idea, bigger scale.
  • CDNs (Content Delivery Networks). Cloudflare, Akamai, Fastly — companies whose entire business is caching. Servers around the world store copies of your website's static content so users get fast responses regardless of location.
  • Spotify offline mode. Songs you've listened to are cached locally. Mobile data savings + works on a plane. The cache IS the offline mode.
  • App store ratings. Apps that feel fast get better ratings. Caching is what makes apps feel fast even on slow networks. Netflix's "skinny bundle" recommendation algorithm caches your home page so it loads instantly.

📖 Lesson-Specific Vocabulary

Cache — Local copy of a previous response.
TTL (Time To Live) — How long cached data stays fresh.
Cache Hit / Miss / Stale — Three outcomes when checking the cache.
User-Agent — Header identifying your app to the server.
Rate Limit — Max requests/window. Exceed = 429.
↑ Back to top

🌤️ Lesson 5 · Weather Dashboard Capstone

Lesson 5 · Weather Dashboard Capstone

Project Plan

At-a-Glance

Objective
Students will design and build a four-file CLI app that consumes two real public APIs (geocoding + forecast), wraps responses in Location and WeatherReport classes (with @property for derived attributes), implements separate JSON caches with appropriate TTLs, persists user favorites, handles network failures with stale-cache fallback, and integrates EVERY skill from Units 6, 7, and 8 into one working app.
Time
Two-three class periods (90–120 minutes total)
Materials
Computer with internet, CodeSkills Lab, projector, paper for SDLC plan stage, whiteboard for four-file architecture diagram
Vocabulary
@property, plus all prior Unit 8 vocabulary
FL Standards
01.01, 01.02, 02.02, 03.01, 03.02, 04.03, 05.06, 05.07, 06.02, 06.03, 07.01, 07.05, 07.07
Exam Obj.
Objectives 1, 3, 4 (primary), 5, 7 — broadest standards coverage of any unit
⚠️ The unit's culminating project — and the largest of the course so far. Weather Dashboard integrates EVERY skill from Unit 8 (HTTP, params, errors, caching, rate-limit politeness) plus Unit 7's class skills plus Unit 6's persistence. It's a 4-file project (models.py, geocoding.py, weather.py, main.py) — bigger than Unit 7's Quest Heroes. The 90–120-minute window is non-negotiable. Day 1 = architecture intro + Plan + Code models. Day 2 = code both API modules + main + run end-to-end. Day 3 = full test (14 cases) + fix + maintain + showcase. Some teachers split into 3 days; others do 2 longer days. Both work.

Project Flow (90–120 min across 2–3 days)

📅 Day 1 — API Chaining + Plan + Code models.py (45 min)

  • Warm-up (5 min): Project a phone weather app. "What does it do internally?" Take answers. Reveal: type a city → look up coordinates → fetch forecast for those coordinates → display. Two API calls chained. That's our capstone.
  • API chaining intro (10 min): Walk through Lesson 8.5 Section 1. Output of one call becomes input of the next. name → lat/lon → forecast. Each step can fail; defensive code at each.
  • Wrapping API data in classes (10 min): Walk through Section 2. The pattern: take raw API JSON, wrap in a Python class with __init__ that pulls fields. Add @property for computed/derived values. The class is the FRIENDLY API your code uses. Live-code:
    class WeatherReport:
        def __init__(self, location, raw):
            self.location = location
            self.raw = raw
     
        @property
        def temperature(self):
            return self.raw["current"]["temperature_2m"]
     
        def is_freezing(self):
            return self.temperature <= 0
  • SDLC Stage 1 — Plan (5 min): Distribute paper. 6-question planning checklist: 4 files in same folder + 2 cache files + favorites file + User-Agent + menu actions + error strategies. Stage 1 deliverable.
  • SDLC Stage 2 — Code (15 min) — start models.py: Pure Python, no network. Build Location class first (city, country, lat, lon, __str__). Then WeatherReport (location, raw, @property for temperature and wind_speed, methods is_freezing and is_windy, __str__). Test with hand-built sample data — no API yet. The bottom-up build keeps Day 1 scope manageable.

📅 Day 2 — Code Both APIs + main.py + Run End-to-End (45 min)

  • Code geocoding.py (10 min): The geocode function. JSON cache (geocode_cache.json, 30-day TTL). User-Agent header. Defensive try/except (RequestException umbrella). Stale-cache fallback. Returns Location object or None. Test with: real city (works), fake city (returns None gracefully), force network off (uses stale cache if available).
  • Code weather.py (10 min): The get_forecast function. JSON cache (weather_cache.json, 10-minute TTL). Same defensive pattern. Returns WeatherReport or None. Test by running twice — second call should print [hit] and be instant.
  • Code main.py (15 min): The orchestrator. Imports from all three other files. load_favorites + save_favorites with JSON+try/except (Unit 6 pattern). The look_up function chains geocode → forecast → display. Six menu options: Look up city, List favorites, Add favorite, Remove favorite, Refresh all, Quit. Wire it together.
  • Run end-to-end (10 min): Type a real city → see geocoding hit/miss → see forecast hit/miss → see formatted weather. Add a favorite. Quit. Restart. Favorite persisted. Refresh — both API caches mostly hit. The dashboard works.

📅 Day 3 — Test + Fix + Maintain + Showcase (45 min)

  • SDLC Stage 3 — Test (15 min): Run all 14 test cases:
    1. Real city → end-to-end success
    2. Fake city ("Asdfgh") → "city not found", no crash
    3. Special characters (São Paulo) → params= handles encoding
    4. Cache hit on second lookup → instant, prints [hit]
    5. Cache miss after TTL expires → re-fetches
    6. Network down (turn off WiFi briefly) → stale-cache fallback OR friendly error
    7. Add favorite → JSON file appears with city
    8. Persistence across restart → favorites survive
    9. Remove favorite → list shrinks, JSON updated
    10. Refresh all → loops favorites, mostly cache hits
    11. Corrupted favorites.json → app starts fresh, no crash
    12. Empty favorites → list option handles gracefully
    13. Special-character favorite (São Paulo) → adds correctly
    14. Two caches independent → geocoding 30 days, weather 10 min
  • SDLC Stage 4 — Fix (10 min): Apply 4-step debugging. Common Day 3 bugs: WeatherReport.temperature() with parens (it's a @property, no parens); cache key collision (using just city name when geocoding might have multiple matches); forgetting timeout=10 (some students); cache file gets corrupted between runs (handle with try/except).
  • SDLC Stage 5 — Maintain (10 min): Polish. Header docstring on EVERY file. Function docstrings on EVERY function. Organized imports. README.md describing how to run. Internal helpers prefixed with _.
  • Showcase & Submit (10 min): Pick 3–5 volunteers. Run their dashboard. Look up Tokyo. Add as favorite. Quit. Restart. Show the favorites list. Refresh all → all cache hits. Note different students' display formats, error messages, stretch goal additions. Standard 05.06 (multiple algorithms) at the system level.

⚠️ Watch For — Common Project Pitfalls

1. "@property called with parens."
What you'll see: r.temperature() — parens after a @property method. Crashes with TypeError ("float not callable").
How to address: @property turns the method into an attribute. Read it without parens: r.temperature. Drill: properties are accessed like attributes, not called like methods. The whole point of @property is to make computed values feel like attributes.
2. "Cache hit logic wrong — TTL math."
What you'll see: time.time() - cached_time > TTL (greater than, treating > as freshness). Backwards — that's the STALE check, not fresh.
How to address: Fresh = time elapsed is LESS THAN TTL. time.time() - cached_time < TTL. Stale = time elapsed is GREATER THAN TTL. Drill the direction.
3. "Stale-cache fallback missing."
What you'll see: Network fails. App returns None. User sees nothing. But cache had a slightly-stale entry that would've been better than nothing.
How to address: Pattern: try/except RequestException → before returning None, check the cache for ANY entry (even stale) and use it as a fallback. print("⚠️ Using stale cached data") to be honest about it. The rubric explicitly tests this.
4. "One cache file for both APIs."
What you'll see: Student combines geocoding + weather into one cache file. Then TTL has to be the same for both — wrong for one or the other.
How to address: Two cache files. Two TTLs. geocode_cache.json (30 days) and weather_cache.json (10 minutes). The rubric explicitly tests this separation.
5. "Bare except: in API code."
What you'll see: Carryover from Unit 6. Especially common when students panic about Day 3 testing — they wrap everything in try: ... except: to "make it work."
How to address: Drill: name the exception. except RequestException is the umbrella. except json.JSONDecodeError for cache. except (FileNotFoundError, json.JSONDecodeError) when both apply. Bare except hides bugs and is a rubric deduction.
6. "Missing the User-Agent."
What you'll see: Open-Meteo doesn't require it (so the app works), but the rubric checks for it.
How to address: Pattern: HEADERS = {"User-Agent": "WeatherDashboard/1.0"} as a module constant. Pass it on every request: requests.get(url, params=..., headers=HEADERS, timeout=10). Polite citizenship; 10 of 100 rubric points.
7. "Class methods that should be properties (and vice versa)."
What you'll see: Student makes is_freezing a @property. Now they call r.is_freezing without parens — works, but feels weird (it reads like an attribute, not a check).
How to address: @property for noun-like accessors (temperature, wind_speed). Regular methods for verb-like checks (is_freezing()). The rubric calls these out specifically.

🎯 Differentiation

For students who finish early
Direct them to stretch goals: 7-day forecast (the API supports daily=temperature_2m_max,temperature_2m_min,precipitation_probability), units toggle (Fahrenheit/Celsius), severe weather alerts (parse warnings from API), cache cleanup utility (CLI tool to view/clear caches), retry-with-backoff on Timeout, color CLI output via colorama. Each adds +5 pts (max +25).
For struggling students
It's OK if their version skips the WeatherReport class (use raw dicts) or skips one cache (cache geocoding only, fetch weather every time). They still demonstrate the API chain + persistence + error handling. Partial credit for genuine API integration — the chained call + at least one cache + favorites persistence is the core demonstration.

💬 Discussion Prompts

  • Why four files instead of one big file? (Four concerns: data shape (models.py), each external service (geocoding.py, weather.py), user interaction (main.py). Each file does ONE thing. Want to swap Open-Meteo for AccuWeather? Only weather.py changes. Want to add a web UI? Build a new top file; keep the API modules. Layering is what makes apps maintainable.)
  • What if both APIs went down at once? (The stale-cache fallback handles single failures. If both go down, your app shows "no fresh data, using last known values." Real apps degrade gracefully — not crash. Your phone's weather app does this when you're on a plane: shows last sync.)
  • Could you add a third API to extend the dashboard? (Yes — air quality, sunrise/sunset, UV index, all available from Open-Meteo. Same pattern: a third module file, third cache, integrate into main.py's display. The pattern is repeatable. That's exactly what real software extensions look like.)

🌍 Real-World Connections

  • Every weather app on the planet. Identical architecture. Geocode the user's location → fetch forecast → display. Add favorites. Cache aggressively. The Weather Dashboard IS a real weather app, just CLI instead of mobile. The pattern is unchanged.
  • Travel apps (Hotels.com, Expedia). Search city → autocomplete via geocoding → fetch hotels for those coordinates → display. Two-API chain, just like ours.
  • Ride-share (Uber, Lyft). Type address → geocode → fetch nearby drivers → display. Same chain pattern.
  • Resume bullet point. "Built a 4-file Python application consuming two REST APIs with JSON caching, stale-fallback graceful degradation, and persistent user favorites." Not a stretch — that's exactly what students did.

📋 Weather Dashboard Project Rubric (100 points)

The rubric matches the one in the student lesson exactly. Share with students at the start of Day 1.

CategoryWhat to look forPts
Four-File ArchitectureCode split across models.py + geocoding.py + weather.py + main.py. Imports work cleanly. Each file has a single concern.10
API Chain WorksType a city name → geocode → forecast → display. The full pipeline runs end-to-end with real APIs.15
Class WrappersLocation and WeatherReport classes. WeatherReport uses @property for temperature and wind_speed; has is_freezing() / is_windy() methods. __str__ on both.15
CachingBoth geocode_cache.json and weather_cache.json work. Cache hits print [hit]. Different TTLs for the two (geocoding long, weather short). Survives restart.15
Error HandlingNetwork errors caught with branched try/except. raise_for_status + timeouts everywhere. "City not found" handled gracefully (no crash). Stale-cache fallback on network errors.15
Favorites PersistenceAdd/remove/list favorites work. favorites.json survives program restart. Favorites can be refreshed in bulk.10
Polite CitizenshipBoth API modules send a User-Agent header. Both set timeout=10. Caching meaningfully reduces network calls.10
Clean Code StyleDocstrings, organized imports, snake_case for funcs/PascalCase for classes, no bare except. Helpers prefixed with _.5
Works CorrectlyAll 14 test cases from Stage 3 pass. Especially: stale-cache fallback, favorites persist, special-character cities, corrupted-file recovery.5
TOTAL100

🎁 Stretch Goals (extra credit)

  • 7-day forecast (additional API parameters): +5 pts
  • Units toggle — Celsius/Fahrenheit user preference saved: +5 pts
  • Severe weather alerts (parse warnings if API provides them): +5 pts
  • Cache cleanup utility — view/clear caches from CLI: +5 pts
  • Retry-with-backoff on Timeout (1s, 2s, 4s): +5 pts
  • Color CLI output via colorama (red for cold, blue for cold, etc): +5 pts

Maximum stretch credit: +25 points. Document each stretch in a README.md with what you built and how it changes the app.

💡 Showcase tip. The Day 3 emotional payoff is multi-layered: students see their app work end-to-end against real servers. The "I typed Tokyo, it actually got the weather" moment. The "I added 5 favorites, restarted, they're STILL there" moment. The "I refreshed all and they were all cache hits" moment. The "I turned off WiFi and the app showed stale data instead of crashing" moment. Project several students' apps back to back. Different students' formatted output, different error messages, different stretch goals — standard 05.06 (multiple algorithms) at the system level. The Weather Dashboard is the largest, most production-shaped project of the year. Celebrate it.

📖 Lesson-Specific Vocabulary

@property — Decorator turning a method into a read-only attribute. Access without parens.
↑ Back to top

🛠️ Appendix · Common Python Errors in Unit 8

This appendix lists the most common Python error messages students will see in Unit 8. Networking introduces a new family of errors — installation issues, network failures, status errors, and the silent bugs of forgotten parens, missing timeouts, and stale caches. Apply Unit 4's 4-step strategy: Reproduce → Read → Hypothesize → Fix & Verify, ONE change at a time.

ERRORModuleNotFoundError: No module named 'requests'

What it usually means: The requests library isn't installed in this Python environment.

How to fix: pip install requests in the terminal. CodeSkills Lab usually has it pre-installed; if not, the install is one command. Verify with import requests; print(requests.__version__).

ERRORrequests.exceptions.ConnectionError: HTTPSConnectionPool... Max retries exceeded

What it usually means: Couldn't reach the server. DNS failure, no internet, server down, or a school firewall blocking outbound HTTPS to the host.

How to fix: Wrap in try/except requests.ConnectionError for graceful failure. If it's the firewall: work with IT to whitelist geocoding-api.open-meteo.com and api.open-meteo.com. The capstone tests stale-cache fallback for this exact case.

ERRORrequests.exceptions.Timeout: HTTPSConnectionPool... Read timed out

What it usually means: The server didn't respond within the time budget you set.

How to fix: Catch requests.Timeout. Optionally retry with backoff. Always pass timeout=10 on every request — without it, the default is "wait forever."

ERRORrequests.exceptions.HTTPError: 404 Client Error / 500 Server Error

What it usually means: raise_for_status() fired because the response was a 4xx or 5xx. 4xx = your fault (bad URL, missing param). 5xx = their fault (server down).

How to fix: Catch requests.HTTPError. Inspect e.response.status_code to differentiate handling. 4xx → fix your request. 5xx → retry with backoff (server might be temporarily overloaded).

ERRORjson.decoder.JSONDecodeError: Expecting value

What it usually means in Unit 8: The response body wasn't valid JSON. Usually because the API returned an HTML error page (e.g., 503 from a CDN) — r.json() tried to parse HTML.

How to fix: Always check r.status_code == 200 OR call r.raise_for_status() BEFORE r.json(). Bad responses often aren't JSON; check status first.

ERRORTypeError: 'method' object is not subscriptable

What it usually means: Forgot the parens on .json(). r.json["results"] instead of r.json()["results"]. Tried to index the method object itself.

How to fix: Always parens on .json(). r.json()["results"]. Drill: methods need parens; attributes don't.

ERRORTypeError: 'float' object is not callable

What it usually means in Unit 8: Called a @property method WITH parens. r.temperature() instead of r.temperature. The @property turned it into an attribute; calling it tries to invoke a float.

How to fix: @property = no parens. Drill: properties are attributes; methods are calls. is_freezing() with parens (it's a method); temperature without parens (it's a @property).

SILENT(request hangs forever)

What it usually means: No timeout. Server is slow or unreachable; default is wait forever.

How to fix: ALWAYS pass timeout=10. Drill it. Mark deductions on rubric for missing timeouts.

SILENT(cache never fills — every call is a miss)

What it usually means: Forgot to _save_cache(cache) after writing the new entry. Cache only lives in memory; lost on program exit.

How to fix: After every cache[key] = {...}, immediately _save_cache(cache). The pattern: load → check → fetch on miss → STORE → SAVE → return.

SILENT(rate limit ban — 429 errors persist)

What it usually means: Hammered an API in a tight loop. Server returned 429s; some APIs ban your IP temporarily.

How to fix: Cache aggressively. Add User-Agent header. Add a deliberate sleep between calls in tight loops (time.sleep(0.5)). The capstone's caching makes this a non-issue for normal use.

CARRYOVERCarryover errors from Units 1–7

Watch list — these still happen in Unit 8 capstones:

  • Bare except: — Unit 6 anti-pattern. Always name the exception.
  • Forgetting self in method def lines — Unit 7 carryover.
  • FileNotFoundError on first run — wrap cache load in try/except. Unit 6.
  • json.JSONDecodeError on corrupted cache — catch alongside FileNotFoundError. Unit 6.
  • Indentation errors when classes, methods, try/except, and conditionals all nest deeply.
  • Circular imports — main.py imports geocoding.py imports models.py: fine. Make sure models.py doesn't import the API modules.
🛟 Unit 8 debugging adds two diagnostic questions to the 4-step strategy. Before applying Reproduce → Read → Hypothesize → Fix & Verify, ask: (A) "Did the request actually reach the server?" Print r.status_code and r.url immediately after every request. The URL print catches bad query params; the status code print catches bad responses. (B) "What does r.text actually contain?" Many Unit 8 bugs are response-shape bugs — the server returned something different from what you expected. Print the raw text first; THEN navigate the JSON.
📘 Companion document. A Unit 8 Answer Key with Knowledge Check answers and lab solutions can be authored separately on request. The Teacher's Guide + Answer Key are designed to be used together.

— End of Unit 8 Teacher's Guide —

↑ Back to top