|
| 1 | +# Contributing to geneva-examples |
| 2 | + |
| 3 | +Thanks for improving the Geneva UDF examples. This guide covers the local setup, |
| 4 | +the project's conventions, and the workflow for adding a new UDF or stage. |
| 5 | + |
| 6 | +## Local setup |
| 7 | + |
| 8 | +Requires Python ≥ 3.12 and [`uv`](https://docs.astral.sh/uv/). |
| 9 | + |
| 10 | +```bash |
| 11 | +make install # uv sync --group dev + install the git pre-commit hook |
| 12 | +make check # the full CI gate: ruff lint + format-check + pytest (90% coverage) |
| 13 | +``` |
| 14 | + |
| 15 | +`geneva`, `lancedb`, and `pylance` are pinned betas served from Gemfury indexes |
| 16 | +(declared in [`pyproject.toml`](pyproject.toml)). If your environment can't reach |
| 17 | +those indexes, `uv sync` will fail on those packages — request access or run in an |
| 18 | +environment that has it. |
| 19 | + |
| 20 | +Useful targets (see `make help`): `make lint-fix`, `make format`, `make test`, |
| 21 | +`make typecheck`, `make precommit`. |
| 22 | + |
| 23 | +## Conventions |
| 24 | + |
| 25 | +- **Formatting & linting:** `ruff` (config in `pyproject.toml`) is the single |
| 26 | + source of truth and gates every commit via pre-commit. Run `make format` before |
| 27 | + pushing. Line length is 88; existing `# noqa` suppressions are kept honest by |
| 28 | + `RUF100`, so don't add ones the selected rules won't use. |
| 29 | +- **Type checking:** `ty` runs in pre-commit and CI but is **non-blocking** by |
| 30 | + design — it's a preview tool with many false positives on the untyped ML deps. |
| 31 | + Prefer precise annotations; for the opaque Geneva/LanceDB runtime objects, use |
| 32 | + the structural `Protocol`s in |
| 33 | + [`geneva_examples/core/_types.py`](geneva_examples/core/_types.py) under a |
| 34 | + `TYPE_CHECKING` guard instead of importing the beta runtime types. Promoting |
| 35 | + `ty` to a blocking gate is deliberately deferred until its false-positive rate |
| 36 | + drops. |
| 37 | +- **Imports in UDF bodies:** a UDF/chunker body is a self-contained closure that |
| 38 | + ships to the remote workers. Nest its imports and helpers *inside* the factory |
| 39 | + function so they serialize with it; keep the driver/CLI code lightweight. |
| 40 | + |
| 41 | +## Adding a new UDF |
| 42 | + |
| 43 | +1. **Prototype in UDF Studio.** Run `uv run udf-studio`, pick a template, point it |
| 44 | + at sample data in `studio_data/`, and iterate on your `transform(value)` (UDF) |
| 45 | + or `chunk(value)` (chunker) locally — no cluster, GPU, or Ray. See the |
| 46 | + [README](README.md#udf-studio). |
| 47 | +2. **Add a factory + manifest.** Create a module in |
| 48 | + [`geneva_examples/udfs/`](geneva_examples/udfs/) following |
| 49 | + [`imageinfo.py`](geneva_examples/udfs/imageinfo.py): export a `build_*_udf(...)` |
| 50 | + factory and a `*_RUNTIME_PIP` list pinning the worker-side packages |
| 51 | + (env-overridable, like `GENEVA_PACKAGE_SPEC`). |
| 52 | +3. **Wire a stage CLI.** Add a Typer CLI under |
| 53 | + [`geneva_examples/pipeline/stages/`](geneva_examples/pipeline/stages/) modeled on |
| 54 | + [`lightweight.py`](geneva_examples/pipeline/stages/lightweight.py): load config, |
| 55 | + `connect`, build a `GenevaManifest`, build the UDF(s), and call the shared |
| 56 | + [`backfill_column()`](geneva_examples/pipeline/stages/_runner.py) runner. Add a |
| 57 | + `project.scripts` entry in `pyproject.toml`. |
| 58 | + |
| 59 | +## Testing & the coverage policy |
| 60 | + |
| 61 | +The suite enforces a **90% coverage gate**, but the pieces that need a live |
| 62 | +cluster, GPU, or model weights are listed in `[tool.coverage.run] omit` in |
| 63 | +`pyproject.toml` (the model UDFs, the pipeline/ops CLIs, the Gradio wiring). Their |
| 64 | +*pure* helpers are still unit-tested — they just don't inflate the percentage. |
| 65 | + |
| 66 | +When you add code: |
| 67 | + |
| 68 | +- Unit-test pure helpers directly (see `tests/test_udfs.py`, |
| 69 | + `tests/test_pipeline_runner.py`, `tests/test_ops_*.py`). |
| 70 | +- For CLI *wiring* that would otherwise hit a cluster, add a mocked smoke test in |
| 71 | + the style of [`tests/test_pipeline_smoke.py`](tests/test_pipeline_smoke.py): use |
| 72 | + `typer.testing.CliRunner` and monkeypatch `load_config`/`connect` plus an |
| 73 | + injected fake `geneva` module. |
| 74 | +- Reuse the synthetic-media fixtures in `tests/conftest.py` |
| 75 | + (`make_png`, `make_mp4`, `data_dir`). |
| 76 | + |
| 77 | +Run `make check` before opening a PR. |
0 commit comments