Working with Paths#

Use slash-separated path strings to navigate, reference, and create notebook tree nodes; paths avoid long chains of indexing when structures are deeply nested.

See also

Accessing Items with Index for duplicate-name and first-match lookup behavior.

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")

Parent Navigation#

Use .. to navigate to the parent container.

parent = page.traverse("..")
grandparent = page.traverse("../..")

Escaping Path Separators#

Use a backslash to keep / as part of a path segment instead of treating it as a separator. This escaping is supported by all methods that accept path strings, including traverse(), create(), dir(), and page().

# Literal name "Figure/1"
figure = notebook.traverse(r"Experiments/Figure\/1")

# Literal name "Reports/2024"
page = notebook.page(r"Reports\/2024")

Programmatic Escaping#

If you are building paths from variables that might contain slashes, use escape() to escape the segments:

from labapi import NotebookPath

raw_name = "Results / Final"
# Escapes / and \ to prevent accidental traversal
safe_name = NotebookPath.escape(raw_name)[0]

# Now safe to use in a path string
page = notebook.page(f"Experiments/{safe_name}")

Use unescape() to remove escapes from a segment string:

print(NotebookPath.unescape(r"Results\/Final")) # "Results/Final"

Note

To list children with the same name, call container[Index.Name: "Results"]; then use Index.Id to select one match (see Accessing Items with Index).

Warning

Nodes with the literal name ".." cannot be accessed via traverse, as .. is reserved for parent navigation.

Enumerating Descendants#

The enumeration methods return relative path strings for descendants through the requested depth, which lets you list or search tree paths without traversing each path separately.

# All descendants (directories and pages) through depth 2
notebook.enumerate_all(depth=2)
# 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). Increase depth to include more levels; each directory visited adds one API request.

# 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)

Note

Use enumerate_nodes() to get (path, node) pairs directly instead of re-traversing, since first-match name lookup can pick the wrong node when sibling names repeat.

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, create() raises ValueError for any multi-segment path,
# even when the intermediate directories already exist
page = notebook.create(NotebookPage, "Experiments/2024/Results")  # raises ValueError

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), and

  • if_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 missing directories or pages should be created and existing matches should be returned.

# Safe to run multiple times; existing nodes are returned.
notebook.dir("Experiments/2024").page("Results")
notebook.page("Experiments/2024/Raw Data")

For read-only navigation, use traverse(). Use dir() or page() only when creating a missing node is acceptable.

# 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 represents parsed notebook paths. Use it to compose, resolve, compare, or stringify paths before calling traverse() or create().

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)