From d92677426436b7fb3aaef05aa97d07dedec22631 Mon Sep 17 00:00:00 2001 From: Eduardo Muniz Alves <82589615+devdudumuniz@users.noreply.github.com> Date: Sat, 29 Aug 2026 22:30:07 -0300 Subject: [PATCH 1/4] fix(web): pass directory picker values outside scripts --- pysus/native_dir_picker.py | 86 +++++++++++++++++++++++ pysus/tests/web/test_native_dir_picker.py | 48 +++++++++++++ pysus/web/pages/1_client.py | 67 +----------------- 3 files changed, 136 insertions(+), 65 deletions(-) create mode 100644 pysus/native_dir_picker.py create mode 100644 pysus/tests/web/test_native_dir_picker.py diff --git a/pysus/native_dir_picker.py b/pysus/native_dir_picker.py new file mode 100644 index 00000000..b457c8a4 --- /dev/null +++ b/pysus/native_dir_picker.py @@ -0,0 +1,86 @@ +"""Cross-platform native directory picker.""" + +import os +import platform +import subprocess + +_WINDOWS_SCRIPT = """ +Add-Type -AssemblyName System.Windows.Forms +$f = New-Object System.Windows.Forms.FolderBrowserDialog +$f.Description = $env:PYSUS_DIALOG_TITLE +$f.SelectedPath = $env:PYSUS_DIALOG_INITIALDIR +$f.ShowDialog() | Out-Null +$f.SelectedPath +""" + +_MACOS_SCRIPT = """ +set dialogTitle to system attribute "PYSUS_DIALOG_TITLE" +set initialDirectory to system attribute "PYSUS_DIALOG_INITIALDIR" +tell application "System Events" + activate + set f to choose folder with prompt dialogTitle ¬ + default location POSIX file initialDirectory + POSIX path of f +end tell +""" + + +def _dialog_environment(title: str, initialdir: str) -> dict[str, str]: + env = os.environ.copy() + env["PYSUS_DIALOG_TITLE"] = title + env["PYSUS_DIALOG_INITIALDIR"] = initialdir + return env + + +def native_dir_picker(title: str, initialdir: str) -> str: + """Open a native directory picker and return the selected path. + + Values are passed as command arguments or environment variables instead + of being interpolated into scripts executed by platform interpreters. + """ + system = platform.system() + + if system == "Linux": + for cmd in ( + [ + "zenity", + "--file-selection", + "--directory", + f"--filename={initialdir}/", + f"--title={title}", + ], + ["kdialog", "--getexistingdirectory", initialdir, "--title", title], + ): + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=30 + ) + return result.stdout.strip() + except (FileNotFoundError, subprocess.TimeoutExpired): + continue + + elif system == "Windows": + result = subprocess.run( + [ + "powershell", + "-NoProfile", + "-NonInteractive", + "-Command", + _WINDOWS_SCRIPT, + ], + capture_output=True, + text=True, + env=_dialog_environment(title, initialdir), + ) + return result.stdout.strip() + + elif system == "Darwin": + result = subprocess.run( + ["osascript", "-e", _MACOS_SCRIPT], + capture_output=True, + text=True, + env=_dialog_environment(title, initialdir), + ) + return result.stdout.strip() + + return "" diff --git a/pysus/tests/web/test_native_dir_picker.py b/pysus/tests/web/test_native_dir_picker.py new file mode 100644 index 00000000..3f6511f2 --- /dev/null +++ b/pysus/tests/web/test_native_dir_picker.py @@ -0,0 +1,48 @@ +from unittest.mock import Mock, patch + +from pysus.native_dir_picker import native_dir_picker + +_TITLE = "Select'; Write-Output injected; '" +_INITIALDIR = 'C:\\data" & do shell script "whoami' + + +@patch("pysus.native_dir_picker.subprocess.run") +@patch("pysus.native_dir_picker.platform.system", return_value="Windows") +def test_windows_values_are_not_interpolated_into_script(_, run): + run.return_value = Mock(stdout="C:\\selected\n") + + assert native_dir_picker(_TITLE, _INITIALDIR) == "C:\\selected" + + args = run.call_args.args[0] + kwargs = run.call_args.kwargs + assert _TITLE not in args[-1] + assert _INITIALDIR not in args[-1] + assert kwargs["env"]["PYSUS_DIALOG_TITLE"] == _TITLE + assert kwargs["env"]["PYSUS_DIALOG_INITIALDIR"] == _INITIALDIR + + +@patch("pysus.native_dir_picker.subprocess.run") +@patch("pysus.native_dir_picker.platform.system", return_value="Darwin") +def test_macos_values_are_not_interpolated_into_script(_, run): + run.return_value = Mock(stdout="/tmp/selected\n") + + assert native_dir_picker(_TITLE, _INITIALDIR) == "/tmp/selected" + + args = run.call_args.args[0] + kwargs = run.call_args.kwargs + assert _TITLE not in args[-1] + assert _INITIALDIR not in args[-1] + assert kwargs["env"]["PYSUS_DIALOG_TITLE"] == _TITLE + assert kwargs["env"]["PYSUS_DIALOG_INITIALDIR"] == _INITIALDIR + + +@patch("pysus.native_dir_picker.subprocess.run") +@patch("pysus.native_dir_picker.platform.system", return_value="Linux") +def test_linux_values_are_passed_as_arguments(_, run): + run.return_value = Mock(stdout="/tmp/selected\n") + + assert native_dir_picker(_TITLE, _INITIALDIR) == "/tmp/selected" + + args = run.call_args.args[0] + assert args[-1] == f"--title={_TITLE}" + assert args[-2] == f"--filename={_INITIALDIR}/" diff --git a/pysus/web/pages/1_client.py b/pysus/web/pages/1_client.py index fd5cfdb5..87a0dc33 100644 --- a/pysus/web/pages/1_client.py +++ b/pysus/web/pages/1_client.py @@ -8,6 +8,7 @@ from pysus import CACHEPATH from pysus.api.client import PySUS from pysus.api.models import BaseRemoteFile +from pysus.native_dir_picker import native_dir_picker from pysus.web.translations import t STATES = [ @@ -699,70 +700,6 @@ def _size_column_config() -> dict[str, Any]: } -def _native_dir_picker(title: str, initialdir: str) -> str: - """Open a native directory picker dialog and return the selected path.""" - import platform - import subprocess - - system = platform.system() - - if system == "Linux": - for cmd in ( - [ - "zenity", - "--file-selection", - "--directory", - f"--filename={initialdir}/", - f"--title={title}", - ], - ["kdialog", "--getexistingdirectory", initialdir, "--title", title], - ): - try: - r = subprocess.run( - cmd, capture_output=True, text=True, timeout=30 - ) - return r.stdout.strip() - except (FileNotFoundError, subprocess.TimeoutExpired): - continue - - elif system == "Windows": - ps = f""" -Add-Type -AssemblyName System.Windows.Forms -$f = New-Object System.Windows.Forms.FolderBrowserDialog -$f.Description = '{title}' -$f.SelectedPath = '{initialdir}' -$f.ShowDialog() | Out-Null -$f.SelectedPath -""" - r = subprocess.run( - ["powershell", "-Command", ps], - capture_output=True, - text=True, - ) - return r.stdout.strip() - - elif system == "Darwin": - prompt_line = ( - 'set f to choose folder with prompt "{}"' - ' default location POSIX file "{}"' - ).format(title, initialdir) - applescript = ( - f'tell application "System Events"\n' - f" activate\n" - f" {prompt_line}\n" - f" POSIX path of f\n" - f"end tell" - ) - r = subprocess.run( - ["osascript", "-e", applescript], - capture_output=True, - text=True, - ) - return r.stdout.strip() - - return "" - - def _show_results(pysus: PySUS, client: str) -> None: query_key = f"_query_results_{client}" queue_key = f"_download_queue_{client}" @@ -879,7 +816,7 @@ def _show_results(pysus: PySUS, client: str) -> None: ) with col_btn: if st.button(t("browse", _lang()), width="stretch"): - folder = _native_dir_picker( + folder = native_dir_picker( title=t("browse_dir_title", _lang()), initialdir=st.session_state[dir_key], ) From 62bd5c53a7e52225cad5493f0951529ca82d1d65 Mon Sep 17 00:00:00 2001 From: Eduardo Muniz Alves <82589615+devdudumuniz@users.noreply.github.com> Date: Sat, 29 Aug 2026 23:02:22 -0300 Subject: [PATCH 2/4] test: cover native directory picker fallbacks --- pysus/tests/web/test_native_dir_picker.py | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pysus/tests/web/test_native_dir_picker.py b/pysus/tests/web/test_native_dir_picker.py index 3f6511f2..53597982 100644 --- a/pysus/tests/web/test_native_dir_picker.py +++ b/pysus/tests/web/test_native_dir_picker.py @@ -1,3 +1,4 @@ +import subprocess from unittest.mock import Mock, patch from pysus.native_dir_picker import native_dir_picker @@ -46,3 +47,42 @@ def test_linux_values_are_passed_as_arguments(_, run): args = run.call_args.args[0] assert args[-1] == f"--title={_TITLE}" assert args[-2] == f"--filename={_INITIALDIR}/" + + +@patch("pysus.native_dir_picker.subprocess.run") +@patch("pysus.native_dir_picker.platform.system", return_value="Linux") +def test_linux_falls_back_to_kdialog_when_zenity_is_unavailable(_, run): + run.side_effect = [ + FileNotFoundError, + Mock(stdout="/tmp/selected-by-kdialog\n"), + ] + + assert native_dir_picker(_TITLE, _INITIALDIR) == "/tmp/selected-by-kdialog" + + assert run.call_count == 2 + assert run.call_args_list[1].args[0] == [ + "kdialog", + "--getexistingdirectory", + _INITIALDIR, + "--title", + _TITLE, + ] + + +@patch("pysus.native_dir_picker.subprocess.run") +@patch("pysus.native_dir_picker.platform.system", return_value="Linux") +def test_linux_returns_empty_when_all_pickers_fail(_, run): + run.side_effect = [ + FileNotFoundError, + subprocess.TimeoutExpired("kdialog", 30), + ] + + assert native_dir_picker(_TITLE, _INITIALDIR) == "" + assert run.call_count == 2 + + +@patch("pysus.native_dir_picker.subprocess.run") +@patch("pysus.native_dir_picker.platform.system", return_value="FreeBSD") +def test_unsupported_platform_returns_empty_without_running_command(_, run): + assert native_dir_picker(_TITLE, _INITIALDIR) == "" + run.assert_not_called() From bbbf5c5296f0a03eb071788b14d8275181d34341 Mon Sep 17 00:00:00 2001 From: Eduardo Muniz Alves Date: Tue, 1 Sep 2026 04:54:59 -0300 Subject: [PATCH 3/4] test(web): use benign directory picker fixtures --- pysus/tests/web/test_native_dir_picker.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pysus/tests/web/test_native_dir_picker.py b/pysus/tests/web/test_native_dir_picker.py index 53597982..d2085546 100644 --- a/pysus/tests/web/test_native_dir_picker.py +++ b/pysus/tests/web/test_native_dir_picker.py @@ -3,13 +3,13 @@ from pysus.native_dir_picker import native_dir_picker -_TITLE = "Select'; Write-Output injected; '" -_INITIALDIR = 'C:\\data" & do shell script "whoami' +_TITLE = "Selecionar exportacao da unidade 'APS Central'" +_INITIALDIR = 'C:\\Dados APS\\Unidade "Central"' @patch("pysus.native_dir_picker.subprocess.run") @patch("pysus.native_dir_picker.platform.system", return_value="Windows") -def test_windows_values_are_not_interpolated_into_script(_, run): +def test_windows_values_are_passed_through_environment(_, run): run.return_value = Mock(stdout="C:\\selected\n") assert native_dir_picker(_TITLE, _INITIALDIR) == "C:\\selected" @@ -24,7 +24,7 @@ def test_windows_values_are_not_interpolated_into_script(_, run): @patch("pysus.native_dir_picker.subprocess.run") @patch("pysus.native_dir_picker.platform.system", return_value="Darwin") -def test_macos_values_are_not_interpolated_into_script(_, run): +def test_macos_values_are_passed_through_environment(_, run): run.return_value = Mock(stdout="/tmp/selected\n") assert native_dir_picker(_TITLE, _INITIALDIR) == "/tmp/selected" From 5b2538dd8d903e4dcff9df378786830752c550a0 Mon Sep 17 00:00:00 2001 From: Eduardo Muniz Alves Date: Tue, 1 Sep 2026 05:14:14 -0300 Subject: [PATCH 4/4] test(web): cover cancelled directory selection --- pysus/tests/web/test_native_dir_picker.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pysus/tests/web/test_native_dir_picker.py b/pysus/tests/web/test_native_dir_picker.py index d2085546..7b342120 100644 --- a/pysus/tests/web/test_native_dir_picker.py +++ b/pysus/tests/web/test_native_dir_picker.py @@ -81,6 +81,14 @@ def test_linux_returns_empty_when_all_pickers_fail(_, run): assert run.call_count == 2 +@patch("pysus.native_dir_picker.subprocess.run") +@patch("pysus.native_dir_picker.platform.system", return_value="Windows") +def test_cancelled_picker_returns_empty(_, run): + run.return_value = Mock(stdout="") + + assert native_dir_picker(_TITLE, _INITIALDIR) == "" + + @patch("pysus.native_dir_picker.subprocess.run") @patch("pysus.native_dir_picker.platform.system", return_value="FreeBSD") def test_unsupported_platform_returns_empty_without_running_command(_, run):