Unit 8 – Teachers Guide
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.
👋 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:
ModuleNotFoundErroron requests,ConnectionError,Timeout,HTTPErrorfromraise_for_status, the silent bug of forgettingresponse.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
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
requestslibrary (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
requestsexceptions:ConnectionError,Timeout,HTTPError, and the umbrellaRequestException - 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
@propertyfor 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.comandapi.open-meteo.com— verify with IT before starting - Knowledge Pillars CodeSkills Lab access (with the
requestslibrary 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)
⏱️ 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
| Day | Lesson | Focus | Homework |
|---|---|---|---|
| Day 1 | L1: HTTP & First Request | What HTTP is. Installing requests. Anatomy of a URL. Response attributes. Request/Response Inspector. Walking JSON. Lab 8.1A. | Read L2 Sections 1–3 |
| Day 2 | L1 finish + L2 start | Knowledge check on requests basics. Preview query params motivation. | Read L2 Sections 4–6 (docs) |
| Day 3 | L2: Query Params & Docs | Query 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 4 | L3: Status Codes & Errors | Why 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 5 | L4: 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 6 | L4: Caching (pt 2) | Cache Inspector widget. Choosing TTL. Rate limits and User-Agent headers. Knowledge check. | Plan Weather Dashboard architecture |
| Day 7 | L5: Weather Dashboard (pt 1) | API chaining intro. Wrapping API data in classes. Plan stage. Code models.py first. | Continue building models |
| Day 8 | L5: 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 9 | L5: 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.
Suggested grading weights for Unit 8
| Component | Weight | Notes |
|---|---|---|
| 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 Project | 45% | Capstone — 100-pt rubric in Lesson 5 plan |
| Class participation | 10% | 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:
| Standard | Description | Lessons |
|---|---|---|
| 01.01 | Use specialized computer coding software to solve problems. | All (CodeSkills Lab) |
| 01.02 | Demonstrate proficiency using specialized computer software (e.g., Python). | All |
| 02.02 | Explain the components of programming languages. | L1, L2, L4, L5 (libraries, modules, classes) |
| 03.01 | Identify and describe text-based file types (txt, csv, json). | L4, L5 (cache JSON files) |
| 03.02 | Demonstrate basic file operations (open, read, write, close). | L4, L5 (cache I/O) |
| 03.03 | Identify and describe binary file types. | L1 (response.content), L3 |
| 04.03 | Demonstrate sorting and filtering of data sets. | L5 (favorites filtering) |
| 05.06 | Recognize that more than one algorithm can solve a given problem. | L3 (error strategies), L4 (cache strategies) |
| 05.07 | Create a program that implements an algorithm to achieve a given goal. | L5 (Weather Dashboard) |
| 06.02 | Apply troubleshooting techniques within the SDLC. | L3, L5 — primary unit standard (network failures) |
| 06.03 | Apply iterative debugging within the SDLC. | L3, L5 (capstone testing) |
| 07.01 | Describe the structure of a simple program; explain why sequencing is important. | L5 (four-file architecture) |
| 07.05 | Explain the types and use of variables in programming. | L1 (Response objects), L5 (custom classes) |
| 07.07 | Troubleshoot 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.
| Obj | Title | Coverage in Unit 8 | Lessons |
|---|---|---|---|
| 1 | Core programming concepts | Continued — HTTP/APIs are core internet concepts | L1, L2, L5 |
| 3 | Algorithm understanding & design | Cache strategies, API chaining | L4, L5 |
| 4 | Write and debug simple programs | Full coverage — primary objective. Live API code throughout. | All |
| 5 | Data manipulation | JSON parsing, response transformation | L1, L4, L5 |
| 7 | Debugging techniques | Full coverage — network failures, status codes, branched try/except | L3, L5 |
Lesson-by-lesson alignment
| Lesson | Florida Standards | Exam Objectives |
|---|---|---|
| L1: HTTP, APIs & Your First Request | 01.01, 01.02, 02.02, 03.03, 07.05 | 1, 4 (primary), 5 |
| L2: Query Parameters & Reading Docs | 01.01, 01.02, 02.02, 05.06 | 1, 4 (primary) |
| L3: Status Codes & Error Handling | 01.01, 01.02, 03.03, 05.06, 06.02, 06.03, 07.07 | 4, 7 (primary) |
| L4: Caching & Being a Good Citizen | 01.01, 01.02, 02.02, 03.01, 03.02, 05.06, 05.07 | 1, 3, 4, 5 (primary) |
| L5: Weather Dashboard Capstone | 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 | 1, 3, 4 (primary), 5, 7 |
📖 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.
| Term | Definition | Example | Intro |
|---|---|---|---|
| HTTP | HyperText Transfer Protocol. The set of rules computers use to talk to web servers. Request goes out; response comes back. | The "talk to a server" protocol | L1 |
| API | Application 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 API | L1 |
| requests library | The Python library for making HTTP calls. Not in the standard library; install with pip install requests. | import requests | L1 |
| GET request | The HTTP verb for "fetch data without changing anything." The most common request type. | requests.get(url) | L1 |
| URL | The 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=Tokyo | L1 |
| Endpoint | A specific URL path on an API that does one thing. Often called by name in docs. | /v1/search | L1 |
| Response Object | What requests.get returns. Has .status_code, .json(), .text, .headers. | r.status_code → 200 | L1 |
| Query Parameter | A key=value pair in the URL query string. Used to customize what the API returns. | ?name=Tokyo&count=1 | L2 |
| params= argument | The clean way to pass query parameters in requests.get. Pass a dict; library URL-encodes for you. | params={"name": "Tokyo"} | L2 |
| API Documentation | The official spec for an API: endpoints, parameters, response shapes, examples. The professional skill of reading docs. | open-meteo.com/en/docs | L2 |
| Status Code | A 3-digit number in every response indicating what happened. 200=OK, 404=Not Found, 500=Server Error. | r.status_code == 200 | L3 |
| Five Status Families | 1xx info, 2xx success, 3xx redirect, 4xx client error (your fault), 5xx server error (their fault). | 200, 301, 404, 500 | L3 |
| raise_for_status() | Method on Response that raises HTTPError on 4xx/5xx. Lets try/except handle bad statuses cleanly. | r.raise_for_status() | L3 |
| ConnectionError | Raised when the request couldn't reach the server (DNS failure, no internet). | except ConnectionError: | L3 |
| Timeout | Raised when the server didn't respond within the time budget. Always set timeout=. | timeout=10 | L3 |
| RequestException | The umbrella exception for ALL requests-library errors. Catches ConnectionError, Timeout, HTTPError, others. | except requests.RequestException: | L3 |
| Cache | A local copy of a previous response. Lets repeated lookups skip the network. | geocode_cache.json | L4 |
| TTL (Time To Live) | How long cached data stays valid. After TTL, treat as stale and re-fetch. | 30 days for geocoding | L4 |
| Cache Hit / Miss / Stale | Three outcomes when checking the cache. Hit = fresh entry exists. Miss = no entry. Stale = entry exists but expired. | [hit] for Tokyo | L4 |
| User-Agent | A header your code sends identifying your app. Polite practice; some APIs require it. | "WeatherDashboard/1.0" | L4 |
| Rate Limit | Maximum requests per time window an API allows. Exceed = HTTP 429. Caching avoids this. | 1000 requests/hour | L4 |
| @property | Decorator that turns a method into a read-only attribute. r.temperature not r.temperature(). | @property\ndef temperature(self): | L5 |
🌐 Lesson 1 · HTTP, APIs & Your First Request
Lesson 1 · HTTP, APIs & Your First Request
Lesson PlanAt-a-Glance
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.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:
- Your code builds a URL
- Sends a REQUEST over the internet to a server
- Server processes, builds a RESPONSE
- Response arrives back at your code
- 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 withimport 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_codedoesn'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
- "Country?" →
- 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); typingrequests.GETinstead ofrequests.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 betweenr.status_codeandr.json()? When would you use each?" - Looking for:
.status_codeis 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
.json()."data = r.json. data is now the method itself, not the parsed JSON. data["results"] crashes with a confusing TypeError..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.requests is in the standard library."import requests on a clean Python install crashes with ModuleNotFoundError.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.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).if "results" in data and data["results"]: before data["results"][0].url = f"https://api.com/search?name={city}". Works for "Tokyo" but breaks on "São Paulo" (the special character needs URL encoding).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.)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 APIhttps://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
.status_code, .json(), .text, .headers.🧩 Lesson 2 · Query Parameters & Reading Docs
Lesson 2 · Query Parameters & Reading Docs
Lesson PlanAt-a-Glance
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.params= argument, API DocumentationLesson 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 whencity = "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 providecount (integer, default: 10)— optionallanguage (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": ..., ...}]})
- The endpoint URL (
- 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 (inparams=).
🚪 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
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.?) and put ALL parameters in params=. Cleaner. Easier to debug. Drill consistency.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."city"; the actual parameter is "name". API returns nothing useful.params= for what's actually a PATH parameter (e.g., /users/{user_id}). The library puts it in the query string instead — wrong.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.params="name=Tokyo&count=5" — passing a string instead of a dict. requests accepts this, but you've defeated the URL-encoding benefit.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 theparams= 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. Thevparameter 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
?.params= argument — Pass a dict to requests.get; library URL-encodes for you.🚦 Lesson 3 · Status Codes & Error Handling
Lesson 3 · Status Codes & Error Handling
Lesson PlanAt-a-Glance
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.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.
requestsfollows 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.
- Manual:
- 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 unreachableTimeout— server too slow. Always settimeout=10HTTPError— 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.
- Branched:
- Drill: ALWAYS pass
timeout=10in 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". Usecode // 100to find the family. - Circulate. Common bugs: bare
except:(carryover from Unit 6 — drill it again); forgettingtimeout=(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
timeout=."timeout=10. Drill it. Mark deductions on rubric for missing timeouts.except HTTPError with no raise_for_status(). The except block never fires because no HTTPError gets raised.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.except: on requests calls."except: with no exception type. Catches ALL exceptions including KeyboardInterrupt.except RequestException: catches all requests errors and is the right scope. Drill: name the exception. Mark deductions.if "results" not in data or not data["results"]: handles "API returned nothing useful." Different from network error.except RequestException BEFORE except Timeout. The umbrella catches first; specific never fires.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: researchresponse.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
exceptis 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
timeout=10.💾 Lesson 4 · Caching & Being a Good Citizen
Lesson 4 · Caching & Being a Good Citizen
Lesson PlanAt-a-Glance
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:
- Check cache for the key
- If found AND fresh (within TTL) → HIT (return cached, skip API)
- If found BUT stale (past TTL) → MISS (re-fetch, update cache)
- 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.
- Send a User-Agent header:
🤝 Guided Practice (5 min)
- Walk through how the cache fills up:
- First lookup of "Tokyo": cache empty → MISS → fetch → store
- Second lookup of "Tokyo" (1 sec later): cache has it, 1 sec < 30 days → HIT
- Lookup of "Paris" (next): not in cache → MISS → fetch → store
- 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_cacheafter 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
30 * 24 * 3600 seconds. Drill the units.cache[key] = {...}; _save_cache(cache). Without that line, your cache never grows.geocode_cache.json with 30-day TTL. weather_cache.json with 10-minute TTL. The capstone uses exactly this pattern.headers={"User-Agent": "AppName/1.0"}. The capstone rubric checks for this.🎯 Differentiation
For early finishers
Challenge: add aforce_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-Controlheaders, 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
🌤️ Lesson 5 · Weather Dashboard Capstone
Lesson 5 · Weather Dashboard Capstone
Project PlanAt-a-Glance
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.@property, plus all prior Unit 8 vocabularymodels.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@propertyfor 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,@propertyfor 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_favoriteswith 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:
- Real city → end-to-end success
- Fake city ("Asdfgh") → "city not found", no crash
- Special characters (São Paulo) → params= handles encoding
- Cache hit on second lookup → instant, prints [hit]
- Cache miss after TTL expires → re-fetches
- Network down (turn off WiFi briefly) → stale-cache fallback OR friendly error
- Add favorite → JSON file appears with city
- Persistence across restart → favorites survive
- Remove favorite → list shrinks, JSON updated
- Refresh all → loops favorites, mostly cache hits
- Corrupted favorites.json → app starts fresh, no crash
- Empty favorites → list option handles gracefully
- Special-character favorite (São Paulo) → adds correctly
- 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
@property called with parens."r.temperature() — parens after a @property method. Crashes with TypeError ("float not callable").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.time.time() - cached_time > TTL (greater than, treating > as freshness). Backwards — that's the STALE check, not fresh.time.time() - cached_time < TTL. Stale = time elapsed is GREATER THAN TTL. Drill the direction.print("⚠️ Using stale cached data") to be honest about it. The rubric explicitly tests this.geocode_cache.json (30 days) and weather_cache.json (10 minutes). The rubric explicitly tests this separation.except: in API code."try: ... except: to "make it work."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.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.is_freezing a @property. Now they call r.is_freezing without parens — works, but feels weird (it reads like an attribute, not a check).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 viacolorama. 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.
| Category | What to look for | Pts |
|---|---|---|
| Four-File Architecture | Code split across models.py + geocoding.py + weather.py + main.py. Imports work cleanly. Each file has a single concern. | 10 |
| API Chain Works | Type a city name → geocode → forecast → display. The full pipeline runs end-to-end with real APIs. | 15 |
| Class Wrappers | Location and WeatherReport classes. WeatherReport uses @property for temperature and wind_speed; has is_freezing() / is_windy() methods. __str__ on both. | 15 |
| Caching | Both 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 Handling | Network 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 Persistence | Add/remove/list favorites work. favorites.json survives program restart. Favorites can be refreshed in bulk. | 10 |
| Polite Citizenship | Both API modules send a User-Agent header. Both set timeout=10. Caching meaningfully reduces network calls. | 10 |
| Clean Code Style | Docstrings, organized imports, snake_case for funcs/PascalCase for classes, no bare except. Helpers prefixed with _. | 5 |
| Works Correctly | All 14 test cases from Stage 3 pass. Especially: stale-cache fallback, favorites persist, special-character cities, corrupted-file recovery. | 5 |
| TOTAL | 100 | |
🎁 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.
📖 Lesson-Specific Vocabulary
🛠️ 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.
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__).
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.
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."
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).
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.
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.
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).
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.
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.
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.
Watch list — these still happen in Unit 8 capstones:
- Bare
except:— Unit 6 anti-pattern. Always name the exception. - Forgetting
selfin method def lines — Unit 7 carryover. FileNotFoundErroron first run — wrap cache load in try/except. Unit 6.json.JSONDecodeErroron 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.
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.
— End of Unit 8 Teacher's Guide —
