From 8590f2f2028a449e150457cbb7a092794266348f Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Wed, 18 Mar 2026 02:41:58 -0400 Subject: [PATCH 1/2] Permission error handling --- src/pythonfinder/finders/system_finder.py | 7 ++++++- src/pythonfinder/utils/path_utils.py | 13 ++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/pythonfinder/finders/system_finder.py b/src/pythonfinder/finders/system_finder.py index 0563b9f..d43fe22 100644 --- a/src/pythonfinder/finders/system_finder.py +++ b/src/pythonfinder/finders/system_finder.py @@ -57,7 +57,12 @@ def __init__( venv = os.environ.get("VIRTUAL_ENV") if venv: bin_dir = "Scripts" if os.name == "nt" else "bin" - venv_path = Path(venv).resolve() / bin_dir + try: + venv_path = Path(venv).resolve() / bin_dir + except (PermissionError, OSError): + # resolve() can raise PermissionError on Windows for restricted + # system directories; fall back to a non-resolving join. + venv_path = Path(venv) / bin_dir # For Windows tests with Unix-style paths if os.name == "nt" and str(venv).startswith("/"): diff --git a/src/pythonfinder/utils/path_utils.py b/src/pythonfinder/utils/path_utils.py index a97f556..ff5deaa 100644 --- a/src/pythonfinder/utils/path_utils.py +++ b/src/pythonfinder/utils/path_utils.py @@ -238,11 +238,14 @@ def exists_and_is_accessible(path: Path) -> bool: """ try: return path.exists() - except PermissionError as pe: - if pe.errno == errno.EACCES: # Permission denied - return False - else: - raise + except PermissionError: + # Treat any permission-denied error (including Windows WinError 5) as + # inaccessible rather than crashing. + return False + except OSError: + # Catch other OS-level errors (e.g. Windows ERROR_ACCESS_DENIED variants + # that may surface as OSError rather than PermissionError). + return False def is_in_path(path: str | Path, parent_path: str | Path) -> bool: From ca747154b7536996b58dbeef456594834afc796b Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Wed, 18 Mar 2026 02:48:40 -0400 Subject: [PATCH 2/2] correction --- src/pythonfinder/utils/path_utils.py | 16 ++++++++-------- tests/test_path_utils.py | 13 +++++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/pythonfinder/utils/path_utils.py b/src/pythonfinder/utils/path_utils.py index ff5deaa..8a633e6 100644 --- a/src/pythonfinder/utils/path_utils.py +++ b/src/pythonfinder/utils/path_utils.py @@ -238,14 +238,14 @@ def exists_and_is_accessible(path: Path) -> bool: """ try: return path.exists() - except PermissionError: - # Treat any permission-denied error (including Windows WinError 5) as - # inaccessible rather than crashing. - return False - except OSError: - # Catch other OS-level errors (e.g. Windows ERROR_ACCESS_DENIED variants - # that may surface as OSError rather than PermissionError). - return False + except PermissionError as error: + if error.errno == errno.EACCES or getattr(error, "winerror", None) == 5: + return False + raise + except OSError as error: + if error.errno == errno.EACCES or getattr(error, "winerror", None) == 5: + return False + raise def is_in_path(path: str | Path, parent_path: str | Path) -> bool: diff --git a/tests/test_path_utils.py b/tests/test_path_utils.py index 70adbe0..ac4d424 100644 --- a/tests/test_path_utils.py +++ b/tests/test_path_utils.py @@ -241,6 +241,19 @@ def test_exists_and_is_accessible(): ): exists_and_is_accessible(Path("/usr/bin/python")) + class WindowsAccessDenied(OSError): + def __init__(self): + super().__init__("Access is denied") + self.winerror = 5 + self.errno = None + + with mock.patch("pathlib.Path.exists", side_effect=WindowsAccessDenied()): + assert not exists_and_is_accessible(Path("/usr/bin/python")) + + with pytest.raises(OSError): + with mock.patch("pathlib.Path.exists", side_effect=OSError(1, "Other error")): + exists_and_is_accessible(Path("/usr/bin/python")) + def test_is_in_path(): """Test that is_in_path correctly checks if a path is inside another path."""