Skip to content
Back to skills

Pep8

ASecurity

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.

  • 28 stars
  • 0 votes
  • 1 copy
  • 6 views
  • Added February 7, 2026
code-qualitypythongofastapirefactoringgitapi

Works with

  • cli
  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 5 files and shows the line behind each finding

Scanned February 10, 2026

npx -y skills add georgekhananaev/claude-skills-vault --skill pep8 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Pep8?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Pep8
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/georgekhananaev-pep8/badge)](https://www.skillsdirectory.com/skills/georgekhananaev-pep8)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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
```

Files in this skill

  • SKILL.md8.8 KB
  • scripts/check_pep8.sh569 B
  • scripts/check_style.py4.3 KB
  • scripts/check_types.sh589 B
  • scripts/fix_style.sh726 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.