Repository: nteract/papermill

Issue:
Failure when a path is pathlib.Path
The Papermill Python API fails if either of the path arguments supplied to `execute_notebook` is a `pathlib.Path`. This is because [`parameterize_path` assumes that the path is always a `str`](https://github.com/nteract/papermill/blob/d5299b0f2705d7a9175c377aa2a2f812c83239e3/papermill/parameterize.py#L50). In order to support parameterized paths, Papermill probably has to continue work with `str`s internally, but it would be nice if Papermill did not fail when given a `Path`. A rasonable fix would be to convert each `Path` object to a `str` before continuing.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/nteract/papermill/pull/544?src=pr&el=h1) Report
> Merging [#544](https://codecov.io/gh/nteract/papermill/pull/544?src=pr&el=desc) into [main](https://codecov.io/gh/nteract/papermill/commit/d5299b0f2705d7a9175c377aa2a2f812c83239e3?el=desc) will **decrease** coverage by `0.22%`.
> The diff coverage is `60.00%`.



```diff
@@            Coverage Diff             @@
##             main     #544      +/-   ##
==========================================
- Coverage   92.30%   92.07%   -0.23%     
==========================================
  Files          16       16              
  Lines        1403     1413      +10     
==========================================
+ Hits         1295     1301       +6     
- Misses        108      112       +4     
```


Patch:
diff --git a/papermill/execute.py b/papermill/execute.py
index 5a2a46f..c16aa88 100644
--- a/papermill/execute.py
+++ b/papermill/execute.py
@@ -2,6 +2,7 @@
 
 import copy
 import nbformat
+from pathlib import Path
 
 from .log import logger
 from .exceptions import PapermillExecutionError
@@ -32,9 +33,9 @@ def execute_notebook(
 
     Parameters
     ----------
-    input_path : str
+    input_path : str or Path
         Path to input notebook
-    output_path : str
+    output_path : str or Path
         Path to save executed notebook
     parameters : dict, optional
         Arbitrary keyword arguments to pass to the notebook parameters
@@ -56,7 +57,7 @@ def execute_notebook(
         Duration in seconds to wait for kernel start-up
     report_mode : bool, optional
         Flag for whether or not to hide input.
-    cwd : str, optional
+    cwd : str or Path, optional
         Working directory to use when executing the notebook
     **kwargs
         Arbitrary keyword arguments to pass to the notebook engine
@@ -66,6 +67,13 @@ def execute_notebook(
     nb : NotebookNode
        Executed notebook object
     """
+    if isinstance(input_path, Path):
+        input_path = str(input_path)
+    if isinstance(output_path, Path):
+        output_path = str(output_path)
+    if isinstance(cwd, Path):
+        cwd = str(cwd)
+
     path_parameters = add_builtin_parameters(parameters)
     input_path = parameterize_path(input_path, path_parameters)
     output_path = parameterize_path(output_path, path_parameters)
diff --git a/papermill/inspection.py b/papermill/inspection.py
index 79d6542..f8c17ba 100644
--- a/papermill/inspection.py
+++ b/papermill/inspection.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 """Deduce parameters of a notebook from the parameters cell."""
 import click
+from pathlib import Path
 
 from .iorw import get_pretty_path, load_notebook_node, local_file_io_cwd
 from .log import logger
@@ -103,7 +104,7 @@ def inspect_notebook(notebook_path, parameters=None):
 
     Parameters
     ----------
-    notebook_path : str
+    notebook_path : str or Path
         Path to notebook
     parameters : dict, optional
         Arbitrary keyword arguments to pass to the notebook parameters
@@ -113,6 +114,9 @@ def inspect_notebook(notebook_path, parameters=None):
     Dict[str, Parameter]
        Mapping of (parameter name, {name, inferred_type_name, default, help})
     """
+    if isinstance(notebook_path, Path):
+        notebook_path = str(notebook_path)
+
     nb = _open_notebook(notebook_path, parameters)
 
     params = _infer_parameters(nb)


Test patch:
diff --git a/papermill/tests/test_execute.py b/papermill/tests/test_execute.py
index dbee1fb..5a60a58 100644
--- a/papermill/tests/test_execute.py
+++ b/papermill/tests/test_execute.py
@@ -5,6 +5,7 @@ import tempfile
 import unittest
 
 from functools import partial
+from pathlib import Path
 
 try:
     from unittest.mock import patch
@@ -278,6 +279,16 @@ class TestCWD(unittest.TestCase):
             os.path.isfile(os.path.join(self.base_test_dir, self.nb_test_executed_fname))
         )
 
+    def test_pathlib_paths(self):
+        # Copy of test_execution_respects_cwd_assignment but with `Path`s
+        with chdir(self.base_test_dir):
+            execute_notebook(
+                Path(self.check_notebook_name),
+                Path(self.nb_test_executed_fname),
+                cwd=Path(self.test_dir),
+            )
+        self.assertTrue(Path(self.base_test_dir).joinpath(self.nb_test_executed_fname).exists())
+
 
 class TestSysExit(unittest.TestCase):
     def setUp(self):
diff --git a/papermill/tests/test_inspect.py b/papermill/tests/test_inspect.py
index 8ca6860..823501f 100644
--- a/papermill/tests/test_inspect.py
+++ b/papermill/tests/test_inspect.py
@@ -52,7 +52,12 @@ def click_context():
     ],
 )
 def test_inspect_notebook(name, expected):
-    assert inspect_notebook(str(name)) == expected
+    assert inspect_notebook(name) == expected
+
+
+def test_str_path():
+    expected = {"msg": {"name": "msg", "inferred_type_name": "None", "default": "None", "help": ""}}
+    assert inspect_notebook(str(_get_fullpath("simple_execute.ipynb"))) == expected
 
 
 @pytest.mark.parametrize(