Skip to content

notebooks

tit.server.routes.notebooks

/api/notebooks/* — the .ipynb files a notebook session edits (v1).

Thin HTTP over :mod:tit.server.notebooks; every path rule, every name check and the nbformat read/write live there. One directory, <project>/code/ti-toolbox/notebooks, which is also where the pipeline canvas's "Export notebook" can now land a notebook instead of only handing it to a download.

Import cost is deliberately small (dev/route_import_guard.py): nbformat is imported inside :mod:tit.server.notebooks' own functions, never here.

create_notebook

create_notebook(request: Request, body: dict[str, Any] = Body(...)) -> dict[str, Any]

Create name.

With no content, the new notebook is the starter: a markdown cell and a first code cell that already imports tit and prints this project's root, so "the TI-Toolbox environment is automatically loaded" is something the author can see rather than be told.

With content, this is the import path — "Import .ipynb" in the UI, and the pipeline canvas saving its export. Same validation either way.

Source code in tit/server/routes/notebooks.py
@router.post(
    "/api/notebooks",
    response_model=Notebook,
    responses={409: {"description": "a notebook of that name already exists"}},
    summary="Create a notebook, or store one that was uploaded",
)
def create_notebook(request: Request, body: dict[str, Any] = Body(...)) -> dict[str, Any]:
    """Create ``name``.

    With no ``content``, the new notebook is the starter: a markdown cell and
    a first code cell that already imports ``tit`` and prints this project's
    root, so "the TI-Toolbox environment is automatically loaded" is something
    the author can see rather than be told.

    With ``content``, this is the import path — "Import .ipynb" in the UI, and
    the pipeline canvas saving its export. Same validation either way.
    """
    root = _project_root(request)
    name = body.get("name")
    if not isinstance(name, str):
        raise HTTPException(status_code=422, detail="'name' is required.")
    content = body.get("content")
    if content is not None and not isinstance(content, dict):
        raise HTTPException(status_code=422, detail="'content' must be a notebook object.")
    try:
        file_name = nb.normalise_name(name)
        path = nb.notebook_path(root, file_name)
        if path.exists() and not bool(body.get("overwrite", False)):
            raise HTTPException(
                status_code=409, detail=f"{file_name} already exists in this project."
            )
        document = content if content is not None else nb.new_notebook()
        nb.write_notebook(root, file_name, document)
        return {"name": file_name, "content": nb.read_notebook(root, file_name)}
    except nb.NotebookError as error:
        raise _fail(error) from error