Python, section 5 of 9, 4h

Type hints and pydantic

By the end: You can annotate code so the editor helps you, and validate untrusted data at the boundary.

  • Basic annotations and why they help
  • Optional values, and the checker following your if statements
  • Lists, dicts and unions
  • pydantic for validating real data
  • Generating a JSON schema from a model

New to python? A programming language is the set of written instructions a computer will follow, and Python is the one this field settled on. It reads close to English: a line that filters a list of records looks roughly like a sentence describing that filter. Every model provider ships a Python library, and nearly every machine learning tool is written in it. Why it matters

A type hint is a note in the code saying what kind of value belongs where. Python ignores hints while running, but your editor and a type checker read them, so a mistake surfaces as you type rather than in production. This section covers the annotations worth writing, then pydantic, which uses the same syntax to check data arriving from outside your program — an API response, a form, a model's output — and to reject it clearly when it is wrong.

What a type hint is#

A type hint says what kind of value something should be. Python ignores hints while running. Your editor and a type checker read them and warn you before you run anything.

def greet(name: str) -> str:
    return f"Hello {name}"

count: int = 0
prices: list[float] = []
user: dict[str, str] = {}
The part after the colon is the argument type. The part after -> is what the function returns.

The practical benefit is autocomplete. Once your editor knows a variable is a str, it can offer you every string method. Without hints it can only guess.

Values that might be missing#

This is the most useful thing type hints do. If a function might return nothing, say so, and the checker will insist that callers handle it.

def find_user(user_id: str) -> dict | None:
    ...

user = find_user("abc")
print(user["name"])          # warning: user might be None

if user is not None:
    print(user["name"])      # fine now
dict | None means 'a dict or nothing'. Older code writes this as Optional[dict].

The common shapes#

from typing import Literal

names: list[str]
scores: dict[str, int]
pair: tuple[int, int]
value: int | str                        # either one
role: Literal["user", "assistant"]      # only these exact strings

Where hints stop being enough#

Nothing checks a hint at runtime. If an API sends you a string where you declared an int, Python will not complain and your code will break later with a confusing error. For data arriving from outside your program, you need real validation.

pydantic#

pydantic takes a class definition and turns it into a validator. You describe the shape once, and it checks incoming data against it and converts types where it safely can.

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int
    email: str | None = None      # optional, defaults to None

user = User(name="Jack", age=29)
user.name                          # 'Jack'

User(name="Jack", age="29")      # works: "29" converts to 29
User(name="Jack", age="abc")     # raises ValidationError

Field adds rules the type alone cannot express.

from pydantic import BaseModel, Field

class Invoice(BaseModel):
    number: str
    amount: float = Field(gt=0)                    # must be positive
    currency: str = Field(min_length=3, max_length=3)
    overdue: bool = False

When validation fails you get a list of every problem, not just the first, which makes it possible to tell a user everything that is wrong at once.

from pydantic import ValidationError

try:
    invoice = Invoice.model_validate_json(raw_text)
except ValidationError as e:
    for err in e.errors():
        field = ".".join(str(p) for p in err["loc"])
        print(f"{field}: {err['msg']}")

Why this matters for AI work#

A model returns text. That text is untrusted, exactly like input from a stranger. A pydantic model is where it stops being a guess and becomes something you can rely on. pydantic can also produce a JSON schema from your class, and that schema is exactly what tool definitions and structured output requests need.

Invoice.model_json_schema()
# Send this to the API as the required output shape,
# then validate the response with the same class.
One definition produces both the contract you send and the check you run. They cannot drift apart.

Validate once, at the edge

Parse incoming data into a model as soon as it arrives, then pass the typed object through your program. Validating deeper down means checking the same dictionary in several places, and those checks slowly disagree with each other.

Where people go wrong

Assuming a type hint protects you. It documents intent, it does not check anything. The moment data comes from a file, an API or a model, you need a real validator.

If this comes up in an interview

Are Python type hints enforced at runtime?

No. Python ignores them while running. They exist for editors and type checkers, which is why data from an API still needs real validation with something like pydantic.

What does dict | None mean in a return type?

The function returns either a dictionary or None. A type checker will then warn if you use the result without checking for None first. Older syntax writes it as Optional[dict].

Why use pydantic rather than checking fields by hand?

It gives you validation, type conversion, clear error messages listing every problem, and a JSON schema, all from one class definition. Hand-written checks drift apart from the shape they are meant to enforce.

Where should validation happen in an application?

At the boundary, as soon as untrusted data arrives. After that point everything downstream can assume the data is the right shape, which keeps the checks in one place.

Build this: Define a pydantic model for a blog post with a title, an optional subtitle, a word count that must be positive, and a status limited to draft or published. Feed it three bad payloads and print a clear message for each.

This is one section of Python, 33 hours across 9 sections. Read it in full to get the whole topic in order.

Stop 1 of 50 on the AI Engineer route

0 of 50 topics done · 0h behind you, 603h to go

Next on this routeHTTP & APIsRequests, status codes, retries, rate limits.8h

What you have marked done is stored in this browser only. It does not reach a server, and clearing site data will clear it.