Working with Paths#
Paths provide a way to navigate, reference, and create nodes in the notebook tree using Unix-style slash-separated strings. This is an alternative to chained index access and is especially useful when working with deeply nested structures.
Warning
For duplicate-name and first-match lookup behavior, see Accessing Items with Index. This page focuses on path syntax and traversal rules.
from labapi import TraversalError
# Index access (chained)
page = notebook["Experiments"]["2024"]["Results"]
# Path-based (equivalent)
page = notebook.traverse("Experiments/2024/Results")
try:
notebook.traverse("Experiments/2024/Results/Figure 1")
except TraversalError:
# An intermediate segment exists but is not a directory
...
Traversing the Tree#
The traverse() method is available on any tree node
and accepts a slash-separated path string.
folder = notebook.traverse("Experiments")
page = notebook.traverse("Experiments/2024/Results")
Note
If duplicate sibling names appear in a path segment, traverse()
selects the first match for that segment. For deterministic selection,
index from the parent container with Index.Id (see Accessing Items with Index).
Absolute vs Relative Paths#
Paths starting with / are absolute — they are resolved from the notebook root regardless
of where you call traverse.
# Relative: resolved from `folder`
page = folder.traverse("2024/Results")
# Absolute: always resolved from the notebook root
page = folder.traverse("/Experiments/2024/Results")
Enumerating Descendants#
The enumeration methods return relative path strings for all descendants, up to a specified depth. This is useful for listing or searching the tree without fetching every node individually.
# All descendants (directories and pages), depth 1 (default)
notebook.enumerate_all()
# e.g. ["Experiments", "Experiments/2024", "Protocols"]
# Only directories
notebook.enumerate_dirs(depth=2)
# Only pages
notebook.enumerate_pages(depth=2)
depth defaults to 1 (immediate children only). To fetch the full tree you can increase
it, but be aware that this makes one API request per directory level visited.
# Enumerate up to 3 levels deep
all_paths = notebook.enumerate_all(depth=3)
for path in all_paths:
node = notebook.traverse(path)
print(path, "->", node.id)
Creating Nodes with Paths#
The create() method accepts a path string (or
NotebookPath) as the name argument. When a multi-segment path is
provided, you must pass parents=True to allow intermediate directories to be created
automatically.
from labapi import NotebookPage, NotebookDirectory
# Create a page at a nested path; intermediate directories are created as needed
page = notebook.create(NotebookPage, "Experiments/2024/Results", parents=True)
# Without parents=True, a ValueError is raised if an intermediate directory is missing
page = notebook.create(NotebookPage, "Experiments/2024/Results") # raises if missing
See Creating Pages and Entries for details on the if_exists parameter and other creation options.
Convenience Methods: dir() and page()#
For common “ensure this path exists” workflows, use the convenience methods
dir() and
page().
These methods are shorthand for create()
with:
parents=True(create missing intermediate directories), andif_exists=InsertBehavior.Retain(return an existing matching node instead of raising).
from labapi import NotebookDirectory, NotebookPage, InsertBehavior
# Equivalent calls:
reports_dir = notebook.dir("Experiments/2024/Reports")
reports_dir = notebook.create(
NotebookDirectory,
"Experiments/2024/Reports",
parents=True,
if_exists=InsertBehavior.Retain,
)
summary_page = notebook.page("Experiments/2024/Summary")
summary_page = notebook.create(
NotebookPage,
"Experiments/2024/Summary",
parents=True,
if_exists=InsertBehavior.Retain,
)
When to Use Them#
Use dir() and page() when you want concise, idempotent setup code.
They are especially useful in scripts that may run repeatedly.
# Safe to run multiple times; existing nodes are returned.
notebook.dir("Experiments/2024").page("Results")
notebook.page("Experiments/2024/Raw Data")
Because both methods retain existing nodes, calling them again for the same path returns the existing directory/page instead of creating a duplicate.
They can also be used for navigation when you expect the path to already exist: the same call either returns the existing node or creates it if missing.
# Navigate to an existing directory (or create it if needed)
reports = notebook.dir("Experiments/2024/Reports")
# Navigate to an existing page (or create it if needed)
summary = notebook.page("Experiments/2024/Summary")
The NotebookPath Class#
NotebookPath is a structured path object that can be constructed from
nodes or strings, combined with /, and converted back to strings. It is used internally by
traverse and create, but is also available for your own path logic.
Constructing a path from a node:
from labapi import NotebookPath
path = NotebookPath(folder)
print(path) # /Experiments/2024
print(path.name) # 2024 (last segment)
print(path.parts) # ['Experiments'] (all but last)
print(path.is_absolute()) # True
Constructing from a string:
abs_path = NotebookPath("/Experiments/2024")
rel_path = NotebookPath("2024/Results")
Combining paths with ``/``:
base = NotebookPath(notebook) # /
path = base / "Experiments" / "2024" # /Experiments/2024
Resolving relative paths:
rel = NotebookPath("Results")
abs_path = rel.resolve(NotebookPath(folder)) # /Experiments/2024/Results
Getting a relative path between two nodes:
page_path = NotebookPath(page) # /Experiments/2024/Results
rel = page_path.relative_to(folder) # Results (relative to /Experiments/2024)
Checking containment:
page_path.is_relative_to(folder) # True if page is inside folder
page_path.is_relative_to(notebook) # True (everything is inside the notebook)