Claude Code Plugins

Community-maintained marketplace

Feedback

Python 3.13+ development expertise with cutting-edge toolchain - pytest 8.4.2 for powerful testing with fixtures and async support, ruff 0.13.1 for unified linting and formatting (100x faster than pylint), mypy 1.8.0 strict type checking, Pydantic 2.7 runtime validation, uv 0.9.3 package manager (10x faster than pip), modern async patterns with TaskGroup and context variables, PEP 695/701/698 features (type parameters, f-strings, override decorator), FastAPI patterns (dependency injection, middleware, exception handling, background tasks), structured logging with structlog, Constitution compliance (≤500 SLOC, ≤10 complexity), and comprehensive TDD workflow with TAG block integration

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name ms-lang-python
description Python 3.13+ development expertise with cutting-edge toolchain - pytest 8.4.2 for powerful testing with fixtures and async support, ruff 0.13.1 for unified linting and formatting (100x faster than pylint), mypy 1.8.0 strict type checking, Pydantic 2.7 runtime validation, uv 0.9.3 package manager (10x faster than pip), modern async patterns with TaskGroup and context variables, PEP 695/701/698 features (type parameters, f-strings, override decorator), FastAPI patterns (dependency injection, middleware, exception handling, background tasks), structured logging with structlog, Constitution compliance (≤500 SLOC, ≤10 complexity), and comprehensive TDD workflow with TAG block integration

Language: Python 3.13+ Expert

Skill Metadata

Field Value
Version 1.0.0
Created 2025-10-26
Python Support 3.13.1 (latest), 3.12.7 (LTS)
Allowed tools Read, Bash, Grep
Auto-load On demand when .py files detected
Trigger cues Python files, pytest, async patterns, FastAPI

What it does

Provides Python 3.13+ expertise for My-Spec TDD development, including:

  • Testing Framework: pytest 8.4.2 (fixtures, asyncio, parametrization)
  • Code Quality: ruff 0.13.1 (unified linter + formatter, replaces black/pylint)
  • Type Safety: mypy 1.8.0 + Pydantic 2.7.0 (static + runtime validation)
  • Package Management: uv 0.9.3 (10x faster than pip)
  • Python 3.13 Features: PEP 695 (type params), PEP 701 (f-strings), PEP 698 (@override)
  • Async/Await: asyncio.TaskGroup, context variables, concurrent patterns
  • Constitution Compliance: TRUST 5 principles, ≤500 SLOC files, ≤10 complexity

When to use

Automatic triggers:

  • Python code discussions, .py files
  • "Writing Python tests", "How to use pytest", "Python type hints"
  • Python SPEC implementation (/ms.implement)
  • Async pattern requests, FastAPI development

Manual invocation:

  • Review Python code for TRUST 5 compliance
  • Design Python microservices (FastAPI recommended)
  • Troubleshoot Python errors or performance issues
  • Migrate from Python 3.12 to 3.13

How it works (Best Practices)

1. Testing Framework (pytest 8.4.2)

Why pytest over unittest?

  • 🎯 Simpler syntax (no class boilerplate)
  • 🔧 Powerful fixtures (dependency injection)
  • Better assertions (detailed error messages)
  • Async support with pytest-asyncio

Test Structure:

# tests/test_calculator.py
import pytest
from src.calculator import add

def test_add_positive_numbers():
    """Verify addition of positive integers."""
    assert add(2, 3) == 5

@pytest.mark.asyncio
async def test_async_operation():
    """Test async functions with pytest-asyncio."""
    result = await async_fetch_data()
    assert result is not None

@pytest.mark.parametrize("a,b,expected", [
    (2, 3, 5),
    (-2, 3, 1),
    (0, 0, 0),
])
def test_add_parametrized(a, b, expected):
    """Parametrized test for multiple cases."""
    assert add(a, b) == expected

Key Points:

  • ✅ Use pytest (not unittest)
  • ✅ One assertion per test (clarity)
  • ✅ Fixtures for setup/teardown
  • pytest.mark.asyncio for async tests
  • pytest.mark.parametrize for data-driven tests
  • ✅ Coverage ≥85% enforced by quality gate

CLI Commands:

pytest                              # Run all tests
pytest -v                           # Verbose output
pytest --cov=src --cov-report=term # Coverage report (≥85% required)
pytest -k "pattern"                 # Run matching tests
pytest -m asyncio                   # Run async tests only
pytest --maxfail=1                  # Stop after first failure

2. Code Quality (ruff 0.13.1 — NEW STANDARD)

Why ruff over black + pylint + isort?

  • 100x faster than pylint
  • 🔧 Single tool (linting + formatting + import sorting)
  • Drop-in replacement for black
  • 🎯 Written in Rust (blazing fast)

Configuration (pyproject.toml):

[tool.ruff]
line-length = 100
target-version = "py313"
exclude = [".venv", "build", "dist", "__pycache__"]

[tool.ruff.lint]
select = [
    "E",   # pycodestyle errors
    "F",   # pyflakes
    "W",   # pycodestyle warnings
    "I",   # isort (import sorting)
    "N",   # pep8-naming
    "UP",  # pyupgrade (use Python 3.13 features)
    "B",   # flake8-bugbear
    "C4",  # flake8-comprehensions
    "C90", # mccabe (complexity)
]
ignore = ["E501"]  # Line length handled by formatter

[tool.ruff.lint.mccabe]
max-complexity = 10  # Constitution requirement

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["F401", "F811"]  # Allow unused imports in tests

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

CLI Commands:

ruff check .                        # Lint all files
ruff check --fix .                  # Lint with auto-fix
ruff format .                       # Format (replaces black)
ruff check --select C90 .           # Check complexity only
ruff check --select I .             # Check imports only

3. Type Safety (mypy 1.8.0 + Pydantic 2.7.0)

Static Type Checking (mypy):

from typing import override

class Parent:
    def method(self, x: int) -> str:
        return str(x)

class Child(Parent):
    @override  # Python 3.13 PEP 698 — mypy validates override
    def method(self, x: int) -> str:
        return str(x * 2)

Configuration (pyproject.toml):

[tool.mypy]
python_version = "3.13"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_any_generics = true
check_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true

[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false

Runtime Validation (Pydantic 2.7.0):

from pydantic import BaseModel, Field

class User(BaseModel):
    id: int = Field(gt=0)           # Must be > 0
    name: str = Field(min_length=1)
    email: str                      # Auto-validated as email
    age: int = Field(ge=13, le=120)

CLI Commands:

mypy .                              # Type check all files
mypy --strict src/                  # Strict mode (recommended)
mypy --show-column-numbers .        # Precise error locations

4. Package Management (uv 0.9.3)

Why uv over pip/poetry?

  • 10x faster than pip
  • 🔧 Zero config (works with pyproject.toml)
  • Compatible with existing tools
  • 🎯 Written in Rust

Setup:

# Install uv
pip install uv

# Create virtual environment
uv venv                             # Creates .venv/
source .venv/bin/activate           # Activate (Linux/Mac)
.venv\Scripts\activate              # Activate (Windows)

# Install dependencies
uv add pytest ruff mypy             # Add to pyproject.toml
uv add --dev pytest-asyncio         # Add as dev dependency
uv sync                             # Install all (from lock file)

pyproject.toml (uv config):

[project]
name = "my-project"
version = "1.0.0"
requires-python = ">=3.13"
dependencies = [
    "fastapi>=0.115.0",
    "pydantic>=2.7.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.4.2",
    "pytest-asyncio",
    "pytest-cov",
    "ruff>=0.13.1",
    "mypy>=1.8.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

5. Python 3.13 New Features

PEP 695 — Type Parameter Syntax

# OLD (3.12)
from typing import TypeVar, Generic
T = TypeVar('T')
class Stack(Generic[T]):
    def push(self, item: T) -> None: ...

# NEW (3.13)
class Stack[T]:
    def push(self, item: T) -> None: ...

PEP 701 — Improved F-Strings

# Nested f-strings and arbitrary expressions
user = {"name": "Alice", "age": 30}
print(f"User: {user['name']}, Age: {user['age']}")  # Works!

# Nested f-strings
x = 10
print(f"Result: {f'{x:>10}'}")  # Works in 3.13!

PEP 698 — Override Decorator

from typing import override

class Parent:
    def method(self) -> None: ...

class Child(Parent):
    @override  # mypy validates this is actually overriding
    def method(self) -> None: ...

6. Async Patterns (Python 3.13)

TaskGroup (cleaner than asyncio.gather):

import asyncio

async def main():
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(fetch_user(1))
        task2 = tg.create_task(fetch_posts(1))
        # Tasks run concurrently
        # Exceptions propagate automatically

asyncio.run(main())

Context Variables (thread-safe in async):

from contextvars import ContextVar

request_id = ContextVar('request_id', default=None)

async def handle_request(req_id):
    token = request_id.set(req_id)
    # All spawned tasks inherit this context var
    await process_async()
    request_id.reset(token)

7. Security Best Practices

Secrets Module (token generation):

import secrets

api_key = secrets.token_urlsafe(32)  # Safe random tokens
nonce = secrets.token_bytes(16)       # Cryptographic nonce

Secure Hashing (use sha256, not md5):

import hashlib

# ✅ SECURE
hash_obj = hashlib.sha256(b"password")

# ❌ INSECURE (removed in Python 3.13)
# hash_obj = hashlib.md5(b"password")  # ValueError!

Input Validation (Pydantic):

from pydantic import BaseModel, Field, field_validator

class UserInput(BaseModel):
    username: str = Field(min_length=3, max_length=20, pattern=r"^[a-zA-Z0-9_]+$")
    email: str  # Auto email validation
    age: int = Field(ge=13, le=120)

    @field_validator("username")
    @classmethod
    def username_no_special_chars(cls, v: str) -> str:
        if not v.replace("_", "").isalnum():
            raise ValueError("Username must be alphanumeric")
        return v

8. FastAPI Patterns (Production-Ready)

Why FastAPI?

  • Fast: Built on Starlette + Pydantic (async by default)
  • 🎯 Type-safe: Automatic validation with Python type hints
  • 📖 Auto docs: OpenAPI/Swagger UI generation
  • Modern: Native async/await support

Dependency Injection (Recommended Pattern)

Reusable dependencies:

from fastapi import Depends, FastAPI, HTTPException
from typing import Annotated
from sqlalchemy.orm import Session

app = FastAPI()

# Database session dependency
def get_db() -> Generator[Session, None, None]:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# Authentication dependency
def get_current_user(
    token: str = Header(...),
    db: Session = Depends(get_db)
) -> User:
    user = verify_token(token, db)
    if user is None:
        raise HTTPException(status_code=401, detail="Invalid authentication")
    return user

# Inject dependencies into route
@app.get("/users/me")
async def read_users_me(
    db: Annotated[Session, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_user)]
):
    return current_user

Benefits:

  • ✅ Shared logic (database connection, auth)
  • ✅ Testable (mock dependencies)
  • ✅ Type-safe (Annotated for clarity)

Exception Handling

Custom exception handlers:

from fastapi import Request, status
from fastapi.responses import JSONResponse

class UserNotFoundError(Exception):
    def __init__(self, user_id: int):
        self.user_id = user_id

@app.exception_handler(UserNotFoundError)
async def user_not_found_handler(request: Request, exc: UserNotFoundError):
    return JSONResponse(
        status_code=status.HTTP_404_NOT_FOUND,
        content={"detail": f"User {exc.user_id} not found"}
    )

# Usage in route
@app.get("/users/{user_id}")
async def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).get(user_id)
    if user is None:
        raise UserNotFoundError(user_id)
    return user

HTTP exceptions:

from fastapi import HTTPException

@app.post("/users/")
async def create_user(user: UserCreate, db: Session = Depends(get_db)):
    existing = db.query(User).filter(User.email == user.email).first()
    if existing:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Email already registered"
        )
    return create_user_in_db(user, db)

Middleware (CORS, Logging, Auth)

CORS middleware:

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://example.com"],  # Specific origins (not "*")
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)

Custom logging middleware:

import time
import structlog

log = structlog.get_logger()

@app.middleware("http")
async def log_requests(request: Request, call_next):
    start_time = time.time()

    # Add request_id for tracing
    request_id = str(uuid.uuid4())
    request.state.request_id = request_id

    log.info(
        "request_start",
        method=request.method,
        path=request.url.path,
        request_id=request_id
    )

    response = await call_next(request)

    duration = time.time() - start_time
    log.info(
        "request_end",
        method=request.method,
        path=request.url.path,
        status_code=response.status_code,
        duration_ms=round(duration * 1000, 2),
        request_id=request_id
    )

    return response

Background Tasks

Send emails asynchronously:

from fastapi import BackgroundTasks

def send_welcome_email(email: str):
    # Simulate email sending (use actual email service)
    time.sleep(2)
    print(f"Email sent to {email}")

@app.post("/users/")
async def create_user(
    user: UserCreate,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db)
):
    db_user = User(**user.dict())
    db.add(db_user)
    db.commit()

    # Send email in background (doesn't block response)
    background_tasks.add_task(send_welcome_email, user.email)

    return {"message": "User created, email will be sent"}

Background task with dependencies:

def update_user_stats(user_id: int, db: Session):
    user = db.query(User).get(user_id)
    user.last_login = datetime.utcnow()
    db.commit()

@app.post("/login")
async def login(
    credentials: LoginCredentials,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db)
):
    user = authenticate(credentials, db)

    # Update stats in background
    background_tasks.add_task(update_user_stats, user.id, db)

    return {"token": create_token(user)}

Testing FastAPI Endpoints

Test with TestClient:

from fastapi.testclient import TestClient
import pytest

client = TestClient(app)

def test_create_user():
    response = client.post(
        "/users/",
        json={"email": "test@example.com", "password": "secret123"}
    )
    assert response.status_code == 200
    assert response.json()["email"] == "test@example.com"

def test_get_user_unauthorized():
    response = client.get("/users/me")
    assert response.status_code == 401

@pytest.fixture
def authenticated_client():
    # Create test user and get token
    token = create_test_token()
    client.headers = {"Authorization": f"Bearer {token}"}
    return client

def test_get_user_authenticated(authenticated_client):
    response = authenticated_client.get("/users/me")
    assert response.status_code == 200

9. Structured Logging (structlog)

Why structlog over stdlib logging?

  • 🔍 Structured: JSON output for log aggregation (ELK, Datadog)
  • 🎯 Context binding: Attach request_id, user_id to all logs
  • Performance: No string formatting until output
  • Type-safe: Works with mypy

Installation:

uv add structlog

Configuration:

import structlog

structlog.configure(
    processors=[
        structlog.stdlib.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.StackInfoRenderer(),
        structlog.dev.ConsoleRenderer()  # Pretty print in dev
        # Use JSONRenderer() in production
    ],
    wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
    context_class=dict,
    logger_factory=structlog.PrintLoggerFactory(),
    cache_logger_on_first_use=True,
)

log = structlog.get_logger()

Basic usage:

log = structlog.get_logger()

log.info("user_login", user_id=42, email="user@example.com")
# Output: {"event": "user_login", "user_id": 42, "email": "user@example.com", ...}

log.error("database_error", error=str(e), query="SELECT * FROM users")

Context binding:

# Bind context for all subsequent logs
log = log.bind(request_id="abc123", user_id=42)

log.info("processing_payment", amount=100)
# Output includes request_id and user_id automatically

log.warning("payment_failed", reason="insufficient_funds")
# Output still includes request_id and user_id

FastAPI integration:

from fastapi import Request
import uuid

@app.middleware("http")
async def add_structured_logging(request: Request, call_next):
    request_id = str(uuid.uuid4())

    # Bind request context
    log = structlog.get_logger().bind(
        request_id=request_id,
        method=request.method,
        path=request.url.path
    )

    # Store in request state for access in routes
    request.state.log = log

    log.info("request_start")

    try:
        response = await call_next(request)
        log.info("request_end", status_code=response.status_code)
        return response
    except Exception as e:
        log.error("request_error", error=str(e), exc_info=True)
        raise

# Access in routes
@app.get("/users/{user_id}")
async def get_user(user_id: int, request: Request):
    log = request.state.log.bind(user_id=user_id)
    log.info("fetching_user")
    # ...

JSON output for production:

import structlog

structlog.configure(
    processors=[
        structlog.stdlib.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.JSONRenderer()  # JSON for log aggregation
    ],
)

log = structlog.get_logger()
log.info("user_login", user_id=42)
# Output: {"event":"user_login","user_id":42,"timestamp":"2025-10-26T...","level":"info"}

Constitution Compliance

My-Spec Requirements:

  • ✅ Files ≤500 SLOC (split if larger)
  • ✅ Functions ≤100 lines
  • ✅ Complexity ≤10 per function
  • ✅ Test coverage ≥85%
  • ✅ Strict typing enabled
  • ✅ TAG blocks in all files

File Size Check:

# Count SLOC (excluding comments/blank lines)
ruff check . --select C901 --statistics

Complexity Check:

# Ruff checks complexity automatically (max=10)
ruff check . --select C90

Tool Version Matrix (2025-10-26)

Tool Version Purpose Status
Python 3.13.1 Runtime ✅ Latest
pytest 8.4.2 Testing ✅ Current
ruff 0.13.1 Lint/Format ✅ New standard
mypy 1.8.0 Type checking ✅ Current
uv 0.9.3 Package manager ✅ Recommended
FastAPI 0.115.0 Web framework ✅ Latest
Pydantic 2.7.0 Validation ✅ Latest

Example Workflow

Setup (uv + Python 3.13):

uv venv --python 3.13               # Create venv with Python 3.13
source .venv/bin/activate
uv add pytest ruff mypy fastapi pydantic

TDD Loop:

pytest                              # RED: Watch tests fail
# [implement code]
pytest                              # GREEN: Watch tests pass
ruff check --fix .                  # REFACTOR: Fix code quality

Quality Gate (before commit):

pytest --cov=src --cov-report=term # Coverage ≥85%?
ruff check .                        # Lint pass?
mypy --strict .                     # Type check pass?

Best Practices

DO:

  • Use ruff for linting + formatting (not black + pylint)
  • Specify exact Python version: requires-python = ">=3.13"
  • Use pytest for all tests
  • Enable mypy strict mode
  • Run quality gate before each commit
  • Use uv for package management (10x faster)
  • Add docstrings to public APIs
  • Use f-strings (PEP 701 supports nested expressions)

DON'T:

  • Use black + pylint (deprecated, use ruff instead)
  • Use md5 hashing (removed in Python 3.13)
  • Mix pytest with unittest
  • Ignore coverage requirements (<85% fails)
  • Use old type hint syntax (use PEP 695 class Foo[T]:)
  • Use asyncio.gather without error handling (use TaskGroup instead)

Integration with My-Spec

TAG Block Format (Python):

"""
@CODE:AUTH-001
@SPEC: specs/001-auth-spec/spec.md
@TEST: tests/test_auth.py
@CHAIN: @SPEC:AUTH-001 → @TEST:AUTH-001 → @CODE:AUTH-001
@STATUS: implemented
@CREATED: 2025-10-26
@UPDATED: 2025-10-26
"""

class AuthService:
    # Implementation...

Test TAG Block:

"""
@TEST:AUTH-001
@SPEC: specs/001-auth-spec/spec.md
@CODE: src/services/auth.py
@CHAIN: @SPEC:AUTH-001 → @TEST:AUTH-001 → @CODE:AUTH-001
@STATUS: passing
"""

def test_auth_service():
    # Tests...

References (Latest Documentation)

Changelog

  • v1.0.0 (2025-10-26): Initial Python Skill for My-Spec workflow with pytest 8.4.2, ruff 0.13.1, mypy 1.8.0, Python 3.13 features, Constitution compliance

Works Well With

  • ms-foundation-trust (TRUST 5 validation)
  • ms-foundation-constitution (file size/complexity checks)
  • ms-workflow-tag-manager (TAG block generation)
  • ms-workflow-living-docs (API documentation sync)