Reading and writing files, numbering and sorting output, keeping secrets out of code — and one final script that pulls together everything from this whole course.
So far every script has printed to the screen and forgotten everything when it ends. Real IT/SecOps scripts need to save reports, read config, and log results — all of that means reading and writing files.
Name one reason a script might need to WRITE a file (not just print).
Name one reason a script might need to READ a file.
What happens to everything a script print()s once the script ends? (one word)
What happens to what a script WRITES to a file once the script ends?
Name one IT/SecOps task from this course's fixture that would make more sense saved to a file than printed to a screen.
open() and the File Handleopen() gives you a "file handle" — an object connected to a file on disk. You read or write through it, then must close() it when done, or changes might not actually get saved.
f = open("report.txt", "w")
f.write("hello\n")
f.close()
Run the example above. Confirm a file called report.txt now exists with "hello" in it.
Print type(f) right after open() — what kind of object is it?
TextIOWrapper objectWrite to a file WITHOUT calling .close() afterward, in a separate short script. What's risky about this?
Open a file that doesn't exist yet with "w" mode. Does it crash, or create the file?
Try to open a file that doesn't exist with "r" mode (read). What happens?
FileNotFoundErrorOpen problems/02_open_and_handle.py and do all 5 problems locally.
withwith opens a file and closes it automatically for you, even if an error happens in between. It's the standard, safer way to work with files — you almost never call open()/close() by hand in real code.
with open("report.txt", "w") as f:
f.write("hello\n")
# file is automatically closed here, even if something crashed above
Run the example above. Confirm the file is written correctly.
What word right after open(...) names the file handle inside the with block?
as — here, fTry accessing f AFTER the with block ends and writing to it. What happens?
ValueError: I/O operation on closed fileIn one sentence: why is with safer than calling open()/close() by hand?
Write a with block that writes two lines to a file using two separate .write() calls.
Open problems/03_with_statement.py and do all 5 problems locally.
"r" mode)"r" mode opens a file for reading. .read() gets the whole file as one string. .readlines() gets a list of lines. Looping directly over the file object gives you one line at a time.
with open("report.txt", "r") as f:
content = f.read()
print(content)
with open("report.txt", "r") as f:
for line in f:
print(line.strip())
Write a 3-line file, then .read() it. Print the type of what you got back.
<class 'str'>Read the same file with .readlines(). Print the type, then len() of the result.
<class 'list'> then 3Loop directly over the file object (no .readlines()), printing each line WITHOUT .strip(). What extra character shows up?
\n on each lineNow loop again WITH .strip(). What's different?
Read a file that doesn't exist. Wrap it in try/except FileNotFoundError, printing "file not found".
file not foundOpen problems/04_reading_files.py and do all 5 problems locally.
Combine everything: write a file with real data, then read it back and process each line.
Write a file with one department name per line: IT, Security, Engineering, Sales.
Read it back with with/"r", printing each stripped line.
\nCount how many lines the file has, using your loop from Lab 2.
4Read the file again, building a Python list of the stripped department names (not just printing).
['IT', 'Security', 'Engineering', 'Sales']Write a function read_department_list(path) that returns the list from Lab 4, guarding with try/except for a missing file (return [] instead of crashing). Test it on both a real path and a fake one.
[]Open problems/05_reading_checkpoint.py — 5 problems, #5 is a checkpoint combining sub-skills 2-4.
"w" mode)"w" mode opens a file for writing — and completely erases whatever was there before, every time you open it. .write() writes a string exactly as given; it doesn't add newlines for you.
with open("report.txt", "w") as f:
f.write("first line\n")
f.write("second line\n")
Run the example above. Read the file back and print its contents.
first line\nsecond line\nWrite a file with content A, then open it AGAIN with "w" mode and write content B. Read it back — is A still there?
"w" erased it, only B remainsCall .write("no newline") twice in a row (no \n). Read the file back — what do you see?
no newlineno newline (both stuck together)Write a list of 3 department names to a file, one per line, using a loop and .write().
Write a function save_lines(path, lines) that takes a list of strings and writes each on its own line. Test it.
Open problems/06_writing_files.py and do all 5 problems locally.
"a" mode)"a" mode adds to the END of a file instead of erasing it — useful for logs, where you want to keep adding without losing history.
| Mode | If file exists | If file doesn't exist |
|---|---|---|
"r" | reads it | crashes |
"w" | erases, then writes | creates it |
"a" | adds to the end | creates it |
Write "first\n" to a new file with "w". Then open it again with "a" and write "second\n". Read it back.
first\nsecond\n, both keptOpen a brand-new (never-created) filename with "a" mode directly. Does it crash?
Call "a" mode three times in a row, each time writing one line. How many lines does the file have?
3In one sentence: why would a logging script use "a" instead of "w"?
Try opening a file that doesn't exist with "r" mode. Confirm the table above — it crashes.
FileNotFoundErrorOpen problems/07_appending_files.py and do all 5 problems locally.
json.load()Module 9 taught json.loads() for text you already have. json.load() (no "s") reads directly from an open file handle — the two are almost always confused, so notice the difference in the name.
import json
with open("data.json", "w") as f:
json.dump({"department": "IT", "count": 2}, f)
with open("data.json", "r") as f:
data = json.load(f)
print(data)
print(type(data))
Run the example above. Confirm both printed lines.
{'department': 'IT', 'count': 2} then <class 'dict'>What's the name of the function that writes JSON directly to a file (used above)?
json.dump()Try json.loads(f) (the Module 9 version, with the "s") on an open file handle instead of json.load(f). What happens?
TypeError, it expects a string, not a file objectWrite a list of 3 dicts to a file with json.dump(), then read it back with json.load(). Print len() of the result.
3Write a function load_config(path) that reads and returns JSON from a file, returning an empty dict {} if the file doesn't exist.
Open problems/08_json_load.py and do all 5 problems locally.
sorted() With a Custom Keysorted() normally sorts by natural order. Pass key= to tell it exactly what to sort BY — like a dict's specific field, or a case-insensitive version of a string.
names = ["Charlie", "alice", "Bob"]
print(sorted(names))
print(sorted(names, key=str.lower))
users = [{"name": "Rivera"}, {"name": "Chen"}, {"name": "Morris"}]
print(sorted(users, key=lambda u: u["name"]))
Run the first two lines above. Why do plain sorted(names) and sorted(names, key=str.lower) give different orders?
key=str.lower ignores caseRun the users example. Print the sorted list of just names.
['Chen', 'Morris', 'Rivera']Sort the real USERS fixture by profile["lastName"]. Print the first result's first name.
Amy (Chen sorts first)Add reverse=True to your sort from Lab 3. What changes?
Sort USERS by number of groups (len(u["groups"])) using a lambda key. Print the first result's first name.
Open problems/09_sorted_key.py and do all 5 problems locally.
enumerate() for Numbered Outputenumerate() gives you both the index and the item while looping — perfect for numbered reports, instead of manually tracking a counter variable.
names = ["Rivera", "Chen", "Morris"]
for i, name in enumerate(names):
print(i, name)
for i, name in enumerate(names, start=1):
print(f"{i}. {name}")
Run the first loop above. What index does the first item get?
0Run the second loop. What index does the first item get now?
1Combine sorted() and enumerate(): sort names alphabetically, then number them starting at 1.
1. Chen, 2. Morris, 3. RiveraSort the real USERS fixture by lastName, then print a numbered list of first names using enumerate(..., start=1).
Write that numbered, sorted list from Lab 4 to a file using with/"w", one "N. Name" line each. Read it back to confirm.
Open problems/10_enumerate_checkpoint.py — 5 problems, #5 is a checkpoint combining sub-skills 6, 9.
A hardcoded token in your script file gets committed to source control, shown on anyone's screen, and copy-pasted into chat. An env var lives outside the file entirely — the script reads it at runtime, but the value never touches the code.
Write a script with token = "abc123" hardcoded. In one sentence, what's the risk if this file gets shared or committed to git?
Rewrite it to read the token from os.environ.get("MY_TOKEN") instead. Does the token appear anywhere in the file now?
Is a department NAME (like "IT") as sensitive as an API token? Should it also live in an env var? One sentence.
Name one thing OTHER than an API token that belongs in an env var, not in code.
Write a function get_required_env(name) that reads an env var and raises a clear ValueError (with the var name in the message) if it's missing, instead of silently returning None.
Open problems/11_secrets_vs_config.py and do all 5 problems locally.
The capstone for this whole course: find every ACTIVE user with zero group memberships, sort them by last name, number them, and write the report to a file — without crashing on the fixture's missing department or None lastLogin.
def fetch_zero_group_active(users):
return [u for u in users if u["status"] == "ACTIVE" and len(u["groups"]) == 0]
def analyze_sorted(users):
return sorted(users, key=lambda u: u["profile"]["lastName"])
def report(users, path):
with open(path, "w") as f:
for i, u in enumerate(users, start=1):
dept = u["profile"].get("department", "Unknown")
f.write(f"{i}. {u['profile']['lastName']}, {u['profile']['firstName']} ({dept})\n")
def main():
zero_group = fetch_zero_group_active(USERS)
sorted_users = analyze_sorted(zero_group)
report(sorted_users, "zero_groups_report.txt")
main()
Write fetch_zero_group_active exactly as shown. Call it on USERS. Print len() of the result.
1 (only Dana Morris)Write analyze_sorted and report exactly as shown. Run the full chain via main().
zero_groups_report.txt, no crashRead the report file back. Print its contents.
1. Morris, Dana (IT)Why doesn't this script crash on Layla's missing department, even though she's ACTIVE? (she just isn't in the zero-groups result)
report ever touches herExtend main() to read the output path from an env var (REPORT_PATH) with a default of "zero_groups_report.txt", instead of hardcoding it. Run it both with and without the env var set.
Open problems/12_final_checkpoint.py — 5 problems, #5 is the final checkpoint for all of Module 10, and the biggest one in the course so far.
Run python check.py in problems/ to grade everything.