# Linting and Formatting with Ruff

This document describes how to use Ruff for linting and formatting in the Pinecone Python SDK.

## Overview

The SDK uses [Ruff](https://docs.astral.sh/ruff/) for both linting and formatting. Ruff is an extremely fast Python linter and formatter written in Rust. The configuration is in `ruff.toml` at the project root.

## Why Ruff?

- **Fast**: 10-100x faster than traditional tools (Flake8, Black, isort combined)
- **All-in-one**: Replaces multiple tools (Flake8, Black, isort, pyupgrade, etc.)
- **Drop-in replacement**: Compatible with existing configurations
- **Active development**: Modern, well-maintained tool

## Running Ruff

### Check for linting issues

```bash
# Check all code
ruff check .

# Check specific file or directory
ruff check pinecone/
ruff check pinecone/_internal/http/client.py

# Show fixes that would be applied
ruff check --diff .

# Apply safe fixes automatically
ruff check --fix .
```

### Format code

```bash
# Format all code
ruff format .

# Format specific file or directory
ruff format pinecone/

# Check if code is formatted (CI mode)
ruff format --check .

# Show what would change
ruff format --diff pinecone/
```

### Combined workflow

```bash
# Fix linting issues and format in one go
ruff check --fix . && ruff format .
```

## Configuration Highlights

### Formatting (100-character lines)

- **Line length**: 100 characters (balanced readability and density)
- **Quote style**: Double quotes (`"`)
- **Indentation**: 4 spaces
- **Trailing commas**: Preserved (Magic trailing comma enabled)

### Enabled Linting Rules

The SDK enables comprehensive linting covering:

- **E/W**: pycodestyle (PEP 8 style)
- **F**: pyflakes (common errors)
- **I**: isort (import sorting)
- **N**: pep8-naming (naming conventions)
- **UP**: pyupgrade (modern Python syntax)
- **B**: flake8-bugbear (common bugs)
- **C4**: flake8-comprehensions (better comprehensions)
- **SIM**: flake8-simplify (code simplification)
- **S**: flake8-bandit (security issues)
- **ASYNC**: async/await best practices
- **PT**: pytest style
- **PERF**: performance anti-patterns
- **And many more...** (see `ruff.toml` for complete list)

### Ignored Rules (with justifications)

- **E501** (line-too-long): Handled by formatter
- **COM812/ISC001**: Conflict with formatter
- **T20** (print-found): Allowed in CLI code
- **TRY003**: Allow inline exception messages
- **D**: Docstring rules (to be enabled when documentation is standardized)

See `ruff.toml` for complete list with justifications.

## Per-File Rule Exceptions

### `__init__.py` files

```python
# F401 (unused-import): Re-exports are expected
# F403 (undefined-local-with-import-star): Star imports for convenience

from .client import Client
from .index import Index
```

### Test files (`tests/**/*.py`)

```python
# S101: Allow assert statements (standard in pytest)
# PLR2004: Allow magic values in test data
# PLR0913: Allow many fixture parameters
# SLF001: Allow testing private members

def test_internal_method():
    obj = MyClass()
    assert obj._internal_state == "expected"  # SLF001 allowed
```

### CLI code (`pinecone/_internal/migrate/**/*.py`)

```python
# T20: Allow print statements for CLI output

def main():
    print("Migration complete!")  # T20 allowed in CLI
```

## Import Sorting

Ruff automatically sorts imports following these rules:

```python
# Standard library
import os
import sys
from pathlib import Path

# Third-party packages
import httpx
from msgspec import Struct

# First-party (pinecone)
from pinecone._internal.http import HTTPClient
from pinecone._internal.models import Vector
```

Configuration:
- **known-first-party**: `["pinecone"]`
- **lines-after-imports**: 2 (blank lines after import block)
- **lines-between-types**: 1 (blank line between import groups)
- **combine-as-imports**: True (combine `import x as y`)

## Handling Linting Issues

### 1. Fix the Issue (Preferred)

Let Ruff fix it automatically:

```bash
ruff check --fix .
```

Or fix manually following the suggestion.

### 2. Refactor the Code

If the rule highlights a real problem, refactor:

```python
# Before: PLR0913 (too many arguments)
def complex_function(a, b, c, d, e, f, g, h):
    ...

# After: Use a configuration object
from msgspec import Struct

class Config(Struct):
    a: int
    b: str
    # ... other fields

def complex_function(config: Config):
    ...
```

### 3. Suppress Specific Rules (Use Sparingly)

For legitimate exceptions, use inline comments:

```python
# Suppress specific rule with explanation
result = eval(user_input)  # noqa: S307 - Input is validated above

# Suppress multiple rules
x = dict((k, v) for k, v in items)  # noqa: C402, C416 - API compatibility
```

**Rules for noqa:**
- Always include the rule code: `# noqa: CODE`
- Always add a comment explaining WHY: `# noqa: CODE - reason`
- Never use bare `# noqa` without a code
- Prefer fixing the issue over suppressing

### 4. Per-File Exceptions

For entire files that need exceptions, add to `ruff.toml`:

```toml
[lint.per-file-ignores]
"scripts/debug.py" = [
    "T20",  # print-found: Debug script needs print statements
]
```

Always document WHY the exception is needed.

## Common Patterns

### Modern Python Syntax (UP rules)

Ruff automatically suggests and fixes modern Python idioms:

```python
# Before (old style)
from typing import List, Dict, Optional
def process(items: List[str]) -> Dict[str, int]:
    ...

# After (Python 3.10+ style)
def process(items: list[str]) -> dict[str, int]:
    ...
```

### Type Checking Imports (TCH rules)

Separate imports used only for type checking:

```python
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pinecone._internal.http import HTTPClient

def create() -> "HTTPClient":
    from pinecone._internal.http import HTTPClient
    return HTTPClient()
```

### Security Issues (S rules)

Ruff catches common security problems:

```python
# S108: Hardcoded temp directory
tempfile = "/tmp/data"  # BAD

# Better
import tempfile
temp_path = tempfile.mkdtemp()  # GOOD
```

### Performance Issues (PERF rules)

```python
# PERF401: Manual list comprehension in loop
result = []
for x in items:
    result.append(x * 2)  # BAD

# Better
result = [x * 2 for x in items]  # GOOD
```

## Integration with Development Workflow

### Pre-commit Hooks

Ruff runs automatically via pre-commit hooks:

```bash
# Runs on git commit
git add file.py
git commit -m "Add feature"  # Ruff runs automatically

# Skip if needed (not recommended)
git commit --no-verify
```

### CI Pipeline

Ruff runs in CI for all pull requests:

```bash
# What CI runs
ruff check .
ruff format --check .
```

PRs with linting or formatting issues will not merge.

### IDE Integration

Most Python IDEs support Ruff:

- **VS Code**: Install the Ruff extension
- **PyCharm**: Install Ruff plugin from marketplace
- **Vim/Neovim**: Use ALE or null-ls with ruff
- **Cursor**: Built-in Python support

Configure your IDE to:
- Run Ruff on save (formatting)
- Show Ruff diagnostics inline (linting)
- Use Ruff for import sorting

## Troubleshooting

### "Ruff command not found"

Ensure you're in the virtual environment:

```bash
source .venv/bin/activate
which ruff  # Should show .venv/bin/ruff
```

### Formatting conflicts with manual edits

Ruff uses the configured line length (100 characters). If your manual formatting doesn't match:

```bash
# Let Ruff reformat
ruff format .
```

### Rule conflicts

Some rules may conflict (e.g., formatter vs. linter). These are handled in `ruff.toml`:

```toml
ignore = [
    "COM812",  # missing-trailing-comma: Handled by formatter
    "ISC001",  # single-line-implicit-string-concatenation: Conflicts with formatter
]
```

### Too many issues to fix at once

Fix incrementally:

```bash
# Fix only safe changes
ruff check --fix --unsafe-fixes=disabled .

# Fix one category at a time
ruff check --select F --fix .  # Fix pyflakes issues first
ruff check --select E --fix .  # Then pycodestyle errors
```

### Cache issues

Clear the cache if you see stale results:

```bash
rm -rf .ruff_cache/
ruff check .
```

## Migrating from Black/Flake8/isort

If migrating from traditional tools:

1. **Remove old configs**: Delete `.flake8`, `setup.cfg` [tool.black], `pyproject.toml` [tool.isort]
2. **Use `ruff.toml`**: All configuration is now in one file
3. **Update CI**: Replace multiple tool calls with `ruff check` and `ruff format`
4. **Update pre-commit**: Use Ruff hooks instead of Black/Flake8/isort hooks

## Rule Reference

For details on specific rules:

```bash
# Explain a rule
ruff rule S101

# Show all available rules
ruff linter

# Search for rules
ruff linter | grep "import"
```

Online documentation: https://docs.astral.sh/ruff/rules/

## References

- [Ruff documentation](https://docs.astral.sh/ruff/)
- [Ruff rules reference](https://docs.astral.sh/ruff/rules/)
- [Ruff settings reference](https://docs.astral.sh/ruff/settings/)
- SDK conventions: [docs/conventions/](./README.md)
