API reference#
The daisy package re-exports the public surface from internal modules. All names below are accessible directly as daisy.<name>.
Top-level helpers#
- daisy.run_blockwise(pipeline, multiprocessing=True, resources=None, progress=True, block_tracking=True, return_states=False, host=None, summary=True)[source]#
Run the given pipeline to completion.
pipeline must be a daisy.Pipeline (or a single Task, which is sugar for a singleton pipeline). The legacy form run_blockwise([task1, task2]) is supported by daisy.v1_compat, which converts a list of tasks into a Pipeline by reading each task’s v1.x-style upstream_tasks= kwarg.
Returns True only if every block of every task either completed successfully in this run or was skipped because a prior run already marked it done. Permanently failed or orphaned blocks cause this to return False.
With
return_states=True, returns thetask_id -> TaskStatedict instead of the bool — per-task counters plusabandoned,abandon_reasonandlast_worker_error— so partial failures can be inspected programmatically without the lower-levelServerclass. Abandonment (worker restart cap exhausted) raisesRuntimeErroreither way.summary=Falsesilences the execution summary that is otherwise printed on return, on KeyboardInterrupt and on abandonment. Each task’s figures (per-block and per-worker timing) are persisted and available afterwards fromtask.report()either way.hostis the address workers are told to connect to. Leave it unset and daisy detects one — this host’s resolved name, else the default-route interface, verified before use — and listens on every interface so workers on other machines can reach it. Set it when detection cannot know the answer: a multi-homed node with a specific fabric, a container whose external address differs from anything it can see, or a DNS name only the compute nodes resolve. Passing"127.0.0.1"keeps the scheduler private to this machine, which also means no remote worker can connect.DAISY_HOSTsets the same value from the environment, for pipeline libraries that don’t thread the argument through.
Tasks and blocks#
- class daisy.Task(*args, num_workers=None, max_workers=None, upstream_tasks=None, **kwargs)[source]#
Bases:
Taskv1.x-compatible Task — a Python subclass of _rs.Task that accepts the daisy 1.x kwargs num_workers=N (alias for max_workers=N) and upstream_tasks=[other_task, …] (declares per-task block-level dependencies, which v2 expresses via Pipeline composition).
Passing either kwarg emits a DeprecationWarning. The upstream_tasks are stored on this Python subclass instance (Python subclasses of #[pyclass(subclass)] get their own __dict__); daisy._task._build_pipeline_from_tasks reads this attribute when bridging a v1.x list-of-tasks call into a v2 Pipeline.
- class daisy.Block(total_roi, read_roi, write_roi, task_id=None, block_id=None)[source]#
Bases:
objectOne unit of work: a read ROI, a write ROI, an id and a status.
total_roionly feeds the derivation of the block id; passblock_idto override it (what daisy does for blocks received off the wire). ROIs may be funlib.geometry.Roi or anything with integeroffsetandshape.- property read_roi#
funlib.geometry.Roi this block reads from.
- property write_roi#
funlib.geometry.Roi this block writes to.
- property block_id#
(task_id, spatial_id), as in daisy 1.x.
- property status#
One of the BlockStatus constants; set it to FAILED to mark the block failed without raising.
- property stats#
Resource measurement attached by profile_block, or None.
- class daisy.TaskState#
Bases:
objectPython-facing wrapper around a TaskCounters snapshot. The Rust scheduler internally tracks tasks as a typestate enum (Running/Done/Abandoned), but observers and post-run consumers only care about the counter values, so we hand them a frozen snapshot at the FFI boundary.
- abandoned#
True iff the task was abandoned (restart cap exhausted, or an upstream task was abandoned) rather than run to completion.
- Type:
- first_worker_error#
The first worker error observed for this task (usually the root cause; later errors are often echoes), or None.
- last_worker_error#
The most recent worker error observed for this task (spawn function failure, dirty worker exit, or a reported block failure), or None if no worker ever errored.
Geometry#
daisy.Roi and daisy.Coordinate are
funlib.geometry’s classes,
as in 1.x. Every daisy entry point accepts them (or anything with integer
offset and shape), and every ROI daisy hands back is one.
Scheduling#
- class daisy.Scheduler(tasks_or_pipeline)[source]#
Bases:
v1.x-compatible Scheduler factory. Accepts a Pipeline, a Task, or a list of tasks (with upstream_tasks= declarations honoured) and returns an _rs.Scheduler.
daisy v2 internalised the scheduler to consume a Pipeline only; this shim builds a Pipeline from the list-of-tasks form by walking the v1.x upstream side-table on each task.
- Return type:
Scheduler
Server / Client#
- class daisy.Server(stop_event=None)[source]#
Bases:
objectCoordinator. Thin shim over _rs._run_distributed_server that wraps process functions with per-worker logging context first.
Most users go through daisy.run_blockwise(…) and read a task’s figures back with Task.report(). Instantiate this class directly when you want the whole pipeline’s last_report or last_task_order after the run.
- last_report#
per-task block aggregates (resource figures only for tasks with
resource_tracking=True) and per-worker startup / idle / compute / teardown timing.- Type:
This run’s daisy.RunReport
- run_blockwise(pipeline, resources=None, progress=True, block_tracking=True, host=None, summary=True)[source]#
Run and return the task_id -> TaskState dict. Never raises for abandoned tasks (inspect the states instead). The execution summary is printed on return and on KeyboardInterrupt; summary=False silences it.
- class daisy.Client(context=None)[source]#
Bases:
objectWorker-side client. Reads connection info from the DAISY_CONTEXT env var (or accepts an explicit Context), connects to the scheduler over TCP, and yields blocks via a context manager that handles status bookkeeping and routes failure tracebacks into the per-worker log.
Every acquired block is watched automatically: when the task has a block timeout (Task(timeout=…), default 600s), a watchdog kills this worker process if the block is still unreleased after that long — true preemption, even for code stuck inside C. This covers every loop built on this Client, including hand-written cluster workers; the server reclaims and retries the block on the same deadline.
The connection also runs TCP keepalive, so a worker waiting in acquire_block() notices a server whose host has vanished (cloud scale-down — no RST will ever arrive) as a connection error within a few minutes, instead of blocking forever on a half-open socket. Together with the block watchdog, this bounds an orphaned worker’s lifetime at roughly the block timeout plus keepalive detection.
A server that is already gone at construction time (connection refused) is treated as “the run has ended”: construction does NOT raise — a WARNING is logged, connected is False, and acquire_block() yields None immediately, so the canonical worker loop exits cleanly. This is the normal fate of straggler cluster jobs that start after the run drained; it must not register as a worker failure. A missing or malformed DAISY_CONTEXT still raises (KeyError / ValueError) — that’s a configuration error, not a race. Mid-run connection losses also still raise.
The acquire_block context manager is implemented in Python because it integrates with daisy.logging (the “logging” carve-out for the otherwise all-Rust runtime).
- class daisy.Context(**kwargs)#
Bases:
objectDict-like key→string store with to_env/from_env helpers. Construct from kwargs (Context(hostname=”…”, port=12345)) or from Context.from_env() to read back the env var on the worker side. Keys and values are always coerced to str.
- static from_env()#
Reconstruct from the DAISY_CONTEXT env var. Raises KeyError if the variable is unset.
- static from_env_string(env)#
Parse a key=value:key=value context string (the to_env encoding) without touching the process environment.
Run report#
- class daisy.RunReport(tasks)[source]#
Bases:
objectReports for every task of one run, keyed by task id.
- Parameters:
tasks (dict)
- property timed: dict#
a worker timeline, a per-block round trip, or resource measurements. A serial task without resource tracking has none of these.
- Type:
The tasks with anything to show
- render(task_order=None, max_workers=10)[source]#
The Resource Utilization panel, one section per task in
task_order. What needs no tracking store — the per-block round trip and the per-worker startup / idle / compute / teardown — is always shown; the in-worker measurements (wall, CPU, RSS, IO, overhead, trend) only for tasks withresource_tracking=True. Empty when there is nothing at all to show (a serial run without resource tracking) — a table of zeros is worse than no table.
- class daisy.TaskReport(task_id, blocks, workers, started_at=0.0, finished_at=0.0, tracked=False)[source]#
Bases:
objectOne task’s report.
blocksandworkersare the summaries;worker(id)andblock(...)drill down.- block(block)[source]#
Look a block up in the task’s tracking store.
blockis a daisy.Block, a grid coordinate (tuple of ints, one per axis, anchored attotal_roi) or a spatial block id (int; found by scanning the grid, so prefer the other two forms on large tasks). None when the task has no store or the block is not in it. Available on reports returned by a run in this process; a report rebuilt from JSON has no task to read the store through.- Return type:
- block_records()[source]#
Every block the tracking store has recorded (done, or with a failure count) as BlockRecord`s sorted by spatial id: the per-block counterpart of ``workers.by_id`. Per-block timing (
round_trip,worker_id,sent_at,returned_at) and resource figures needresource_tracking=True; a plain done-marker store yieldsdoneandfailuresonly. Walks the whole grid, so it is O(blocks); for very large grids read the zarr arrays in the store directly. Empty without a store; needs the task attached, like block.- Return type:
- class daisy._report.BlockSummary(count=0, skipped=0, failures=0, has_stats=False, round_trip=Stat(), wall=Stat(), cpu=Stat(), peak_rss=Stat(), io_read=Stat(), io_write=Stat(), overhead=Stat(), trend_slope_ms=0.0)[source]#
Bases:
objectPer-block figures for one task over this run.
round_trip(dispatch → return, measured by the scheduler) needs no tracking store and is there for every distributed task. The remainingStatfields are measured inside the worker and are empty unless the task setresource_tracking=True;overheadis the round trip minus the block’s own wall time.- Parameters:
- class daisy._report.WorkerSummary(count=0, unmanaged_clients=0, startup=Stat(), idle=Stat(), compute=Stat(), teardown=Stat(), exits=dict(), by_id=dict())[source]#
Bases:
objectThe workers daisy spawned for one task.
startupandteardownhave one sample per worker;idleandcomputepool every worker’s per-block samples.
- class daisy._report.WorkerRecord(worker_id, startup=None, blocks_ok=0, blocks_failed=0, idle=Stat(), compute=Stat(), final_wait=None, teardown=None, exit=None)[source]#
Bases:
objectOne worker’s lifecycle. Durations in seconds;
Nonewhere the phase never happened (nostartupfor a worker that never connected, noteardownfor one that crashed or was cut off).- Parameters:
- class daisy._report.BlockRecord(block_id, done, failures, stats, worker_id, sent_at, returned_at)[source]#
Bases:
objectWhat the tracking store holds for one block.
- Parameters:
Progress observers#
- class daisy.JsonProgressObserver(path=None, stream=None)[source]#
Bases:
objectStreaming JSON observer for monitoring/dashboards.
Emits one JSON object per task per state-changing event, with a monotonic timestamp and the full counter snapshot. Pipes cleanly into log aggregators (fluentd, journald), JSON-line tools (jq -c), or anything that reads line-delimited JSON.
Usage:
# to a file obs = JsonProgressObserver(path="/tmp/progress.jsonl") daisy.run_blockwise(tasks, progress=obs) # to stdout daisy.run_blockwise(tasks, progress=JsonProgressObserver()) # to an existing file-like object with open("progress.jsonl", "w") as f: daisy.run_blockwise(tasks, progress=JsonProgressObserver(stream=f))
One line per task per call. With three tasks and an on_progress rate of thousands per second, expect a high-volume sink — the observer is the right level of granularity for dashboards but not for tail -f on a slow run.
Output schema (one line, pretty-printed for readability):
{ "t": 1745781234.123, # time.time() at emit "event": "progress", # "start" | "progress" | "finish" "task": "extract", "total": 100000, "ready": 99, "processing": 4, "completed": 4321, "skipped": 0, "failed": 2, "orphaned": 0, "restarts": 1, # worker_restart_count "failures": 1 # worker_failure_count (raw deaths) }