# Developer Guide

## Setup

### Prerequisites

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

### Install

**Note:** This project requires Python 3.12 or higher.

```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 extensions:**

- [Mypy Type Checker](https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker) (real-time type checking)
- [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) (markdown formatting)

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

```json
{
  "evenBetterToml.schema.enabled": false, // doesn't work with ruff > 0.1.x apparently
  "ruff.configurationPreference": "filesystemFirst",
  "python.defaultInterpreterPath": ".venv/bin/python",
  "python.analysis.typeCheckingMode": "off",
  "python.analysis.useLibraryCodeForTypes": true,
  "python.analysis.extraPaths": ["src"],
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.codeActionsOnSave": {
      "source.fixAll.ruff": "explicit",
    },
  },
  "[markdown]": {
    "editor.defaultFormatter": "DavidAnson.vscode-markdownlint",
    "editor.formatOnSave": true,
    "editor.formatOnSaveMode": "file",
  },
  "mypy-type-checker.interpreter": [".venv/bin/python"],
  "mypy-type-checker.args": ["--strict"],
  "mypy-type-checker.path": [".venv/bin/mypy"],
  "mypy-type-checker.severity": {
    "error": "Error",
    "note": "Information"
  }
}
```

## Documentation

The documentation is built with [MkDocs](https://www.mkdocs.org/) and the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme. API reference is automatically generated from docstrings using `mkdocstrings`. Versioning is managed with [mike](https://github.com/jimporter/mike).

### Previewing locally

```bash
uv run --group docs mkdocs serve
```

Starts a local server at `http://127.0.0.1:8000/` with live-reloading.

### Building

```bash
uv run --group docs mkdocs build
```

The output will be in the `site/` directory.

### Deployment

Documentation is automatically deployed to GitHub Pages via GitHub Actions (`.github/workflows/docs.yml`):

- **Push to `main`**: Deploys the `dev` version of the docs.
- **Push a `v*` tag**: Deploys a versioned release and updates the `latest` alias.
- **Pull request**: Build-only validation (no deploy).
- **Manual trigger**: `workflow_dispatch` is enabled in the Actions tab.

`mike` maintains multiple doc versions on the `gh-pages` branch. The `latest` alias is the default landing page.

### Updating docs for an existing release

To update the docs for a published version without cutting a new release (e.g., fixing a typo in the README):

```bash
uv run --group docs mike deploy <version> latest --update-aliases --push
```

For example:

```bash
uv run --group docs mike deploy 0.0.1 latest --update-aliases --push
```

This builds docs from your **current working tree** (not the Git tag) and commits the built HTML to `gh-pages`. No source code is pushed — only static site output.

## Pre-Commit Checklist

Run the automated check-and-fix script before every commit:

```bash
./scripts/check-fix.sh
```

Alternatively, run each check individually:

```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)
  _base_file_client.py        # Shared base for file-based clients (Videre, Curate)
  allego_client.py            # Real-time client
  videre_client.py            # Offline file client (extends BaseFileClient)
  curate_client.py            # Curation client (extends BaseFileClient)
  _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/` |
| Client classes | `_services/`, `_transport/`, `models/`, `specs` |

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

### Implementation Status

See [IMPLEMENTATION_STATUS.md](IMPLEMENTATION_STATUS.md) for a detailed list of implemented and pending gRPC endpoints across all clients.

### 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/` -> 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

Publishing to PyPI is fully automated via GitHub Actions using Trusted Publishing (OIDC). You do not need local PyPI credentials or manual build steps.

### Local Safety Hook (Highly Recommended)

To prevent accidentally pushing a Git tag that doesn't match the version in `pyproject.toml`, install the provided pre-push Git hook:

```bash
chmod +x scripts/pre-push.sh
ln -sf ../../scripts/pre-push.sh .git/hooks/pre-push
```

### Release Workflow

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

   ```toml
   version = "x.x.x"  # or whatever the new version should be
   ```

2. **Update `CHANGELOG.md`**:
   - Rename `[Unreleased]` to `[x.x.x] - YYYY-MM-DD`.
   - Add a fresh `[Unreleased]` section above it with the placeholder `*No unreleased changes.*`.
   - Remove the placeholder when the first unreleased entry is added.
   - **Beta consolidation:** When promoting from pre-release to stable, collapse all beta entries into a single release entry organized by client/area. Beta users are a small audience; the stable changelog should read cleanly.

3. **Run pre-commit checks** to ensure everything is stable:

   ```bash
   ./scripts/check-fix.sh
   ```

4. **Review `README.md`** — update the overview, capabilities, or install instructions if the release adds/removes clients, changes requirements, or shifts beta/stable status.

5. **Commit the version bump**:

   ```bash
   git add pyproject.toml
   git commit -m "chore: bump version to x.x.x"
   ```

6. **Tag and Push**:

   ```bash
   git tag vx.x.x
   git push origin main
   git push origin vx.x.x
   ```

   Pushing the `v*` tag triggers the `.github/workflows/publish.yml` GitHub Action, which builds and publishes the package to PyPI automatically.

### Version Management

- `pyproject.toml` is the absolute source of truth for the package's internal metadata.
- The Git tag (e.g., `v1.2.3`) is the trigger for the release pipeline.
- Both must perfectly match (ignoring the `v` prefix in the tag) for the release to succeed.
- Use PEP 440 versioning: `0.0.0b1`, `0.0.0b2`, `0.0.0`, `0.1.0`, etc.
- Cannot overwrite published versions - increment for each release.
