insert_lines
def insert_lines(
text, insert_line, new_lines
):Insert new_lines before a 1-based line boundary in text; use n+1 to append.
edit_notebook is the production notebook mutation adapter. It keeps text transforms deterministic, applies notebook changes all-or-nothing, and returns structured diffs without raw .ipynb JSON.
The shape is intentionally small: pure text helpers first, then one notebook/cell adapter. Read tools still provide context; this module only mutates notebooks.
Craft warnings make risky notebook edits visible without changing edit semantics. They now flag calls whose definitions remain below the edited cell, so a notebook stays executable from top to bottom.
The first layer edits plain text. Keeping it independent from notebook I/O makes the same operations useful in direct Python calls and structured notebook edits.
The text helpers do not know about notebooks. They take strings, return strings, and raise clear ValueErrors for invalid line bounds or malformed replacements. That makes cell-level and notebook-level editing share the same behavior.
Insert new_lines before a 1-based line boundary in text; use n+1 to append.
Replace a 1-based inclusive line range in text.
Delete a 1-based inclusive line range, or lines matching re_filter.
Replace literal old with new, optionally inside a line range.
Apply several literal text replacements in order.
The public helper functions insert_lines, replace_lines, delete_lines, replace_text, and replace_texts use one-based line bounds for insert_line, start_line, and end_line, plus the same text semantics that edit_notebook applies to notebook cells.
These operations add the notebook concerns: stable cell ids, metadata, output clearing, validation, and one atomic commit.
The adapter selects cells, applies one deterministic operation per selected cell, validates the edited notebook before writing, clears stale outputs for changed code cells, stamps metadata, exports with nbdev, and returns structured results. The whole call succeeds or fails as one unit.
fastcore 2.0 now provides the raw notebook insertion and movement mechanics through fastcore.nbio.Notebook. nbskill still builds cells, applies directives, validates code, records hashes, and formats structured diffs here; only the low-level list mutation is delegated.
An edited cell may contain global imports together with exported code, tests, or examples. Before committing, the editor moves those imports into the notebook’s single matching import cell: exported code uses exported_import_cell; all runtime-only cells use internal_import_cell. Function-local imports stay in place.
Apply structured notebook edits atomically and return structured details.
The facade keeps repeated edits scoped to one notebook while the lower-level functions remain useful for one-off transformations.
NotebookEditor is a small facade over edit_notebook for one notebook, and Notebook.edit hands one back. Edits accept a directive (a Directive, its name, or a comma string such as export,no_test) so callers can say export and do not test instead of typing #| lines. The directive is stamped onto code cells, and context strips it back out because the semantic type already conveys it.
Notebook-scoped facade over edit_notebook.
Replace one cell source, optionally stamping directives onto code.
Insert one cell relative to anchor_id; pass None to append.
Split top-level function definitions in one cell, or matching cells, into separate cells.
Replace literal text in one cell, or across the notebook when cell_id is omitted.
with write_demo_notebook('editor_directive_demo.ipynb', cells=[mk_cell('def helper():\n return 1')]) as demo:
editor = Notebook.from_path(demo).edit()
print(type(editor).__name__)
cell_id = read_nb(demo).cells[0].id
editor.replace_cell(cell_id, 'def helper():\n return 2', directive='export,no_test')
print(read_nb(demo).cells[0].source)A notebook-wide rename is just a replace_text operation with target="all". That path is the default for deterministic refactors such as renaming a public symbol across Markdown, examples, and tests.
An expected_hash protects the exact cell source selected by context(..., mode="edit"). Use a dry run to inspect the one-cell diff and replacement count before writing. Pass auto_feedback=False to suppress automatic feedback; smoke snippets remain an advanced, explicit option.
with write_demo_notebook("02_edit_hash_example.ipynb", cells=[mk_cell("value = 1")]) as path:
cell = read_nb(path).cells[0]
result = edit_notebook(path, [dict(op="replace_text", cell_id=cell.id, old="1", new="2", expected_hash=source_hash(cell_source(cell)))], auto_feedback=False, dry_run=True)
print(result["affected_cell_ids"], result["replacement_summary"])