From ef5027ad1b10d6d03e554db995e017ec7c222e50 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Aug 2026 14:27:24 -0400 Subject: [PATCH 1/3] This adds a new blocks option which allows for a silent deploy --- robotpy_installer/cli_deploy.py | 22 ++++++++--- tests/test_local_deploy.py | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/robotpy_installer/cli_deploy.py b/robotpy_installer/cli_deploy.py index 43d99ed..0c804c8 100644 --- a/robotpy_installer/cli_deploy.py +++ b/robotpy_installer/cli_deploy.py @@ -185,6 +185,7 @@ def run( no_resolve: bool, local: bool, cache_root: typing.Optional[pathlib.Path], + assume_yes: bool = False, ): if main_file.parent == pathlib.Path.home(): print_err( @@ -228,7 +229,7 @@ def run( robot_filename = main_file.name - if not large and not self._check_large_files(project_path): + if not large and not self._check_large_files(project_path, assume_yes): return 1 project = None @@ -291,6 +292,7 @@ def run( no_install, force_install, no_uninstall, + assume_yes, ) if not self._do_deploy(ssh, debug, nc, nc_ds, robot_filename, project_path): @@ -351,7 +353,7 @@ def _generate_build_data(self, project_path: pathlib.Path) -> dict: return deploy_data - def _check_large_files(self, robot_path: pathlib.Path): + def _check_large_files(self, robot_path: pathlib.Path, assume_yes: bool = False): large_sz = 250000 large_files = [] @@ -365,7 +367,7 @@ def _check_large_files(self, robot_path: pathlib.Path): for fname, sz in sorted(large_files): print_err(f"- {fname} ({sz} bytes)") - if not yesno("Upload anyways?"): + if not assume_yes and not yesno("Upload anyways?"): return False return True @@ -398,6 +400,7 @@ def _ensure_requirements( no_install: bool, force_install: bool, no_uninstall: bool, + assume_yes: bool = False, ): python_exists = False python_invalid: typing.Union[bool, str] = False @@ -438,7 +441,7 @@ def _ensure_requirements( f"and install Python {rm}.{rmn}.\n" ) - if not yesno("Reinstall Python"): + if not assume_yes and not yesno("Reinstall Python"): raise Error("User declined reinstallation") if python_exists: @@ -492,7 +495,7 @@ def _ensure_requirements( "If you do not wish to do this, specify --no-install as a deploy argument, or answer 'n'.\n" ) - if not yesno(prompt): + if not assume_yes and not yesno(prompt): requirements_installed = True if ( @@ -783,6 +786,13 @@ def __init__(self, parser: argparse.ArgumentParser): help="Override RobotPy installer cache location; defaults to /opt/blocks/cache", ) + parser.add_argument( + "--blocks", + action="store_true", + default=False, + help="If specified, do not ask any interactive questions; assume 'yes' and deploy unattended", + ) + self._packages_in_cache: typing.Optional[pypackages.Packages] = None self._robot_packages: typing.Optional[pypackages.Packages] = None @@ -798,6 +808,7 @@ def run( force_install: bool, large: bool, cache_root: typing.Optional[pathlib.Path], + blocks: bool = False, ): return Deploy.run( self, @@ -820,4 +831,5 @@ def run( no_resolve=False, local=True, cache_root=cache_root, + assume_yes=blocks, ) diff --git a/tests/test_local_deploy.py b/tests/test_local_deploy.py index f056dcf..170be4b 100644 --- a/tests/test_local_deploy.py +++ b/tests/test_local_deploy.py @@ -167,6 +167,73 @@ def test_local_deploy_installer_subcommand_is_registered(): assert ("local-deploy", LocalDeploy) in Installer.subcommands +def test_local_deploy_parser_has_blocks_option(): + parser = argparse.ArgumentParser() + LocalDeploy(parser) + + args = parser.parse_args([]) + assert args.blocks is False + + args = parser.parse_args(["--blocks"]) + assert args.blocks is True + + +def test_local_deploy_blocks_does_not_prompt_on_requirements_mismatch(tmp_path): + deploy = LocalDeploy(argparse.ArgumentParser()) + main_file = tmp_path / "robot.py" + main_file.write_text("print('robot')") + + fake_project = MagicMock() + fake_project.get_install_list.return_value = ["robotpy"] + fake_project.are_requirements_met.side_effect = [ + (False, ["robotpy missing"]), + (True, []), + (True, []), + ] + fake_project.get_deploy_list.return_value = ["robotpy"] + + fake_installer = MagicMock() + fake_installer.is_python_installed.return_value = True + fake_installer.get_python_version.return_value = required_pyversion + fake_installer.connect_to_robot.return_value.__enter__.return_value = ( + LocalController() + ) + fake_installer.connect_to_robot.return_value.__exit__.return_value = None + + with ( + patch("robotpy_installer.cli_deploy.pyproject.load", return_value=fake_project), + patch( + "robotpy_installer.cli_deploy.RobotpyInstaller", return_value=fake_installer + ), + patch.object(deploy, "_get_robot_packages", return_value={}), + patch.object(deploy, "_get_cached_packages", return_value={}), + patch.object(deploy, "_do_deploy", return_value=True), + patch( + "robotpy_installer.cli_deploy.robot_utils.uninstall_cpp_java", + return_value=True, + ), + patch("robotpy_installer.cli_deploy.yesno") as fake_yesno, + ): + result = deploy.run( + main_file=main_file, + project_path=tmp_path, + debug=False, + ignore_image_version=False, + no_install=False, + no_uninstall=False, + force_install=False, + large=False, + cache_root=None, + blocks=True, + ) + + assert result == 0 + fake_yesno.assert_not_called() + fake_installer.pip_install.assert_called_once_with( + False, False, False, False, [], ["robotpy"] + ) + + def test_deploy_run_has_no_suppress_no_verify_warning_argument(): assert "suppress_no_verify_warning" not in inspect.signature(Deploy.run).parameters From 24f87a31399404622269988a4d746348a4683420 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Aug 2026 15:22:50 -0400 Subject: [PATCH 2/3] Change from --blocks to --yes --- robotpy_installer/cli_deploy.py | 6 +++--- tests/test_local_deploy.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/robotpy_installer/cli_deploy.py b/robotpy_installer/cli_deploy.py index 0c804c8..641d5bd 100644 --- a/robotpy_installer/cli_deploy.py +++ b/robotpy_installer/cli_deploy.py @@ -787,7 +787,7 @@ def __init__(self, parser: argparse.ArgumentParser): ) parser.add_argument( - "--blocks", + "--yes", action="store_true", default=False, help="If specified, do not ask any interactive questions; assume 'yes' and deploy unattended", @@ -808,7 +808,7 @@ def run( force_install: bool, large: bool, cache_root: typing.Optional[pathlib.Path], - blocks: bool = False, + yes: bool = False, ): return Deploy.run( self, @@ -831,5 +831,5 @@ def run( no_resolve=False, local=True, cache_root=cache_root, - assume_yes=blocks, + assume_yes=yes, ) diff --git a/tests/test_local_deploy.py b/tests/test_local_deploy.py index 170be4b..f993b00 100644 --- a/tests/test_local_deploy.py +++ b/tests/test_local_deploy.py @@ -167,18 +167,18 @@ def test_local_deploy_installer_subcommand_is_registered(): assert ("local-deploy", LocalDeploy) in Installer.subcommands -def test_local_deploy_parser_has_blocks_option(): +def test_local_deploy_parser_has_yes_option(): parser = argparse.ArgumentParser() LocalDeploy(parser) args = parser.parse_args([]) assert args.blocks is False - args = parser.parse_args(["--blocks"]) - assert args.blocks is True + args = parser.parse_args(["--yes"]) + assert args.yes is True -def test_local_deploy_blocks_does_not_prompt_on_requirements_mismatch(tmp_path): +def test_local_deploy_yes_does_not_prompt_on_requirements_mismatch(tmp_path): deploy = LocalDeploy(argparse.ArgumentParser()) main_file = tmp_path / "robot.py" main_file.write_text("print('robot')") @@ -224,7 +224,7 @@ def test_local_deploy_blocks_does_not_prompt_on_requirements_mismatch(tmp_path): force_install=False, large=False, cache_root=None, - blocks=True, + yes=True, ) assert result == 0 From 8f2db498bd39253a163fcf3aec23e06fa4dbddd6 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Aug 2026 15:29:06 -0400 Subject: [PATCH 3/3] Fixed remaining "blocks" --- tests/test_local_deploy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_local_deploy.py b/tests/test_local_deploy.py index f993b00..8472464 100644 --- a/tests/test_local_deploy.py +++ b/tests/test_local_deploy.py @@ -172,7 +172,7 @@ def test_local_deploy_parser_has_yes_option(): LocalDeploy(parser) args = parser.parse_args([]) - assert args.blocks is False + assert args.yes is False args = parser.parse_args(["--yes"]) assert args.yes is True