diff --git a/docs/src/python/python.md b/docs/src/python/python.md index 0a3eaf4dc..d0ebbd00b 100644 --- a/docs/src/python/python.md +++ b/docs/src/python/python.md @@ -377,11 +377,64 @@ code that used the former LanceDB Enterprise-only argument order. ## Functions and Jobs +A decorated `udf` definition is also a local callable. Call a decorated function +directly with row inputs, or call a decorated class with its initialization fields +and then call the returned instance; call `close()` if the class defines it. This +path uses the current Python environment and does not create a registered +`FunctionVersion`, build the declared remote runtime, resolve Secrets, or execute +the remote Arrow adapter. A registered `FunctionVersion` call instead binds +`col(...)` inputs and returns a remote `FunctionApplication`. + +```python +from lancedb import udf + + +@udf +def normalize(text: str) -> str: + return text.strip().lower() + + +@udf +class Scale: + def __init__(self, factor: float): + self.factor = factor + + def __call__(self, value: float) -> float: + return value * self.factor + + def close(self) -> None: + self.factor = 0.0 + + +assert normalize(" Hello ") == "hello" +scaler = Scale(factor=2.0) +assert scaler(3.0) == 6.0 +scaler.close() +``` + Annotate a blob input with `lancedb.BlobReader` for lazy, seekable access, or with `bytes` to materialize its contents before the Function runs. Reader inputs infer the Blob V2 type without an explicit input schema. Readers are scoped to each invocation and support `read`, `readinto`, `seek`, `tell`, -`size`, and cursor-independent `read_range`. +`size`, and cursor-independent `read_range`. For a local test or another +engine, construct `BlobReader(read_range, size)` with a callback over an +in-memory bytes fixture or the engine's byte-range reader; `read_range` does +not advance the cursor. + +```python +from lancedb import BlobReader + +payload = b"0123456789" + + +def read_range(offset: int, length: int) -> bytes: + return payload[offset : offset + length] + + +with BlobReader(read_range, len(payload)) as reader: + assert reader.read_range(6, 4) == b"6789" + assert reader.tell() == 0 +``` ::: lancedb.BlobReader diff --git a/python/python/lancedb/_blob_reader.py b/python/python/lancedb/_blob_reader.py index c47f8074d..dbc6d4a5e 100644 --- a/python/python/lancedb/_blob_reader.py +++ b/python/python/lancedb/_blob_reader.py @@ -26,6 +26,26 @@ class BlobReader(io.RawIOBase): For local calls, construct a reader from a range-reading callable and the blob's size. The callable accepts an offset and length and returns bytes. + This is also the interface for a reader backed by another engine. + + Examples + -------- + ```python + payload = b"hello world" + + def read_range(offset, length): + return payload[offset : offset + length] + + with BlobReader(read_range, len(payload)) as reader: + assert reader.read_range(6, 5) == b"world" + assert reader.tell() == 0 + assert reader.read(5) == b"hello" + assert reader.tell() == 5 + ``` + + ``read_range`` does not change the sequential cursor. Use ``size()`` for + the declared payload size and close the reader when done; a local reader + owns no server state. """ def __init__(self, read_range: Callable[[int, int], bytes], size: int): diff --git a/python/python/lancedb/functions.py b/python/python/lancedb/functions.py index e294fc750..80d1e08eb 100644 --- a/python/python/lancedb/functions.py +++ b/python/python/lancedb/functions.py @@ -1957,14 +1957,20 @@ def _class_call(cls: type) -> Callable[..., Any]: class UdfDefinition: """A Python callable prepared for remote Function registration. - Instances are created with :func:`udf`. Calling an instance executes the - original Python function, or constructs the original class, which keeps - local unit testing ordinary. Remote execution adapts the callable to the - internal Arrow batch ABI described by the registration artifact. By - default it invokes the callable once per row. A callable whose parameters - are all annotated as ``pyarrow.Array`` receives whole Arrow arrays and - returns a ``pyarrow.Array`` instead. This authoring choice is independent - of the internal artifact adapter in the registration request. + Instances are created with :func:`udf`. Calling an instance is ordinary + local Python: a decorated function is called directly, while a decorated + class is constructed using its initialization fields and the returned + instance is called with each input. Reuse the instance and call its + ``close`` method when it defines one. This public call does not create a + remote application or build the declared remote runtime, and it does not + need the private implementation attribute. + + Remote execution adapts the callable to the internal Arrow batch ABI + described by the registration artifact. By default it invokes the callable + once per row. A callable whose parameters are all annotated as + ``pyarrow.Array`` receives whole Arrow arrays and returns a + ``pyarrow.Array`` instead. This authoring choice is independent of the + internal artifact adapter in the registration request. """ def __init__( @@ -2390,8 +2396,14 @@ def udf( ... self.factor, self.offset = factor, offset ... def __call__(self, value: float) -> float: ... return value * self.factor + self.offset - >>> scale(factor=2.0)(1.5) + ... def close(self): + ... self.factor = 0.0 + >>> scale_fn = scale(factor=2.0) + >>> scale_fn(1.5) 3.0 + >>> scale_fn(2.0) + 4.0 + >>> scale_fn.close() >>> [field.name for field in scale.registration_request.signature.initialization] ['factor', 'offset'] >>> import lancedb @@ -2402,6 +2414,27 @@ def udf( ... def copy_blobs(image: pa.Array) -> pa.Array: ... return image + **Local execution.** A function definition is callable with its decorated + input signature and runs the original function in the current Python + environment. A class definition is called with its initialization + arguments and constructs one instance; call that instance for row inputs, + reuse it as needed, and call ``close`` if it defines that method. This path + does not create a registered + [FunctionVersion][lancedb.functions.FunctionVersion], build the declared + remote environment, resolve Secrets, or run the remote Arrow adapter or + sandbox. A registered ``FunctionVersion`` call is different: it binds + ``col(...)`` inputs and returns a remote + [FunctionApplication][lancedb.functions.FunctionApplication]. + + ```python + scaled = scale(factor=2.0) + try: + assert scaled(1.5) == 3.0 + assert scaled(2.0) == 4.0 + finally: + scaled.close() + ``` + Blob v2 input is exposed as ``pyarrow.LargeBinaryArray`` in vectorized mode (and as ``bytes`` in row mode). Return a ``LargeBinaryArray`` with the declared nullability. Use :func:`lancedb.blob` in the explicit schemas;