def test_post_save(file_contents_manager, notebook_file, notebook_model, caplog): """Test that post_save function works when FileContentsManager saves based on config file.""" caplog.set_level(logging.DEBUG) config = NbAutoexportConfig(export_formats=[ExportFormat.script], organize_by=OrganizeBy.extension) with (notebook_file.parent / SAVE_PROGRESS_INDICATOR_FILE).open( "w", encoding="utf-8") as fp: fp.write(config.json()) file_contents_manager.save(notebook_model, path=notebook_file.name) assert caplog_contains( caplog, level=logging.INFO, in_msg=f"nbautoexport | Exporting {notebook_file}", ) caplog.clear() assert (notebook_file.parent / "script" / f"{notebook_file.stem}.py").exists() assert not (notebook_file.parent / notebook_file.stem / f"{notebook_file.stem}.html").exists() # Update config and check that output is different (notebook_file.parent / "script" / f"{notebook_file.stem}.py").unlink() config = NbAutoexportConfig(export_formats=[ExportFormat.html], organize_by=OrganizeBy.notebook) with (notebook_file.parent / SAVE_PROGRESS_INDICATOR_FILE).open( "w", encoding="utf-8") as fp: fp.write(config.json()) file_contents_manager.save(notebook_model, path=notebook_file.name) assert caplog_contains( caplog, level=logging.INFO, in_msg=f"nbautoexport | Exporting {notebook_file}", ) caplog.clear() assert not (notebook_file.parent / "script" / f"{notebook_file.stem}.py").exists() assert (notebook_file.parent / notebook_file.stem / f"{notebook_file.stem}.html").exists()
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 test_export_with_config_no_cli_opts(notebooks_dir, input_type, organize_by): """Test that export works with a config and no CLI options. Should use config options. """ expected_notebooks = find_notebooks(notebooks_dir) assert len(expected_notebooks) == len(EXPECTED_NOTEBOOKS) if input_type == "dir": expected_to_convert = expected_notebooks input_path = str(notebooks_dir) elif input_type == "notebook": expected_to_convert = expected_notebooks[:1] input_path = str(expected_notebooks[0].path) 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()) result = CliRunner().invoke(app, ["export", input_path]) assert result.exit_code == 0 expected_notebook_files = {nb.path for nb in expected_notebooks} expected_exports = set(get_expected_exports(expected_to_convert, config)) all_expected = expected_notebook_files | expected_exports | {sentinel_path} assert set(notebooks_dir.glob("**/*")) == all_expected
def test_clean_relative_subdirectory(notebooks_dir, input_type, organize_by): """ Test that export works for subdirectory relative to current working directory. """ with working_directory(notebooks_dir): # Set up subdirectory subdir = Path("subdir") subdir.mkdir() for subfile in Path().iterdir(): shutil.move(str(subfile), str(subdir)) sentinel_path = subdir / 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()) expected_notebooks = find_notebooks(subdir) assert len(expected_notebooks) == len(EXPECTED_NOTEBOOKS) if input_type == "dir": expected_to_convert = expected_notebooks input_path = "subdir" elif input_type == "notebook": expected_to_convert = expected_notebooks[:1] input_path = str(subdir / f"{expected_notebooks[0].path.name}") result = CliRunner().invoke(app, ["export", input_path]) assert result.exit_code == 0 expected_notebook_files = {nb.path for nb in expected_notebooks} expected_exports = set(get_expected_exports(expected_to_convert, config)) all_expected = expected_notebook_files | expected_exports | {sentinel_path} assert set(subdir.glob("**/*")) == all_expected
def test_export_relative(notebooks_dir, input_type, organize_by): """ Test that export works relative to current working directory. """ with working_directory(notebooks_dir): expected_notebooks = find_notebooks(Path()) assert len(expected_notebooks) == len(EXPECTED_NOTEBOOKS) 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()) if input_type == "dir": expected_to_convert = expected_notebooks input_path = "." elif input_type == "notebook": expected_to_convert = expected_notebooks[:1] input_path = f"{expected_notebooks[0].path.name}" result = CliRunner().invoke(app, ["export", input_path]) assert result.exit_code == 0 expected_notebook_files = {nb.path for nb in expected_notebooks} expected_exports = set(get_expected_exports(expected_to_convert, config)) all_expected = expected_notebook_files | expected_exports | {sentinel_path} assert set(Path().glob("**/*")) == all_expected
def test_export_with_config_with_cli_opts(notebooks_dir, input_type, organize_by): """Test that export works with both config and CLI options. CLI options should overide config. """ expected_notebooks = find_notebooks(notebooks_dir) assert len(expected_notebooks) == len(EXPECTED_NOTEBOOKS) if input_type == "dir": expected_to_convert = expected_notebooks input_path = str(notebooks_dir) elif input_type == "notebook": expected_to_convert = expected_notebooks[:1] input_path = str(expected_notebooks[0].path) sentinel_path = notebooks_dir / SAVE_PROGRESS_INDICATOR_FILE written_config = NbAutoexportConfig() with sentinel_path.open("w") as fp: fp.write(written_config.json()) expected_config = NbAutoexportConfig(export_formats=EXPECTED_FORMATS, organize_by=organize_by) assert expected_config != written_config flags = list(chain(["-b", organize_by], *(["-f", fmt] for fmt in EXPECTED_FORMATS))) result = CliRunner().invoke(app, ["export", input_path] + flags) assert result.exit_code == 0 expected_notebook_files = {nb.path for nb in expected_notebooks} expected_exports = set(get_expected_exports(expected_to_convert, expected_config)) expected_exports_from_written = set(get_expected_exports(expected_to_convert, written_config)) assert expected_exports != expected_exports_from_written all_expected = expected_notebook_files | expected_exports | {sentinel_path} assert set(notebooks_dir.glob("**/*")) == all_expected
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 test_invalid_config(file_contents_manager, notebook_file, notebook_model, caplog): """Test that post_save function gracefully logs errors.""" config = NbAutoexportConfig(export_formats=[ExportFormat.script], organize_by=OrganizeBy.extension) invalid_config = json.loads(config.json()) invalid_config["export_formats"] = ["triplicate"] with (notebook_file.parent / SAVE_PROGRESS_INDICATOR_FILE).open( "w", encoding="utf-8") as fp: json.dump(invalid_config, fp) # Runs through, since error is caught file_contents_manager.save(notebook_model, path=notebook_file.name) assert caplog_contains( caplog, level=logging.ERROR, in_msg="nbautoexport | post_save failed due to ValidationError", ) assert not (notebook_file.parent / "script" / f"{notebook_file.stem}.py").exists()
def test_not_notebook(file_contents_manager, tmp_path): """Test that post_save function ignores non-notebook file when FileContentsManager saves.""" config = NbAutoexportConfig(export_formats=[ExportFormat.script], organize_by=OrganizeBy.extension) with (tmp_path / SAVE_PROGRESS_INDICATOR_FILE).open( "w", encoding="utf-8") as fp: fp.write(config.json()) file_path = tmp_path / "journal.txt" with file_path.open("w", encoding="utf-8") as fp: fp.write("I'm a journal.") model = { "type": "file", "format": "text", "mimetype": "text/plain", "content": "I'm a journal.", } file_contents_manager.save(model, path=str(file_path.name)) assert not (tmp_path / "script" / f"{file_path.stem}.py").exists()