plugins/language-pro/skills/python-pro/SKILL.md
Modern Python with uv, ruff, pyright, strict typing, and clean module design. Use when implementing, debugging, refactoring, or reviewing Python code; designing module boundaries; setting up Python projects; resolving type errors; tightening pyproject.toml; choosing between dataclasses and Pydantic; or working with async code, pytest, or packaging. Applies to any Python work unless a more specific role overrides.
npx skillsauth add rbergman/dark-matter-marketplace python-proInstall 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.
Senior-level Python expertise for production projects. Focuses on modern tooling, strict type checking, and Pythonic idioms.
pyproject.toml for project conventions and tooling configRequired:
Foundational Principles:
Pin Python version with mise: mise use [email protected] (creates .mise.toml — commit it). Team members run mise install. See mise skill for setup.
uv is the modern Python package manager (10-100x faster than pip).
# Initialize project
uv init project-name
cd project-name
# Or in existing directory
uv init
# Add dependencies
uv add httpx pydantic
# Add dev dependencies
uv add --dev pytest pytest-cov pytest-asyncio ruff pyright
# Set up src layout (required for imports to work)
mkdir -p src/projectname tests
mv *.py src/projectname/ 2>/dev/null || true
touch src/projectname/__init__.py tests/__init__.py tests/conftest.py
# Copy configs from this skill's references/ directory:
# references/gitignore -> .gitignore
# references/pyproject-template.toml -> use as pyproject.toml base
# references/pyrightconfig.json -> pyrightconfig.json
# For build system, invoke just-pro skill
# Verify
just check # Or: uv run ruff check . && uv run pyright
Note: uv init creates a minimal pyproject.toml. For src layout to work, add these sections:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/projectname"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
Without [build-system] and [tool.hatch.build.targets.wheel], tests cannot import your package.
git clone <repo> && cd <repo>
just setup # Runs mise trust/install + uv sync
just check # Verify everything works
Or manually:
mise trust && mise install # Get pinned Python version
uv sync # Install dependencies from lockfile
Why uv? Lockfile-based reproducibility, automatic venv management, 10-100x faster than pip.
Invoke the just-pro skill for build system setup. It covers:
Why just? Consistent toolchain frontend between agents and humans. Instead of remembering uv run ruff check --fix ., use just fix.
Auto-Fix First - Always try auto-fix before manual fixes:
just fix # Or: uv run ruff check --fix . && uv run ruff format .
Verification:
just check # Or: uv run ruff check . && uv run pyright && uv run pytest
Some libraries lack type stubs. In pyright strict mode, use Any:
from typing import Any
import structlog # No complete type stubs
# Annotate as Any to silence pyright
logger: Any = structlog.get_logger()
When to use Any:
Avoid # type: ignore - it silences all errors. Explicit Any is clearer.
Ruff's TCH rules flag imports used only for type hints. Move them to a TYPE_CHECKING block:
from __future__ import annotations # Required for forward refs
from typing import TYPE_CHECKING
from mypackage.service import run_service # Runtime import
if TYPE_CHECKING:
from mypackage.models import User # Type-only import
def process(user: User) -> None: # Works due to __future__ annotations
run_service(user)
Rules:
TYPE_CHECKINGfrom __future__ import annotations enables string-based forward refsWhen a field might be None, assert before accessing:
# BAD - pyright error: "x" could be None
result.error_message.lower()
# GOOD - narrow the type first
assert result.error_message is not None
result.error_message.lower()
# OR use conditional
if result.error_message:
result.error_message.lower()
from typing import TypeVar, Protocol
from collections.abc import Callable, Iterator, Sequence
# Basic annotations
def process(items: list[str], timeout: float = 30.0) -> dict[str, int]:
...
# Generic functions
T = TypeVar("T")
def first(items: Sequence[T]) -> T | None:
return items[0] if items else None
# Protocols for structural typing (duck typing with types)
class Readable(Protocol):
def read(self, n: int = -1) -> bytes: ...
def load_data(source: Readable) -> bytes:
return source.read()
# Custom exceptions with context
class ValidationError(Exception):
def __init__(self, field: str, message: str) -> None:
self.field = field
self.message = message
super().__init__(f"{field}: {message}")
# Explicit error handling
def parse_config(path: str) -> Config:
try:
with open(path) as f:
data = json.load(f)
except FileNotFoundError:
raise ConfigError(f"Config file not found: {path}") from None
except json.JSONDecodeError as e:
raise ConfigError(f"Invalid JSON in {path}: {e}") from e
return Config.from_dict(data)
# Use Result pattern for expected failures (optional)
from dataclasses import dataclass
@dataclass
class Ok[T]:
value: T
@dataclass
class Err[E]:
error: E
type Result[T, E] = Ok[T] | Err[E]
from dataclasses import dataclass, field
from pydantic import BaseModel, Field
# Simple data containers
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: float
# Pydantic for validation and serialization
class UserCreate(BaseModel):
name: str = Field(min_length=1, max_length=100)
email: str
age: int = Field(ge=0, le=150)
model_config = {"strict": True}
import asyncio
from collections.abc import AsyncIterator
# Async context managers
async def fetch_with_timeout(url: str, timeout: float = 10.0) -> bytes:
async with asyncio.timeout(timeout):
async with httpx.AsyncClient() as client:
response = await client.get(url)
response.raise_for_status()
return response.content
# Async generators
async def paginate(client: Client, url: str) -> AsyncIterator[Item]:
while url:
response = await client.get(url)
for item in response.items:
yield item
url = response.next_url
# Gather with error handling
async def fetch_all(urls: list[str]) -> list[bytes | Exception]:
tasks = [fetch_with_timeout(url) for url in urls]
return await asyncio.gather(*tasks, return_exceptions=True)
from contextlib import contextmanager, asynccontextmanager
from collections.abc import Generator, AsyncGenerator
@contextmanager
def temporary_config(overrides: dict[str, str]) -> Generator[None, None, None]:
original = config.copy()
config.update(overrides)
try:
yield
finally:
config.clear()
config.update(original)
@asynccontextmanager
async def database_transaction(db: Database) -> AsyncGenerator[Transaction, None]:
tx = await db.begin()
try:
yield tx
await tx.commit()
except Exception:
await tx.rollback()
raise
import pytest
from unittest.mock import Mock, patch
# Parametrized tests
@pytest.mark.parametrize(
"input_val,expected",
[
("hello", "HELLO"),
("", ""),
("123", "123"),
],
ids=["normal", "empty", "digits"],
)
def test_uppercase(input_val: str, expected: str) -> None:
assert uppercase(input_val) == expected
# Fixtures
@pytest.fixture
def sample_user() -> User:
return User(name="Test", email="[email protected]")
@pytest.fixture
def mock_client() -> Mock:
client = Mock(spec=APIClient)
client.get.return_value = {"status": "ok"}
return client
# Async tests
@pytest.mark.asyncio
async def test_fetch_data(mock_client: Mock) -> None:
result = await fetch_data(mock_client, "test-id")
assert result.status == "ok"
# Exception testing
def test_invalid_input_raises() -> None:
with pytest.raises(ValueError, match="must be positive"):
process_value(-1)
import logging
from typing import Any
import structlog
# Note: structlog lacks complete type stubs. Use Any for logger type.
logger: Any = structlog.get_logger()
# Configure structlog for console output
structlog.configure(
processors=[
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.dev.ConsoleRenderer(), # Or JSONRenderer() for prod
],
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
)
# Structured logging with context
logger.info("request_processed", method="GET", path="/api/users", duration_ms=42)
logger.error("operation_failed", error=str(e), user_id=user_id)
# Bind context for a scope
bound_logger: Any = logger.bind(request_id=request_id, user_id=user_id)
bound_logger.info("starting_operation")
project/
├── src/
│ └── projectname/
│ ├── __init__.py
│ ├── api/ # HTTP handlers
│ ├── domain/ # Business logic
│ └── infra/ # External integrations
├── tests/
│ ├── conftest.py # Shared fixtures
│ ├── test_api/
│ └── test_domain/
├── pyproject.toml
├── pyrightconfig.json
├── uv.lock
└── justfile
Rules: Use src/ layout for installable packages. One module = one purpose. Avoid utils, common, helpers modules.
# Validate toolchain versions meet requirements
doctor:
#!/usr/bin/env bash
set -euo pipefail
echo "Checking toolchain..."
# Validate Python version (requires 3.12+)
PY_VERSION=$(python --version | grep -oE '[0-9]+\.[0-9]+')
if [[ "$(printf '%s\n' "3.12" "$PY_VERSION" | sort -V | head -1)" != "3.12" ]]; then
echo "FAIL: Python $PY_VERSION < 3.12 required"
exit 1
fi
echo "OK: Python $PY_VERSION"
# Check uv is available
if ! command -v uv &> /dev/null; then
echo "FAIL: uv not found. Install: curl -LsSf https://astral.sh/uv/install.sh | sh"
exit 1
fi
echo "OK: uv $(uv --version | head -1)"
echo "All checks passed"
# Setup with first-run detection
setup:
#!/usr/bin/env bash
if [[ -f .setup-complete ]]; then
echo "Already set up. Run 'just setup-force' to reinstall."
exit 0
fi
mise trust && mise install
uv sync
touch .setup-complete
echo "Setup complete"
setup-force:
rm -f .setup-complete
@just setup
except: clauses (always specify exception type)def f(items=[]) - use None and create inside)from module import *)Any when specific types workprint() for logging (use logging/structlog)Before writing code:
pyproject.toml for project structure and dependenciesWhen writing code:
Before committing:
just check (standard for projects using just)uv run ruff check --fix . && uv run ruff format .uv run pyright && uv run pytestdevelopment
Initialize a new repository with standard scaffolding - git, gitignore, AGENTS.md, justfile, mise, beads, and timbers. Use when starting a new project or setting up an existing repo for Claude Code workflows.
data-ai
Activate at session start when using Agent Teams for complex multi-agent work. Establishes team lead role with delegation protocols, teammate spawning, model selection, and beads integration. You coordinate the team; teammates implement.
data-ai
Use when creating a worktree, setting up a worktree, starting feature work that needs isolation, or before executing implementation plans. Covers git worktree creation under .worktrees/, gitignore setup, beads integration, and merge guardrails.
data-ai
Activate when you are a delegated subagent (not the orchestrator). Establishes subagent protocol with terse returns, details to history/, file ownership boundaries, and escalation rules. You implement; orchestrator reviews and commits.