원클릭으로
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>