From 8b4581b317c8eb56610bd6d32d3a57e082ade45a Mon Sep 17 00:00:00 2001 From: kaggle-marvin Date: Thu, 12 Nov 2026 19:10:55 +0000 Subject: [PATCH] fix(cli): re-prompt on invalid confirmation answers instead of cancelling --- src/kaggle/api/kaggle_api_extended.py | 10 ++++++---- tests/unit/test_confirmation.py | 28 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 tests/unit/test_confirmation.py diff --git a/src/kaggle/api/kaggle_api_extended.py b/src/kaggle/api/kaggle_api_extended.py index fbe5dc5d..6ed1bee7 100644 --- a/src/kaggle/api/kaggle_api_extended.py +++ b/src/kaggle/api/kaggle_api_extended.py @@ -10130,12 +10130,14 @@ def confirmation(self, action: str = "", default_to_yes: bool = False): options[""] = True while True: sys.stdout.write("{} {} ".format(question, prompt)) - choice = input().lower() + try: + choice = input().strip().lower() + except EOFError: + # Closed stdin can never produce a valid answer; treat it as "no" rather than looping. + return False if choice in options: return options[choice] - else: - sys.stdout.write("Please respond with 'yes' or 'no'.\n") - return False + sys.stdout.write("Please respond with 'y' or 'n'.\n") def _check_response_version(self, response: Response): if self.already_printed_version_warning: diff --git a/tests/unit/test_confirmation.py b/tests/unit/test_confirmation.py new file mode 100644 index 00000000..a785a0fb --- /dev/null +++ b/tests/unit/test_confirmation.py @@ -0,0 +1,28 @@ +from unittest.mock import patch + +import pytest + + +@pytest.mark.parametrize( + "answer, default_to_yes, expected", + [ + ("", True, True), + (" Y \r", True, True), + ("NO", True, False), + ], +) +def test_confirmation_single_answer(api, answer, default_to_yes, expected): + with patch("builtins.input", return_value=answer): + assert api.confirmation("do it", default_to_yes=default_to_yes) is expected + + +def test_confirmation_reprompts_after_invalid_answer(api): + # Without default_to_yes, Enter is not an answer either. + with patch("builtins.input", side_effect=["", "x", "y"]) as mock_input: + assert api.confirmation("do it") is True + assert mock_input.call_count == 3 + + +def test_confirmation_closed_stdin_cancels(api): + with patch("builtins.input", side_effect=["x", EOFError]): + assert api.confirmation("do it") is False