← eval5_submission

rich_3882

resolved RESOLVED PASS · 15 tool calls · 69 s · Textualize/rich

Task input

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 calls (16)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"PromptBase.on_validate_error\" ."
}
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
full
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
2run_command
{
 "command": "grep -rn \"on_validate_error\" ."
}
{"status": "ok", "stdout": "./rich/prompt.py:258: def on_validate_error(self, value: str, error: InvalidResponse) -> None:\n./rich/prompt.py:298: self.on_valida
full
{"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-4 ×2read_file
{
 "filepath": "rich/prompt.py",
 "start_line": "250",
 "end_line": "270"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
5read_file
{
 "filepath": "rich/prompt.py"
}
{"status": "ok", "filepath": "rich/prompt.py", "content": "from typing import Any, Generic, List, Optional, TextIO, TypeVar, Union, overload\n\nfrom . import ge
full
{"status": "ok", "filepath": "rich/prompt.py", "content": "from typing import Any, Generic, List, Optional, TextIO, TypeVar, Union, overload\n\nfrom . import get_console\nfrom .console import Console\nfrom .text import Text, TextType\n\nPromptType = TypeVar(\"PromptType\")\nDefaultType = TypeVar(\"DefaultType\")\n\n\nclass PromptError(Exception):\n    \"\"\"Exception base class for prompt related errors.\"\"\"\n\n\nclass InvalidResponse(PromptError):\n    \"\"\"Exception to indicate a response was invalid. Raise this within process_response() to indicate an error\n    and provide an error message.\n\n    Args:\n        message (Union[str, Text]): Error message.\n    \"\"\"\n\n    def __init__(self, message: TextType) -> None:\n        self.message = message\n\n    def __rich__(self) -> TextType:\n        return self.message\n\n\nclass PromptBase(Generic[PromptType]):\n    \"\"\"Ask the user for input until a valid response is received. This is the base class, see one of\n    the concrete classes for examples.\n\n    Args:\n        prompt (TextType, optional): Prompt text. Defaults to \"\".\n        console (Console, optional): A Console instance or None to use global console. Defaults to None.\n        password (bool, optional): Enable password input. Defaults to False.\n        choices (List[str], optional): A list of valid choices. Defaults to None.\n        case_sensitive (bool, optional): Matching of choices should be case-sensitive. Defaults to True.\n        show_default (bool, optional): Show default in prompt. Defaults to True.\n        show_choices (bool, optional): Show choices in prompt. Defaults to True.\n    \"\"\"\n\n    response_type: type = str\n\n    validate_error_message = \"[prompt.invalid]Please enter a valid value\"\n    illegal_choice_message = (\n        \"[prompt.invalid.choice]Please select one of the available options\"\n    )\n    prompt_suffix = \": \"\n\n    choices: Optional[List[str]] = None\n\n    def __init__(\n        self,\n      
6-7 ×2read_file
{
 "filepath": "rich/prompt.py",
 "start_line": "151"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
8run_command
{
 "command": "sed -n '250,310p' 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() for
full
{"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    def __call__(self, *, stream: Optional[TextIO] = None) -> PromptType:\n        ...\n\n    @overload\n    def __call__(\n        self, *, default: DefaultType, stream: Optional[TextIO] = None\n    ) -> Union[PromptType, DefaultType]:\n        ...\n\n    def __call__(self, *, default: Any = ..., stream: Optional[TextIO] = None) -> Any:\n        \"\"\"Run the prompt loop.\n\n        Args:\n            default (Any, optional): Optional default value.\n\n        Returns:\n            PromptType: Processed value.\n        \"\"\"\n        while True:\n            self.pre_prompt()\n            prompt = self.make_prompt(default)\n            value = self.get_input(self.console, prompt, self.password, stream=stream)\n            if value == \"\" and default != ...:\n                return default\n            try:\n                return_value = self.process_response(value)\n            except InvalidResponse as error:\n                self.on_validate_error(value, error)\n                continue\n            else:\n                return return_value\n\n\nclass Prompt(PromptBase[str]):\n    \"\"\"A prompt that returns a str.\n\n    Example:\n        >>> name = Prompt.ask(\"Enter your 
9run_command
{
 "command": "grep -n \"def on_validate_error\" rich/prompt.py"
}
{"status": "ok", "stdout": "258: def on_validate_error(self, value: str, error: InvalidResponse) -> None:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "258:    def on_validate_error(self, value: str, error: InvalidResponse) -> None:\n", "stderr": "", "exit_code": 0}
10run_command
{
 "command": "sed -n '258,265p' rich/prompt.py"
}
{"status": "ok", "stdout": " def on_validate_error(self, value: str, error: InvalidResponse) -> None:\n \"\"\"Called to handle validation error.\n\n Args:\n val
full
{"status": "ok", "stdout": "    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", "stderr": "", "exit_code": 0}
11run_command
{
 "command": "grep -rn \"markup=\" . | head -20"
}
{"status": "ok", "stdout": "./tests/test_progress.py:70: text_column = TextColumn(\"[b]bar\", markup=False)\n./tests/test_logging.py:75: markup=True,\n./tests/t
full
{"status": "ok", "stdout": "./tests/test_progress.py:70:    text_column = TextColumn(\"[b]bar\", markup=False)\n./tests/test_logging.py:75:        markup=True,\n./tests/test_console.py:834:    assert options.update(markup=False).markup == False\n./tests/test_console.py:835:    assert options.update(markup=True).markup == True\n./tests/test_text.py:818:    console = Console(file=StringIO(), markup=False)\n./docs/source/markup.rst:105:Console markup is convenient, but you may wish to disable it if the syntax clashes with the string you want to print. You can do this by setting ``markup=False`` on the :meth:`~rich.console.Print.print` method or on the :class:`~rich.console.Console` constructor.\n./docs/source/logging.rst:19:Rich logs won't render :ref:`console_markup` in logging by default as most libraries won't be aware of the need to escape literal square brackets, but you can enable it by setting ``markup=True`` on the handler. Alternatively you can enable it per log message by supplying the ``extra`` argument as follows::\n./rich/logging.py:297:    log.info(\"[bold]EXITING...[/bold]\", extra=dict(markup=True))\n./rich/console.py:1328:                renderable, highlight=_options.highlight, markup=_options.markup\n./rich/console.py:1561:                        markup=markup,\n./rich/console.py:1641:            markup=False,\n./rich/console.py:1704:                markup=markup,\n./rich/console.py:1715:                markup=markup,\n./rich/console.py:1972:                markup=markup,\n./rich/console.py:2163:            self.print(prompt, markup=markup, emoji=emoji, end=\"\")\n./rich/measure.py:100:                renderable, markup=options.markup, highlight=False\n./rich/progress.py:731:            markup=markup,\n", "stderr": "", "exit_code": 0}
12run_command
{
 "command": "cat <<EOF > /tmp/repro.py\nfrom rich.console import Console\nfrom rich.prompt import Prompt, InvalidResponse\n\nclass MyPrompt(Prompt):\n    def process_response(self, value):\n        if value != \"valid\":\n            raise InvalidResponse(\"[bold red]Invalid value![/bold red]\")\n        return value\n\ndef test_markup_disabled():\n    console = Console(markup=False)\n    prompt = MyPrompt(\"Enter value\", console=console)\n    \n    # We need to mock input to avoid hanging\n    import sys\n    from io import StringIO\n    \n    # First input is invalid, second is valid\n    sys.stdin = StringIO(\"invalid\\nvalid\\n\")\n    \n    # Capture stdout\n    import io\n    capture = io.StringIO()\n    console.file = capture\n    \n    prompt.ask(\"Enter value\")\n    \n    output = capture.getvalue()\n    print(f\"Output: {output!r}\")\n    if \"[bold red]Invalid value![/bold red]\" in output:\n        print(\"FAIL: Raw markup printed\")\n    elif \"Invalid value!\" in output:\n        print(\"SUCCESS: Markup rendered (or at least not raw)\")\n    else:\n        print(\"FAIL: Error message not found\")\n\nif __name__ == \"__main__\":\n    test_markup_disabled()\nEOF\npython3 /tmp/repro.py\n"
}
{"status": "ok", "stdout": "Enter value: Invalid value!\nEnter value: Output: ''\nFAIL: Error message not found\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Enter value: Invalid value!\nEnter value: Output: ''\nFAIL: Error message not found\n", "stderr": "", "exit_code": 0}
13run_command
{
 "command": "cat <<EOF > /tmp/repro.py\nfrom rich.console import Console\nfrom rich.prompt import Prompt, InvalidResponse\nimport sys\nfrom io import StringIO\n\nclass MyPrompt(Prompt):\n    def process_response(self, value):\n        if value != \"valid\":\n            raise InvalidResponse(\"[bold red]Invalid value![/bold red]\")\n        return value\n\ndef test_markup_disabled():\n    console = Console(markup=False)\n    prompt = MyPrompt(\"Enter value\", console=console)\n    \n    # Mock stdin\n    sys.stdin = StringIO(\"invalid\\nvalid\\n\")\n    \n    # Capture stdout\n    capture = StringIO()\n    console.file = capture\n    \n    # Use the __call__ method or ask method\n    # Prompt.ask is a class method that creates a new Prompt instance\n    # We already have a prompt instance, so we call it.\n    prompt(default=\"valid\")\n    \n    output = capture.getvalue()\n    print(f\"Output: {output!r}\")\n    if \"[bold red]Invalid value![/bold red]\" in output:\n        print(\"FAIL: Raw markup printed\")\n    elif \"Invalid value!\" in output:\n        print(\"SUCCESS: Markup rendered\")\n    else:\n        print(\"FAIL: Error message not found\")\n\nif __name__ == \"__main__\":\n    test_markup_disabled()\nEOF\npython3 /tmp/repro.py\n"
}
{"status": "ok", "stdout": "Output: 'Enter value (valid): [bold red]Invalid value![/bold red]\\nEnter value (valid): '\nFAIL: Raw markup printed\n", "stderr": "
full
{"status": "ok", "stdout": "Output: 'Enter value (valid): [bold red]Invalid value![/bold red]\\nEnter value (valid): '\nFAIL: Raw markup printed\n", "stderr": "", "exit_code": 0}
14edit_file
{
 "filepath": "rich/prompt.py",
 "new_string": "        self.console.print(error, markup=True)",
 "old_string": "        self.console.print(error)"
}
{"status": "ok", "filepath": "rich/prompt.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/rich/prompt.py\n+++ b/rich/prompt.py\n@@ -262,7 +262,7 @@\n
full
{"status": "ok", "filepath": "rich/prompt.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/rich/prompt.py\n+++ b/rich/prompt.py\n@@ -262,7 +262,7 @@\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", "is_truncated": false}
15run_command
{
 "command": "python3 /tmp/repro.py"
}
{"status": "ok", "stdout": "Output: 'Enter value (valid): Invalid value!\\nEnter value (valid): '\nSUCCESS: Markup rendered\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Output: 'Enter value (valid): Invalid value!\\nEnter value (valid): '\nSUCCESS: Markup rendered\n", "stderr": "", "exit_code": 0}
16submit_patch
{}
{"status": "ok", "patch_size": 503, "files_changed": 1}
full
{"status": "ok", "patch_size": 503, "files_changed": 1}

Patch

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."""

Test output

show
........                                                                 [100%]
8 passed in 0.15s