feat: add scalar function authoring and catalog client (#3991)

## Problem

The canonical Function wire values and typed remote Job contract do not
yet provide a Python authoring surface or catalog client, so users
cannot package a scalar callable, register it, or reopen the exact
immutable Function version.

## Behavior

This adds scalar-only `@udf` authoring with deterministic annotation or
explicit Arrow schema validation, content-addressed Python artifacts,
and an internal scalar-to-Arrow-batch adapter descriptor. Registration
payloads model non-secret environment values and secret names only.

Remote connections can submit `create_function_async` and receive a
typed `Job<FunctionVersion>`, then reopen that exact version by name and
version ID. Synchronous connections can call `create_function` to submit
and wait for the immutable version in one operation. Local Function
catalog operations return a stable `NotSupported` error. Shared
Rust/Python golden payloads and mocked catalog responses freeze the
request, typed terminal result, and exact lookup contract.

## Validation

- Rust formatting, remote check, clippy, and focused LDB-1/LDB-2 tests
- Python formatting, lint, and focused LDB-1/LDB-2 tests
- Python API documentation build
This commit is contained in:
Xuanwo
2026-08-21 17:19:13 +08:00
committed by GitHub
parent 685cb01d6d
commit a588208de6
19 changed files with 1326 additions and 27 deletions
+32
View File
@@ -563,6 +563,38 @@ impl Connection {
Ok(crate::job::Job::new(inner.job(job_id).infer_error()?))
}
pub fn create_function_async(
self_: PyRef<'_, Self>,
request_json: String,
) -> PyResult<Bound<'_, PyAny>> {
let inner = self_.get_inner()?.clone();
let request = lancedb::function::FunctionRegistrationRequest::from_json(&request_json)
.infer_error()?;
future_into_py(self_.py(), async move {
inner
.create_function_async(request)
.await
.infer_error()
.map(crate::job::FunctionJob::new)
})
}
pub fn get_function(
self_: PyRef<'_, Self>,
name: String,
version: String,
) -> PyResult<Bound<'_, PyAny>> {
let inner = self_.get_inner()?.clone();
future_into_py(self_.py(), async move {
inner
.get_function(name, version)
.await
.infer_error()?
.to_canonical_json()
.infer_error()
})
}
pub fn list_jobs(self_: PyRef<'_, Self>) -> PyResult<Bound<'_, PyAny>> {
let inner = self_.get_inner()?.clone();
future_into_py(self_.py(), async move {