P1 — Python Fundamentals I: Language and Data Structures

Learning objective

Write Python functions that manipulate strings, lists, and dictionaries, handle errors gracefully, and produce correctly formatted output — without reference to documentation.

Why this matters

Every AI integration you will architect in Modules 1–5 runs on Python at the execution layer. When an engineering team shows you a failing script, you need to be able to read it, understand the data flow, and identify where the structure breaks down. When you design a prompt pipeline, you need to understand how strings are constructed and passed. When you review a tool definition, you need to understand how dictionaries map to JSON schema. Python literacy is not optional for production AI architecture.

Pre-work — complete before the core concept

  • freeCodeCamp: Learn Python — Full Course for Beginners (YouTube, first 2 hours covering variables through functions) — this is the anchor for Week P1. It covers the mechanics; this week's exercise applies them at architect level.
  • Read: Python official tutorial sections 3–5 at docs.python.org/3/tutorial (numbers, strings, lists, control flow, functions). Skim, do not memorise — the exercise is the learning.

Core concept explained

Python as a configuration language for AI systems

For an enterprise architect, Python serves three roles in AI systems. First, it is the glue layer — the code that receives a user request, formats it into an API call, parses the response, and routes it to the right downstream system. Second, it is the inspection layer — scripts that examine data, validate schemas, and verify outputs behave as designed. Third, it is the specification language — most AI system configurations (tool schemas, prompt templates, agent instructions) are expressed as Python data structures before they become JSON.

Understanding Python data structures is therefore understanding how AI systems are parameterised.

The four structures you will use constantly

A string is how prompts are constructed. F-strings — f"Hello, {name}" — are how dynamic prompt content is inserted. Multi-line strings using triple quotes are how system prompts are stored in code.

A list is how multi-turn conversation history is represented in the Claude API messages array. Understanding list indexing, appending, and slicing is understanding how conversation state is managed.

A dictionary is how JSON objects are represented in Python. Every API request body, every tool definition, every structured response from Claude is a dictionary. Nested dictionaries — dictionaries that contain other dictionaries or lists — are how complex API payloads are constructed.

A function is how prompt templates are encapsulated. A well-designed prompt function takes parameters, constructs the appropriate messages array, and returns a formatted payload ready for the API. Functions with clear parameter names and docstrings are how you document your intent for the engineers implementing your designs.

Error handling

try/except blocks are the difference between a script that crashes and exposes raw error messages and a script that handles failures gracefully and routes them appropriately. In AI systems, you will encounter API errors (rate limits, timeouts, malformed requests), data errors (unexpected input formats, missing fields), and logic errors (tool returning unexpected schema). Every function that calls an external service should have explicit error handling. The except clause should catch specific exception types, not bare except: which swallows everything silently.

import json

def parse_claude_response(response_text: str) -> dict:
    """
    Parse a Claude API response string to a Python dictionary.
    Returns an error dictionary if parsing fails.
    """
    try:
        parsed = json.loads(response_text)
        return {"success": True, "data": parsed}
    except json.JSONDecodeError as e:
        return {"success": False, "error": f"JSON parse failed: {str(e)}", "raw": response_text}
    except Exception as e:
        return {"success": False, "error": f"Unexpected error: {str(e)}"}

Common mistake: Using print() as the primary debugging tool leads to cluttered scripts and missed errors. Use logging module from the start — it gives you log levels, timestamps, and the ability to suppress debug output in production without changing code. Introduce import logging and logging.basicConfig(level=logging.DEBUG) in every script from Week P1 onwards.

Step-by-step exercise — Tier 1 (Guided)

What Tier 1 means for this week: every piece of code in the Core Concept section above is complete and runnable. Your job is to type it into your editor — not copy-paste — and then implement the stub function using the pattern shown. The exercise tells you exactly what to write and where. There are no gaps to infer from context. By the end of this week you will have typed the pattern at least once, which is what makes it recognisable when you encounter it again in Module 1.

Deliverable: A Python module prompt_utils.py — a reusable utility library for prompt construction that you will extend throughout the curriculum. This becomes infrastructure for all later modules.

Step 1 — Set up your project structure

In VS Code, create a new folder cca-prep/ and initialise it as a Git repository:

mkdir cca-prep
cd cca-prep
git init
python -m venv venv
source venv/bin/activate     # Mac/Linux
# venv\Scripts\activate      # Windows

Create a .gitignore file containing:

venv/
__pycache__/
*.pyc
.env

Commit this structure: git add .gitignore && git commit -m "initial project structure"

Step 2 — Write the data structures

Create prompt_utils.py and implement the following function. Write the docstring before the function body — this forces you to specify what each function does before writing how it does it.

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(funcName)s - %(message)s'
)
logger = logging.getLogger(__name__)

def build_message(role: str, content: str) -> dict:
    """
    Build a single message dictionary for the Claude API messages array.
    
    Args:
        role: Either 'user' or 'assistant'
        content: The text content of the message
    
    Returns:
        Dictionary with 'role' and 'content' keys
    
    Raises:
        ValueError: If role is not 'user' or 'assistant'
    """
    # YOUR IMPLEMENTATION HERE
    pass

A note on scope. This is the only function in prompt_utils.py this week, and it's fully solvable from the Core Concept above — the dictionary-construction and error-handling patterns you just typed are exactly what it needs. Three more functions (build_messages_array, extract_text_response, save_interaction_log) get added to this same file in Week P3, once you've learned the Claude API request/response shape and the JSONL file-write pattern those functions depend on. prompt_utils.py is a file you build up incrementally across the prerequisite track — it is not meant to be "finished" after Week P1. (Week P2 also revisits build_message() below to harden its input validation, and adds three unrelated functions of its own.)

Step 3 — Implement and verify

Implement the function. Then write a test_prompt_utils.py script that calls it with at least two inputs — one valid, one intentionally malformed — and verifies the output. Do not use a testing framework; use assert statements and print to verify. Example:

# Test build_message
result = build_message("user", "Hello")
assert result["role"] == "user"
assert result["content"] == "Hello"
print("build_message: PASS")

# Test with invalid role
try:
    build_message("system", "Hello")
    print("build_message invalid role: FAIL — should have raised ValueError")
except ValueError:
    print("build_message invalid role: PASS")

Step 4 — Git commit

Once all tests pass: git add prompt_utils.py test_prompt_utils.py && git commit -m "add build_message with tests"

Step 5 — Reflection

Create journal.md in your cca-prep/ folder. This is the only time you create it — from here through the end of Week P5 it is a single running file you add a new entry to each week, the same way prompt_utils.py accumulates functions rather than getting replaced. Structure and format, established now and unchanged for the rest of the track:

  • Start each week's entry with a level-2 heading naming the week: ## Week P1.
  • Under the heading, write your answer as plain prose paragraphs — sentences that read naturally, not a bulleted list of one-line answers. The instruction "write 3–5 sentences" means an actual short paragraph, not three one-word answers.
  • Be specific: reference the real error message, the real function name, the real line you struggled with. "It was tricky" or "the test passed" is not a reflection, it's a status update.
  • Do not delete or edit previous weeks' entries when you add a new one — the file is a record of how your understanding changed, and later modules occasionally ask you to look back at what you wrote here in Week P1.

Under ## Week P1, write 3–5 sentences answering: you wrote build_message()'s docstring — including the Raises: ValueError line — before writing any implementation code. Did committing to that behaviour on paper first change how you wrote the function, compared to how you'd have written it if you'd started with the code? And: what would break later in the curriculum (Week P2 hardens this same function; Week P3 builds build_messages_array on top of it) if build_message() failed silently on a bad role instead of raising?

Commit the journal: git add journal.md && git commit -m "week P1 reflection"

Self-check questions

Q1. Your build_message() function is called as build_message("system", "You are a helpful assistant"). What should happen?

Q2. Your prompt_utils.py uses logging.basicConfig(level=logging.DEBUG) and logger.debug(...) calls instead of print() statements. Per this week's material, what is the main advantage of this?

Q3. You're extending build_message() to also accept a list of content blocks (not just a string), and you want to catch a TypeError (wrong type entirely) and a ValueError (right type, invalid value) with a different log message for each. Which pattern is correct?

Progression gate

No standalone progression-gate section in the source for this week — self-attestation still available below.