failed WRONG_FIX wrong_fix · 8 tool calls · 126 s · Textualize/rich
Case insensitive prompt choices
## Type of changes
- [ ] Bug fix
- [x] 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.
- [x] I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate.
- [x] I've added tests for new code.
- [x] I accept that @willmcgugan may be pedantic in the code review.
## Description
Optional flag for `Prompt.ask` allowing case-insensitive responses. Just add `case_sensitive=False` to the command options:
```python
from rich.prompt import Prompt
name = Prompt.ask("Enter your name", choices=["Paul", "Jessica", "Duncan"], default="Paul", case_sensitive=False)
```
Now, it would accept "paul" or "Paul" as valid responses. Defaults to True, which is the usual result.
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"Prompt\" rich | grep \"ask\" | head -20"
} | {"status": "ok", "stdout": "rich/prompt.py:122: >>> filename = Prompt.ask(\"Enter a filename\")\nrich/prompt.py:227: PromptType: The value to be returned from afull{"status": "ok", "stdout": "rich/prompt.py:122: >>> filename = Prompt.ask(\"Enter a filename\")\nrich/prompt.py:227: PromptType: The value to be returned from ask method.\nrich/prompt.py:290: >>> name = Prompt.ask(\"Enter your name\")\nrich/prompt.py:302: >>> burrito_count = IntPrompt.ask(\"How many burritos do you want to order\")\nrich/prompt.py:314: >>> temperature = FloatPrompt.ask(\"Enter desired temperature\")\nrich/prompt.py:354: result = IntPrompt.ask(\nrich/prompt.py:363: password = Prompt.ask(\nrich/prompt.py:372: fruit = Prompt.ask(\"Enter a fruit\", choices=[\"apple\", \"orange\", \"pear\"])\n", "stderr": "", "exit_code": 0} |
| 2 | run_command | {
"command": "sed -n '200,400p' rich/prompt.py"
} | {"status": "ok", "stdout": " Returns:\n str: String from user.\n \"\"\"\n return console.input(prompt, password=password, stream=stream)\n\n def check_choice(sefull{"status": "ok", "stdout": " Returns:\n str: String from user.\n \"\"\"\n return console.input(prompt, password=password, stream=stream)\n\n def check_choice(self, value: str) -> bool:\n \"\"\"Check value is in the list of valid choices.\n\n Args:\n value (str): Value entered by user.\n\n Returns:\n bool: True if choice was valid, otherwise False.\n \"\"\"\n assert self.choices is not None\n return value.strip() in self.choices\n\n def process_response(self, value: str) -> PromptType:\n \"\"\"Process response from user, convert to prompt type.\n\n Args:\n value (str): String typed by user.\n\n Raises:\n InvalidResponse: If ``value`` is invalid.\n\n Returns:\n PromptType: The value to be returned from ask method.\n \"\"\"\n value = value.strip()\n try:\n return_value: PromptType = self.response_type(value)\n except ValueError:\n raise InvalidResponse(self.validate_error_message)\n\n if self.choices is not None and not self.check_choice(value):\n raise InvalidResponse(self.illegal_choice_message)\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, * |
| 3 | run_command | {
"command": "sed -n '1,200p' rich/prompt.py"
} | {"status": "ok", "stdout": "from typing import Any, Generic, List, Optional, TextIO, TypeVar, Union, overload\n\nfrom . import get_console\nfrom .console importfull{"status": "ok", "stdout": "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 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 prompt: TextType = \"\",\n *,\n console: Optional[Console] = None,\n password: bool = False,\n choices: Opt |
| 4 | run_command | {
"command": "python3 - <<'PY'\nfrom rich.prompt import Prompt\nfrom unittest.mock import patch\n\ndef test_case_sensitive():\n with patch('builtins.input', return_value='paul'):\n # By default it should be case sensitive, so 'paul' should not be in ['Paul']\n # However, we need to see if it loops or raises. Prompt.ask loops.\n # Let's use a more controlled test.\n pass\n\n# I will create a separate test file.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 5 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/prompt.py\"); s = p.read_text()\n\n# Update PromptBase.__init__\nold_init = \"\"\" def __init__(\n self,\n prompt: TextType = \"\",\n *,\n console: Optional[Console] = None,\n password: bool = False,\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n ) -> None:\n self.console = console or get_console()\n self.prompt = (\n Text.from_markup(prompt, style=\"prompt\")\n if isinstance(prompt, str)\n else prompt\n )\n self.password = password\n if choices is not None:\n self.choices = choices\n self.show_default = show_default\n self.show_choices = show_choices\"\"\"\n\nnew_init = \"\"\" def __init__(\n self,\n prompt: TextType = \"\",\n *,\n console: Optional[Console] = None,\n password: bool = False,\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n case_sensitive: bool = True,\n ) -> None:\n self.console = console or get_console()\n self.prompt = (\n Text.from_markup(prompt, style=\"prompt\")\n if isinstance(prompt, str)\n else prompt\n )\n self.password = password\n if choices is not None:\n self.choices = choices\n self.show_default = show_default\n self.show_choices = show_choices\n self.case_sensitive = case_sensitive\"\"\"\n\ns = s.replace(old_init, new_init)\n\n# Update PromptBase.ask overloads and implementation\n# First overload\nold_ask1 = \"\"\" @classmethod\n @overload\n def ask(\n cls,\n prompt: TextType = \"\",\n *,\n console: Optional[Console] = None,\n password: bool = False,\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n default: DefaultType,\n stream: Optional[TextIO] = None,\n ) -> Union[DefaultType, PromptType]:\n ...\"\"\"\n\nnew_ask1 = \"\"\" @classmethod\n @overload\n def ask(\n cls,\n prompt: TextType = \"\",\n *,\n console: Optional[Console] = None,\n password: bool = False,\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n case_sensitive: bool = True,\n default: DefaultType,\n stream: Optional[TextIO] = None,\n ) -> Union[DefaultType, PromptType]:\n ...\"\"\"\n\ns = s.replace(old_ask1, new_ask1)\n\n# Second overload\nold_ask2 = \"\"\" @classmethod\n @overload\n def ask(\n cls,\n prompt: TextType = \"\",\n *,\n console: Optional[Console] = None,\n password: bool = False,\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n stream: Optional[TextIO] = None,\n ) -> PromptType:\n ...\"\"\"\n\nnew_ask2 = \"\"\" @classmethod\n @overload\n def ask(\n cls,\n prompt: TextType = \"\",\n *,\n console: Optional[Console] = None,\n password: bool = False,\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n case_sensitive: bool = True,\n stream: Optional[TextIO] = None,\n ) -> PromptType:\n ...\"\"\"\n\ns = s.replace(old_ask2, new_ask2)\n\n# Actual ask implementation\nold_ask_impl = \"\"\" @classmethod\n def ask(\n cls,\n prompt: TextType = \"\",\n *,\n console: Optional[Console] = None,\n password: bool = False,\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 6 | 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} |
| 7 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom rich.prompt import Prompt\nfrom unittest.mock import patch\n\ndef test_case_sensitive_true():\n # Case sensitive: 'paul' should NOT be accepted if choices are ['Paul']\n # Since Prompt.ask loops, we can't easily use it without mocking input multiple times.\n # Instead we can instantiate Prompt and call check_choice.\n prompt = Prompt(\"Enter name\", choices=[\"Paul\"])\n prompt.case_sensitive = True\n assert prompt.check_choice(\"Paul\") is True\n assert prompt.check_choice(\"paul\") is False\n\ndef test_case_sensitive_false():\n # Case insensitive: 'paul' SHOULD be accepted if choices are ['Paul']\n prompt = Prompt(\"Enter name\", choices=[\"Paul\"])\n prompt.case_sensitive = False\n assert prompt.check_choice(\"Paul\") is True\n assert prompt.check_choice(\"paul\") is True\n assert prompt.check_choice(\"PAUL\") is True\n assert prompt.check_choice(\"Jessica\") is False\n\nprint(\"Tests passed!\")\nPY\n"
} | {"status": "ok", "stdout": "Tests passed!\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Tests passed!\n", "stderr": "", "exit_code": 0} |
| 8 | run_command | {
"command": "git diff rich/prompt.py"
} | {"status": "ok", "stdout": "diff --git a/rich/prompt.py b/rich/prompt.py\nindex 064c959b..309ee6d0 100644\n--- a/rich/prompt.py\n+++ b/rich/prompt.py\n@@ -59,6 full{"status": "ok", "stdout": "diff --git a/rich/prompt.py b/rich/prompt.py\nindex 064c959b..309ee6d0 100644\n--- a/rich/prompt.py\n+++ b/rich/prompt.py\n@@ -59,6 +59,7 @@ class PromptBase(Generic[PromptType]):\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n+ case_sensitive: bool = True,\n ) -> None:\n self.console = console or get_console()\n self.prompt = (\n@@ -71,6 +72,7 @@ class PromptBase(Generic[PromptType]):\n self.choices = choices\n self.show_default = show_default\n self.show_choices = show_choices\n+ self.case_sensitive = case_sensitive\n \n @classmethod\n @overload\n@@ -83,6 +85,7 @@ class PromptBase(Generic[PromptType]):\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n+ case_sensitive: bool = True,\n default: DefaultType,\n stream: Optional[TextIO] = None,\n ) -> Union[DefaultType, PromptType]:\n@@ -99,6 +102,7 @@ class PromptBase(Generic[PromptType]):\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n+ case_sensitive: bool = True,\n stream: Optional[TextIO] = None,\n ) -> PromptType:\n ...\n@@ -113,6 +117,7 @@ class PromptBase(Generic[PromptType]):\n choices: Optional[List[str]] = None,\n show_default: bool = True,\n show_choices: bool = True,\n+ case_sensitive: bool = True,\n default: Any = ...,\n stream: Optional[TextIO] = None,\n ) -> Any:\n@@ -128,6 +133,7 @@ class PromptBase(Generic[PromptType]):\n choices (List[str], optional): A list of valid choices. Defaults to None.\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+ |
| 9 | submit_patch | {} | {"status": "ok", "patch_size": 2844, "files_changed": 1}full{"status": "ok", "patch_size": 2844, "files_changed": 1} |
diff --git a/rich/prompt.py b/rich/prompt.py
index 064c959b..309ee6d0 100644
--- a/rich/prompt.py
+++ b/rich/prompt.py
@@ -59,6 +59,7 @@ class PromptBase(Generic[PromptType]):
choices: Optional[List[str]] = None,
show_default: bool = True,
show_choices: bool = True,
+ case_sensitive: bool = True,
) -> None:
self.console = console or get_console()
self.prompt = (
@@ -71,6 +72,7 @@ class PromptBase(Generic[PromptType]):
self.choices = choices
self.show_default = show_default
self.show_choices = show_choices
+ self.case_sensitive = case_sensitive
@classmethod
@overload
@@ -83,6 +85,7 @@ class PromptBase(Generic[PromptType]):
choices: Optional[List[str]] = None,
show_default: bool = True,
show_choices: bool = True,
+ case_sensitive: bool = True,
default: DefaultType,
stream: Optional[TextIO] = None,
) -> Union[DefaultType, PromptType]:
@@ -99,6 +102,7 @@ class PromptBase(Generic[PromptType]):
choices: Optional[List[str]] = None,
show_default: bool = True,
show_choices: bool = True,
+ case_sensitive: bool = True,
stream: Optional[TextIO] = None,
) -> PromptType:
...
@@ -113,6 +117,7 @@ class PromptBase(Generic[PromptType]):
choices: Optional[List[str]] = None,
show_default: bool = True,
show_choices: bool = True,
+ case_sensitive: bool = True,
default: Any = ...,
stream: Optional[TextIO] = None,
) -> Any:
@@ -128,6 +133,7 @@ class PromptBase(Generic[PromptType]):
choices (List[str], optional): A list of valid choices. Defaults to None.
show_default (bool, optional): Show default in prompt. Defaults to True.
show_choices (bool, optional): Show choices in prompt. Defaults to True.
+ case_sensitive (bool, optional): Case sensitive choices. Defaults to True.
stream (TextIO, optional): Optional text file open for reading to get input. Defaults to None.
"""
_prompt = cls(
@@ -137,6 +143,7 @@ class PromptBase(Generic[PromptType]):
choices=choices,
show_default=show_default,
show_choices=show_choices,
+ case_sensitive=case_sensitive,
)
return _prompt(default=default, stream=stream)
@@ -212,6 +219,8 @@ class PromptBase(Generic[PromptType]):
bool: True if choice was valid, otherwise False.
"""
assert self.choices is not None
+ if not self.case_sensitive:
+ return any(value.strip().lower() == choice.lower() for choice in self.choices)
return value.strip() in self.choices
def process_response(self, value: str) -> PromptType:
.F
=================================== FAILURES ===================================
_______________________ test_prompt_str_case_insensitive _______________________
def test_prompt_str_case_insensitive():
INPUT = "egg\nFoO"
console = Console(file=io.StringIO())
name = Prompt.ask(
"what is your name",
console=console,
choices=["foo", "bar"],
default="baz",
case_sensitive=False,
stream=io.StringIO(INPUT),
)
> assert name == "foo"
E AssertionError: assert 'FoO' == 'foo'
E
E - foo
E + FoO
tests/test_prompt.py:35: AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 1 passed in 0.12s