# Developer Guide

## Setup

### Prerequisites

- Python 3.12+
- [uv](https://docs.astral.sh/uv/)

### Install

```bash
uv sync
```

This creates the venv, installs all deps (including dev), and generates `uv.lock`.

### Editor (VS Code)

**Required extensions:**

- [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) (type checking)
- [Ruff](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff) (linting + formatting)

**Recommended settings** (`.vscode/settings.json`):

```json
{
  "evenBetterToml.schema.enabled": false, // as of now, doesn't work well with ruff > 0.1.x
  "ruff.configurationPreference": "filesystemFirst",
  "python.defaultInterpreterPath": ".venv/bin/python",
  "python.analysis.typeCheckingMode": "strict",
  "python.analysis.extraPaths": ["src"],
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.codeActionsOnSave": {
      "source.fixAll.ruff": "explicit",
    },
  },
}
```

## Pre-Commit Checklist

Run all three before every commit:

```bash
uv run mypy --strict src/ tests/  # Type check (the key gate)
uv run ruff check src/ tests/    # Lint
uv run pytest tests/unit/ -v     # Tests
```

`ruff check --fix` will auto-fix import sorting and other safe issues.

## Architecture

```text
src/radiens_core/
  __init__.py                 # Public API surface
  specs.py                    # TimeRangeSpec, SignalSpec (user-facing)
  exceptions.py               # Exception hierarchy
  models/                     # Pydantic v2, ZERO proto dependency
  _transport/                 # gRPC channel pool, auth, error mapping
  _converters/                # Proto <-> domain (ONLY place importing both)
  _services/                  # RPC wrappers (domain in, domain out)
  _sub_clients/               # Domain-grouped API surfaces
  allego_client.py            # Real-time client
  videre_client.py            # Offline file client
  curate_client.py            # Curation client
  _generated/                 # Committed proto stubs (DO NOT EDIT)
```

### Layer Rules

| Layer | Can import from |
| --- | --- |
| `models/` | stdlib, pydantic, numpy |
| `_converters/` | `models/`, `_generated/` |
| `_services/` | `_converters/`, `_generated/`, `_transport/`, `models/` |
| `_sub_clients/` | `_services/`, `models/`, `specs` |
| Client classes | `_sub_clients/`, `_transport/`, `models/` |

**Only `_converters/` and `_services/` may import from `_generated/`.** Nothing user-facing touches proto types.

### Proto-Driven Type Safety

This is the core design principle. When a proto definition changes and stubs are regenerated, `mypy --strict` will flag every location in `_converters/` that references a renamed/removed field. Fixing the converter then cascades type errors into `models/`, `_services/`, and up. The type system is the change-detection mechanism.

**The chain:** Proto `.pyi` stubs -> `_converters/` -> `models/` -> `_services/` -> `_sub_clients/` -> clients

### Updating Protos

Proto generation is owned by the radix repo. When protos change:

1. Run the radix proto generation script (produces `_pb2.py` + `.pyi` stubs via `mypy-protobuf`)
2. Copy updated files into `src/radiens_core/_generated/`
3. Run `uv run mypy --strict src/` -- it will show you exactly what broke
4. Fix converters first, then follow the type errors outward
5. Commit the updated `_generated/` alongside your fixes

### Notable Conventions

- **`from __future__ import annotations`** is used everywhere. This makes all annotations strings at runtime, which enables `TYPE_CHECKING` imports.
- **Pydantic models** use `# type: ignore[explicit-any]` on the `class X(BaseModel):` line. This is required because Pydantic's `BaseModel` internally uses `Any`, which conflicts with `disallow_any_explicit = true`. The ignore is surgical and intentional.
- **Frozen models:** All Pydantic domain models use `model_config = ConfigDict(frozen=True)`. Data is immutable once created.
- **`_generated/`** is excluded from ruff and mypy error checking (but mypy still reads the `.pyi` stubs for type info).

## Publishing to PyPI

### Setup PyPI Credentials

Configure your PyPI credentials in `~/.pypirc`:

```ini
[radiens-core]
repository = https://upload.pypi.org/legacy/
username = __token__
password = pypi-your-project-scoped-token-here
```

Create project-scoped tokens at <https://pypi.org/manage/account/token/>

### Release Workflow

1. **Update version** in `pyproject.toml`:

   ```toml
   version = "0.1.0b2"  # or whatever the new version should be
   ```

2. **Run pre-commit checks**:

   ```bash
   uv run mypy --strict src/ tests/
   uv run ruff check src/ tests/
   uv run pytest tests/unit/ -v
   ```

3. **Build and publish**:

   ```bash
   # Clean previous builds
   rm -rf dist/ build/
   
   # Build package
   uv run python -m build
   
   # Publish to PyPI
   uv run twine upload --repository radiens-core dist/*
   ```

### Version Management

- Edit version directly in `pyproject.toml` - this is the single source of truth
- Use PEP 440 versioning: `0.1.0b1`, `0.1.0rc1`, `0.1.0`, etc.
- Cannot overwrite published versions - increment for each release
