Claude Code for Ruff: Fast Python Linter and Formatter — Claude Skills 360 Blog
Blog / AI / Claude Code for Ruff: Fast Python Linter and Formatter
AI

Claude Code for Ruff: Fast Python Linter and Formatter

Published: December 17, 2027
Read time: 5 min read
By: Claude Skills 360

Ruff is an extremely fast Python linter and formatter written in Rust. pip install ruff. Lint: ruff check src/. Format: ruff format src/. Fix: ruff check --fix src/. Check only: ruff format --check src/. Watch: ruff check --watch src/. Rule docs: ruff rule E501. Config in pyproject.toml: [tool.ruff] line-length=88 target-version="py312". [tool.ruff.lint] select=["E","W","F","I","N","UP","B","S","C4","PTH","RUF"]. extend-ignore=["E501","S101"]. [tool.ruff.lint.per-file-ignores] "tests/*"=["S101","S105"]. Inline: x = 1 # noqa: E501 — suppress specific rule. # noqa (no code) — suppress all rules on line (avoid). Format settings: [tool.ruff.format] quote-style="double" indent-style="space" skip-magic-trailing-comma=false. [tool.ruff.lint.isort] known-first-party=["mypackage"]. Rule families: E/W (pycodestyle), F (Pyflakes), I (isort), N (pep8-naming), UP (pyupgrade), B (flake8-bugbear), S (flake8-bandit), C4 (flake8-comprehensions), PTH (pathlib), A (flake8-builtins), ARG (unused-arguments), RET (return), SIM (simplify), TID (tidy-imports), ERA (eradicated), PL (Pylint), PERF (Perflint), RUF (Ruff-specific). --select ALL selects every rule. Unsafe fixes: ruff check --unsafe-fixes --fix src/ — applies fixes that may change semantics. ruff check --show-fixes — preview what would be auto-fixed. Exclude: [tool.ruff] exclude=[".venv","dist","migrations"]. extend-exclude=["generated_pb2.py"]. Output: ruff check src/ --format=github — GitHub Actions annotations. ruff check src/ --format=json — machine-readable. Pre-commit: - repo: https://github.com/astral-sh/ruff-pre-commit; hooks: [{id: ruff, args: ["--fix"]},{id: ruff-format}]. Claude Code generates Ruff configurations, rule sets for specific project types, and CI linting pipelines.

CLAUDE.md for Ruff

## Ruff Stack
- Version: ruff >= 0.4 | pip install ruff
- Lint: ruff check src/ | ruff check --fix (auto-repair)
- Format: ruff format src/ | ruff format --check (CI)
- Config: pyproject.toml [tool.ruff.lint] select= extend-ignore=
- Per-file: [tool.ruff.lint.per-file-ignores] "tests/*"=["S101"]
- Suppress: # noqa: E501 (specific code, never bare # noqa)
- Watch: ruff check --watch src/ for live feedback during development

Ruff Linting and Formatting Pipeline

# This file demonstrates common Ruff rules — what they catch and the fix.
# Run: ruff check src/ruff_examples.py --fix
# Format: ruff format src/ruff_examples.py
from __future__ import annotations

import os
import sys
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    pass


# ─────────────────────────────────────────────────────────────────────────────
# E / W — pycodestyle style rules
# ─────────────────────────────────────────────────────────────────────────────

# E711 — comparison to None should use `is` / `is not`
# BAD:  if value == None:
# GOOD:
def check_none(value: object) -> bool:
    return value is None


# E712 — comparison to True/False should use `is` or `if cond:`
# BAD:  if flag == True:
# GOOD:
def check_flag(flag: bool) -> bool:
    return flag is True


# W291 / W293 — trailing whitespace (auto-fixed by ruff format)


# ─────────────────────────────────────────────────────────────────────────────
# F — Pyflakes rules
# ─────────────────────────────────────────────────────────────────────────────

# F401 — unused import (auto-fixable)
# BAD:  import json  # never used
# Ruff removes it with --fix

# F841 — local variable assigned but never used
# BAD:  result = compute()  # result never read
# GOOD: drop the assignment or use _

def compute_and_discard() -> None:
    _ = expensive_computation()   # explicit discard with _


def expensive_computation() -> int:
    return 42


# F811 — redefinition of unused name
# BAD:  def foo(): pass; def foo(): pass  # second def shadows first

# F821 — undefined name (Pyflakes catches NameError before runtime)


# ─────────────────────────────────────────────────────────────────────────────
# I — isort import sorting
# ─────────────────────────────────────────────────────────────────────────────

# Ruff enforces isort-compatible ordering:
# 1. stdlib  2. third-party  3. first-party  4. local
# --fix reorders automatically

# Configure known-first-party in pyproject.toml:
# [tool.ruff.lint.isort]
# known-first-party = ["mypackage"]
# combine-as-imports = true


# ─────────────────────────────────────────────────────────────────────────────
# UP — pyupgrade: modernize Python syntax
# ─────────────────────────────────────────────────────────────────────────────

# UP007 — use X | Y instead of Optional[X] / Union[X, Y]  (Python 3.10+)
# BAD:  from typing import Optional; def f(x: Optional[int]) -> None:
# GOOD:
def process_optional(x: int | None) -> int:
    return x or 0


# UP006 — use list/dict/tuple instead of typing.List/Dict/Tuple
# BAD:  from typing import List; def f() -> List[int]:
# GOOD:
def get_ids() -> list[int]:
    return [1, 2, 3]


# UP034 — extraneous parentheses in return
# BAD:  return (x + y)
# GOOD:
def add(x: int, y: int) -> int:
    return x + y


# UP035 — deprecated `typing` imports — use `collections.abc`
# BAD:  from typing import Callable, Iterator
# GOOD: from collections.abc import Callable, Iterator


# ─────────────────────────────────────────────────────────────────────────────
# B — flake8-bugbear: likely bugs and design issues
# ─────────────────────────────────────────────────────────────────────────────

# B006 — mutable default argument — classic Python footgun
# BAD:
# def append_to(item, lst=[]):  # B006 — shared across calls!
#     lst.append(item); return lst

# GOOD:
def append_to(item: int, lst: list[int] | None = None) -> list[int]:
    if lst is None:
        lst = []
    lst.append(item)
    return lst


# B007 — loop variable unused in loop body (rename to _)
# BAD:  for i in range(10): print("hello")
# GOOD:
def print_ten() -> None:
    for _ in range(10):
        print("hello")


# B008 — function call in default argument
# BAD:  def handler(ts=datetime.now()):  # evaluated once at import
# GOOD: use None and compute inside the function

# B023 — function defined in loop captures loop variable by reference
# BAD:  fns = [lambda: i for i in range(3)]  # all return 2
# GOOD: fns = [lambda i=i: i for i in range(3)]


# ─────────────────────────────────────────────────────────────────────────────
# C4 — flake8-comprehensions: use comprehensions where appropriate
# ─────────────────────────────────────────────────────────────────────────────

# C400 — replace list() call with list comprehension
# BAD:  list(x for x in range(10))
# GOOD:
squares = [x * x for x in range(10)]

# C401 — replace set() call with set comprehension
unique_ids = {u["id"] for u in [{"id": 1}, {"id": 2}]}

# C416 — unnecessary comprehension (just use the iterable)
# BAD:  [x for x in items]  →  list(items)
# BAD:  {k: v for k, v in d.items()}  →  dict(d)


# ─────────────────────────────────────────────────────────────────────────────
# PTH — use pathlib instead of os.path
# ─────────────────────────────────────────────────────────────────────────────

# PTH100 — os.path.abspath → Path.resolve()
# PTH110 — os.path.exists  → Path.exists()
# PTH118 — os.path.join    → Path / operator
# PTH119 — os.path.basename → Path.name

# GOOD:
def read_config(config_path: str) -> str:
    path = Path(config_path).resolve()
    if not path.exists():
        raise FileNotFoundError(f"Config not found: {path}")
    return path.read_text(encoding="utf-8")


# ─────────────────────────────────────────────────────────────────────────────
# S — flake8-bandit security rules (subset, not all)
# ─────────────────────────────────────────────────────────────────────────────

# S101 — use of assert (stripped by -O optimization)
# Ruff flags this — typically suppressed in test files:
# per-file-ignores: "tests/*" = ["S101"]

# S105 — hardcoded password string
# BAD:  API_KEY = "sk-abc123"
# GOOD:
API_KEY = os.environ.get("API_KEY", "")

# S603 / S607 — subprocess without shell / starting process
# GOOD:
import subprocess
def run_safe(args: list[str]) -> str:
    result = subprocess.run(args, capture_output=True, text=True,
                            check=True, shell=False)
    return result.stdout


# ─────────────────────────────────────────────────────────────────────────────
# RUF — Ruff-specific rules
# ─────────────────────────────────────────────────────────────────────────────

# RUF001/002/003 — ambiguous unicode characters in string/docstring/comment
# RUF005 — use unpacking instead of concatenation: [*a, *b] vs a + b
combined = [*[1, 2], *[3, 4]]           # RUF005 fix

# RUF010 — use explicit conversion flag in f-strings:
# BAD:  f"{str(x)}"  →  f"{x!s}"
# GOOD:
def format_value(x: object) -> str:
    return f"{x!s}"

# RUF012 — mutable class var should be annotated with ClassVar
from typing import ClassVar

class Registry:
    _handlers: ClassVar[dict[str, object]] = {}   # RUF012 — must be ClassVar


# ─────────────────────────────────────────────────────────────────────────────
# SIM — flake8-simplify
# ─────────────────────────────────────────────────────────────────────────────

# SIM102 — use single if instead of nested if
# BAD:  if a: if b: do()
# GOOD:
def nested_condition(a: bool, b: bool) -> None:
    if a and b:
        print("both true")

# SIM108 — use ternary instead of if-else assignment
# BAD:  if cond: x = a  else: x = b
# GOOD:
def ternary(cond: bool, a: int, b: int) -> int:
    return a if cond else b

# SIM117 — merge nested with statements
# BAD:  with open(f1) as a: with open(f2) as b:
# GOOD:
def read_two(f1: str, f2: str) -> tuple[str, str]:
    with Path(f1).open() as a, Path(f2).open() as b:
        return a.read(), b.read()


# ─────────────────────────────────────────────────────────────────────────────
# pyproject.toml configuration reference
# ─────────────────────────────────────────────────────────────────────────────

PYPROJECT_RUFF = """
[tool.ruff]
line-length = 88
target-version = "py312"
exclude = [
    ".venv", ".nox", "dist", "build",
    "migrations", "*_pb2.py", "conftest.py",
]

[tool.ruff.lint]
# Start with a focused set; add rule families as team comfort grows
select = [
    "E", "W",    # pycodestyle
    "F",         # Pyflakes
    "I",         # isort
    "N",         # pep8-naming
    "UP",        # pyupgrade
    "B",         # flake8-bugbear
    "C4",        # flake8-comprehensions
    "PTH",       # use pathlib
    "SIM",       # simplify
    "RUF",       # Ruff-specific
]
extend-ignore = [
    "E501",   # line too long — formatter handles this
    "N818",   # Exception suffix — not always wanted
]

[tool.ruff.lint.per-file-ignores]
"tests/*"     = ["S101", "S105", "S106", "ARG001", "ARG002"]
"migrations/*"= ["E501", "N999"]
"scripts/*"   = ["T201"]    # allow print()

[tool.ruff.lint.isort]
known-first-party = ["mypackage"]
combine-as-imports = true
force-sort-within-sections = true

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
"""


if __name__ == "__main__":
    print("Ruff linting and formatting examples")
    print("Run: ruff check src/ruff_examples.py --fix")
    print("     ruff format src/ruff_examples.py")
    print("\nKey rule families:")
    for family, desc in [
        ("E/W",  "pycodestyle — spacing, blank lines"),
        ("F",    "Pyflakes — unused imports, undefined names"),
        ("I",    "isort — import order"),
        ("UP",   "pyupgrade — modern Python syntax"),
        ("B",    "bugbear — mutable defaults, loop capture"),
        ("C4",   "comprehensions — replace list()/set() calls"),
        ("PTH",  "pathlib — replace os.path usage"),
        ("SIM",  "simplify — ternary, nested if, nested with"),
        ("RUF",  "Ruff-specific — unicode, ClassVar, f-strings"),
    ]:
        print(f"  {family:6}  {desc}")

For the flake8 + isort + pyupgrade + Black alternative — running four separate tools requires four config files (.flake8, .isort.cfg, a pyupgrade pre-commit invocation, and pyproject.toml [tool.black]), four separate pre-commit hooks each spawning a Python process, and four different # noqa / # type: ignore syntaxes, while Ruff replaces all four with a single process that runs in under 100 ms on a 100k-line repo because it is implemented in Rust and processes files in parallel — and ruff check --fix applies isort sorting, pyupgrade syntax modernization, and bugbear fixes in one pass. For the pylint alternative — pylint has broader code-smell detection (too-many-branches, too-many-arguments) but is 10–100× slower than Ruff because it builds a full AST call graph, and every pylint suppression requires a # pylint: disable= comment while Ruff’s # noqa: B006 code is self-documenting — Ruff is the right first-pass linter for CI speed; pylint can run as a scheduled deeper check. The Claude Skills 360 bundle includes Ruff skill sets covering pyproject.toml rule selection, E/W/F/I/UP/B/C4/PTH/SIM/RUF rule families, per-file-ignores patterns, —fix and —unsafe-fixes workflows, isort configuration, ruff format Black-compatible settings, pre-commit hook setup, GitHub Actions CI with —format=github, noqa inline suppression, and ruff rule EXXXX lookup. Start with the free tier to try linting and formatting code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free