Skip to content

Commit be274cb

Browse files
authored
Merge pull request #344 from rstudio/242-potentially-misleading-permissions-error-when-incorrectly-specifying-python-path
Adds a check to Python executable validation.
2 parents 40c12fc + d6c30d6 commit be274cb

File tree

3 files changed

+46
-20
lines changed

3 files changed

+46
-20
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010

1111
- The `--cacert` option now supports certificate files encoded in the Distinguished Encoding Rules (DER) binary format. Certificate files with DER encoding must end in a `.cer` or `.der` suffix.
12+
- The `--python` option now provides additional user guidance when an invalid path is provided.
1213

1314
### Changed
1415

rsconnect/bundle.py

+18-13
Original file line numberDiff line numberDiff line change
@@ -1074,19 +1074,24 @@ def are_apis_supported_on_server(connect_details):
10741074
return connect_details["python"]["api_enabled"]
10751075

10761076

1077-
def which_python(python, env=os.environ):
1078-
"""Determine which python binary should be used.
1079-
1080-
In priority order:
1081-
* --python specified on the command line
1082-
* the python binary running this script
1083-
"""
1084-
if python:
1085-
if not (exists(python) and os.access(python, os.X_OK)):
1086-
raise RSConnectException('The file, "%s", does not exist or is not executable.' % python)
1087-
return python
1088-
1089-
return sys.executable
1077+
def which_python(python: typing.Optional[str] = None):
1078+
"""Determines which Python executable to use.
1079+
1080+
If the :param python: is provided, then validation is performed to check if the path is an executable file. If
1081+
None, the invoking system Python executable location is returned.
1082+
1083+
:param python: (Optional) path to a python executable.
1084+
:return: :param python: or `sys.executable`.
1085+
"""
1086+
if python is None:
1087+
return sys.executable
1088+
if not exists(python):
1089+
raise RSConnectException(f"The path '{python}' does not exist. Expected a Python executable.")
1090+
if isdir(python):
1091+
raise RSConnectException(f"The path '{python}' is a directory. Expected a Python executable.")
1092+
if not os.access(python, os.X_OK):
1093+
raise RSConnectException(f"The path '{python}' is not executable. Expected a Python executable")
1094+
return python
10901095

10911096

10921097
def inspect_environment(

tests/test_bundle.py

+27-7
Original file line numberDiff line numberDiff line change
@@ -646,13 +646,6 @@ def test_validate_entry_point(self):
646646
finally:
647647
shutil.rmtree(directory)
648648

649-
def test_which_python(self):
650-
with self.assertRaises(RSConnectException):
651-
which_python("fake.file")
652-
653-
self.assertEqual(which_python(sys.executable), sys.executable)
654-
self.assertEqual(which_python(None), sys.executable)
655-
656649
def test_default_title(self):
657650
self.assertEqual(_default_title("testing.txt"), "testing")
658651
self.assertEqual(_default_title("this.is.a.test.ext"), "this.is.a.test")
@@ -781,3 +774,30 @@ def fake_inspect_environment(
781774

782775
assert python == expected_python
783776
assert environment == expected_environment
777+
778+
779+
class WhichPythonTestCase(TestCase):
780+
def test_default(self):
781+
self.assertEqual(which_python(), sys.executable)
782+
783+
def test_none(self):
784+
self.assertEqual(which_python(None), sys.executable)
785+
786+
def test_sys(self):
787+
self.assertEqual(which_python(sys.executable), sys.executable)
788+
789+
def test_does_not_exist(self):
790+
with tempfile.NamedTemporaryFile() as tmpfile:
791+
name = tmpfile.name
792+
with self.assertRaises(RSConnectException):
793+
which_python(name)
794+
795+
def test_is_directory(self):
796+
with tempfile.TemporaryDirectory() as tmpdir:
797+
with self.assertRaises(RSConnectException):
798+
which_python(tmpdir)
799+
800+
def test_is_not_executable(self):
801+
with tempfile.NamedTemporaryFile() as tmpfile:
802+
with self.assertRaises(RSConnectException):
803+
which_python(tmpfile.name)

0 commit comments

Comments
 (0)