Have you ever merged a config file, applied an input that says "update only these fields", or handled an API payload, only to overwrite data because "missing", None, and "unset" were treated as the same thing?
In real-world projects, "no value" has at least three meanings:
- a missing key (the field is absent),
- an explicit
None(the value is present and intentionally null), - an unset input (the caller didn't specify the field, so you must not touch it).
When we collapse these into T | None (a nullable type) or fall back to dict[str, Any], type checking becomes noisy, branching becomes fragile, and refactors get risky. Every field nullable, every check defensive, every change scary.
This is a practical talk about using typing to model and safely consume real-world data with TypedDict and dataclasses, not a typing tutorial or a framework pitch. We'll walk through one end-to-end example (payload -> normalization -> domain model -> safe updates) with before/after code and focus on patterns you can apply immediately:
- Use
Required/NotRequiredto model missing keys instead of abusingT | None. - Use
T | Noneonly whenNoneis a meaningful value. - Represent unset inputs with a sentinel pattern (starting from
UNSET = object()and optionally evolving totyping_extensions.Sentinel) so partial updates don't silently overwrite data and the type checker can distinguish unset fromNone. - Keep extraction code honest with type aliases,
TypeIs, and (optionally) smallmatchcases for readability.
After this session, you'll be able to define stricter data models, write safer extraction code, and make schema changes less painful, because your types will reflect what your data actually means. This talk is for intermediate Python developers who already use type hints in production.