# 🤝 Contributing to r_bridge

Thank you for your interest in contributing!

---

## 📋 Prerequisites

- **Python ≥ 3.11**
- **R** installed and on your `PATH` (R 4.2+ UCRT build required on Windows for correct UTF-8 handling)
- **[uv](https://docs.astral.sh/uv/)** — fast Python package manager

---

## 🚀 Getting started

```bash
git clone https://gitlab.com/emzed3/r_bridge.git
cd r_bridge
uv sync --extra dev
```

This installs the library in editable mode plus all dev dependencies (`pytest`, `pytest-cov`, `pytest-timeout`, `zensical`).

---

## 🧪 Running the tests

```bash
uv run pytest                                              # all tests
uv run pytest tests/test_bridge.py -v                     # integration tests (spawn real R)
uv run pytest tests/test_serializer.py                    # unit tests (no R needed)
uv run pytest --cov=r_bridge --cov-report=term-missing    # with coverage
```

> ⚠️ Integration tests require R on `PATH`. A global 30 s timeout is configured in `pyproject.toml` via `pytest-timeout`.

---

## 🏗️ Project architecture

r_bridge spawns `Rscript` once and communicates over **stdin/stdout** using line-delimited JSON with a `__RBRIDGE__:` sentinel prefix. Here is the module layout:

| File | Purpose |
|---|---|
| `bridge.py` | `RBridge` class — lifecycle, send/recv, lock, timeouts, verbose mode |
| `protocol.py` | Envelope construction and sentinel stripping |
| `serializer.py` | 🐍 Python / numpy / pandas → tagged JSON dict |
| `deserializer.py` | 📊 Tagged JSON dict → Python / numpy / pandas |
| `type_hints.py` | `TypeHint` enum (`SCALAR`, `NUMPY`, `PANDAS`, `LIST`, `RAW`) |
| `exceptions.py` | `RBridgeError` hierarchy |
| `utils.py` | `drain_stderr` (with optional `on_line` callback), `get_r_executable` |
| `_r_scripts/bridge_loop.R` | R-side REPL — serializers, deserializers, main loop |

See [`CLAUDE.md`](CLAUDE.md) for a detailed explanation of the key design decisions (sentinel protocol, single-lock design, stderr drain thread, locale handling, etc.).

---

## ✍️ Writing tests

A few conventions to follow:

- 🔬 **Integration tests** that need their own isolated bridge must create one **inline** — do not rely on the module-scoped shared `bridge` fixture for tests with side effects.
- 📢 **Verbose / `capsys` tests** must create `RBridge(verbose=True)` inside the test body, because the shared fixture uses `verbose=False`.
- ⏱️ **Long-running R calls** should use a short `call_timeout` on the bridge itself rather than relying on the 30 s global timeout.

---

## 🔌 Adding type converters

You can register custom serializers and deserializers without modifying the library:

```python
from r_bridge import register_serializer, register_deserializer

register_serializer(MyType, lambda obj: {"__type__": "mytype", "value": obj.to_dict()})
register_deserializer("mytype", lambda d: MyType.from_dict(d["value"]))
```

The `__type__` tag is the key that links the Python serializer to the R-side handler. If your type also needs an R-side deserializer, add a corresponding handler in `_r_scripts/bridge_loop.R`.

---

## 📝 Changelog and versioning

This project follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

1. Add an entry to `CHANGELOG.md` under the appropriate section (`Added`, `Fixed`, `Changed`, `Removed`).
2. Bump `version` in `pyproject.toml` to match.
3. Pushing a `vX.Y.Z` tag triggers the automated GitLab release job, which extracts the matching section from `CHANGELOG.md` as the release description.

---

## ⚙️ CI

The GitLab pipeline runs on **Ubuntu 22.04**, **Ubuntu 24.04**, and **Windows** for **Python 3.11 and 3.12**. All jobs must pass before a merge request can be merged.

Coverage is reported from the `ubuntu_24_04` job and exposed as the coverage badge on the repository.

---

## 🐛 Opening issues and merge requests

- 🐞 **Bugs:** Open an issue at <https://gitlab.com/emzed3/r_bridge/-/issues> and include a minimal reproducible example.
- 💡 **New features:** Open an issue first to discuss the approach before writing code.
- ✅ **Merge requests:** Please ensure all tests pass locally (`uv run pytest`) before submitting.
