Thanks for improving the Geneva UDF examples. This guide covers the local setup, the project's conventions, and the workflow for adding a new UDF or stage.
Requires Python ≥ 3.12 and uv.
make install # uv sync --group dev + install the git pre-commit hook
make check # the full CI gate: ruff lint + format-check + pytest (90% coverage)geneva, lancedb, and pylance are pinned betas served from Gemfury indexes
(declared in pyproject.toml). If your environment can't reach
those indexes, uv sync will fail on those packages — request access or run in an
environment that has it.
Useful targets (see make help): make lint-fix, make format, make test,
make typecheck, make precommit.
- Formatting & linting:
ruff(config inpyproject.toml) is the single source of truth and gates every commit via pre-commit. Runmake formatbefore pushing. Line length is 88; existing# noqasuppressions are kept honest byRUF100, so don't add ones the selected rules won't use. - Type checking:
tyruns in pre-commit and CI but is non-blocking by design — it's a preview tool with many false positives on the untyped ML deps. Prefer precise annotations; for the opaque Geneva/LanceDB runtime objects, use the structuralProtocols ingeneva_examples/core/_types.pyunder aTYPE_CHECKINGguard instead of importing the beta runtime types. Promotingtyto a blocking gate is deliberately deferred until its false-positive rate drops. - Imports in UDF bodies: a UDF/chunker body is a self-contained closure that ships to the remote workers. Nest its imports and helpers inside the factory function so they serialize with it; keep the driver/CLI code lightweight.
Examples are self-contained packages under
geneva_examples/examples/. Each declares a spec
(Example → Steps → Params) that both the generated CLIs and the TUI render,
so params and descriptions are defined once.
- Prototype in UDF Studio. Run
uv run udf-studio, pick a template, point it at sample data instudio_data/, and iterate on yourtransform(value)(UDF) orchunk(value)(chunker) locally — no cluster, GPU, or Ray. See the README. - Add the UDF factory + manifest. In your example package (new or existing),
add a UDF module following
examples/images/imageinfo.py: abuild_*_udf(...)factory and a*_RUNTIME_PIPlist pinning the worker-side packages (env-overridable, likeGENEVA_PACKAGE_SPEC). Shared model UDFs go inexamples/_shared/. - Add a step run-function. Write
run(cfg: Config, *, ...) -> Nonemodeled onexamples/images/lightweight.py:connect(cfg),build_manifest(cfg, ...)(→Nonelocally), build the UDF(s) viaresolve_resources(cfg, ...), and callbackfill_column()insideruntime_session(conn, cfg). Heavy imports stay nested insiderun. - Declare the spec. In the example's
__init__.py, add aStep(title + markdown description +params=params_from_signature(run, help=...)), and list it on theExample. Register a brand-new example inexamples/__init__.py— the TUI picks it up automatically. - Expose the CLI. Add a
build_command(...)binding inexamples/cli.pyand aproject.scriptsentry inpyproject.toml.
The suite enforces a 90% coverage gate, but the pieces that need a live
cluster, GPU, or model weights are listed in [tool.coverage.run] omit in
pyproject.toml (the model UDFs, the pipeline/ops CLIs, the Gradio wiring). Their
pure helpers are still unit-tested — they just don't inflate the percentage.
When you add code:
- Unit-test pure helpers directly (see
tests/test_udfs.py,tests/test_pipeline_runner.py,tests/test_spec.py,tests/test_ops_*.py). - Registry + spec invariants live in
tests/test_registry.py/tests/test_spec.py; TUI behavior intests/test_tui.py(Textual pilot) andtests/test_tui_forms.py. - For CLI wiring that would otherwise hit a cluster, add a mocked smoke test in
the style of
tests/test_pipeline_smoke.py: drive the generated command fromgeneva_examples.examples.cliwithclick'sCliRunnerin--mode local, monkeypatch the step module'sconnect, and (for model steps) use the injected fakegenevamodule. - Reuse the synthetic-media fixtures in
tests/conftest.py(make_png,make_mp4,data_dir).
Run make check before opening a PR.