How to Validate Data in Python with Typing Hints
Build a runtime validation helper that checks values against Python type hints like Optional, list, and basic types.
Python code
72 linesfrom typing import Any, Optional, Union, TypeVar, get_origin, get_args
T = TypeVar("T")
def validate(value: Any, expected_type: type) -> Optional[str]:
"""Returns an error message if value doesn't match expected_type, else None."""
# Handle Optional[...] types
origin = get_origin(expected_type)
if origin is Union:
args = get_args(expected_type)
if type(None) in args and value is None:
return None
# Check if value matches any of the Union members
for arg in args:
if arg is not type(None) and validate(value, arg) is None:
return None
return f"Value {value!r} does not match any of {expected_type}"
# Handle list[...], dict[...], etc.
if origin is not None:
if not isinstance(value, origin):
return f"Expected {origin.__name__}, got {type(value).__name__}"
# Validate container elements
args = get_args(expected_type)
if origin is list and args:
for item in value:
error = validate(item, args[0])
if error:
return f"List element error: {error}"
return None
# Basic type check
if expected_type is Any:
return None
if not isinstance(value, expected_type):
return f"Expected {expected_type.__name__}, got {type(value).__name__}"
return None
def validate_dict(data: dict, schema: dict) -> list[str]:
"""Validates a dict against a schema of {field: type}."""
errors = []
for field, expected_type in schema.items():
if field not in data:
errors.append(f"Missing required field: {field}")
continue
error = validate(data[field], expected_type)
if error:
errors.append(f"Field '{field}': {error}")
return errors
if __name__ == "__main__":
user_schema = {
"name": str,
"age": int,
"email": Optional[str],
"tags": list[str],
}
test_users = [
{"name": "Alice", "age": 30, "email": "alice@example.com", "tags": ["admin"]},
{"name": "Bob", "age": "thirty", "tags": [1, 2]},
{"name": "Charlie", "age": 25},
]
for user in test_users:
errors = validate_dict(user, user_schema)
if errors:
print(f"User {user.get('name', '?')}: INVALID -> {errors}")
else:
print(f"User {user['name']}: VALID")
Output
User Alice: VALID
User Bob: INVALID -> ['Field \'age\': Expected int, got str', 'List element error: Expected str, got int']
User Charlie: INVALID -> ['Missing required field: email', "Field 'tags': Expected list, got NoneType"]
How it works
The validate function uses get_origin and get_args from typing to inspect complex type hints at runtime. For Optional types (which are Union with None), it first accepts None, then tries each non-None member. For containers like list, it checks the container type first, then recursively validates each element. The validate_dict function iterates through a schema dictionary, checking required fields and validating each value, collecting all errors rather than stopping at the first one. This pattern provides safe, readable validation for small to medium datasets without needing external libraries.
Common mistakes
- Using isinstance with generic types like list[str] directly, which raises TypeError
- Forgetting that Optional[str] is actually Union[str, None] and needs special handling
- Not recursing into container elements, so list[int] accepts ['a', 'b']
- Stopping at the first validation error instead of collecting all issues
Variations
- Use the pydantic library for automatic validation with dataclasses or models
- Replace the recursive function with a match statement on get_origin result for clearer branching
Real-world use cases
- Validating webhook payloads before processing payment events in a Django API.
- Sanitizing user-submitted JSON in a CLI tool before writing to a database.
- Checking configuration dictionary values at service startup to fail fast with clear messages.
Sponsored
More from Testing & modern typing
- Capture stdout and stderr with pytest capsys easy
- Characterization Test for Legacy Python Code medium
- Dataclass with Type Hints Fields in Python easy
- Dependency Injection in Python for Testability easy
- Design Data Helpers with Python TypedDict and Literal easy
- Fix and Test a Regression Bug in Python with Unit Tests easy
Keep learning
Related tutorials and quizzes for this topic.