# Conventions

> Project coding conventions covering code quality, structure, typing, logging, and style. Always apply when writing, reviewing, or modifying Python code.

- Skill: `mesca/conventions` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add mesca/conventions`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mesca/conventions/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: mesca (https://skillmd.com/u/mesca)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/mesca/conventions

---


# Conventions

Project coding conventions for all Python code. Covers structure, style, quality, and logging. For detailed tool configurations see [tooling.md](./tooling.md). For file templates see [templates.md](./templates.md).

## General Principles

- Prioritize readability over cleverness
- Follow principle of least surprise
- Keep functions small and focused (single responsibility)
- Profile before optimizing — never guess at bottlenecks
- Write tests before or alongside implementation

## Project Structure

Use src-layout with layered architecture:

```
${PROJECT_NAME}/
├── pyproject.toml
├── mkdocs.yml
├── docs/
│   ├── index.md
│   └── scripts/
│       └── gen_ref_pages.py
├── src/
│   └── ${PROJECT_NAME}/
│       ├── __init__.py
│       ├── _version.py        # Auto-generated by hatch-vcs
│       ├── contracts/         # API schemas — single source of truth
│       ├── core/              # Infrastructure only (logger, config, exceptions)
│       ├── models/            # Shared domain models (Pydantic)
│       ├── services/          # Business logic (framework-agnostic)
│       └── interfaces/        # Entry points
│           ├── cli/           # CLI (Typer)
│           ├── rest/          # REST API (FastAPI)
│           └── rpc/           # JSON-RPC over WebSocket
└── tests/
    ├── conftest.py
    └── ...                    # Mirror src/ structure
```

### Layer Rules

- **contracts/**: API schemas (OpenAPI, OpenRPC) — single source of truth, written first
- **core/**: Infrastructure only — NO business logic (logger, config, exceptions, utils)
- **models/**: Pydantic models matching contract schemas — written before implementation
- **services/**: Business logic — framework-agnostic, no Typer/FastAPI imports, fully testable
- **interfaces/**: Thin wrappers — call services, handle I/O, minimal logic

## Python Version

Target Python 3.13+:

```toml
[project]
requires-python = ">=3.13"
```

## Imports

1. Standard library
2. Third-party packages
3. Local imports

```python
import os
from pathlib import Path

import httpx
from pydantic import BaseModel

from ${PROJECT_NAME}.core import logger
from ${PROJECT_NAME}.services.processor import process_data
```

- Prefer `from x import y` for specific items
- Use absolute imports, not relative

## Type Hints

Always use type hints on all function signatures:

```python
def process_file(
    filepath: Path,
    options: dict[str, Any] | None = None,
) -> ProcessResult:
    ...
```

### Modern Typing (Python 3.13+)

```python
list[str]           # not List[str]
dict[str, int]      # not Dict[str, int]
str | None          # not Optional[str]
int | str           # not Union[int, str]
```

## Docstrings

Google-style for all public functions:

```python
def fetch_data(url: str, timeout: int = 30) -> dict[str, Any]:
    """Fetch data from a URL and return parsed JSON.

    Args:
        url: The URL to fetch data from.
        timeout: Request timeout in seconds. Defaults to 30.

    Returns:
        Parsed JSON response as a dictionary.

    Raises:
        httpx.HTTPError: If the request fails.
    """
    ...
```

## Naming

- Variables: explicit names (`file_count` not `fc`, `user_data` not `ud`)
- Constants: `ALL_CAPS` at module level — extract magic numbers
- Functions/methods: `snake_case`, imperative verbs

```python
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30
SUPPORTED_FORMATS = {"json", "yaml", "toml"}
```

## Logging

**Never use `print()` — always use logger.**

```python
from ${PROJECT_NAME}.core import logger

logger.debug(f"Processing item {i} of {total}")
logger.info(f"Processing {count} files")
logger.success(f"Completed: {result}")
logger.warning(f"Rate limit approaching")
logger.error(f"Failed to process: {error}")
logger.critical("System failure")
```

### Key Patterns

```python
# Log exceptions with context
try:
    process()
except Exception as e:
    logger.error(f"Processing failed: {e}", exc_info=True)
    raise

# Progress logging for long operations
for i, item in enumerate(items, 1):
    process_item(item)
    if i % 100 == 0:
        logger.info(f"Progress: {i}/{total} items processed")
logger.success(f"Completed processing {total} items")
```

### Testing with Logging

```python
def test_function_logs_correctly(caplog):
    with caplog.at_level("INFO"):
        my_function()
        assert "Expected message" in caplog.text
```

## Error Handling

```python
# Specific exceptions, not bare except
try:
    result = process(data)
except FileNotFoundError:
    logger.error(f"File not found: {filepath}")
    raise
except ValueError as e:
    logger.error(f"Invalid data: {e}")
    raise

# Custom exceptions in core/exceptions.py
class ProcessingError(Exception):
    """Raised when processing fails."""
    pass

# Validate early, fail fast
def process(data: str) -> Result:
    if not data:
        raise ValueError("data cannot be empty")
    ...
```

## Configuration

Use environment variables via pydantic-settings:

```python
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    debug: bool = False
    api_key: str
    database_url: str

    model_config = {"env_prefix": "${PROJECT_NAME}_"}

settings = Settings()
```

## Refactoring

- Look for duplication, long functions, unclear names
- Extract common functionality to reusable functions
- Keep refactoring commits separate from feature commits

## Code Review Checklist

Before committing:

- [ ] Type hints complete and accurate
- [ ] Google-style docstrings on public APIs
- [ ] Logger used, not `print()`
- [ ] Magic numbers extracted to constants
- [ ] Specific exception handling (no bare `except`)
- [ ] Tests comprehensive and passing
- [ ] No sensitive info in logs or code
- [ ] `uv run ruff check .` passes
- [ ] `uv run pyright` passes

## Quick Reference

| Aspect | Convention |
|--------|------------|
| Python version | 3.13+ |
| Package manager | uv |
| Build system | Hatchling + hatch-vcs |
| Versioning | Git tags (vX.Y.Z) |
| Linter/Formatter | Ruff |
| Type checker | Pyright (strict) |
| Test framework | pytest |
| Documentation | Material for MkDocs |
| Docstring style | Google |
| Logging | loguru (never print()) |
| CLI | Typer |
| REST API | FastAPI |
| JSON-RPC / WebSocket | jsonrpc-websocket, websockets |
| Line length | 88 |
| Import style | Absolute, sorted |

## See Also

- **spec-driven** — contracts-first development and models placement
- **project-name** — resolves `${PROJECT_NAME}`
- **documentation** — MkDocs setup and API doc generation
- **tdd** — test-driven development with pytest

