Files
AGENTS/rules/languages/python.md
m3tm3re 8910413315 feat(rules): add initial rule files for concerns, languages, and frameworks
Concerns (6 files):
- coding-style.md (163 lines): patterns, anti-patterns, error handling, SOLID
- naming.md (105 lines): naming conventions table per language
- documentation.md (149 lines): docstrings, WHY vs WHAT, README standards
- testing.md (134 lines): AAA pattern, mocking philosophy, TDD
- git-workflow.md (118 lines): conventional commits, branch naming, PR format
- project-structure.md (82 lines): directory layout, entry points, config placement

Languages (4 files):
- python.md (224 lines): uv, ruff, pyright, pytest, pydantic, idioms, anti-patterns
- typescript.md (150 lines): strict mode, discriminated unions, satisfies, as const
- nix.md (129 lines): flake structure, module patterns, alejandra, anti-patterns
- shell.md (100 lines): set -euo pipefail, shellcheck, quoting, POSIX

Frameworks (1 file):
- n8n.md (42 lines): workflow design, node patterns, Error Trigger, security

Context budget: 975 lines (concerns + python) < 1500 limit

Refs: T6-T16 of rules-system plan
2026-02-17 19:05:45 +01:00

3.9 KiB

Python Language Rules

Toolchain

Package Management (uv)

uv init my-project --package
uv add numpy pandas
uv add --dev pytest ruff pyright hypothesis
uv run python -m pytest
uv lock --upgrade-package numpy

Linting & Formatting (ruff)

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

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

[tool.ruff.format]
quote-style = "double"

Type Checking (pyright)

[tool.pyright]
typeCheckingMode = "strict"
reportMissingTypeStubs = true
reportUnknownMemberType = true

Testing (pytest + hypothesis)

import pytest
from hypothesis import given, strategies as st

@given(st.integers(), st.integers())
def test_addition_commutative(a, b):
    assert a + b == b + a

@pytest.fixture
def user_data():
    return {"name": "Alice", "age": 30}

def test_user_creation(user_data):
    user = User(**user_data)
    assert user.name == "Alice"

Data Validation (Pydantic)

from pydantic import BaseModel, Field, validator

class User(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    age: int = Field(ge=0, le=150)
    email: str

    @validator('email')
    def email_must_contain_at(cls, v):
        if '@' not in v:
            raise ValueError('must contain @')
        return v

Idioms

Comprehensions

# List comprehension
squares = [x**2 for x in range(10) if x % 2 == 0]

# Dict comprehension
word_counts = {word: text.count(word) for word in unique_words}

# Set comprehension
unique_chars = {char for char in text if char.isalpha()}

Context Managers

# Built-in context managers
with open('file.txt', 'r') as f:
    content = f.read()

# Custom context manager
from contextlib import contextmanager

@contextmanager
def timer():
    start = time.time()
    yield
    print(f"Elapsed: {time.time() - start:.2f}s")

Generators

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

def read_lines(file_path):
    with open(file_path) as f:
        for line in f:
            yield line.strip()

F-strings

name = "Alice"
age = 30

# Basic interpolation
msg = f"Name: {name}, Age: {age}"

# Expression evaluation
msg = f"Next year: {age + 1}"

# Format specs
msg = f"Price: ${price:.2f}"
msg = f"Hex: {0xFF:X}"

Anti-Patterns

Bare Except

# AVOID: Catches all exceptions including SystemExit
try:
    risky_operation()
except:
    pass

# USE: Catch specific exceptions
try:
    risky_operation()
except ValueError as e:
    log_error(e)
except KeyError as e:
    log_error(e)

Mutable Defaults

# AVOID: Default argument created once
def append_item(item, items=[]):
    items.append(item)
    return items

# USE: None as sentinel
def append_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

Global State

# AVOID: Global mutable state
counter = 0

def increment():
    global counter
    counter += 1

# USE: Class-based state
class Counter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1

Star Imports

# AVOID: Pollutes namespace, unclear origins
from module import *

# USE: Explicit imports
from module import specific_function, MyClass
import module as m

Project Setup

pyproject.toml Structure

[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
    "pydantic>=2.0",
    "httpx>=0.25",
]

[project.optional-dependencies]
dev = ["pytest", "ruff", "pyright", "hypothesis"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

src Layout

my-project/
├── pyproject.toml
└── src/
    └── my_project/
        ├── __init__.py
        ├── main.py
        └── utils/
            ├── __init__.py
            └── helpers.py