Repository: kynan/nbstripout

Issue:
Specifying Python executable path in `nbstripout --install`
It would be helpful if there was a way to specify the path `nbstripout --install` puts into e.g. `.git/config`. This would enable to specify only `python` or `python3`, not the full path, which can differ between runtimes, e.g. when using [Apptainer/Singularity](https://apptainer.org) or Docker containers (`/usr/bin/python` vs `/usr/local/bin/python`) occasionally as well as the native Python on the same repo.

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index c2f55a7..a161c65 100644
--- a/README.rst
+++ b/README.rst
@@ -95,6 +95,19 @@ Set up the git filter using ``.gitattributes`` ::
 
     nbstripout --install --attributes .gitattributes
 
+Specify a different path to the Python interpreter to be used for the git
+filters (default is the path to the Python interpreter used when ``nbstripout``
+is installed). This is useful if you have Python installed in different or
+unusual locations across machines (e.g. ``/usr/bin/python3`` on your machine vs
+``/usr/local/bin/python3`` in a container or elsewhere):
+
+.. code-block:: sh
+
+    # Using just 'python3' lets each machine find its Python itself. 
+    # However, keep in mind that depending on your setup this might not be 
+    # the Python version you want or even fail because an absolute path is required.
+    nbstripout --install --python python3
+
 Set up the git filter in your global ``~/.gitconfig`` ::
 
     nbstripout --install --global
diff --git a/nbstripout/_nbstripout.py b/nbstripout/_nbstripout.py
index 9fac7fc..3957395 100644
--- a/nbstripout/_nbstripout.py
+++ b/nbstripout/_nbstripout.py
@@ -214,10 +214,10 @@ def _parse_size(num_str):
         raise ValueError(f"Unknown size identifier {num_str[-1]}")
 
 
-def install(git_config, install_location=INSTALL_LOCATION_LOCAL, attrfile=None):
+def install(git_config, install_location=INSTALL_LOCATION_LOCAL, python=None, attrfile=None):
     """Install the git filter and set the git attributes."""
     try:
-        filepath = f'"{PureWindowsPath(sys.executable).as_posix()}" -m nbstripout'
+        filepath = f'"{PureWindowsPath(python or sys.executable).as_posix()}" -m nbstripout'
         check_call(git_config + ['filter.nbstripout.clean', filepath])
         check_call(git_config + ['filter.nbstripout.smudge', 'cat'])
         check_call(git_config + ['diff.ipynb.textconv', filepath + ' -t'])
@@ -394,6 +394,9 @@ def main():
                           help='Use global git config (default is local config)')
     location.add_argument('--system', dest='_system', action='store_true',
                           help='Use system git config (default is local config)')
+    location.add_argument('--python', dest='_python', metavar="PATH",
+                          help='Path to python executable to use when --install\'ing '
+                          '(default is deduced from `sys.executable`)')
     parser.add_argument('--force', '-f', action='store_true',
                         help='Strip output also from files with non ipynb extension')
     parser.add_argument('--max-size', metavar='SIZE',
@@ -420,7 +423,7 @@ def main():
         install_location = INSTALL_LOCATION_LOCAL
 
     if args.install:
-        raise SystemExit(install(git_config, install_location, attrfile=args.attributes))
+        raise SystemExit(install(git_config, install_location, python=args._python, attrfile=args.attributes))
     if args.uninstall:
         raise SystemExit(uninstall(git_config, install_location, attrfile=args.attributes))
     if args.is_installed:


Test patch:
diff --git a/tests/test_git_integration.py b/tests/test_git_integration.py
index d21f0a8..7d58cdc 100644
--- a/tests/test_git_integration.py
+++ b/tests/test_git_integration.py
@@ -1,6 +1,7 @@
 from configparser import ConfigParser
 from pathlib import Path
 import re
+import sys
 
 import pytest
 
@@ -27,6 +28,25 @@ def test_install(pytester: pytest.Pytester):
     assert re.match(r".*python.* -m nbstripout -t", config['diff "ipynb"']["textconv"])
 
 
+def test_install_different_python(pytester: pytest.Pytester):
+    pytester.run("git", "init")
+    assert pytester.run("nbstripout", "--is-installed").ret == 1
+    pytester.run("nbstripout", "--install", "--python", "DIFFERENTPYTHON")
+    assert pytester.run("nbstripout", "--is-installed").ret == 0
+
+    config = ConfigParser()
+    config.read(".git/config")
+    assert re.match(
+        r".*DIFFERENTPYTHON.* -m nbstripout", config['filter "nbstripout"']["clean"]
+    )
+    assert sys.executable not in config['filter "nbstripout"']["clean"]
+    assert config['filter "nbstripout"']["smudge"] == "cat"
+    assert re.match(
+        r".*DIFFERENTPYTHON.* -m nbstripout -t", config['diff "ipynb"']["textconv"]
+    )
+    assert sys.executable not in config['diff "ipynb"']["textconv"]
+
+
 def test_uninstall(pytester: pytest.Pytester):
     pytester.run('git', 'init')
     # add extra filter at the start, so we can check we don't remove it