3.14 Libraries

Source lesson: python/libraries

Learning Objective: Import Python libraries, use their API documentation to call procedures correctly, and read a requirements.txt file.

Submission notes

  • Lesson: CSP 3.14 Libraries
  • Popcorn 1: what I changed = jersey range 1–99 → 1–55, court area 144 → 2500 sq ft
  • Popcorn 2: my extra route = /coach (plus /record)
  • Homework: libraries used = json, statistics, datetime, math
  • Homework: days until my next game = 16 (printed on the 2026-10-01 run; next game is 2026-10-17)

Popcorn Hack 1: Read the API, Then Use It

Changes: jersey range randint(1, 55) and a 2500 sq ft court instead of 144.

Prediction: the jersey is a random whole number from 1 to 55, and both ends are possible because the randint docs say “including both end points”. math.sqrt(2500) = 50.0 feet (a float, not an int). The average is (24 + 18 + 31 + 27 + 22) / 5 = 24.4, and math.ceil rounds it up to 25. The random game is one of the five values, but which one changes every run.

Code Runner Challenge

Run it, then change the jersey range to 1-55 and predict the new output

View IPYNB Source
# CODE_RUNNER: Run it, then change the jersey range to 1-55 and predict the new output
import math
import random

# Read the API before calling a procedure
print("random.randint:", random.randint.__doc__)
print("math.ceil:", math.ceil.__doc__)

points_per_game = [24, 18, 31, 27, 22]

# random.choice picks one item out of a list
print("Random game to review:", random.choice(points_per_game), "points")

# CHANGED: random.randint(a, b) returns a whole number from a to b, including both
jersey = random.randint(1, 55)
print("Random jersey number:", jersey)

# CHANGED: a 2500 square foot square court
side = math.sqrt(2500)
print("Square court side:", side, "feet")

# math.ceil rounds up to the next whole number
average = sum(points_per_game) / len(points_per_game)
rounded_up = math.ceil(average)
print("Points per game:", average, "-> rounded up:", rounded_up)
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Actual output: the side is 50.0 feet, the average is 24.4 → 25, and the jersey and review game are random within their ranges. I left out the dir(math) / dir(random) lines from my copy because they print the full list of names in each library, which is long. I did read those lists while working in the lesson runner.

Popcorn Hack 2: Write a Flask Route

Changed the roster to my own players and added two routes, /coach and /record, above app.test_client().

Code Runner Challenge

Run it, then change the roster and add a route of your own, such as /coach

View IPYNB Source
# CODE_RUNNER: Run it, then change the roster and add a route of your own, such as /coach
from flask import Flask, jsonify

app = Flask(__name__)

# A route is a procedure with a web address attached to it
@app.route("/score")
def score():
    return jsonify({"home": 58, "away": 52, "quarter": 3})

@app.route("/roster")
def roster():
    return jsonify({"players": ["Ishan", "Maya", "Leo", "Priya", "Jordan"]})

@app.route("/coach")
def coach():
    return jsonify({"name": "Coach Rivera", "role": "head coach", "years": 6})

@app.route("/record")
def record():
    wins = 7
    losses = 3
    return jsonify({"wins": wins, "losses": losses, "win_pct": round(wins / (wins + losses), 3)})

# test_client visits the addresses without needing a browser
client = app.test_client()
print("/score says:", client.get("/score").get_json())
print("/roster says:", client.get("/roster").get_json())
print("/coach says:", client.get("/coach").get_json())
print("/record says:", client.get("/record").get_json())
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Homework Hack: Season Stats Report

My own version: a soccer forward’s season, counting goals instead of points. It uses four standard library modules:

Library Procedure Used for
statistics mean, median average and median goals per game
math floor rounding minutes-per-goal down to a whole number
datetime date, date.today(), .days days until the next game
json dumps(..., indent=2) printing the report the way a scoreboard page would receive it

Code Runner Challenge

Homework - Season Stats Report (soccer)

View IPYNB Source
# CODE_RUNNER: Homework - Season Stats Report (soccer)
import json
import math
import statistics
from datetime import date

games = [
    {"date": "2026-09-05", "opponent": "Westview",   "goals": 2, "minutes": 80},
    {"date": "2026-09-12", "opponent": "Rancho Bernardo", "goals": 0, "minutes": 65},
    {"date": "2026-09-19", "opponent": "Westview",   "goals": 1, "minutes": 75},
    {"date": "2026-09-23", "opponent": "Del Norte",  "goals": 3, "minutes": 80},
    {"date": "2026-09-30", "opponent": "Mt. Carmel", "goals": 1, "minutes": 70},
]

def summarize_season(season):
    """
    Summarize one player's soccer season.

    Parameters:
        season: list of dictionaries, one per game, each with
                "date" (str, YYYY-MM-DD), "opponent" (str),
                "goals" (int) and "minutes" (int, minutes played)

    Returns:
        dictionary with
            total_goals (int): goals across every game
            average_goals (float): mean goals per game, rounded to 2 places
            median_goals (int or float): middle value of goals per game
            best_game (dict): {"opponent", "date", "goals"} for the highest-scoring game
            goals_by_opponent (dict): opponent name -> total goals against them
            minutes_per_goal (int or None): total minutes / total goals, rounded down;
                                            None if no goals were scored
    """
    goals = []
    total_minutes = 0
    goals_by_opponent = {}
    best = season[0]
    for game in season:
        goals.append(game["goals"])
        total_minutes += game["minutes"]
        opponent = game["opponent"]
        goals_by_opponent[opponent] = goals_by_opponent.get(opponent, 0) + game["goals"]
        if game["goals"] > best["goals"]:
            best = game

    total_goals = sum(goals)
    return {
        "total_goals": total_goals,
        "average_goals": round(statistics.mean(goals), 2),
        "median_goals": statistics.median(goals),
        "best_game": {"opponent": best["opponent"], "date": best["date"], "goals": best["goals"]},
        "goals_by_opponent": goals_by_opponent,
        "minutes_per_goal": math.floor(total_minutes / total_goals) if total_goals > 0 else None,
    }

report = summarize_season(games)

# Subtracting two dates gives a timedelta; .days is the whole number of days
next_game = date(2026, 10, 17)
report["next_game"] = {"opponent": "Torrey Pines", "date": next_game.isoformat()}
report["days_until_next_game"] = (next_game - date.today()).days

print(json.dumps(report, indent=2))

# requirements.txt lists only EXTERNAL packages that must be installed with pip.
# json, math, statistics and datetime are part of Python's standard library,
# so they come with every Python install and are never listed.
requirements_txt = """pandas
Flask
"""
print()
print("requirements.txt:")
print(requirements_txt)
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Checking the numbers by hand: goals 2 + 0 + 1 + 3 + 1 = 7. Mean 7 / 5 = 1.4. Sorted [0, 1, 1, 2, 3] gives a median of 1. The best game is Del Norte (3). Westview totals 2 + 1 = 3. Minutes 370 / 7 = 52.86, rounded down to 52. All of these match the printed JSON. days_until_next_game is computed from today’s date each time the cell runs.

requirements.txt: a real version of this app would use pandas for the season table and Flask for the scoreboard page, so both are listed. Standard library modules (json, datetime, math, statistics) ship with Python itself, so pip has nothing to install for them.