Skip to content

example_data

tit.server.routes.example_data

GET/POST /api/example-data — the example-data catalogue, and downloading one part.

Not a job, on purpose. Example data was briefly wired as a project_init job, which meant asking an established project for a sample re-ran the initializer and reprinted its "New project detected / Initializing BIDS-compliant structure" banner. A download is not an initialization: :mod:tit.examples is a plain function (catalogue(), status(), fetch()) and this module is the thin HTTP skin over it -- no :mod:tit.jobs, no stages, no spec.

GET  /api/example-data                    datasets (each with its parts) + per-part
                                          {installed, bytes, downloading, queued, received,
                                          total, error}
POST /api/example-data/{dataset}/{part}   start that one part, return at once

One download at a time, because the files are 15-590 MB and two competing 500 MB streams help nobody. A POST while one is in flight is not an error: the part is appended to a small queue the worker drains in order (that is what the chooser's Download selected relies on) and the response is that part's current state -- queued for one waiting its turn. The renderer polls GET while anything is downloading or queued and stops when nothing is.

example_data

example_data() -> dict[str, Any]

{datasets, status}. status is one entry per part, read off disk (no network) plus the live progress of whichever part is downloading right now, so one poll answers the page.

Source code in tit/server/routes/example_data.py
@router.get(
    "/api/example-data",
    response_model=ExampleDataCatalog,
    summary="The example-data catalogue, what is installed, and any download in flight",
    responses={409: {"description": "this server is not bound to a project"}},
)
def example_data() -> dict[str, Any]:
    """``{datasets, status}``. ``status`` is one entry per *part*, read off disk (no network) plus
    the live progress of whichever part is downloading right now, so one poll answers the page."""
    from tit import examples

    project_dir = _bound_project_dir()
    status = [{**entry, **_STATE.snapshot(entry["id"])} for entry in examples.status(project_dir)]
    return {"datasets": [d.to_dict() for d in examples.catalogue()], "status": status}

start_example_data

start_example_data(dataset_id: str, part_id: str, force: bool = False) -> dict[str, Any]

Start (or queue) the fetch and return immediately; the renderer polls GET for progress.

Returns this part's {id, dataset, part, installed, bytes, downloading, queued, received, total, error?} -- the same shape GET reports per part -- whether it started a download, queued one behind the fetch already running, or found the part already installed.

Source code in tit/server/routes/example_data.py
@router.post(
    "/api/example-data/{dataset_id}/{part_id}",
    response_model=ExampleDataStatus,
    summary="Start downloading one part of one example dataset into this project",
    responses={
        409: {"description": "this server is not bound to a project"},
        422: {"description": "unknown dataset or part"},
    },
)
def start_example_data(dataset_id: str, part_id: str, force: bool = False) -> dict[str, Any]:
    """Start (or queue) the fetch and return immediately; the renderer polls ``GET`` for progress.

    Returns this part's ``{id, dataset, part, installed, bytes, downloading, queued, received,
    total, error?}`` -- the same shape ``GET`` reports per part -- whether it started a download,
    queued one behind the fetch already running, or found the part already installed.
    """
    from tit import examples

    project_dir = _bound_project_dir()
    try:
        part = examples.part_by_id(dataset_id, part_id)
    except KeyError as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc

    full_id = part.full_id
    with _STATE.lock:
        if _STATE.active():
            if _STATE.part_id != full_id and not any(q == full_id for q, _ in _STATE.queue):
                _STATE.queue.append((full_id, force))
                _STATE.errors.pop(full_id, None)
        else:
            _STATE.part_id, _STATE.received, _STATE.total = full_id, 0, 0
            _STATE.queue.clear()
            _STATE.errors.pop(full_id, None)
            _STATE.thread = threading.Thread(
                target=_run, args=(full_id, project_dir, force), daemon=True
            )
            _STATE.thread.start()

    entry = next(s for s in examples.status(project_dir) if s["id"] == full_id)
    return {**entry, **_STATE.snapshot(full_id)}