Agent Skill
2/7/2026py-standards
MANDATORY for ALL Python output - files AND conversational snippets. Covers uv for package management, ruff for linting/formatting, ty for type checking, and project structure patterns. Trigger: any Python code, packages, dependencies, linting, type hints. No exceptions.
D
dungle
0GitHub Stars
1Views
npx skills add dungle-scrubs/agent-config
SKILL.md
| Name | py-standards |
| Description | MANDATORY for ALL Python output - files AND conversational snippets. Covers uv for package management, ruff for linting/formatting, ty for type checking, and project structure patterns. Trigger: any Python code, packages, dependencies, linting, type hints. No exceptions. |
name: py-standards description: "MANDATORY for ALL Python output - files AND conversational snippets. Covers uv for package management, ruff for linting/formatting, ty for type checking, and project structure patterns. Trigger: any Python code, packages, dependencies, linting, type hints. No exceptions."
Python Best Practices
When to Use This Skill
This skill should be triggered when:
- Writing or reviewing Python code
- Setting up Python projects or dependencies
- Configuring linting, formatting, or type checking
- Discussing Python patterns and conventions
- Working with FastAPI, Click/Typer, or any Python framework
Core Capabilities
- Package Management: uv for fast, reliable dependency management
- Code Quality: ruff for linting and formatting (replaces black, isort, flake8)
- Type Checking: ty for type validation
- Project Structure: Shared core pattern for API + CLI projects
Package Management with uv
Why uv
- 10-100x faster than pip
- Replaces pip, pip-tools, virtualenv, and pyenv
- Lockfile support for reproducible builds
- Built-in Python version management
Common Commands
# Create new project
uv init my-project
cd my-project
# Add dependencies
uv add fastapi uvicorn
uv add --dev pytest ruff
# Sync dependencies (install from lockfile)
uv sync
# Run commands in venv
uv run python script.py
uv run pytest
# Pin Python version
uv python pin 3.12
pyproject.toml Structure
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115.0",
"uvicorn>=0.32.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"ruff>=0.8.0",
]
[tool.uv]
dev-dependencies = [
"pytest>=8.0.0",
"ruff>=0.8.0",
]
Code Quality with ruff
Configuration
[tool.ruff]
line-length = 88
target-version = "py312"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
"ARG", # flake8-unused-arguments
"SIM", # flake8-simplify
]
ignore = [
"E501", # line too long (handled by formatter)
]
[tool.ruff.lint.isort]
known-first-party = ["my_project"]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
Commands
# Check for issues
uv run ruff check .
# Fix auto-fixable issues
uv run ruff check --fix .
# Format code
uv run ruff format .
Type Checking with ty
Configuration
[tool.ty]
python-version = "3.12"
Commands
# Type check
uv run ty check
Project Structure
Single Package Project
my-project/
├── pyproject.toml
├── uv.lock
├── src/
│ └── my_project/
│ ├── __init__.py
│ └── main.py
└── tests/
└── test_main.py
API + CLI with Shared Core
When building projects with both a web API and CLI:
your_app/
├── pyproject.toml
├── uv.lock
├── src/
│ └── your_app/
│ ├── __init__.py
│ ├── core/ # Shared business logic + DB access
│ │ ├── __init__.py
│ │ ├── models.py # Domain models, Pydantic schemas
│ │ ├── services.py # Business logic
│ │ └── db.py # Database access
│ ├── api/ # FastAPI endpoints import from core
│ │ ├── __init__.py
│ │ ├── main.py
│ │ └── routes/
│ └── cli/ # Click/Typer commands import from core
│ ├── __init__.py
│ └── main.py
└── tests/
Why this pattern:
- Single source of truth for business logic and validation
- No network dependency for CLI operations
- No code duplication between API and CLI
- CLI and API behave consistently
- Direct DB access means no latency penalty for CLI
Example Core Module
# src/your_app/core/services.py
from your_app.core.db import get_db
from your_app.core.models import User, CreateUserRequest
def create_user(request: CreateUserRequest) -> User:
"""Business logic shared by API and CLI."""
db = get_db()
user = User(name=request.name, email=request.email)
db.add(user)
db.commit()
return user
Example API Using Core
# src/your_app/api/routes/users.py
from fastapi import APIRouter
from your_app.core.models import User, CreateUserRequest
from your_app.core.services import create_user
router = APIRouter()
@router.post("/users", response_model=User)
def create_user_endpoint(request: CreateUserRequest) -> User:
return create_user(request)
Example CLI Using Core
# src/your_app/cli/main.py
import typer
from your_app.core.services import create_user
from your_app.core.models import CreateUserRequest
app = typer.Typer()
@app.command()
def add_user(name: str, email: str) -> None:
"""Create a new user."""
request = CreateUserRequest(name=name, email=email)
user = create_user(request)
typer.echo(f"Created user: {user.id}")
Type Hints
Always Use Type Hints
# BAD
def process(data):
return data["value"]
# GOOD
def process(data: dict[str, Any]) -> str:
return data["value"]
Use Modern Syntax (3.10+)
# BAD - old style
from typing import List, Dict, Optional, Union
def process(items: List[str]) -> Optional[Dict[str, int]]:
pass
# GOOD - modern syntax
def process(items: list[str]) -> dict[str, int] | None:
pass
Pydantic for Data Validation
from pydantic import BaseModel, EmailStr
class CreateUserRequest(BaseModel):
email: EmailStr
name: str
age: int | None = None
class User(BaseModel):
id: int
email: EmailStr
name: str
age: int | None = None
Naming Conventions
- snake_case: variables, functions, modules
- PascalCase: classes
- UPPER_SNAKE_CASE: constants
MAX_RETRIES = 3
class UserService:
def get_user_by_id(self, user_id: int) -> User:
pass
Imports
Order (handled by ruff)
- Standard library
- Third-party packages
- Local imports
import os
from pathlib import Path
from fastapi import FastAPI
from pydantic import BaseModel
from your_app.core.models import User
from your_app.core.services import create_user
Error Handling
Custom Exception Classes
class AppError(Exception):
"""Base application error."""
def __init__(self, message: str, code: str) -> None:
self.message = message
self.code = code
super().__init__(message)
class NotFoundError(AppError):
"""Resource not found."""
def __init__(self, resource: str, id: str) -> None:
super().__init__(
message=f"{resource} with id {id} not found",
code="NOT_FOUND"
)
Result Pattern (Optional)
For functions that can fail predictably:
from dataclasses import dataclass
from typing import Generic, TypeVar
T = TypeVar("T")
E = TypeVar("E", bound=Exception)
@dataclass
class Ok(Generic[T]):
value: T
@dataclass
class Err(Generic[E]):
error: E
type Result[T, E] = Ok[T] | Err[E]
def parse_config(path: str) -> Result[Config, ConfigError]:
try:
data = load_file(path)
return Ok(Config.model_validate(data))
except ValidationError as e:
return Err(ConfigError(str(e)))
CLI Applications
Required Stack
| Purpose | Package |
|---|---|
| CLI framework | Typer |
| Colors/output | Rich |
| Spinners | halo |
| Progress bars | tqdm |
Example CLI Setup
import typer
from rich import print
from rich.console import Console
from halo import Halo
from tqdm import tqdm
app = typer.Typer()
console = Console()
@app.command()
def process(
path: str = typer.Argument(..., help="Path to process"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
) -> None:
"""Process files at the given path."""
spinner = Halo(text="Loading files...", spinner="dots")
spinner.start()
try:
files = load_files(path)
spinner.succeed(f"Loaded {len(files)} files")
except Exception as e:
spinner.fail(f"Failed to load files: {e}")
raise typer.Exit(1)
for file in tqdm(files, desc="Processing"):
process_file(file)
print("[green]Done![/green]")
if __name__ == "__main__":
app()
Dependencies
uv add typer rich halo tqdm
LLM-Friendly Output
All CLIs must support both human and machine consumption:
import json
import typer
from rich import print
from rich.console import Console
from rich.table import Table
from pydantic import BaseModel
app = typer.Typer()
console = Console()
class User(BaseModel):
id: str
name: str
email: str
@app.command()
def list_users(
json_output: bool = typer.Option(False, "--json", help="Output as JSON for programmatic consumption"),
limit: int = typer.Option(50, "--limit", "-l", help="Maximum number of users to return"),
) -> None:
"""
List all users.
Returns array of user objects with id, name, and email fields.
Use --json for structured output suitable for piping to other tools or LLMs.
"""
users = get_users(limit)
if json_output:
# Machine-readable: structured, no formatting
print(json.dumps([u.model_dump() for u in users], indent=2))
else:
# Human-readable: colors, tables, pleasant
table = Table(title="Users")
table.add_column("Name", style="cyan")
table.add_column("Email")
for user in users:
table.add_row(user.name, user.email)
console.print(table)
if __name__ == "__main__":
app()
Rules:
--jsonflag on every command that outputs data- JSON output: structured, complete, no ANSI codes (Rich auto-strips when not TTY)
- Default output: human-readable with Rich formatting
- Docstrings must explain what the command returns, not just what it does
Quick Reference
| Tool | Purpose | Command |
|---|---|---|
| uv | Package management | uv add, uv sync, uv run |
| ruff | Linting + formatting | ruff check, ruff format |
| ty | Type checking | ty check |
| Pattern | Preference |
|---|---|
| Package manager | uv (not pip, poetry, pipenv) |
| Linter/formatter | ruff (replaces black, isort, flake8) |
| Type checker | ty |
| Type hints | Modern syntax (list[str] not List[str]) |
| Data validation | Pydantic |
| API framework | FastAPI |
| CLI framework | Typer (or Click) |
| Project structure | Shared core for API + CLI |
Notes
- Always use uv for new projects
- ruff replaces multiple tools - don't install black, isort, or flake8 separately
- Use the shared core pattern when building API + CLI to avoid duplication
- Modern type hint syntax requires Python 3.10+
- Pydantic v2 for data validation and serialization
Skills Info
Original Name:py-standardsAuthor:dungle
Download