1515
1616# ββ Fixtures ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1717
18+
1819def _make_nb (cells ):
1920 """Build a minimal NotebookNode from a list of (type, source, pm_meta) tuples."""
2021 nb = nbformat .v4 .new_notebook ()
@@ -25,8 +26,11 @@ def _make_nb(cells):
2526 else :
2627 cell = nbformat .v4 .new_code_cell (source )
2728 cell .metadata ["papermill" ] = pm_meta or {
28- "start_time" : None , "end_time" : None ,
29- "duration" : None , "status" : "pending" , "exception" : False ,
29+ "start_time" : None ,
30+ "end_time" : None ,
31+ "duration" : None ,
32+ "status" : "pending" ,
33+ "exception" : False ,
3034 }
3135 nb .cells .append (cell )
3236 return nb
@@ -44,6 +48,7 @@ def _executed_cell_meta(duration, status="completed", exception=False):
4448
4549# ββ build_sections ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
4650
51+
4752class TestBuildSections :
4853 def test_no_headings_creates_preamble (self ):
4954 nb = _make_nb ([("code" , "x = 1" , None ), ("code" , "y = 2" , None )])
@@ -54,54 +59,75 @@ def test_no_headings_creates_preamble(self):
5459 assert len (sections [0 ].cells ) == 2
5560
5661 def test_single_heading (self ):
57- nb = _make_nb ([
58- ("markdown" , "# Data Loading" , None ),
59- ("code" , "import pandas" , None ),
60- ])
62+ nb = _make_nb (
63+ [
64+ ("markdown" , "# Data Loading" , None ),
65+ ("code" , "import pandas" , None ),
66+ ]
67+ )
6168 sections = build_sections (nb )
6269 assert len (sections ) == 1
6370 assert sections [0 ].number == "1"
6471 assert sections [0 ].display_label == "Section 1"
6572
6673 def test_sequential_numbering (self ):
67- nb = _make_nb ([
68- ("markdown" , "# First" , None ), ("code" , "a=1" , None ),
69- ("markdown" , "# Second" , None ), ("code" , "b=2" , None ),
70- ("markdown" , "# Third" , None ), ("code" , "c=3" , None ),
71- ])
74+ nb = _make_nb (
75+ [
76+ ("markdown" , "# First" , None ),
77+ ("code" , "a=1" , None ),
78+ ("markdown" , "# Second" , None ),
79+ ("code" , "b=2" , None ),
80+ ("markdown" , "# Third" , None ),
81+ ("code" , "c=3" , None ),
82+ ]
83+ )
7284 sections = build_sections (nb )
7385 assert [s .number for s in sections ] == ["1" , "2" , "3" ]
7486 assert [s .display_label for s in sections ] == ["Section 1" , "Section 2" , "Section 3" ]
7587
7688 def test_nested_sub_sections (self ):
77- nb = _make_nb ([
78- ("markdown" , "# Analysis" , None ), ("code" , "x=1" , None ),
79- ("markdown" , "## Cleaning" , None ), ("code" , "y=2" , None ),
80- ("markdown" , "## Feature Engineering" , None ), ("code" , "z=3" , None ),
81- ("markdown" , "# Results" , None ), ("code" , "w=4" , None ),
82- ])
89+ nb = _make_nb (
90+ [
91+ ("markdown" , "# Analysis" , None ),
92+ ("code" , "x=1" , None ),
93+ ("markdown" , "## Cleaning" , None ),
94+ ("code" , "y=2" , None ),
95+ ("markdown" , "## Feature Engineering" , None ),
96+ ("code" , "z=3" , None ),
97+ ("markdown" , "# Results" , None ),
98+ ("code" , "w=4" , None ),
99+ ]
100+ )
83101 sections = build_sections (nb )
84102 labels = [s .display_label for s in sections ]
85103 assert labels == ["Section 1" , "Sub-section 1.1" , "Sub-section 1.2" , "Section 2" ]
86104
87105 def test_sub_section_counter_resets_across_top_sections (self ):
88- nb = _make_nb ([
89- ("markdown" , "# A" , None ), ("code" , "a=1" , None ),
90- ("markdown" , "## A1" , None ), ("code" , "b=2" , None ),
91- ("markdown" , "# B" , None ), ("code" , "c=3" , None ),
92- ("markdown" , "## B1" , None ), ("code" , "d=4" , None ),
93- ])
106+ nb = _make_nb (
107+ [
108+ ("markdown" , "# A" , None ),
109+ ("code" , "a=1" , None ),
110+ ("markdown" , "## A1" , None ),
111+ ("code" , "b=2" , None ),
112+ ("markdown" , "# B" , None ),
113+ ("code" , "c=3" , None ),
114+ ("markdown" , "## B1" , None ),
115+ ("code" , "d=4" , None ),
116+ ]
117+ )
94118 sections = build_sections (nb )
95119 numbers = [s .number for s in sections ]
96120 # After # B the sub-counter resets, so ## B1 becomes 2.1 not 1.2
97121 assert numbers == ["1" , "1.1" , "2" , "2.1" ]
98122
99123 def test_heading_cells_not_added_to_cell_list (self ):
100- nb = _make_nb ([
101- ("markdown" , "# Title" , None ),
102- ("markdown" , "Some prose (no heading)" , None ),
103- ("code" , "x=1" , None ),
104- ])
124+ nb = _make_nb (
125+ [
126+ ("markdown" , "# Title" , None ),
127+ ("markdown" , "Some prose (no heading)" , None ),
128+ ("code" , "x=1" , None ),
129+ ]
130+ )
105131 sections = build_sections (nb )
106132 assert len (sections ) == 1
107133 # Only the prose markdown + code cell should be in cells
@@ -110,6 +136,7 @@ def test_heading_cells_not_added_to_cell_list(self):
110136
111137# ββ SectionProfile ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
112138
139+
113140class TestSectionProfile :
114141 def test_display_label_preamble (self ):
115142 s = SectionProfile (title = "[preamble]" , level = 0 , number = "" )
@@ -149,16 +176,19 @@ def test_to_dict_contains_label_and_title(self):
149176
150177# ββ build_profile βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
151178
179+
152180class TestBuildProfile :
153181 def _make_executed_nb (self ):
154- nb = _make_nb ([
155- ("markdown" , "# Imports" , None ),
156- ("code" , "import numpy as np" , _executed_cell_meta (0.1 )),
157- ("markdown" , "## Heavy computation" , None ),
158- ("code" , "result = np.sum(range(1000))" , _executed_cell_meta (5.0 )),
159- ("markdown" , "# Results" , None ),
160- ("code" , "print(result)" , _executed_cell_meta (0.05 )),
161- ])
182+ nb = _make_nb (
183+ [
184+ ("markdown" , "# Imports" , None ),
185+ ("code" , "import numpy as np" , _executed_cell_meta (0.1 )),
186+ ("markdown" , "## Heavy computation" , None ),
187+ ("code" , "result = np.sum(range(1000))" , _executed_cell_meta (5.0 )),
188+ ("markdown" , "# Results" , None ),
189+ ("code" , "print(result)" , _executed_cell_meta (0.05 )),
190+ ]
191+ )
162192 nb .metadata ["papermill" ] = {
163193 "start_time" : "2026-01-01T00:00:00+00:00" ,
164194 "end_time" : "2026-01-01T00:00:06+00:00" ,
@@ -203,6 +233,7 @@ def test_n_errors_counts_exceptions(self):
203233
204234# ββ profile_notebook ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
205235
236+
206237class TestProfileNotebook :
207238 def test_returns_dict (self , tmp_path ):
208239 nb = nbformat .v4 .new_notebook ()
@@ -236,7 +267,9 @@ def test_writes_json_file(self, tmp_path):
236267
237268# ββ live_tree availability guard ββββββββββββββββββββββββββββββββββββββββββββββ
238269
270+
239271class TestLiveTreeAvailability :
240272 def test_is_available_returns_bool (self ):
241273 from papermill .live_tree import is_available
274+
242275 assert isinstance (is_available (), bool )
0 commit comments