Get the FREE Ultimate OpenClaw Setup Guide →

Pep8

npx machina-cli add skill hackermanishackerman/claude-skills-vault/pep8 --openclaw
Files (1)
SKILL.md
8.8 KB

Python Style & PEP 8 Enforcement

Auto-enforce Python 3.11+ standards.

When to Use

  • Writing/reviewing Python code
  • Type hint issues or style violations
  • User requests PEP 8 check

Core Standards

StandardDesc
PEP 8Naming, imports, spacing
PEP 484/585Type hints (modern)
PEP 257Docstrings
PEP 604Union |
PEP 570/3102/ positional, * keyword

Naming

class UserAccount: pass        # PascalCase
class HTTPClient: pass         # Acronyms: all caps
def calculate_total(): pass    # snake_case
async def fetch_data(): pass   # async same

user_name = "john"             # Variables: snake_case
MAX_RETRIES = 3                # Constants: SCREAMING_SNAKE

def _internal(): pass          # Private: underscore
__mangled = "hidden"           # Name mangling: double

T = TypeVar("T")               # TypeVars: PascalCase
UserT = TypeVar("UserT", bound="User")

Type Hints (3.11+)

Modern Syntax (Required)

# Built-in generics (NOT typing module)
def process(items: list[str]) -> dict[str, int]: ...

# Union w/ | (NOT Optional/Union)
def find_user(id: str) -> User | None: ...

# Self type
from typing import Self
class Builder:
    def chain(self) -> Self: return self

Patterns

from collections.abc import Callable, Awaitable
from typing import TypedDict, Literal, TypeAlias, ParamSpec, Generic

# Callable
Handler = Callable[[Request], Response]
AsyncHandler = Callable[[Request], Awaitable[Response]]

# TypedDict
class UserData(TypedDict):
    id: str
    email: Required[str]
    phone: NotRequired[str | None]

# Literal
Status = Literal["pending", "active", "disabled"]

# TypeAlias
JsonValue: TypeAlias = str | int | float | bool | None | list["JsonValue"] | dict[str, "JsonValue"]

# ParamSpec (decorators)
P = ParamSpec("P")
def logged(fn: Callable[P, T]) -> Callable[P, T]: ...

# Generic
class Repo(Generic[T]):
    def get(self, id: str) -> T | None: ...

Deprecated (Never Use)

# WRONG                          # RIGHT
List[str]                        # list[str]
Optional[int]                    # int | None
Dict[str, int]                   # dict[str, int]
Tuple[int, str]                  # tuple[int, str]
Union[int, str]                  # int | str

Docstrings (Google Style)

def calculate_discount(price: float, percent: float, min_price: float = 0.0) -> float:
    """Calculate discounted price w/ floor.

    Args:
        price: Original price.
        percent: Discount (0-100).
        min_price: Min allowed price.

    Returns:
        Final price, never below min_price.

    Raises:
        ValueError: If invalid inputs.
    """

Skip docstrings for: self-documenting fns, _private methods, trivial @property

Imports

# 1. Stdlib (alphabetical)
import asyncio
from pathlib import Path
from typing import Any

# 2. Third-party
import httpx
from fastapi import Depends, HTTPException
from pydantic import BaseModel

# 3. Local
from app.core.config import settings
from app.models import User

Rules: No wildcards (*), group from same module, parentheses for long imports

Function Signatures (PEP 570/3102)

def api_fn(
    x: int, y: int,           # positional-only
    /,
    z: int = 0,               # positional or keyword
    *,
    strict: bool = False,     # keyword-only
) -> Result: ...

# Prevents: api_fn(x=1, y=2) - forces positional
# Requires: api_fn(1, 2, strict=True) - explicit keyword

Overloads

from typing import overload

@overload
def process(v: int) -> int: ...
@overload
def process(v: str) -> str: ...
def process(v: int | str) -> int | str:
    return v * 2 if isinstance(v, int) else v.upper()

Function Design

LinesStatus
< 20Ideal
20-30OK
30-50Split
> 50Refactor

Params: Max 5 → use dataclass/config obj for more Returns: Always annotate; no flag-based return types

Exception Handling

# DO: Specific exceptions w/ context
try:
    user = await db.get(User, id)
except IntegrityError as e:
    raise UserExistsError(id) from e

# DO: Context managers
async with AsyncSession(engine) as session:
    async with session.begin(): ...

# DON'T
except:           # bare - catches SystemExit
except Exception: # swallows errors
    pass          # silent - at minimum log

Constants

MAX_RETRIES = 3
DEFAULT_TIMEOUT = timedelta(seconds=30)
FORMATS = frozenset({"json", "xml"})

class Status(StrEnum):
    PENDING = "pending"
    ACTIVE = "active"

# NO magic values
await asyncio.sleep(5)         # Bad
await asyncio.sleep(INTERVAL)  # Good

Async

# Context managers
async with httpx.AsyncClient() as client:
    resp = await client.get(url)

# Concurrent
results = await asyncio.gather(fetch_a(), fetch_b(), return_exceptions=True)

# TaskGroup (3.11+)
async with asyncio.TaskGroup() as tg:
    tg.create_task(fetch_a())
    tg.create_task(fetch_b())

# Timeout
async with asyncio.timeout(5.0):
    await slow_op()

# Never block loop
await asyncio.to_thread(blocking_io)  # sync I/O
await asyncio.sleep(1)                # NOT time.sleep()

Pathlib (NOT os.path)

from pathlib import Path

data = Path("data") / "config.json"
text = data.read_text(encoding="utf-8")
data.write_text(json.dumps(obj))

path.parent   # dir
path.stem     # name w/o ext
path.suffix   # .ext
path.name     # filename

Logging

logger = logging.getLogger(__name__)

# Lazy formatting (NOT f-strings)
logger.info("Processing %s items", count)  # YES
logger.info(f"Processing {count}")         # NO - always evaluated

# Exception
logger.exception("Failed")  # auto-includes traceback

Data Models

TypeUse Case
TypedDictExternal JSON/dicts
dataclassInternal DTOs
PydanticValidation needed
NamedTupleImmutable, hashable

Context Managers

from contextlib import suppress, asynccontextmanager

# suppress replaces try/except pass
with suppress(FileNotFoundError):
    Path("temp.txt").unlink()

@asynccontextmanager
async def connection():
    conn = await create()
    try: yield conn
    finally: await conn.close()

Anti-Patterns

BadFix
No type hintsType all params & returns
List, Optionallist, | None
Bare except:Specific exceptions
Magic numbersNamed constants
d, x, tempDescriptive names
process()process_orders()
> 50 linesSplit fn
Mutable defaultsNone + factory
== Noneis None
f-strings in logger%s formatting
os.pathpathlib

Python 3.11+ Features

# match/case
match cmd:
    case {"action": "create", "data": d}: create(d)
    case _: raise ValueError()

# Exception groups
except* ValueError as eg:
    for e in eg.exceptions: handle(e)

# tomllib (built-in TOML)
import tomllib
config = tomllib.load(open("config.toml", "rb"))

# Self type
from typing import Self
def build(self) -> Self: return self

Formatting

  • Line: 88 (Black) or 79 (strict PEP 8)
  • Indent: 4 spaces
  • Blanks: 2 top-level, 1 methods
  • Trailing commas in multi-line

Scripts

Available in scripts/:

ScriptPurposeUsage
check_style.pyFull check (ruff + pycodestyle + mypy)python check_style.py src/ --fix
check_pep8.shQuick PEP 8 only./check_pep8.sh script.py
check_types.shType hints only./check_types.sh src/ --strict
fix_style.shAuto-fix issues./fix_style.sh src/

Install deps: pip install ruff pycodestyle mypy

Tooling

pyproject.toml

[tool.ruff]
target-version = "py311"
line-length = 88

[tool.ruff.lint]
select = ["E", "W", "F", "I", "B", "UP", "N", "RUF", "ASYNC", "S"]
ignore = ["E501"]

[tool.ruff.lint.isort]
known-first-party = ["app"]

[tool.mypy]
python_version = "3.11"
strict = true

Pre-commit

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.4.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.9.0
    hooks:
      - id: mypy

Source

git clone https://github.com/hackermanishackerman/claude-skills-vault/blob/main/.claude/skills/pep8/SKILL.mdView on GitHub

Overview

Pep8 automatically enforces Python 3.11+ standards, PEP 8 compliance, and type-hinting best practices across your codebase. It helps you write, review, or refactor Python code with consistent naming, imports, docstrings, and modern idioms.

How This Skill Works

The skill analyzes code against core standards (PEP 8, PEP 484/585 for type hints, PEP 257 for docstrings, and modern constructs like PEP 604 unions and 3.11 built-in generics). It applies or flags improvements for naming, imports, typing, and docstrings, guiding consistent rewriting and refactoring.

When to Use It

  • Writing or reviewing Python code to ensure adherence to standards
  • Fixing type-hint issues or style violations
  • Responding to user requests for a PEP 8 check
  • Refactoring to modern Python idioms (3.11+)
  • Ensuring Google-style docstrings are present for public APIs

Quick Start

  1. Step 1: Run the pep8 checker on your codebase to identify style, typing, and docstring issues
  2. Step 2: Apply automatic fixes where available and adjust any complex cases manually
  3. Step 3: Re-run the checker to verify all violations are resolved and the code aligns with 3.11+ standards

Best Practices

  • Prefer built-in generics (list[str], dict[str, int]) over typing.List or typing.Dict
  • Use the union operator (A | B) instead of Optional/Union where appropriate
  • Group imports as standard library, third-party, then local, with alphabetical ordering within groups
  • Write Google-style docstrings for public functions and classes (when applicable)
  • Follow PEP 257 for docstring quality and avoid wildcard imports

Example Use Cases

  • Convert legacy typing.List usage to list[str] and typing.Dict to dict[str, int]
  • Replace Optional[T] with T | None where it improves readability
  • Rename variables to snake_case and constants to SCREAMING_SNAKE_CASE
  • Add Google-style docstrings to a public function and its parameters
  • Organize imports into stdlib, third-party, and local groups

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers