skills/python-best-practices/SKILL.md
Provides Python patterns for type-first development with dataclasses, discriminated unions, NewType, and Protocol. Must use when reading or writing Python files.
npx skillsauth add MileniumTick/skills python-best-practicesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Types define the contract before implementation. Follow this workflow:
Use Python's type system to prevent invalid states at type-check time.
Dataclasses for structured data:
from dataclasses import dataclass
from datetime import datetime
@dataclass(frozen=True)
class User:
id: str
email: str
name: str
created_at: datetime
@dataclass(frozen=True)
class CreateUser:
email: str
name: str
# Frozen dataclasses are immutable - no accidental mutation
Discriminated unions with Literal:
from dataclasses import dataclass
from typing import Literal
@dataclass
class Idle:
status: Literal["idle"] = "idle"
@dataclass
class Loading:
status: Literal["loading"] = "loading"
@dataclass
class Success:
status: Literal["success"] = "success"
data: str
@dataclass
class Failure:
status: Literal["error"] = "error"
error: Exception
RequestState = Idle | Loading | Success | Failure
def handle_state(state: RequestState) -> None:
match state:
case Idle():
pass
case Loading():
show_spinner()
case Success(data=data):
render(data)
case Failure(error=err):
show_error(err)
NewType for domain primitives:
from typing import NewType
UserId = NewType("UserId", str)
OrderId = NewType("OrderId", str)
def get_user(user_id: UserId) -> User:
# Type checker prevents passing OrderId here
...
def create_user_id(raw: str) -> UserId:
return UserId(raw)
Enums for constrained values:
from enum import Enum, auto
class Role(Enum):
ADMIN = auto()
USER = auto()
GUEST = auto()
def check_permission(role: Role) -> bool:
match role:
case Role.ADMIN:
return True
case Role.USER:
return limited_check()
case Role.GUEST:
return False
# Type checker warns if case is missing
Protocol for structural typing:
from typing import Protocol
class Readable(Protocol):
def read(self, n: int = -1) -> bytes: ...
def process_input(source: Readable) -> bytes:
# Accepts any object with a read() method
return source.read()
TypedDict for external data shapes:
from typing import TypedDict, Required, NotRequired
class UserResponse(TypedDict):
id: Required[str]
email: Required[str]
name: Required[str]
avatar_url: NotRequired[str]
def parse_user(data: dict) -> UserResponse:
# Runtime validation needed - TypedDict is structural
return UserResponse(
id=data["id"],
email=data["email"],
name=data["name"],
)
Prefer smaller, focused files: one class or closely related set of functions per module. Split when a file handles multiple concerns or exceeds ~300 lines. Use __init__.py to expose public API; keep implementation details in private modules (_internal.py). Colocate tests in tests/ mirroring the source structure.
@dataclass(frozen=True) for immutable data; avoid mutable default arguments.functools.partial for partial application; compose small functions over large classes.from err; catching requires re-raising or returning a meaningful result. Swallowed exceptions hide root causes.None, boundary values. Include else clauses in conditionals where appropriate.pathlib and explicit encodings. Resource leaks cause production issues.Explicit failure for unimplemented logic:
def build_widget(widget_type: str) -> Widget:
raise NotImplementedError(f"build_widget not implemented for type: {widget_type}")
Propagate with context to preserve the original traceback:
try:
data = json.loads(raw)
except json.JSONDecodeError as err:
raise ValueError(f"invalid JSON payload: {err}") from err
Exhaustive match with explicit default:
def process_status(status: str) -> str:
match status:
case "active":
return "processing"
case "inactive":
return "skipped"
case _:
raise ValueError(f"unhandled status: {status}")
Debug-level tracing with namespaced logger:
import logging
logger = logging.getLogger("myapp.widgets")
def create_widget(name: str) -> Widget:
logger.debug("creating widget: %s", name)
widget = Widget(name=name)
logger.debug("created widget id=%s", widget.id)
return widget
os.getenv scattered throughout code.Typed config with dataclass:
import os
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
port: int = 3000
database_url: str = ""
api_key: str = ""
env: str = "development"
@classmethod
def from_env(cls) -> "Config":
database_url = os.environ.get("DATABASE_URL", "")
if not database_url:
raise ValueError("DATABASE_URL is required")
return cls(
port=int(os.environ.get("PORT", "3000")),
database_url=database_url,
api_key=os.environ["API_KEY"], # required, will raise if missing
env=os.environ.get("ENV", "development"),
)
config = Config.from_env()
For fast type checking, consider ty from Astral (creators of ruff and uv). Written in Rust, it's significantly faster than mypy or pyright.
Installation and usage:
# Run directly with uvx (no install needed)
uvx ty check
# Check specific files
uvx ty check src/main.py
# Install permanently
uv tool install ty
Key features:
VIRTUAL_ENV or .venv)pyproject.tomlConfiguration in pyproject.toml:
[tool.ty]
python-version = "3.12"
When to use ty vs alternatives:
ty - fastest, good for CI and large codebases (early stage, rapidly evolving)pyright - most complete type inference, VS Code integrationmypy - mature, extensive plugin ecosystemdevelopment
Writes, reviews, and debugs idiomatic Rust code with memory safety and zero-cost abstractions. Implements ownership patterns, manages lifetimes, designs trait hierarchies, builds async applications with tokio, and structures error handling with Result/Option. Use when building Rust applications, solving ownership or borrowing issues, designing trait-based APIs, implementing async/await concurrency, creating FFI bindings, or optimizing for performance and memory safety. Invoke for Rust, Cargo, ownership, borrowing, lifetimes, async Rust, tokio, zero-cost abstractions, memory safety, systems programming.
development
Guide for writing idiomatic Rust code based on Apollo GraphQL's best practices handbook. Use this skill when: (1) writing new Rust code or functions, (2) reviewing or refactoring existing Rust code, (3) deciding between borrowing vs cloning or ownership patterns, (4) implementing error handling with Result types, (5) optimizing Rust code for performance, (6) writing tests or documentation for Rust projects.
development
Master Rust async programming with Tokio, async traits, error handling, and concurrent patterns. Use when building async Rust applications, implementing concurrent systems, or debugging async code.
tools
When the user wants help with revenue operations, lead lifecycle management, or marketing-to-sales handoff processes. Also use when the user mentions 'RevOps,' 'revenue operations,' 'lead scoring,' 'lead routing,' 'MQL,' 'SQL,' 'pipeline stages,' 'deal desk,' 'CRM automation,' 'marketing-to-sales handoff,' 'data hygiene,' 'leads aren't getting to sales,' 'pipeline management,' 'lead qualification,' or 'when should marketing hand off to sales.' Use this for anything involving the systems and processes that connect marketing to revenue. For cold outreach emails, see cold-email. For email drip campaigns, see email-sequence. For pricing decisions, see pricing-strategy.