Hello UDF
Example code:
09-hello-queue-udf
Decorate a Python function with @udf, bind it to a queue, submit it,
and get the result back by invocation id. Part 1 needs nothing but
Python; Part 2 points the same code at a control plane.
Prerequisites
| Requirement | Why |
|---|---|
| Python 3.11+ | dreamlake-lakeshore requires 3.11 or later. |
pip install dreamlake-lakeshore | Ships the @udf decorator, queues, and the local dispatch plane. |
Verify the install:
Step 1 — Write a @udf function
Create hello_udf.py:
Two things to notice:
queue=takes a string, not aQueueobject. The decorator does not create the queue and does not talk to a server at import time.- Because the UDF is queue-bound, a plain call
add(2, 3)submits and waits. To get the id without waiting, calladd.submit(2, 3).
Step 2 — Run it with zero infrastructure
Add a main() that stands up the minimal dispatch plane, submits, drains
one job in-process, and reads the result:
2 + 3 = 5. If the last line prints, the submit → claim → execute →
return round-trip works.
Step 3 — The id is the handle
There is no Future type. submit() returns a plain string — persist it,
log it, hand it to another process. Anything that can reach the same
dispatch plane re-attaches with it:
Step 4 — The local escape hatch
.local() bypasses the queue and runs in-process, which is what you
want in unit tests and while debugging:
A bare @dls.udf (no queue=) behaves this way on every call.
Step 5 — Against a real control plane
Two things change: the dispatch plane, and who runs the worker.
Start a Python worker on a host that can import your module:
lakeshore daemon … manages the Rust fleet daemon, which runs shell
commands and control-plane invocations. Queue-bound Python UDFs are
drained by lakeshore worker start, which spawns
python -m dreamlake.lakeshore.daemon. The worker deliberately ignores
saved auth login credentials — pass --url / --token explicitly, or
export LAKESHORE_URL and LAKESHORE_CLIENT_TOKEN.
Then drop the _dispatch= arguments. With LAKESHORE_URL set (or an
auth.yml present), the SDK's default plane is already HttpDispatch:
Step 6 — Look at it from the CLI
Step 7 — Clean up
rm refuses with a 409 while the queue still has members; add --force
to scrub the name out of every Worker.queues array first.
What you learned
@udf(queue="…")binds a function to a queue by name.submit()returns a durable invocation id — a string, not a Future.q.result(id, timeout=…)fetches the value from any process holding the id..local(...)always runs in-process.dls.Dispatchmakes the whole loop runnable with no infrastructure.
Read next
- Fan out — dispatch N invocations and
join with
asyncio.gather. @udfdecorator — the full decorator surface.- Queue API —
Queue,SyncQueue, streaming, and the worker verbs. - Queues — the operator side of the queue you just created.