Review Python code ensuring strict type safety with Python 3.12+ conventions. Use when reviewing Python code, writing new Python code, or refactoring existing Python code.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
examples/EXAMPLES.mdexamples/example-1-mixed-syntax/expected.mdexamples/example-1-mixed-syntax/input.pyexamples/example-1-mixed-syntax/scenario.mdexamples/example-2-untyped-code/expected.mdexamples/example-2-untyped-code/input.pyexamples/example-2-untyped-code/scenario.mdexamples/example-3-complex-generics/expected.mdexamples/example-3-complex-generics/input.pyexamples/example-3-complex-generics/scenario.mdThis skill provides systematic Python code review with emphasis on Python 3.12+ type annotations. It enforces strict typing and built-in generic types (list, dict, type) instead of deprecated typing equivalents (List, Dict, Type).
Scope: This skill focuses exclusively on type annotations, not code logic, design patterns, performance, or general quality.
Focus only on type annotation issues.
Check every function, method, and variable for proper type annotations:
# ✅ CORRECT - Modern Python 3.12 syntax
def process_items(items: list[str], count: int) -> dict[str, int]:
result: dict[str, int] = {}
for item in items[:count]:
result[item] = len(item)
return result
# ❌ INCORRECT - Missing types
def process_items(items, count):
result = {}
for item in items[:count]:
result[item] = len(item)
return result
# ❌ INCORRECT - Old typing module syntax
from typing import List, Dict
def process_items(items: List[str], count: int) -> Dict[str, int]:
...
Required checks:
-> None if no return)typing for List, Dict, Set, Tuple, TypeVerify usage of built-in generics instead of typing module equivalents:
Use these (Python 3.12+):
list[T] not List[T]dict[K, V] not Dict[K, V]set[T] not Set[T]tuple[T, ...] not Tuple[T, ...]type[T] not Type[T]Import from typing:
Any, Optional, Union, Callable, Protocol, TypeVar, GenericLiteral, TypedDict, NotRequired, Requiredoverload, final, override# ✅ CORRECT - Modern syntax
from typing import Protocol, TypeVar
T = TypeVar('T')
class Container(Protocol):
def get_items(self) -> list[str]: ...
def merge_dicts(a: dict[str, int], b: dict[str, int]) -> dict[str, int]:
return {**a, **b}
# ❌ INCORRECT - Old typing module imports
from typing import List, Dict, Protocol
def merge_dicts(a: Dict[str, int], b: Dict[str, int]) -> Dict[str, int]:
return {**a, **b}
Check for proper use of modern union syntax:
# ✅ CORRECT - Python 3.10+ union syntax
def find_user(user_id: int) -> dict[str, str] | None:
return users.get(user_id)
def process(value: int | str | float) -> str:
return str(value)
# ❌ INCORRECT - Old Optional/Union syntax
from typing import Optional, Union
def find_user(user_id: int) -> Optional[dict[str, str]]:
return users.get(user_id)
def process(value: Union[int, str, float]) -> str:
return str(value)
Ensure no untyped code exists:
Check these locations:
# ✅ CORRECT - Fully typed
class UserService:
_cache: dict[int, str]
def __init__(self, cache: dict[int, str] | None = None) -> None:
self._cache = cache or {}
def get_user(self, user_id: int) -> str | None:
return self._cache.get(user_id)
# ❌ INCORRECT - Untyped attribute
class UserService:
def __init__(self, cache=None):
self._cache = cache or {}
def get_user(self, user_id):
return self._cache.get(user_id)
Run static type checker:
mypy --strict your_module.py
Configure in pyproject.toml:
[tool.mypy]
strict = true
python_version = "3.12"
List, Dict, Set, Tuple, Type from typing| syntax, not Union[]X | None, not Optional[X]mypy --strict passes without errors# type: ignore comments without justificationBefore (❌):
from typing import Dict, List, Optional
def get_users(limit=10, offset=0):
users = fetch_from_db(limit, offset)
return {"users": users, "count": len(users)}
def create_user(data):
user_id = save_to_db(data)
return {"id": user_id}
After (✅):
from typing import TypedDict
class UserResponse(TypedDict):
users: list[dict[str, str]]
count: int
class CreateResponse(TypedDict):
id: int
def get_users(limit: int = 10, offset: int = 0) -> UserResponse:
users: list[dict[str, str]] = fetch_from_db(limit, offset)
return {"users": users, "count": len(users)}
def create_user(data: dict[str, str]) -> CreateResponse:
user_id: int = save_to_db(data)
return {"id": user_id}
Before (❌):
from typing import List, Dict, Callable
def transform_data(data, transformers):
result = []
for item in data:
for transformer in transformers:
item = transformer(item)
result.append(item)
return result
After (✅):
from typing import Callable, TypeVar
T = TypeVar('T')
def transform_data(
data: list[T],
transformers: list[Callable[[T], T]]
) -> list[T]:
result: list[T] = []
for item in data:
for transformer in transformers:
item = transformer(item)
result.append(item)
return result
Before (❌):
from typing import Generic, TypeVar, List, Optional
T = TypeVar('T')
class Container(Generic[T]):
def __init__(self):
self._items = []
def add(self, item):
self._items.append(item)
def get_all(self):
return self._items
After (✅):
from typing import Generic, TypeVar
T = TypeVar('T')
class Container(Generic[T]):
_items: list[T]
def __init__(self) -> None:
self._items = []
def add(self, item: T) -> None:
self._items.append(item)
def get_all(self) -> list[T]:
return self._items
❌ Don't: Import List, Dict, Set, Tuple, Type from typing module
list, dict, set, tuple, type with generic syntax❌ Don't: Use Optional[X] or Union[X, Y] syntax
X | None and X | Y union syntax❌ Don't: Leave any function, method, or class attribute untyped
❌ Don't: Use # type: ignore without explanation
❌ Don't: Use Any as a shortcut to avoid thinking about types
❌ Don't: Skip type annotations on simple functions
❌ Don't: Mix old and new typing syntax in the same codebase
Follow instructions in examples/EXAMPLES.md.
Remember: No untyped code. Use Python 3.12+ built-in generics. Type everything.