python-typing-ops
Python type hints and type safety patterns. Triggers on: type hints, typing, TypeVar, Generic, Protocol, mypy, pyright, type annotation, overload, TypedDict.
Install
npx skills add https://github.com/0xDarkMatter/claude-mods/tree/main/skills/python-typing-ops
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install 0xdarkmatter-claude-mods@llmmart
git clone https://github.com/0xDarkMatter/claude-mods.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole 0xdarkmatter/claude-mods collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Python Typing Patterns
Modern type hints for safe, documented Python code.
Basic Annotations
# Variables
name: str = "Alice"
count: int = 42
items: list[str] = ["a", "b"]
mapping: dict[str, int] = {"key": 1}
# Function signatures
def greet(name: str, times: int = 1) -> str:
return f"Hello, {name}!" * times
# None handling
def find(id: int) -> str | None:
return db.get(id) # May return None
Collections
from collections.abc import Sequence, Mapping, Iterable
# Use collection ABCs for flexibility
def process(items: Sequence[str]) -> list[str]:
"""Accepts list, tuple, or any sequence."""
return [item.upper() for item in items]
def lookup(data: Mapping[str, int], key: str) -> int:
"""Accepts dict or any mapping."""
return data.get(key, 0)
# Nested types
Matrix = list[list[float]]
Config = dict[str, str | int | bool]
Optional and Union
# Modern syntax (3.10+)
def find(id: int) -> User | None:
pass
def parse(value: str | int | float) -> str:
pass
# With default None
def fetch(url: str, timeout: float | None = None) -> bytes:
pass
TypedDict
from typing import TypedDict, Required, NotRequired
class UserDict(TypedDict):
id: int
name: str
email: str | None
class ConfigDict(TypedDict, total=False): # All optional
debug: bool
log_level: str
class APIResponse(TypedDict):
data: Required[list[dict]]
error: NotRequired[str]
def process_user(user: UserDict) -> str:
return user["name"] # Type-safe key access
Callable
from collections.abc import Callable
# Function type
Handler = Callable[[str, int], bool]
def register(callback: Callable[[str], None]) -> None:
pass
# With keyword args (use Protocol instead)
from typing import Protocol
class Processor(Protocol):
def __call__(self, data: str, *, verbose: bool = False) -> int:
...
Generics
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T | None:
return items[0] if items else None
# Bounded TypeVar
from typing import SupportsFloat
N = TypeVar("N", bound=SupportsFloat)
def average(values: list[N]) -> float:
return sum(float(v) for v in values) / len(values)
Protocol (Structural Typing)
from typing import Protocol
class Readable(Protocol):
def read(self, n: int = -1) -> bytes:
...
def load(source: Readable) -> dict:
"""Accepts any object with read() method."""
data = source.read()
return json.loads(data)
# Works with file, BytesIO, custom classes
load(open("data.json", "rb"))
load(io.BytesIO(b"{}"))
Type Guards
from typing import TypeGuard
def is_string_list(val: list[object]) -> TypeGuard[list[str]]:
return all(isinstance(x, str) for x in val)
def process(items: list[object]) -> None:
if is_string_list(items):
# items is now list[str]
print(", ".join(items))
Literal and Final
from typing import Literal, Final
Mode = Literal["read", "write", "append"]
def open_file(path: str, mode: Mode) -> None:
pass
# Constants
MAX_SIZE: Final = 1024
API_VERSION: Final[str] = "v2"
Quick Reference
| Type | Use Case |
|---|---|
X \| None |
Optional value |
list[T] |
Homogeneous list |
dict[K, V] |
Dictionary |
Callable[[Args], Ret] |
Function type |
TypeVar("T") |
Generic parameter |
Protocol |
Structural typing |
TypedDict |
Dict with fixed keys |
Literal["a", "b"] |
Specific values only |
Final |
Cannot be reassigned |
Type Checker Commands
# mypy (run inside the project env)
uv run mypy src/ --strict
# pyright
uv run pyright src/
# In pyproject.toml
[tool.mypy]
strict = true
python_version = "3.11"
Emerging: ty — Astral's Rust-based type checker (same toolchain as uv +
ruff), dramatically faster than mypy. Still in preview (pre-1.0), so mypy or
pyright remain the production default — but worth watching, and easy to try:
uvx ty check. Adopt for new projects once it stabilizes.
Additional Resources
./references/generics-advanced.md- TypeVar, ParamSpec, TypeVarTuple./references/protocols-patterns.md- Structural typing, runtime protocols./references/type-narrowing.md- Guards, isinstance, assert./references/mypy-config.md- mypy/pyright configuration./references/runtime-validation.md- Pydantic v2, typeguard, beartype./references/overloads.md- @overload decorator patterns
Scripts
./scripts/check-types.sh- Run type checkers with common options
Assets
./assets/pyproject-typing.toml- Recommended mypy/pyright config
See Also
This is a foundation skill with no prerequisites.
Related Skills:
python-pytest-ops- Type-safe fixtures and mocking
Build on this skill:
python-async-ops- Async type annotationspython-fastapi-ops- Pydantic models and validationpython-database-ops- SQLAlchemy type annotations
Files (claude-mods)
-
assets
-
pyproject-typing.toml 2.5 KB
# pyproject.toml - Type checker configuration # Copy these sections to your pyproject.toml # ============================================================ # mypy Configuration # ============================================================ [tool.mypy] # Python version to target python_version = "3.11" # Enable strict mode (recommended for new projects) strict = true # Additional strictness warn_return_any = true warn_unused_ignores = true warn_unreachable = true # Error reporting show_error_codes = true show_error_context = true show_column_numbers = true pretty = true # Paths files = ["src", "tests"] exclude = [ "migrations/", "venv/", ".venv/", "__pycache__/", "build/", "dist/", ] # Plugin support (uncomment as needed) # plugins = [ # "pydantic.mypy", # "sqlalchemy.ext.mypy.plugin", # ] # ============================================================ # Per-module overrides # ============================================================ # Relax strictness for tests [[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false disallow_untyped_calls = false # Ignore missing stubs for common libraries [[tool.mypy.overrides]] module = [ "requests.*", "boto3.*", "botocore.*", "celery.*", "redis.*", ] ignore_missing_imports = true # Legacy code - gradually add types # [[tool.mypy.overrides]] # module = "legacy.*" # ignore_errors = true # ============================================================ # pyright Configuration # ============================================================ [tool.pyright] # Python version pythonVersion = "3.11" # Paths include = ["src"] exclude = [ "**/node_modules", "**/__pycache__", "venv", ".venv", "build", "dist", ] # Type checking mode: off, basic, standard, strict typeCheckingMode = "strict" # Report settings (strict mode enables all by default) reportMissingTypeStubs = false reportUnusedImport = "warning" reportUnusedVariable = "warning" reportUnusedFunction = "warning" # Useful additional checks reportUninitializedInstanceVariable = true reportIncompatibleMethodOverride = true reportIncompatibleVariableOverride = true # ============================================================ # Recommended dev dependencies # ============================================================ # [project.optional-dependencies] # dev = [ # "mypy>=1.8.0", # "pyright>=1.1.350", # # Common type stubs # "types-requests", # "types-redis", # "types-PyYAML", # "types-python-dateutil", # ]
-
-
references
-
generics-advanced.md 6.3 KB
# Advanced Generics Deep dive into Python's generic type system. ## TypeVar Basics ```python from typing import TypeVar # Unconstrained TypeVar T = TypeVar("T") def identity(x: T) -> T: return x # Usage - type is preserved reveal_type(identity(42)) # int reveal_type(identity("hello")) # str ``` ## Bounded TypeVar ```python from typing import TypeVar # Upper bound - T must be subtype of bound class Animal: def speak(self) -> str: return "..." class Dog(Animal): def speak(self) -> str: return "woof" A = TypeVar("A", bound=Animal) def make_speak(animal: A) -> A: print(animal.speak()) return animal # Works with Animal or any subclass dog = make_speak(Dog()) # Returns Dog, not Animal ``` ## Constrained TypeVar ```python from typing import TypeVar # Constrained to specific types StrOrBytes = TypeVar("StrOrBytes", str, bytes) def concat(a: StrOrBytes, b: StrOrBytes) -> StrOrBytes: return a + b # Must be same type concat("a", "b") # OK -> str concat(b"a", b"b") # OK -> bytes # concat("a", b"b") # Error: can't mix ``` ## Generic Classes ```python from typing import Generic, TypeVar T = TypeVar("T") class Stack(Generic[T]): def __init__(self) -> None: self._items: list[T] = [] def push(self, item: T) -> None: self._items.append(item) def pop(self) -> T: return self._items.pop() def peek(self) -> T | None: return self._items[-1] if self._items else None # Usage int_stack: Stack[int] = Stack() int_stack.push(1) int_stack.push(2) value = int_stack.pop() # int str_stack: Stack[str] = Stack() str_stack.push("hello") ``` ## Multiple Type Parameters ```python from typing import Generic, TypeVar K = TypeVar("K") V = TypeVar("V") class Pair(Generic[K, V]): def __init__(self, key: K, value: V) -> None: self.key = key self.value = value def swap(self) -> "Pair[V, K]": return Pair(self.value, self.key) pair: Pair[str, int] = Pair("age", 30) swapped = pair.swap() # Pair[int, str] ``` ## Self Type (Python 3.11+) ```python from typing import Self class Builder: def __init__(self) -> None: self.value = "" def add(self, text: str) -> Self: self.value += text return self def build(self) -> str: return self.value class HTMLBuilder(Builder): def tag(self, name: str) -> Self: self.value = f"<{name}>{self.value}</{name}>" return self # Chaining works with correct types html = HTMLBuilder().add("Hello").tag("p").build() ``` ## ParamSpec (Python 3.10+) ```python from typing import ParamSpec, TypeVar, Callable P = ParamSpec("P") R = TypeVar("R") def with_logging(func: Callable[P, R]) -> Callable[P, R]: """Decorator that preserves function signature.""" def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper @with_logging def greet(name: str, excited: bool = False) -> str: return f"Hello, {name}{'!' if excited else '.'}" # Signature preserved: greet("Alice", excited=True) # OK # greet(123) # Type error ``` ## TypeVarTuple (Python 3.11+) ```python from typing import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") def concat_tuples( a: tuple[*Ts], b: tuple[*Ts] ) -> tuple[*Ts, *Ts]: return (*a, *b) # Usage result = concat_tuples((1, "a"), (2, "b")) # result: tuple[int, str, int, str] ``` ## Covariance and Contravariance ```python from typing import TypeVar # Covariant: Can use subtype T_co = TypeVar("T_co", covariant=True) class Reader(Generic[T_co]): def read(self) -> T_co: ... # Contravariant: Can use supertype T_contra = TypeVar("T_contra", contravariant=True) class Writer(Generic[T_contra]): def write(self, value: T_contra) -> None: ... # Invariant (default): Must be exact type T = TypeVar("T") # Invariant class Container(Generic[T]): def get(self) -> T: ... def set(self, value: T) -> None: ... ``` ## Generic Protocols ```python from typing import Protocol, TypeVar T = TypeVar("T") class Comparable(Protocol[T]): def __lt__(self, other: T) -> bool: ... def __gt__(self, other: T) -> bool: ... def max_value(a: T, b: T) -> T: return a if a > b else b # Works with any comparable type max_value(1, 2) # int max_value("a", "b") # str ``` ## Type Aliases ```python from typing import TypeAlias # Simple alias Vector: TypeAlias = list[float] Matrix: TypeAlias = list[Vector] # Generic alias from typing import TypeVar T = TypeVar("T") Result: TypeAlias = tuple[T, str | None] def parse(data: str) -> Result[int]: try: return (int(data), None) except ValueError as e: return (0, str(e)) ``` ## NewType ```python from typing import NewType # Create distinct types for type safety UserId = NewType("UserId", int) OrderId = NewType("OrderId", int) def get_user(user_id: UserId) -> dict: ... def get_order(order_id: OrderId) -> dict: ... user_id = UserId(42) order_id = OrderId(42) get_user(user_id) # OK # get_user(order_id) # Type error! # get_user(42) # Type error! ``` ## PEP 695 Type Parameter Syntax (Python 3.12+) ```python # No explicit TypeVar declaration needed def first[T](items: list[T]) -> T | None: return items[0] if items else None class Stack[T]: def push(self, item: T) -> None: ... # Bounded type parameters def largest[T: (int, float)](items: list[T]) -> T: return max(items) # Type alias statement replaces TypeAlias type Vector = list[float] type Result[T] = tuple[T, str | None] ``` ## Override Decorator (Python 3.12+) ```python from typing import override class Parent: def greet(self) -> str: return "Hello" class Child(Parent): @override def greet(self) -> str: # Type checker errors if Parent.greet is renamed return "Hi" ``` ## Best Practices 1. **Name TypeVars descriptively** - `T`, `K`, `V` for simple cases; `ItemT`, `KeyT` for complex 2. **Use bounds** - When you need method access on type parameter 3. **Prefer Protocol** - Over ABC for structural typing 4. **Use Self** - Instead of quoted class names in return types 5. **Covariance** - For read-only containers 6. **Contravariance** - For write-only/function parameter types 7. **Invariance** - For mutable containers (default, usually correct) -
mypy-config.md 5.8 KB
# mypy and pyright Configuration Type checker setup for strict, practical type safety. ## mypy Configuration ### pyproject.toml (Recommended) ```toml [tool.mypy] python_version = "3.11" strict = true warn_return_any = true warn_unused_ignores = true show_error_codes = true show_error_context = true # Paths files = ["src", "tests"] exclude = [ "migrations/", "venv/", "__pycache__/", ] # Per-module overrides [[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false [[tool.mypy.overrides]] module = [ "requests.*", "boto3.*", "botocore.*", ] ignore_missing_imports = true ``` ### mypy.ini (Alternative) ```ini [mypy] python_version = 3.11 strict = True warn_return_any = True warn_unused_ignores = True show_error_codes = True [mypy-tests.*] disallow_untyped_defs = False [mypy-requests.*] ignore_missing_imports = True ``` ## mypy Flags Explained ### Strict Mode Components ```toml [tool.mypy] # strict = true enables all of these: warn_unused_configs = true disallow_any_generics = true disallow_subclassing_any = true disallow_untyped_calls = true disallow_untyped_defs = true disallow_incomplete_defs = true check_untyped_defs = true disallow_untyped_decorators = true warn_redundant_casts = true warn_unused_ignores = true warn_return_any = true no_implicit_reexport = true strict_equality = true extra_checks = true ``` ### Commonly Adjusted Flags ```toml [tool.mypy] # Allow untyped defs in some files disallow_untyped_defs = true # But not for tests [[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false # Ignore third-party stubs ignore_missing_imports = true # Global fallback # Show where errors occur show_error_context = true show_column_numbers = true show_error_codes = true # Error output format pretty = true ``` ## pyright Configuration ### pyrightconfig.json ```json { "include": ["src"], "exclude": ["**/node_modules", "**/__pycache__", "venv"], "pythonVersion": "3.11", "pythonPlatform": "All", "typeCheckingMode": "strict", "reportMissingImports": true, "reportMissingTypeStubs": false, "reportUnusedImport": true, "reportUnusedClass": true, "reportUnusedFunction": true, "reportUnusedVariable": true, "reportDuplicateImport": true, "reportPrivateUsage": true, "reportConstantRedefinition": true, "reportIncompatibleMethodOverride": true, "reportIncompatibleVariableOverride": true, "reportInconsistentConstructor": true, "reportOverlappingOverload": true, "reportUninitializedInstanceVariable": true } ``` ### pyproject.toml (pyright) ```toml [tool.pyright] include = ["src"] exclude = ["**/node_modules", "**/__pycache__", "venv"] pythonVersion = "3.11" typeCheckingMode = "strict" reportMissingTypeStubs = false ``` ## Type Checking Modes ### pyright Modes ```json { "typeCheckingMode": "off" // No checking "typeCheckingMode": "basic" // Basic checks "typeCheckingMode": "standard" // Standard checks "typeCheckingMode": "strict" // All checks enabled } ``` ## Inline Type Ignores ```python # Ignore specific error result = some_call() # type: ignore[arg-type] # Ignore all errors on line result = some_call() # type: ignore # With mypy error code value = data["key"] # type: ignore[typeddict-item] # With pyright result = func() # pyright: ignore[reportGeneralTypeIssues] ``` ## Type Stub Files (.pyi) ```python # mymodule.pyi - Type stubs for mymodule.py def process(data: dict[str, int]) -> list[int]: ... class Handler: def __init__(self, name: str) -> None: ... def handle(self, event: Event) -> bool: ... ``` ### Stub Package Structure ``` stubs/ ├── mypackage/ │ ├── __init__.pyi │ ├── module.pyi │ └── subpackage/ │ └── __init__.pyi ``` ```toml [tool.mypy] mypy_path = "stubs" ``` ## CI Integration ### GitHub Actions ```yaml name: Type Check on: [push, pull_request] jobs: mypy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install uv uses: astral-sh/setup-uv@v5 - name: Install dependencies run: uv sync - name: Run mypy run: uv run mypy src/ pyright: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install uv uses: astral-sh/setup-uv@v5 - name: Install dependencies run: uv sync - name: Run pyright uses: jakebailey/pyright-action@v2 ``` ### Pre-commit Hook ```yaml # .pre-commit-config.yaml repos: - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.8.0 hooks: - id: mypy additional_dependencies: [types-requests] args: [--strict] ``` ## Common Type Stubs ```bash # Install type stubs (dev-only) uv add --dev types-requests types-redis types-PyYAML "boto3-stubs[essential]" # Or use mypy to find missing stubs uv run mypy --install-types src/ ``` ## Gradual Typing Strategy ### Phase 1: Basic ```toml [tool.mypy] python_version = "3.11" warn_return_any = true warn_unused_ignores = true ``` ### Phase 2: Stricter ```toml [tool.mypy] python_version = "3.11" disallow_untyped_defs = true disallow_incomplete_defs = true check_untyped_defs = true ``` ### Phase 3: Strict ```toml [tool.mypy] python_version = "3.11" strict = true # Temporarily ignore problem areas [[tool.mypy.overrides]] module = "legacy.*" ignore_errors = true ``` ## Quick Reference | mypy Flag | Description | |-----------|-------------| | `--strict` | Enable all strict checks | | `--show-error-codes` | Show error codes for ignores | | `--ignore-missing-imports` | Skip untyped libraries | | `--python-version 3.11` | Target Python version | | `--install-types` | Install missing stubs | | `--config-file` | Specify config file | | pyright Mode | Description | |--------------|-------------| | `off` | No checking | | `basic` | Minimal checks | | `standard` | Recommended | | `strict` | All checks | -
overloads.md 6.3 KB
# Function Overloads Type-safe function signatures with @overload. ## Basic Overloads ```python from typing import overload, Literal # Overload signatures (no implementation) @overload def process(data: str) -> str: ... @overload def process(data: bytes) -> bytes: ... @overload def process(data: int) -> int: ... # Actual implementation def process(data: str | bytes | int) -> str | bytes | int: if isinstance(data, str): return data.upper() elif isinstance(data, bytes): return data.upper() else: return data * 2 # Type checker knows the return type result = process("hello") # str result = process(b"hello") # bytes result = process(42) # int ``` ## Overloads with Literal ```python from typing import overload, Literal @overload def fetch(url: str, format: Literal["json"]) -> dict: ... @overload def fetch(url: str, format: Literal["text"]) -> str: ... @overload def fetch(url: str, format: Literal["bytes"]) -> bytes: ... def fetch(url: str, format: str) -> dict | str | bytes: response = requests.get(url) if format == "json": return response.json() elif format == "text": return response.text else: return response.content # Usage - return type is known data = fetch("https://api.example.com", "json") # dict text = fetch("https://api.example.com", "text") # str ``` ## Overloads with Optional Parameters ```python from typing import overload @overload def get_user(user_id: int) -> User: ... @overload def get_user(user_id: int, include_posts: Literal[True]) -> UserWithPosts: ... @overload def get_user(user_id: int, include_posts: Literal[False]) -> User: ... def get_user(user_id: int, include_posts: bool = False) -> User | UserWithPosts: user = db.get_user(user_id) if include_posts: user.posts = db.get_posts(user_id) return UserWithPosts(**user.__dict__) return user # Type-safe usage user = get_user(1) # User user_with_posts = get_user(1, include_posts=True) # UserWithPosts ``` ## Overloads with None Returns ```python from typing import overload @overload def find(items: list[T], predicate: Callable[[T], bool]) -> T | None: ... @overload def find(items: list[T], predicate: Callable[[T], bool], default: T) -> T: ... def find( items: list[T], predicate: Callable[[T], bool], default: T | None = None ) -> T | None: for item in items: if predicate(item): return item return default # Without default - might be None result = find([1, 2, 3], lambda x: x > 5) # int | None # With default - never None result = find([1, 2, 3], lambda x: x > 5, default=0) # int ``` ## Class Method Overloads ```python from typing import overload, Self from dataclasses import dataclass @dataclass class Point: x: float y: float @overload @classmethod def from_tuple(cls, coords: tuple[float, float]) -> Self: ... @overload @classmethod def from_tuple(cls, coords: tuple[float, float, float]) -> "Point3D": ... @classmethod def from_tuple(cls, coords: tuple[float, ...]) -> "Point | Point3D": if len(coords) == 2: return cls(coords[0], coords[1]) elif len(coords) == 3: return Point3D(coords[0], coords[1], coords[2]) raise ValueError("Expected 2 or 3 coordinates") ``` ## Overloads with Generics ```python from typing import overload, TypeVar, Sequence T = TypeVar("T") K = TypeVar("K") V = TypeVar("V") @overload def first(items: Sequence[T]) -> T | None: ... @overload def first(items: Sequence[T], default: T) -> T: ... def first(items: Sequence[T], default: T | None = None) -> T | None: return items[0] if items else default @overload def get(d: dict[K, V], key: K) -> V | None: ... @overload def get(d: dict[K, V], key: K, default: V) -> V: ... def get(d: dict[K, V], key: K, default: V | None = None) -> V | None: return d.get(key, default) ``` ## Async Overloads ```python from typing import overload @overload async def fetch_data(url: str, as_json: Literal[True]) -> dict: ... @overload async def fetch_data(url: str, as_json: Literal[False] = False) -> str: ... async def fetch_data(url: str, as_json: bool = False) -> dict | str: async with aiohttp.ClientSession() as session: async with session.get(url) as response: if as_json: return await response.json() return await response.text() ``` ## Property Overloads (Getter/Setter) ```python from typing import overload class Temperature: def __init__(self, celsius: float): self._celsius = celsius @property def value(self) -> float: return self._celsius @overload def convert(self, unit: Literal["C"]) -> float: ... @overload def convert(self, unit: Literal["F"]) -> float: ... @overload def convert(self, unit: Literal["K"]) -> float: ... def convert(self, unit: str) -> float: if unit == "C": return self._celsius elif unit == "F": return self._celsius * 9/5 + 32 elif unit == "K": return self._celsius + 273.15 raise ValueError(f"Unknown unit: {unit}") ``` ## Common Patterns ```python from typing import overload, Literal, TypeVar T = TypeVar("T") # Pattern 1: Return type based on flag @overload def parse(data: str, strict: Literal[True]) -> Result: ... @overload def parse(data: str, strict: Literal[False] = False) -> Result | None: ... # Pattern 2: Different return for different input types @overload def normalize(value: str) -> str: ... @overload def normalize(value: list[str]) -> list[str]: ... @overload def normalize(value: dict[str, str]) -> dict[str, str]: ... # Pattern 3: Optional vs required parameter @overload def create(name: str) -> Item: ... @overload def create(name: str, *, template: str) -> Item: ... ``` ## Quick Reference | Pattern | Use Case | |---------|----------| | `@overload` | Define signature (no body) | | `Literal["value"]` | Specific string/int values | | `T \| None` vs `T` | Optional default changes return | | Implementation | Must handle all overload cases | | Rule | Description | |------|-------------| | No body in overloads | Use `...` (ellipsis) | | Implementation last | After all overloads | | Cover all cases | Implementation must accept all overload inputs | | Static only | Overloads are for type checkers, not runtime | -
protocols-patterns.md 6.1 KB
# Protocol Patterns Structural typing with Protocol for flexible, decoupled code. ## Basic Protocol ```python from typing import Protocol class Drawable(Protocol): def draw(self) -> None: ... class Circle: def draw(self) -> None: print("Drawing circle") class Square: def draw(self) -> None: print("Drawing square") def render(shape: Drawable) -> None: shape.draw() # Both work - no inheritance needed render(Circle()) render(Square()) ``` ## Protocol with Attributes ```python from typing import Protocol class Named(Protocol): name: str class HasId(Protocol): id: int name: str class User: def __init__(self, id: int, name: str): self.id = id self.name = name def greet(entity: Named) -> str: return f"Hello, {entity.name}" # Works with any object having 'name' attribute greet(User(1, "Alice")) ``` ## Protocol with Methods ```python from typing import Protocol class Closeable(Protocol): def close(self) -> None: ... class Flushable(Protocol): def flush(self) -> None: ... class CloseableAndFlushable(Closeable, Flushable, Protocol): """Combined protocol.""" pass def cleanup(resource: CloseableAndFlushable) -> None: resource.flush() resource.close() ``` ## Callable Protocol ```python from typing import Protocol class Comparator(Protocol): def __call__(self, a: int, b: int) -> int: """Return negative, zero, or positive.""" ... def sort_with(items: list[int], cmp: Comparator) -> list[int]: return sorted(items, key=lambda x: cmp(x, 0)) # Lambda works sort_with([3, 1, 2], lambda a, b: a - b) # Function works def compare(a: int, b: int) -> int: return a - b sort_with([3, 1, 2], compare) ``` ## Generic Protocol ```python from typing import Protocol, TypeVar T = TypeVar("T") class Container(Protocol[T]): def get(self) -> T: ... def set(self, value: T) -> None: ... class Box: def __init__(self, value: int): self._value = value def get(self) -> int: return self._value def set(self, value: int) -> None: self._value = value def process(container: Container[int]) -> int: value = container.get() container.set(value * 2) return container.get() process(Box(5)) # Returns 10 ``` ## Runtime Checkable Protocol ```python from typing import Protocol, runtime_checkable @runtime_checkable class Sized(Protocol): def __len__(self) -> int: ... # Now isinstance() works def process(obj: object) -> int: if isinstance(obj, Sized): return len(obj) return 0 process([1, 2, 3]) # 3 process("hello") # 5 process(42) # 0 ``` ## Protocol vs ABC ```python from abc import ABC, abstractmethod from typing import Protocol # ABC - Requires explicit inheritance class AbstractReader(ABC): @abstractmethod def read(self) -> str: pass class FileReader(AbstractReader): # Must inherit def read(self) -> str: return "content" # Protocol - Structural (duck typing) class ReaderProtocol(Protocol): def read(self) -> str: ... class AnyReader: # No inheritance needed def read(self) -> str: return "content" def process(reader: ReaderProtocol) -> str: return reader.read() process(AnyReader()) # Works! process(FileReader()) # Also works! ``` ## Common Protocols ### Supports Protocols ```python from typing import SupportsInt, SupportsFloat, SupportsBytes, SupportsAbs def to_int(value: SupportsInt) -> int: return int(value) to_int(3.14) # OK - float supports __int__ to_int("42") # Error - str doesn't support __int__ ``` ### Iterator Protocol ```python from typing import Protocol, TypeVar T = TypeVar("T", covariant=True) class Iterator(Protocol[T]): def __next__(self) -> T: ... class Iterable(Protocol[T]): def __iter__(self) -> Iterator[T]: ... ``` ### Context Manager Protocol ```python from typing import Protocol, TypeVar T = TypeVar("T") class ContextManager(Protocol[T]): def __enter__(self) -> T: ... def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: object | None, ) -> bool | None: ... ``` ## Real-World Patterns ### Repository Pattern ```python from typing import Protocol, TypeVar T = TypeVar("T") class Repository(Protocol[T]): def get(self, id: int) -> T | None: ... def save(self, entity: T) -> None: ... def delete(self, id: int) -> bool: ... class User: id: int name: str class InMemoryUserRepo: def __init__(self): self._data: dict[int, User] = {} def get(self, id: int) -> User | None: return self._data.get(id) def save(self, entity: User) -> None: self._data[entity.id] = entity def delete(self, id: int) -> bool: return self._data.pop(id, None) is not None def process_users(repo: Repository[User]) -> None: user = repo.get(1) if user: repo.delete(user.id) ``` ### Event Handler ```python from typing import Protocol class Event: pass class UserCreated(Event): def __init__(self, user_id: int): self.user_id = user_id class EventHandler(Protocol): def can_handle(self, event: Event) -> bool: ... def handle(self, event: Event) -> None: ... class UserCreatedHandler: def can_handle(self, event: Event) -> bool: return isinstance(event, UserCreated) def handle(self, event: Event) -> None: if isinstance(event, UserCreated): print(f"User {event.user_id} created") def dispatch(event: Event, handlers: list[EventHandler]) -> None: for handler in handlers: if handler.can_handle(event): handler.handle(event) ``` ## Best Practices 1. **Prefer Protocol over ABC** - For external interfaces 2. **Use @runtime_checkable sparingly** - Has performance cost 3. **Keep protocols minimal** - Single responsibility 4. **Document expected behavior** - Protocols only define shape, not behavior 5. **Combine protocols** - For complex requirements 6. **Use Generic protocols** - For type-safe containers -
runtime-validation.md 7.4 KB
# Runtime Type Validation Enforce type hints at runtime with Pydantic, typeguard, and beartype. ## Choosing a Model Library | Library | Use When | |---------|----------| | **dataclasses** | Simple data containers, internal models, no validation needed | | **Pydantic** | API boundaries, user input, config, JSON serialization | | **attrs** | Performance-critical, many instances, custom validators | ```python # dataclasses - standard library, simple from dataclasses import dataclass @dataclass class Point: x: float y: float # Pydantic - validation + serialization from pydantic import BaseModel, Field class User(BaseModel): name: str = Field(min_length=1) email: EmailStr # attrs - fast, flexible import attrs @attrs.define class Record: id: int data: str = attrs.field(validator=attrs.validators.min_len(1)) ``` ## Pydantic v2 Validation ```python from pydantic import BaseModel, Field, field_validator, model_validator from pydantic import EmailStr, HttpUrl, PositiveInt from datetime import datetime from typing import Self class User(BaseModel): """Model with automatic validation.""" id: PositiveInt name: str = Field(..., min_length=1, max_length=100) email: EmailStr website: HttpUrl | None = None created_at: datetime = Field(default_factory=datetime.now) @field_validator("name") @classmethod def name_must_be_title_case(cls, v: str) -> str: return v.title() @model_validator(mode="after") def check_consistency(self) -> Self: # Cross-field validation return self # Usage - raises ValidationError on invalid data user = User(id=1, name="john doe", email="john@example.com") print(user.name) # "John Doe" (transformed) # From dict user = User.model_validate({"id": 1, "name": "jane", "email": "jane@example.com"}) # Validation error try: User(id=-1, name="", email="invalid") except ValidationError as e: print(e.errors()) ``` ## Pydantic for Function Arguments ```python from pydantic import validate_call, Field from typing import Annotated @validate_call def greet( name: Annotated[str, Field(min_length=1)], count: Annotated[int, Field(ge=1, le=10)] = 1, ) -> str: return f"Hello, {name}!" * count # Valid greet("World") # OK greet("World", count=3) # OK # Invalid - raises ValidationError greet("") # Error: min_length greet("World", count=100) # Error: le ``` ## typeguard (Runtime Type Checking) ```python from typeguard import typechecked, check_type from typing import TypeVar, Generic # Decorator for function checking @typechecked def process(items: list[int], multiplier: float) -> list[float]: return [item * multiplier for item in items] # Valid process([1, 2, 3], 1.5) # OK # Invalid - raises TypeCheckError at runtime process(["a", "b"], 1.5) # Error: list[int] expected # Check types manually from typeguard import check_type value = [1, 2, 3] check_type(value, list[int]) # OK value = [1, "two", 3] check_type(value, list[int]) # TypeCheckError # Class checking @typechecked class DataProcessor(Generic[T]): def __init__(self, data: list[T]): self.data = data def process(self) -> T: return self.data[0] ``` ## beartype (Fast Runtime Checking) ```python from beartype import beartype from beartype.typing import List, Optional # ~200x faster than typeguard @beartype def fast_process(items: List[int], factor: float) -> List[float]: return [i * factor for i in items] # With optional @beartype def find_user(user_id: int) -> Optional[dict]: return None # Class decorator @beartype class FastProcessor: def __init__(self, data: list[int]): self.data = data def sum(self) -> int: return sum(self.data) ``` ## TypedDict Runtime Validation ```python from typing import TypedDict, Required, NotRequired from pydantic import TypeAdapter class UserDict(TypedDict): id: Required[int] name: Required[str] email: NotRequired[str] # Using Pydantic to validate TypedDict adapter = TypeAdapter(UserDict) # Valid user = adapter.validate_python({"id": 1, "name": "John"}) # Invalid - raises ValidationError adapter.validate_python({"id": "not-int", "name": "John"}) # JSON parsing with validation user = adapter.validate_json('{"id": 1, "name": "John"}') ``` ## dataclass Validation with Pydantic ```python from dataclasses import dataclass from pydantic import TypeAdapter from typing import Annotated from annotated_types import Gt, Lt @dataclass class Point: x: Annotated[float, Gt(-100), Lt(100)] y: Annotated[float, Gt(-100), Lt(100)] # Create validator validator = TypeAdapter(Point) # Validate point = validator.validate_python({"x": 10.5, "y": 20.3}) # Or with init point = validator.validate_python(Point(x=10.5, y=20.3)) ``` ## Custom Validators ```python from pydantic import BaseModel, field_validator, ValidationInfo from pydantic_core import PydanticCustomError import re class Account(BaseModel): username: str password: str @field_validator("username") @classmethod def validate_username(cls, v: str) -> str: if not re.match(r"^[a-z][a-z0-9_]{2,19}$", v): raise PydanticCustomError( "invalid_username", "Username must be 3-20 chars, start with letter, contain only a-z, 0-9, _" ) return v @field_validator("password") @classmethod def validate_password(cls, v: str, info: ValidationInfo) -> str: if len(v) < 8: raise ValueError("Password must be at least 8 characters") if info.data.get("username") and info.data["username"] in v: raise ValueError("Password cannot contain username") return v ``` ## Constrained Types ```python from pydantic import ( BaseModel, PositiveInt, NegativeFloat, conint, constr, conlist, ) class Order(BaseModel): quantity: PositiveInt # > 0 discount: NegativeFloat | None = None # < 0 # Custom constraints product_code: constr(pattern=r"^[A-Z]{3}-\d{4}$") priority: conint(ge=1, le=5) tags: conlist(str, min_length=1, max_length=10) # Usage order = Order( quantity=5, product_code="ABC-1234", priority=3, tags=["urgent"] ) ``` ## When to Use Each | Tool | Speed | Strictness | Use Case | |------|-------|------------|----------| | Pydantic | Medium | High | API validation, config | | typeguard | Slow | Very high | Testing, debugging | | beartype | Fast | Medium | Production code | ```python # Development: Use typeguard for strictest checking from typeguard import typechecked @typechecked def dev_function(x: list[int]) -> int: return sum(x) # Production: Use beartype for minimal overhead from beartype import beartype @beartype def prod_function(x: list[int]) -> int: return sum(x) # API boundaries: Use Pydantic for validation + serialization from pydantic import BaseModel class Request(BaseModel): items: list[int] def api_function(request: Request) -> int: return sum(request.items) ``` ## Quick Reference | Library | Decorator | Check | |---------|-----------|-------| | Pydantic | `@validate_call` | `Model.model_validate()` | | typeguard | `@typechecked` | `check_type(val, Type)` | | beartype | `@beartype` | Automatic on call | | Pydantic Type | Constraint | |---------------|------------| | `PositiveInt` | `> 0` | | `NegativeInt` | `< 0` | | `conint(ge=0, le=100)` | `0 <= x <= 100` | | `constr(min_length=1)` | Non-empty string | | `EmailStr` | Valid email | | `HttpUrl` | Valid URL | -
type-narrowing.md 6 KB
# Type Narrowing Techniques for narrowing types in conditional branches. ## isinstance Narrowing ```python def process(value: str | int | list[str]) -> str: if isinstance(value, str): # value is str here return value.upper() elif isinstance(value, int): # value is int here return str(value * 2) else: # value is list[str] here return ", ".join(value) ``` ## None Checks ```python def greet(name: str | None) -> str: if name is None: return "Hello, stranger" # name is str here (not None) return f"Hello, {name}" # Also works with truthiness def greet_truthy(name: str | None) -> str: if name: # name is str here return f"Hello, {name}" return "Hello, stranger" ``` ## Assertion Narrowing ```python def process(data: dict | None) -> str: assert data is not None # data is dict here return str(data.get("key")) def validate(value: int | str) -> int: assert isinstance(value, int), "Must be int" # value is int here return value * 2 ``` ## Type Guards ```python from typing import TypeGuard def is_string_list(val: list[object]) -> TypeGuard[list[str]]: """Check if all elements are strings.""" return all(isinstance(x, str) for x in val) def process(items: list[object]) -> str: if is_string_list(items): # items is list[str] here return ", ".join(items) return "Not all strings" # With TypeVar from typing import TypeVar T = TypeVar("T") def is_not_none(val: T | None) -> TypeGuard[T]: return val is not None def process_optional(value: str | None) -> str: if is_not_none(value): # value is str here return value.upper() return "default" ``` ## TypeIs (Python 3.13+) ```python from typing import TypeIs # TypeIs narrows more aggressively than TypeGuard def is_str(val: object) -> TypeIs[str]: return isinstance(val, str) def process(value: object) -> str: if is_str(value): # value is str here return value.upper() return "not a string" ``` ## Discriminated Unions ```python from typing import Literal, TypedDict class SuccessResult(TypedDict): status: Literal["success"] data: dict class ErrorResult(TypedDict): status: Literal["error"] message: str Result = SuccessResult | ErrorResult def handle_result(result: Result) -> str: if result["status"] == "success": # result is SuccessResult return str(result["data"]) else: # result is ErrorResult return f"Error: {result['message']}" ``` ## Match Statement (Python 3.10+) ```python def describe(value: int | str | list[int]) -> str: match value: case int(n): return f"Integer: {n}" case str(s): return f"String: {s}" case [first, *rest]: return f"List starting with {first}" case _: return "Unknown" ``` ## hasattr Narrowing ```python from typing import Protocol class HasName(Protocol): name: str def greet(obj: object) -> str: if hasattr(obj, "name") and isinstance(obj.name, str): # Type checkers may not narrow here # Use Protocol + isinstance instead return f"Hello, {obj.name}" return "Hello" ``` ## Callable Narrowing ```python from collections.abc import Callable def execute(func_or_value: Callable[[], int] | int) -> int: if callable(func_or_value): # func_or_value is Callable[[], int] return func_or_value() else: # func_or_value is int return func_or_value ``` ## Exhaustiveness Checking ```python from typing import Literal, Never def assert_never(value: Never) -> Never: raise AssertionError(f"Unexpected value: {value}") Status = Literal["pending", "active", "closed"] def handle_status(status: Status) -> str: if status == "pending": return "Waiting..." elif status == "active": return "In progress" elif status == "closed": return "Done" else: # If we add a new status, type checker will error here assert_never(status) ``` ## Narrowing in Loops ```python from typing import TypeGuard def is_valid(item: str | None) -> TypeGuard[str]: return item is not None def process_items(items: list[str | None]) -> list[str]: result: list[str] = [] for item in items: if is_valid(item): # item is str here result.append(item.upper()) return result # Or use filter with type guard def process_items_functional(items: list[str | None]) -> list[str]: valid_items = filter(is_valid, items) return [item.upper() for item in valid_items] ``` ## Class Type Narrowing ```python class Animal: pass class Dog(Animal): def bark(self) -> str: return "Woof!" class Cat(Animal): def meow(self) -> str: return "Meow!" def make_sound(animal: Animal) -> str: if isinstance(animal, Dog): return animal.bark() # animal is Dog elif isinstance(animal, Cat): return animal.meow() # animal is Cat return "..." ``` ## Common Patterns ### Optional Unwrapping ```python def unwrap_or_default(value: T | None, default: T) -> T: if value is not None: return value return default # With early return def process(data: dict | None) -> dict: if data is None: return {} # data is dict for rest of function return {k: v.upper() for k, v in data.items()} ``` ### Safe Dictionary Access ```python def get_nested(data: dict, *keys: str) -> object | None: result: object = data for key in keys: if not isinstance(result, dict): return None result = result.get(key) if result is None: return None return result ``` ## Best Practices 1. **Prefer isinstance** - Most reliable for type narrowing 2. **Use TypeGuard** - For complex conditions 3. **Check None explicitly** - `is None` or `is not None` 4. **Use exhaustiveness checks** - Catch missing cases 5. **Avoid hasattr** - Type checkers struggle with it 6. **Match statements** - Clean pattern matching (3.10+)
-
-
scripts
-
check-types.sh 3.2 KB
#!/bin/bash # Run type checkers with common options # Usage: ./check-types.sh [--mypy|--pyright|--both] [--strict] [path] set -e # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Defaults CHECKER="both" STRICT="" TARGET="src" # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --mypy) CHECKER="mypy" shift ;; --pyright) CHECKER="pyright" shift ;; --both) CHECKER="both" shift ;; --strict) STRICT="--strict" shift ;; *) TARGET="$1" shift ;; esac done # Check if target exists if [[ ! -e "$TARGET" ]]; then echo -e "${RED}Target not found: $TARGET${NC}" exit 1 fi run_mypy() { echo -e "${BLUE}=== Running mypy ===${NC}" if ! command -v mypy &> /dev/null; then echo -e "${YELLOW}mypy not found. Install with: uv add --dev mypy${NC}" return 1 fi MYPY_ARGS="--show-error-codes --show-error-context --pretty" if [[ -n "$STRICT" ]]; then MYPY_ARGS="$MYPY_ARGS --strict" fi echo "mypy $MYPY_ARGS $TARGET" echo "" if mypy $MYPY_ARGS "$TARGET"; then echo -e "${GREEN}✓ mypy passed${NC}" return 0 else echo -e "${RED}✗ mypy found errors${NC}" return 1 fi } run_pyright() { echo -e "${BLUE}=== Running pyright ===${NC}" if ! command -v pyright &> /dev/null; then echo -e "${YELLOW}pyright not found. Install with: uv add --dev pyright${NC}" return 1 fi PYRIGHT_ARGS="" if [[ -n "$STRICT" ]]; then # Create temporary config for strict mode TEMP_CONFIG=$(mktemp) cat > "$TEMP_CONFIG" << EOF { "typeCheckingMode": "strict" } EOF PYRIGHT_ARGS="--project $TEMP_CONFIG" fi echo "pyright $PYRIGHT_ARGS $TARGET" echo "" if pyright $PYRIGHT_ARGS "$TARGET"; then echo -e "${GREEN}✓ pyright passed${NC}" [[ -n "$STRICT" ]] && rm -f "$TEMP_CONFIG" return 0 else echo -e "${RED}✗ pyright found errors${NC}" [[ -n "$STRICT" ]] && rm -f "$TEMP_CONFIG" return 1 fi } # Run checkers MYPY_STATUS=0 PYRIGHT_STATUS=0 case $CHECKER in mypy) run_mypy || MYPY_STATUS=$? ;; pyright) run_pyright || PYRIGHT_STATUS=$? ;; both) run_mypy || MYPY_STATUS=$? echo "" run_pyright || PYRIGHT_STATUS=$? ;; esac # Summary echo "" echo -e "${BLUE}=== Summary ===${NC}" if [[ "$CHECKER" == "both" ]] || [[ "$CHECKER" == "mypy" ]]; then if [[ $MYPY_STATUS -eq 0 ]]; then echo -e "mypy: ${GREEN}✓ passed${NC}" else echo -e "mypy: ${RED}✗ failed${NC}" fi fi if [[ "$CHECKER" == "both" ]] || [[ "$CHECKER" == "pyright" ]]; then if [[ $PYRIGHT_STATUS -eq 0 ]]; then echo -e "pyright: ${GREEN}✓ passed${NC}" else echo -e "pyright: ${RED}✗ failed${NC}" fi fi # Exit with error if any checker failed if [[ $MYPY_STATUS -ne 0 ]] || [[ $PYRIGHT_STATUS -ne 0 ]]; then exit 1 fi
-
-
SKILL.md 5.3 KB
--- name: python-typing-ops description: "Python type hints and type safety patterns. Triggers on: type hints, typing, TypeVar, Generic, Protocol, mypy, pyright, type annotation, overload, TypedDict." license: MIT compatibility: "Python 3.10+ (uses union syntax X | Y). Some patterns require 3.11+ (Self, TypeVarTuple)." allowed-tools: "Read Write" metadata: author: claude-mods related-skills: python-pytest-ops --- # Python Typing Patterns Modern type hints for safe, documented Python code. ## Basic Annotations ```python # Variables name: str = "Alice" count: int = 42 items: list[str] = ["a", "b"] mapping: dict[str, int] = {"key": 1} # Function signatures def greet(name: str, times: int = 1) -> str: return f"Hello, {name}!" * times # None handling def find(id: int) -> str | None: return db.get(id) # May return None ``` ## Collections ```python from collections.abc import Sequence, Mapping, Iterable # Use collection ABCs for flexibility def process(items: Sequence[str]) -> list[str]: """Accepts list, tuple, or any sequence.""" return [item.upper() for item in items] def lookup(data: Mapping[str, int], key: str) -> int: """Accepts dict or any mapping.""" return data.get(key, 0) # Nested types Matrix = list[list[float]] Config = dict[str, str | int | bool] ``` ## Optional and Union ```python # Modern syntax (3.10+) def find(id: int) -> User | None: pass def parse(value: str | int | float) -> str: pass # With default None def fetch(url: str, timeout: float | None = None) -> bytes: pass ``` ## TypedDict ```python from typing import TypedDict, Required, NotRequired class UserDict(TypedDict): id: int name: str email: str | None class ConfigDict(TypedDict, total=False): # All optional debug: bool log_level: str class APIResponse(TypedDict): data: Required[list[dict]] error: NotRequired[str] def process_user(user: UserDict) -> str: return user["name"] # Type-safe key access ``` ## Callable ```python from collections.abc import Callable # Function type Handler = Callable[[str, int], bool] def register(callback: Callable[[str], None]) -> None: pass # With keyword args (use Protocol instead) from typing import Protocol class Processor(Protocol): def __call__(self, data: str, *, verbose: bool = False) -> int: ... ``` ## Generics ```python from typing import TypeVar T = TypeVar("T") def first(items: list[T]) -> T | None: return items[0] if items else None # Bounded TypeVar from typing import SupportsFloat N = TypeVar("N", bound=SupportsFloat) def average(values: list[N]) -> float: return sum(float(v) for v in values) / len(values) ``` ## Protocol (Structural Typing) ```python from typing import Protocol class Readable(Protocol): def read(self, n: int = -1) -> bytes: ... def load(source: Readable) -> dict: """Accepts any object with read() method.""" data = source.read() return json.loads(data) # Works with file, BytesIO, custom classes load(open("data.json", "rb")) load(io.BytesIO(b"{}")) ``` ## Type Guards ```python from typing import TypeGuard def is_string_list(val: list[object]) -> TypeGuard[list[str]]: return all(isinstance(x, str) for x in val) def process(items: list[object]) -> None: if is_string_list(items): # items is now list[str] print(", ".join(items)) ``` ## Literal and Final ```python from typing import Literal, Final Mode = Literal["read", "write", "append"] def open_file(path: str, mode: Mode) -> None: pass # Constants MAX_SIZE: Final = 1024 API_VERSION: Final[str] = "v2" ``` ## Quick Reference | Type | Use Case | |------|----------| | `X \| None` | Optional value | | `list[T]` | Homogeneous list | | `dict[K, V]` | Dictionary | | `Callable[[Args], Ret]` | Function type | | `TypeVar("T")` | Generic parameter | | `Protocol` | Structural typing | | `TypedDict` | Dict with fixed keys | | `Literal["a", "b"]` | Specific values only | | `Final` | Cannot be reassigned | ## Type Checker Commands ```bash # mypy (run inside the project env) uv run mypy src/ --strict # pyright uv run pyright src/ # In pyproject.toml [tool.mypy] strict = true python_version = "3.11" ``` **Emerging: `ty`** — Astral's Rust-based type checker (same toolchain as uv + ruff), dramatically faster than mypy. Still in preview (pre-1.0), so mypy or pyright remain the production default — but worth watching, and easy to try: `uvx ty check`. Adopt for new projects once it stabilizes. ## Additional Resources - `./references/generics-advanced.md` - TypeVar, ParamSpec, TypeVarTuple - `./references/protocols-patterns.md` - Structural typing, runtime protocols - `./references/type-narrowing.md` - Guards, isinstance, assert - `./references/mypy-config.md` - mypy/pyright configuration - `./references/runtime-validation.md` - Pydantic v2, typeguard, beartype - `./references/overloads.md` - @overload decorator patterns ## Scripts - `./scripts/check-types.sh` - Run type checkers with common options ## Assets - `./assets/pyproject-typing.toml` - Recommended mypy/pyright config --- ## See Also This is a **foundation skill** with no prerequisites. **Related Skills:** - `python-pytest-ops` - Type-safe fixtures and mocking **Build on this skill:** - `python-async-ops` - Async type annotations - `python-fastapi-ops` - Pydantic models and validation - `python-database-ops` - SQLAlchemy type annotations
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.