3.4. Tracking Job Progress

Long-running queries and data loads report live progress while they run. There are three ways to consume it, depending on whether you want the built-in display, a custom renderer, or to read the progress yourself.

3.4.1. Built-in Display

The simplest option is to pass show_progress = True, which renders a one-line progress indicator to standard error while the job runs. It is available on run_job(), schedule_job(), wait_for_job(), save_graph(), load_graph(), and the frame load and save methods.

conn.run_job("MATCH (a)-[e:Edge]->(b) RETURN count(*)", show_progress = True)

3.4.2. Custom Rendering

To render progress your own way, pass a progress_callback. It is called periodically with the job’s live JobProgress, instead of or alongside show_progress.

Which field carries the live count depends on the job (see below), so a general renderer prefers percent when present and otherwise falls back to whichever counter applies.

def on_progress(p):
    if p.percent is not None:
        detail = f"{p.percent:.0f}%"
    elif p.rows_total is not None:
        detail = f"{p.rows_processed}/{p.rows_total} rows"
    elif p.traversed_edges:
        detail = f"{p.traversed_edges} edges"
    else:
        detail = f"{p.rows_processed} rows"
    print(f"{p.phase}: {detail}")

conn.run_job(query, progress_callback = on_progress)

3.4.3. Asynchronous Jobs

For a job submitted asynchronously with schedule_job(), the simplest way to surface progress is to pass show_progress or progress_callback to wait_for_job() when you block on the job.

job = conn.schedule_job(query)
# ... do other work ...
conn.wait_for_job(job, progress_callback = on_progress)

To poll the progress yourself instead, read the progress property in a loop. The property returns a JobProgress while the job is running, but None while it is still queued and again once it has finished, so drive the loop off status rather than off progress itself.

import time

TERMINAL = ('completed', 'failed', 'canceled', 'rollback',
            'unknown_job_status')

job = conn.schedule_job(query)
while job.status not in TERMINAL:
    p = job.progress
    if p is not None:
        on_progress(p)
    time.sleep(0.5)

3.4.4. What Progress Reports

A JobProgress reports the current phase (for example "reading", "executing", or "writing"), an optional finer substep, rows_processed and rows_total (which may be None), a percent scoped to the current phase (None when no reliable total exists), the live traversed_edges count, and the wall-clock phase_elapsed.

Which counter is meaningful depends on the kind of job. A data load or egest advances rows_processed toward rows_total (and percent when the total is known up front, as for Parquet). A query does not consume input rows, so rows_processed stays 0 and percent is usually None; its live indicator is traversed_edges, the number of edges the query has walked so far (0 for non-query jobs).

Two more things are worth keeping in mind. The percent value covers only the current phase rather than the whole job, and is often None, so a display should fall back to substep and phase_elapsed when it is. Progress is a liveness signal, not committed state: a job is all-or-nothing, so it can report substantial progress and then fail or roll back, discarding all of it. Use the job’s final status for the authoritative outcome.