home / skills / georgekhananaev / claude-skills-vault / pep8
/.claude/skills/pep8
This skill enforces modern Python 3.11+ standards, PEP 8 compliance, and type hints to ensure clean, maintainable code.
npx playbooks add skill georgekhananaev/claude-skills-vault --skill pep8Review the files below or copy the command above to add this skill to your agents.
---
name: pep8
description: Enforces modern Python 3.11+ coding standards, PEP 8 compliance, and type-hinting best practices automatically. This skill should be used when writing, reviewing, or refactoring Python code to ensure consistency with PEP 8, proper type hints, Google-style docstrings, and modern Python idioms.
---
# 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
| Standard | Desc |
|----------|------|
| PEP 8 | Naming, imports, spacing |
| PEP 484/585 | Type hints (modern) |
| PEP 257 | Docstrings |
| PEP 604 | Union `\|` |
| PEP 570/3102 | `/` positional, `*` keyword |
## Naming
```python
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)
```python
# 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
```python
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)
```python
# 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)
```python
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
```python
# 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)
```python
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
```python
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
| Lines | Status |
|-------|--------|
| < 20 | Ideal |
| 20-30 | OK |
| 30-50 | Split |
| > 50 | Refactor |
**Params:** Max 5 → use dataclass/config obj for more
**Returns:** Always annotate; no flag-based return types
## Exception Handling
```python
# 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
```python
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
```python
# 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)
```python
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
```python
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
| Type | Use Case |
|------|----------|
| TypedDict | External JSON/dicts |
| dataclass | Internal DTOs |
| Pydantic | Validation needed |
| NamedTuple | Immutable, hashable |
## Context Managers
```python
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
| Bad | Fix |
|-----|-----|
| No type hints | Type all params & returns |
| `List`, `Optional` | `list`, `\| None` |
| Bare `except:` | Specific exceptions |
| Magic numbers | Named constants |
| `d`, `x`, `temp` | Descriptive names |
| `process()` | `process_orders()` |
| > 50 lines | Split fn |
| Mutable defaults | `None` + factory |
| `== None` | `is None` |
| f-strings in logger | `%s` formatting |
| os.path | pathlib |
## Python 3.11+ Features
```python
# 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/`:
| Script | Purpose | Usage |
|--------|---------|-------|
| `check_style.py` | Full check (ruff + pycodestyle + mypy) | `python check_style.py src/ --fix` |
| `check_pep8.sh` | Quick PEP 8 only | `./check_pep8.sh script.py` |
| `check_types.sh` | Type hints only | `./check_types.sh src/ --strict` |
| `fix_style.sh` | Auto-fix issues | `./fix_style.sh src/` |
**Install deps:** `pip install ruff pycodestyle mypy`
## Tooling
### pyproject.toml
```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
```yaml
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
```This skill enforces modern Python 3.11+ coding standards, PEP 8 compliance, and type-hinting best practices automatically. It streamlines style, typing, imports, docstrings, async patterns, and common anti-patterns so code is consistent and production-ready. Use it to catch style regressions, missing or outdated type hints, and to apply recommended idioms across a codebase.
The skill inspects Python source for PEP 8 violations, outdated typing patterns, import ordering, docstring format, and modern 3.11+ features. It flags problems (naming, imports, function signatures, async usage, logging, magic values) and suggests fixes following built-in generics, | unions, google-style docstrings, pathlib, TaskGroup, and other idioms. It can be run as a check or used with auto-fix tooling configured in pyproject and pre-commit.
Will this change runtime behavior?
No — the skill focuses on style, typing, and idiomatic rewrites. It recommends changes that are semantic-preserving; structural refactors should be reviewed before applying.
Which Python versions are targeted?
Python 3.11+ features are the baseline; the skill enforces modern typing and stdlib utilities available in 3.11 and later.
Can it auto-fix issues?
Yes — use included fix scripts or configure pre-commit hooks (ruff --fix, mypy checks) to apply many fixes automatically. Some design changes still require human review.