def test_find_notebooks(tmp_path, notebook_asset): shutil.copy(notebook_asset.path, tmp_path / "the_notebook_0.ipynb") shutil.copy(notebook_asset.path, tmp_path / "the_notebook_1.ipynb") expected_notebooks = [ JupyterNotebook.from_file(tmp_path / "the_notebook_0.ipynb"), JupyterNotebook.from_file(tmp_path / "the_notebook_1.ipynb"), ] # Non-notebook files (tmp_path / "the_journal.txt").touch() with (tmp_path / "the_log.json").open("w", encoding="utf-8") as fp: json.dump( { "LOG ENTRY: SOL 61": "How come Aquaman can control whales?", "LOG ENTRY: SOL 381": "That makes me a pirate! A space pirate!", }, fp, ) with (tmp_path / SAVE_PROGRESS_INDICATOR_FILE).open( "w", encoding="utf-8") as fp: fp.write(NbAutoexportConfig().json()) found_notebooks = find_notebooks(tmp_path) assert set(found_notebooks) == set(expected_notebooks)
def test_clean_relative(notebooks_dir, organize_by): """ Test that cleaning works relative to current working directory. """ with working_directory(notebooks_dir): sentinel_path = Path(SAVE_PROGRESS_INDICATOR_FILE) config = NbAutoexportConfig(export_formats=EXPECTED_FORMATS, organize_by=organize_by) with sentinel_path.open("w") as fp: fp.write(config.json()) result = CliRunner().invoke(app, ["clean", "."], input="y") assert result.exit_code == 0 expected_notebooks = [ JupyterNotebook.from_file(f"{nb}.ipynb") for nb in EXPECTED_NOTEBOOKS ] expected_exports = set(get_expected_exports(expected_notebooks, config)) all_expected = {nb.path for nb in expected_notebooks } | expected_exports | {sentinel_path} assert set(Path().glob("**/*")) == all_expected # Run clean again, there should be nothing to do result_rerun = CliRunner().invoke(app, ["clean", "."]) assert result_rerun.exit_code == 0 assert result_rerun.stdout.strip().endswith( "No files identified for cleaning. Exiting.")
def test_clean(notebooks_dir, need_confirmation, organize_by): sentinel_path = notebooks_dir / SAVE_PROGRESS_INDICATOR_FILE config = NbAutoexportConfig(export_formats=EXPECTED_FORMATS, organize_by=organize_by) with sentinel_path.open("w") as fp: fp.write(config.json()) if need_confirmation: result = CliRunner().invoke(app, ["clean", str(notebooks_dir)], input="y") else: result = CliRunner().invoke( app, ["clean", str(notebooks_dir), "--yes"]) assert result.exit_code == 0 expected_notebooks = [ JupyterNotebook.from_file(notebooks_dir / f"{nb}.ipynb") for nb in EXPECTED_NOTEBOOKS ] expected_exports = set(get_expected_exports(expected_notebooks, config)) all_expected = {nb.path for nb in expected_notebooks } | expected_exports | {sentinel_path} assert set(notebooks_dir.glob("**/*")) == all_expected # Run clean again, there should be nothing to do result_rerun = CliRunner().invoke(app, ["clean", str(notebooks_dir)]) assert result_rerun.exit_code == 0 assert result_rerun.stdout.strip().endswith( "No files identified for cleaning. Exiting.")
def notebooks_dir(tmp_path, notebook_asset): notebooks = EXPECTED_NOTEBOOKS + [UNEXPECTED_NOTEBOOK] export_formats = [ ExportFormat(fmt) for fmt in EXPECTED_FORMATS + [UNEXPECTED_FORMAT] ] for nb_name in notebooks: shutil.copy(notebook_asset.path, tmp_path / f"{nb_name}.ipynb") nb = JupyterNotebook.from_file(tmp_path / f"{nb_name}.ipynb") # organize_by notebook nb_subfolder = tmp_path / nb.name nb_subfolder.mkdir() for fmt in export_formats: (nb_subfolder / f"{nb.name}{get_extension(nb, fmt)}").touch() # organize_by extension for fmt in export_formats: format_subfolder = tmp_path / fmt.value format_subfolder.mkdir(exist_ok=True) (format_subfolder / f"{nb.name}{get_extension(nb, fmt)}").touch() # add latex image dir (nb_subfolder / f"{nb.name}_files").mkdir() (nb_subfolder / f"{nb.name}_files" / f"{nb.name}_1_1.png").touch() (tmp_path / "latex" / f"{nb.name}_files").mkdir() (tmp_path / "latex" / f"{nb.name}_files" / f"{nb.name}_1_1.png").touch() (tmp_path / f"{UNEXPECTED_NOTEBOOK}.ipynb").unlink() return tmp_path
def test_export_notebook(notebooks_dir, organize_by): """Test that export notebook works. Explicitly write out expected files, because tests for get_expected_exports will compare against export_notebook. """ notebook = JupyterNotebook.from_file(notebooks_dir / "the_notebook.ipynb") config = NbAutoexportConfig(export_formats=EXPORT_FORMATS_TO_TEST, organize_by=organize_by) export_notebook(notebook.path, config) expected_exports = set() for fmt in EXPORT_FORMATS_TO_TEST: if organize_by == "extension": subfolder = notebooks_dir / fmt.value elif organize_by == "notebook": subfolder = notebooks_dir / notebook.name extension = get_extension(notebook, fmt) expected_exports.add(subfolder) # subfolder expected_exports.add(subfolder / f"{notebook.name}{extension}") # export file if fmt in FORMATS_WITH_IMAGE_DIR: image_subfolder = subfolder / f"{notebook.name}_files" expected_exports.add(image_subfolder) # image subdir expected_exports.add(image_subfolder / f"{notebook.name}_1_1.png") # image file all_expected = {notebook.path} | expected_exports assert all_expected.issubset(set(notebooks_dir.glob("**/*")))
def test_clean_exclude(notebooks_dir): """Test that cleaning works with exclude""" with working_directory(notebooks_dir): # Set up subdirectory subdir = Path("subdir") subdir.mkdir() for subfile in Path().iterdir(): shutil.move(str(subfile), str(subdir)) # Set up extra files extra_files = [ subdir / "keep.txt", subdir / "delete.txt", subdir / "images" / "keep.jpg", subdir / "images" / "delete.png", subdir / "pictures" / "delete.jpg", subdir / "keep.md", subdir / "docs" / "keep.md", ] for extra_file in extra_files: extra_file.parent.mkdir(exist_ok=True) extra_file.touch() sentinel_path = subdir / SAVE_PROGRESS_INDICATOR_FILE config = NbAutoexportConfig( export_formats=EXPECTED_FORMATS, organize_by="extension", clean=CleanConfig(exclude=["keep.txt", "images/*.jpg"]), ) with sentinel_path.open("w", encoding="utf-8") as fp: fp.write(config.json()) command = ["clean", "subdir", "-e", "**/*.md"] result = CliRunner().invoke(app, command, input="y") assert result.exit_code == 0 expected_notebooks = [ JupyterNotebook.from_file(subdir / f"{nb}.ipynb") for nb in EXPECTED_NOTEBOOKS ] expected_exports = set(get_expected_exports(expected_notebooks, config)) expected_extra = { extra_file for extra_file in extra_files if extra_file.match("keep.*") } | {subdir / "images", subdir / "docs"} all_expected = ({nb.path for nb in expected_notebooks} | expected_exports | {sentinel_path} | expected_extra) assert set(subdir.glob("**/*")) == all_expected # Run clean again, there should be nothing to do result_rerun = CliRunner().invoke(app, command) assert result_rerun.exit_code == 0 assert result_rerun.stdout.strip().endswith( "No files identified for cleaning. Exiting.")
def get_extension(notebook: JupyterNotebook, export_format: ExportFormat) -> str: """Given a notebook and export format, return expected export file extension. Args: notebook (JupyterNotebook): notebook to determine extension for export_format (str): export format name Returns: str: file extension, e.g., '.py' """ # Script format needs notebook to determine appropriate language's extension if ExportFormat(export_format) == ExportFormat.script: return notebook.get_script_extension() exporter = get_exporter(ExportFormat(export_format).value) if ExportFormat(export_format) == ExportFormat.notebook: return f".nbconvert{exporter().file_extension}" return exporter().file_extension
def notebook_asset(): return JupyterNotebook.from_file( Path(__file__).parent / "assets" / "the_notebook.ipynb")