resolved RESOLVED PASS · 13 tool calls · 114 s · Textualize/rich
Fix raw markup printed on prompt errors sometimes ## Type of changes - [x] Bug fix - [ ] New feature - [ ] Documentation / docstrings - [ ] Tests - [ ] Other ## Checklist - [x] I've run the latest [black](https://github.com/psf/black) with default args on new code. - [ ] I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate (see note about typos above). - [x] I've added tests for new code. - [x] I accept that @willmcgugan may be pedantic in the code review. ## Description PromptBase.on_validate_error() should explicitly ask for markup in case the console passed to it has markup disabled globally. Fixes #3881.
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn 'PromptBase' . | grep 'on_validate_error' | head -20"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 2 | run_command | {
"command": "grep -rn 'on_validate_error' . | head -20"
} | {"status": "ok", "stdout": "./rich/prompt.py:258: def on_validate_error(self, value: str, error: InvalidResponse) -> None:\n./rich/prompt.py:298: self.on_validafull{"status": "ok", "stdout": "./rich/prompt.py:258: def on_validate_error(self, value: str, error: InvalidResponse) -> None:\n./rich/prompt.py:298: self.on_validate_error(value, error)\n", "stderr": "", "exit_code": 0} |
| 3 | run_command | {
"command": "sed -n '250,270p' rich/prompt.py"
} | {"status": "ok", "stdout": " # return the original choice, not the lower case version\n return_value = self.response_type(\n self.choices[\n [choice.lower() forfull{"status": "ok", "stdout": " # return the original choice, not the lower case version\n return_value = self.response_type(\n self.choices[\n [choice.lower() for choice in self.choices].index(value.lower())\n ]\n )\n return return_value\n\n def on_validate_error(self, value: str, error: InvalidResponse) -> None:\n \"\"\"Called to handle validation error.\n\n Args:\n value (str): String entered by user.\n error (InvalidResponse): Exception instance the initiated the error.\n \"\"\"\n self.console.print(error)\n\n def pre_prompt(self) -> None:\n \"\"\"Hook to display something before the prompt.\"\"\"\n\n @overload\n", "stderr": "", "exit_code": 0} |
| 4 | run_command | {
"command": "cat > /tmp/repro.py <<'PY'\nfrom rich.console import Console\nfrom rich.prompt import Prompt, InvalidResponse\n\nclass MyPrompt(Prompt):\n def validate(self, value: str) -> bool:\n if value != \"yes\":\n raise InvalidResponse(\"[red]Invalid response![/red]\")\n return True\n\ndef test_markup():\n console = Console(markup=False)\n prompt = MyPrompt(console=console)\n \n # We need to simulate user input and handle the exception\n # Since we are in a non-interactive environment, we can mock input\n import unittest.mock as mock\n with mock.patch('builtins.input', side_effect=['no', 'yes']):\n # The first 'no' should trigger on_validate_error\n # We want to see if [red]Invalid response![/red] is printed as raw text or markup\n # But wait, if markup=False, we WANT it to be rendered if the code explicitly asks for it.\n # The issue says: PromptBase.on_validate_error() should explicitly ask for markup\n # in case the console passed to it has markup disabled globally.\n \n # Let's capture the output\n with console.capture() as capture:\n prompt.ask(\"Confirm\")\n \n print(capture)\n\nif __name__ == \"__main__\":\n test_markup()\nPY\npython3 /tmp/repro.py\n"
} | {"status": "ok", "stdout": "Confirm: <rich.console.Capture object at 0x102696660>\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Confirm: <rich.console.Capture object at 0x102696660>\n", "stderr": "", "exit_code": 0} |
| 5 | run_command | {
"command": "cat > /tmp/repro.py <<'PY'\nfrom rich.console import Console\nfrom rich.prompt import Prompt, InvalidResponse\nimport unittest.mock as mock\nimport io\n\nclass MyPrompt(Prompt):\n def validate(self, value: str) -> bool:\n if value != \"yes\":\n raise InvalidResponse(\"[red]Invalid response![/red]\")\n return True\n\ndef test_markup():\n console = Console(markup=False, force_terminal=True)\n prompt = MyPrompt(console=console)\n \n with mock.patch('builtins.input', side_effect=['no', 'yes']):\n # We use a StringIO to capture stdout if console.capture() is not working as expected\n # or just use console.print and check what's being produced.\n # Rich console's capture might be better.\n \n # For the purpose of this reproduction, we can just check if on_validate_error\n # prints the error message with markup=True.\n \n # Since Prompt.ask loops until valid, we can't easily capture the intermediate print\n # unless we mock the console.print.\n \n original_print = console.print\n printed_messages = []\n def mock_print(*args, **kwargs):\n printed_messages.append((args, kwargs))\n return original_print(*args, **kwargs)\n \n console.print = mock_print\n \n prompt.ask(\"Confirm\")\n \n for args, kwargs in printed_messages:\n print(f\"Args: {args}, Kwargs: {kwargs}\")\n\nif __name__ == \"__main__\":\n test_markup()\nPY\npython3 /tmp/repro.py\n"
} | {"status": "ok", "stdout": "Confirm: ", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Confirm: ", "stderr": "", "exit_code": 0} |
| 6 | run_command | {
"command": "cat > /tmp/repro.py <<'PY'\nfrom rich.console import Console\nfrom rich.prompt import Prompt, InvalidResponse\nimport unittest.mock as mock\n\nclass MyPrompt(Prompt):\n def validate(self, value: str) -> bool:\n if value != \"yes\":\n raise InvalidResponse(\"[red]Invalid response![/red]\")\n return True\n\ndef test_markup():\n console = Console(markup=False, force_terminal=True)\n prompt = MyPrompt(console=console)\n \n # We want to see what happens when on_validate_error is called.\n # Instead of full ask(), let's just call on_validate_error directly.\n error = InvalidResponse(\"[red]Invalid response![/red]\")\n \n # We capture the output of console.print\n # Use a Mock to check if markup=True was passed.\n import unittest.mock as mock\n console.print = mock.Mock()\n \n prompt.on_validate_error(\"no\", error)\n \n console.print.assert_called_with(error)\n print(\"Called with:\", console.print.call_args)\n\nif __name__ == \"__main__\":\n test_markup()\nPY\npython3 /tmp/repro.py\n"
} | {"status": "ok", "stdout": "Called with: call(InvalidResponse('[red]Invalid response![/red]'))\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Called with: call(InvalidResponse('[red]Invalid response![/red]'))\n", "stderr": "", "exit_code": 0} |
| 7 | run_command | {
"command": "python3 -c \"from rich.prompt import Prompt; print(Prompt.__mro__)\""
} | {"status": "ok", "stdout": "(<class 'rich.prompt.Prompt'>, <class 'rich.prompt.PromptBase'>, <class 'typing.Generic'>, <class 'object'>)\n", "stderr": "", "exitfull{"status": "ok", "stdout": "(<class 'rich.prompt.Prompt'>, <class 'rich.prompt.PromptBase'>, <class 'typing.Generic'>, <class 'object'>)\n", "stderr": "", "exit_code": 0} |
| 8 | run_command | {
"command": "sed -n '250,270p' rich/prompt.py"
} | {"status": "ok", "stdout": " # return the original choice, not the lower case version\n return_value = self.response_type(\n self.choices[\n [choice.lower() forfull{"status": "ok", "stdout": " # return the original choice, not the lower case version\n return_value = self.response_type(\n self.choices[\n [choice.lower() for choice in self.choices].index(value.lower())\n ]\n )\n return return_value\n\n def on_validate_error(self, value: str, error: InvalidResponse) -> None:\n \"\"\"Called to handle validation error.\n\n Args:\n value (str): String entered by user.\n error (InvalidResponse): Exception instance the initiated the error.\n \"\"\"\n self.console.print(error)\n\n def pre_prompt(self) -> None:\n \"\"\"Hook to display something before the prompt.\"\"\"\n\n @overload\n", "stderr": "", "exit_code": 0} |
| 9 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/prompt.py\")\ns = p.read_text()\nold = \"\"\" def on_validate_error(self, value: str, error: InvalidResponse) -> None:\n \\\"\\\"\\\"Called to handle validation error.\n\n Args:\n value (str): String entered by user.\n error (InvalidResponse): Exception instance the initiated the error.\n \\\"\\\"\\\"\n self.console.print(error)\"\"\"\nnew = \"\"\" def on_validate_error(self, value: str, error: InvalidResponse) -> None:\n \\\"\\\"\\\"Called to handle validation error.\n\n Args:\n value (str): String entered by user.\n error (InvalidResponse): Exception instance the initiated the error.\n \\\"\\\"\\\"\n self.console.print(error, markup=True)\"\"\"\nassert s.count(old) == 1, (\"old text found this many times:\", s.count(old))\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "python3 -m py_compile rich/prompt.py"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "cat > /tmp/repro_fixed.py <<'PY'\nfrom rich.console import Console\nfrom rich.prompt import Prompt, InvalidResponse\nimport unittest.mock as mock\n\nclass MyPrompt(Prompt):\n def validate(self, value: str) -> bool:\n if value != \"yes\":\n raise InvalidResponse(\"[red]Invalid response![/red]\")\n return True\n\ndef test_markup():\n console = Console(markup=False, force_terminal=True)\n prompt = MyPrompt(console=console)\n \n error = InvalidResponse(\"[red]Invalid response![/red]\")\n \n console.print = mock.Mock()\n \n prompt.on_validate_error(\"no\", error)\n \n console.print.assert_called_with(error, markup=True)\n print(\"Called with markup=True\")\n\nif __name__ == \"__main__\":\n test_markup()\nPY\npython3 /tmp/repro_fixed.py\n"
} | {"status": "ok", "stdout": "Called with markup=True\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Called with markup=True\n", "stderr": "", "exit_code": 0} |
| 12 | run_command | {
"command": "git diff"
} | {"status": "ok", "stdout": "diff --git a/rich/prompt.py b/rich/prompt.py\nindex c7cf25ba..ae94d9bb 100644\n--- a/rich/prompt.py\n+++ b/rich/prompt.py\n@@ -262,7full{"status": "ok", "stdout": "diff --git a/rich/prompt.py b/rich/prompt.py\nindex c7cf25ba..ae94d9bb 100644\n--- a/rich/prompt.py\n+++ b/rich/prompt.py\n@@ -262,7 +262,7 @@ class PromptBase(Generic[PromptType]):\n value (str): String entered by user.\n error (InvalidResponse): Exception instance the initiated the error.\n \"\"\"\n- self.console.print(error)\n+ self.console.print(error, markup=True)\n \n def pre_prompt(self) -> None:\n \"\"\"Hook to display something before the prompt.\"\"\"\n", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "submit_patch"
} | {"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/basfull{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: submit_patch: command not found\n", "exit_code": 127}} |
| 14 | submit_patch | {} | {"status": "ok", "patch_size": 503, "files_changed": 1}full{"status": "ok", "patch_size": 503, "files_changed": 1} |
diff --git a/rich/prompt.py b/rich/prompt.py
index c7cf25ba..ae94d9bb 100644
--- a/rich/prompt.py
+++ b/rich/prompt.py
@@ -262,7 +262,7 @@ class PromptBase(Generic[PromptType]):
value (str): String entered by user.
error (InvalidResponse): Exception instance the initiated the error.
"""
- self.console.print(error)
+ self.console.print(error, markup=True)
def pre_prompt(self) -> None:
"""Hook to display something before the prompt."""
........ [100%] 8 passed in 0.13s