Variables disappear when a Python process ends. Files let an application retain information between runs. The challenge is more than calling a write method: you must choose a location, encode text, recognize failure, and validate what comes back. This lesson applies specific exception handling to a small JSON storage format.
A path is a location, not an open file
pathlib.Path represents a filesystem location using the conventions of the operating system. Join segments with / rather than manually combining platform-specific separators. A relative path is interpreted from the process's current working directory, which may differ from the directory containing your script. The pathlib reference documents these operations.
Path("sessions.json") does not create or open a file. Methods such as read_text and write_text perform the I/O. write_text replaces the contents of an existing file, so do not try examples against important data. The runnable example below uses a temporary directory that cleans itself up.
Text files convert between characters and bytes through an encoding. Specify UTF-8 so names and topics do not depend on a machine's default encoding. For binary formats, work with bytes and binary mode instead. A with statement keeps an open resource available inside its block and closes it on exit, including when an exception occurs. See the input/output tutorial.
JSON syntax is only the first check
JSON is a text format for objects, arrays, strings, numbers, booleans, and null. Python's json module converts dictionaries, lists, and compatible primitive values to and from it. dumps produces a string; loads reads a string. dump and load work with file objects.
A successful parse does not prove that a value matches your application's expected structure. An array containing true could be perfectly valid JSON and still be an invalid list of study sessions. The JSON reference describes conversion behavior and input-size concerns.
Save the complete example as json_demo.py:
import json
from pathlib import Path
from tempfile import TemporaryDirectory
def total_minutes(records: object) -> int:
if not isinstance(records, list):
raise ValueError("sessions must be a list")
total = 0
for row in records:
if not isinstance(row, dict):
raise ValueError("each session must be an object")
topic = row.get("topic")
minutes = row.get("minutes")
if not isinstance(topic, str) or not topic.strip():
raise ValueError("topic must be nonempty text")
if type(minutes) is not int or minutes <= 0:
raise ValueError("minutes must be a positive integer")
total += minutes
return total
with TemporaryDirectory() as directory:
path = Path(directory) / "sessions.json"
records = [
{"topic": "fichiers", "minutes": 20},
{"topic": "JSON", "minutes": 30},
]
text = json.dumps(records, ensure_ascii=False, indent=2, allow_nan=False)
path.write_text(text + "\n", encoding="utf-8")
with path.open(encoding="utf-8") as source:
loaded = json.load(source)
print(total_minutes(loaded))
print(loaded[0]["topic"])
Expect 50 and fichiers. ensure_ascii=False keeps non-ASCII characters readable in the serialized text. allow_nan=False prevents the encoder from writing nonstandard NaN and infinity values. This record schema accepts only positive integers for durations, excluding booleans explicitly. The temporary-file reference explains automatic cleanup.
Plan for failures and partial writes
Reading a missing file raises FileNotFoundError; malformed JSON raises json.JSONDecodeError; invalid UTF-8 can raise UnicodeDecodeError. Permission and other filesystem failures are OSError subclasses. Handle them according to what your application can reasonably do. For a new tracker, a missing file can mean no sessions yet. Malformed existing data should produce an error and remain available for repair.
Do not check existence and assume the following read is guaranteed to succeed. Another process can change the filesystem between those operations. Attempt the read and handle the relevant exception.
Writing directly over a destination can leave truncated data if the process fails. For a small local tool, write a temporary sibling and replace the destination after writing succeeds. That reduces exposure to partial contents, but it does not coordinate simultaneous writers or guarantee survival of every power failure. The final project makes its storage limits explicit.
Pitfalls and practice
JSON object keys are strings. Sets, paths, custom objects, and dates need an explicit representation before serialization. Never use eval to parse data. Keep untrusted inputs bounded before parsing; a small-looking API call can otherwise load a huge response into memory.
Exercise: change one stored duration to the JSON boolean true, then try malformed JSON. Explain which stage rejects each input.
Solution guidance: decoding accepts the boolean, but total_minutes rejects its type. Broken JSON syntax fails during json.load, before record validation runs. Keep those two failure categories distinct so a useful error can identify the repair needed.
Next: Python Testing with unittest.

