ワンクリックで
python-typing-patterns
Type-first Python with Pydantic, Protocol, and discriminated unions. Load when writing models or enforcing typing
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Type-first Python with Pydantic, Protocol, and discriminated unions. Load when writing models or enforcing typing
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
External API evaluation and MCP server wrapper generation. Load when integrating a REST/GraphQL API as a workspace tool
Capability-based provider system patterns. Load when creating providers or wiring module-provider integration
Full-stack WebSocket subscription development. Load when adding WS routes, topic services, or debugging WS data flows
Systematic Python type error resolution (mypy/pyright). Load when fixing backend type check failures
Systematic type error resolution for Python (mypy/pyright) and TypeScript (vue-tsc). Use when fixing type errors, resolving type check failures, or optimizing type imports.
Terminal command safety, delegation routing, and guarded execution. Load when running shell commands or delegating to Bash subagents
| name | python-typing-patterns |
| description | Type-first Python with Pydantic, Protocol, and discriminated unions. Load when writing models or enforcing typing |
| user-invocable | false |
Type-first development methodology for Python backends using Pydantic models, Protocol-based interfaces, and strict type enforcement. Adapted from industry best practices for contract-first architectures.
Apply when:
Do NOT use:
typescript-contract-types)backend-testing)</when_to_use>
Types define the contract before implementation:
| Rule | Rationale |
|---|---|
No Any | Defeats the type system — use object or unknown generics |
No # type: ignore without comment | Must explain why suppression is unavoidable |
| Full return types | Every function declares its return type |
No untyped def | Parameters and returns always annotated |
Base model with strict config:
from pydantic import BaseModel, Field, ConfigDict
class StrictModel(BaseModel):
model_config = ConfigDict(strict=True, frozen=True)
Discriminated unions with Literal:
from typing import Literal, Annotated, Union
from pydantic import BaseModel, Field
class MarketOrder(BaseModel):
type: Literal["market"] = "market"
quantity: int
class LimitOrder(BaseModel):
type: Literal["limit"] = "limit"
quantity: int
price: float
Order = Annotated[
Union[MarketOrder, LimitOrder],
Field(discriminator="type"),
]
# Pydantic auto-selects the right model based on `type` field
NewType for domain primitives:
from typing import NewType
OrderId = NewType("OrderId", int)
AccountId = NewType("AccountId", str)
def get_order(order_id: OrderId) -> Order:
# Type checker prevents passing AccountId here
...
SQLModel table classes with complex fields:
from sqlalchemy import JSON, Column
from sqlmodel import Field, SQLModel
class MyTable(SQLModel, table=True):
# Pydantic BaseModel fields need sa_column — SQLModel can't auto-map them
metadata: Optional[MyPydanticModel] = Field(
default=None,
sa_column=Column(JSON, nullable=True),
)
Annotated for validation metadata:
from typing import Annotated
from pydantic import BaseModel, Field
class Position(BaseModel):
symbol: Annotated[str, Field(min_length=1, max_length=10)]
quantity: Annotated[int, Field(gt=0)]
price: Annotated[float, Field(ge=0.0)]
Use Protocol to define interfaces without inheritance coupling:
from typing import Protocol, runtime_checkable
@runtime_checkable
class OrderExecutor(Protocol):
async def place_order(self, order: Order) -> OrderId: ...
async def cancel_order(self, order_id: OrderId) -> bool: ...
class ServiceInterface(Protocol):
"""Base protocol for module services."""
async def health_check(self) -> dict[str, str]: ...
When to use Protocol vs ABC:
| Use Protocol when | Use ABC when |
|---|---|
| Defining capability contracts | Providing shared implementation |
| Consumer doesn't control producer | Enforcing inheritance hierarchy |
| Multiple unrelated implementors | Single implementation chain |
| Testing with lightweight mocks | Need super() calls in chain |
from typing import Never
def handle_order(order: Order) -> str:
match order:
case MarketOrder():
return "executing at market"
case LimitOrder():
return f"queued at {order.price}"
case _ as unreachable:
assert_never(unreachable)
def assert_never(value: Never) -> Never:
raise AssertionError(f"Unhandled value: {value!r}")
# Always chain with `from` to preserve traceback
try:
data = parse_message(raw)
except ValueError as err:
raise InvalidOrderError(f"bad order payload: {err}") from err
# Every code path returns a value or raises
def resolve_symbol(raw: str) -> str:
if not raw:
raise ValueError("empty symbol")
return raw.upper().strip()
from collections.abc import Iterable, Sequence, Mapping
# Accept broad types in parameters
def process_orders(orders: Iterable[Order]) -> list[OrderId]:
return [execute(o) for o in orders]
# Return concrete types
def get_positions() -> list[Position]:
...
| Situation | Pattern |
|---|---|
| "New data model" | Pydantic BaseModel with ConfigDict |
| "Multiple variants of same concept" | Discriminated union with Literal + Field(discriminator=) |
| "ID that shouldn't mix with other IDs" | NewType |
| "Interface for capability injection" | Protocol (runtime_checkable if needed) |
| "Dict with known keys from external data" | TypedDict (not BaseModel — no validation overhead) |
| "Constant that shouldn't change" | Final |
| "Function parameter is any collection" | Iterable/Sequence/Mapping from collections.abc |
| "Need to narrow type in conditional" | TypeGuard or isinstance |
| "Match must cover all cases" | assert_never in default case |
</decision_shortcuts>
| Anti-Pattern | Fix |
|---|---|
Any as parameter or return type | Use specific type, object, or bounded TypeVar |
dict without key/value types | dict[str, int] or TypedDict |
# type: ignore without explanation | Add comment: # type: ignore[assignment] # reason |
Bare except Exception swallowing errors | Re-raise with context or return meaningful result |
Mutable default arguments (def f(x=[])) | Use None default + create inside function |
Protocol with __init__ | Protocols define behavior, not construction |
| Dataclass for API boundary data | Use Pydantic BaseModel (validation + serialization) |
isinstance checks on Pydantic unions | Use discriminator field instead |
</anti_patterns>