Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
prompt.py401 linesDownload Raw Back to rich
1from typing import Any, Generic, List, Optional, TextIO, TypeVar, Union, overload2 3from . import get_console4from .console import Console5from .text import Text, TextType6 7PromptType = TypeVar("PromptType")8DefaultType = TypeVar("DefaultType")9 10 11class PromptError(Exception):12    """Exception base class for prompt related errors."""13 14 15class InvalidResponse(PromptError):16    """Exception to indicate a response was invalid. Raise this within process_response() to indicate an error17    and provide an error message.18 19    Args:20        message (Union[str, Text]): Error message.21    """22 23    def __init__(self, message: TextType) -> None:24        self.message = message25 26    def __rich__(self) -> TextType:27        return self.message28 29 30class PromptBase(Generic[PromptType]):31    """Ask the user for input until a valid response is received. This is the base class, see one of32    the concrete classes for examples.33 34    Args:35        prompt (TextType, optional): Prompt text. Defaults to "".36        console (Console, optional): A Console instance or None to use global console. Defaults to None.37        password (bool, optional): Enable password input. Defaults to False.38        choices (List[str], optional): A list of valid choices. Defaults to None.39        case_sensitive (bool, optional): Matching of choices should be case-sensitive. Defaults to True.40        show_default (bool, optional): Show default in prompt. Defaults to True.41        show_choices (bool, optional): Show choices in prompt. Defaults to True.42    """43 44    response_type: type = str45 46    validate_error_message = "[prompt.invalid]Please enter a valid value"47    illegal_choice_message = (48        "[prompt.invalid.choice]Please select one of the available options"49    )50    prompt_suffix = ": "51 52    choices: Optional[List[str]] = None53 54    def __init__(55        self,56        prompt: TextType = "",57        *,58        console: Optional[Console] = None,59        password: bool = False,60        choices: Optional[List[str]] = None,61        case_sensitive: bool = True,62        show_default: bool = True,63        show_choices: bool = True,64    ) -> None:65        self.console = console or get_console()66        self.prompt = (67            Text.from_markup(prompt, style="prompt")68            if isinstance(prompt, str)69            else prompt70        )71        self.password = password72        if choices is not None:73            self.choices = choices74        self.case_sensitive = case_sensitive75        self.show_default = show_default76        self.show_choices = show_choices77 78    @classmethod79    @overload80    def ask(81        cls,82        prompt: TextType = "",83        *,84        console: Optional[Console] = None,85        password: bool = False,86        choices: Optional[List[str]] = None,87        case_sensitive: bool = True,88        show_default: bool = True,89        show_choices: bool = True,90        default: DefaultType,91        stream: Optional[TextIO] = None,92    ) -> Union[DefaultType, PromptType]:93        ...94 95    @classmethod96    @overload97    def ask(98        cls,99        prompt: TextType = "",100        *,101        console: Optional[Console] = None,102        password: bool = False,103        choices: Optional[List[str]] = None,104        case_sensitive: bool = True,105        show_default: bool = True,106        show_choices: bool = True,107        stream: Optional[TextIO] = None,108    ) -> PromptType:109        ...110 111    @classmethod112    def ask(113        cls,114        prompt: TextType = "",115        *,116        console: Optional[Console] = None,117        password: bool = False,118        choices: Optional[List[str]] = None,119        case_sensitive: bool = True,120        show_default: bool = True,121        show_choices: bool = True,122        default: Any = ...,123        stream: Optional[TextIO] = None,124    ) -> Any:125        """Shortcut to construct and run a prompt loop and return the result.126 127        Example:128            >>> filename = Prompt.ask("Enter a filename")129 130        Args:131            prompt (TextType, optional): Prompt text. Defaults to "".132            console (Console, optional): A Console instance or None to use global console. Defaults to None.133            password (bool, optional): Enable password input. Defaults to False.134            choices (List[str], optional): A list of valid choices. Defaults to None.135            case_sensitive (bool, optional): Matching of choices should be case-sensitive. Defaults to True.136            show_default (bool, optional): Show default in prompt. Defaults to True.137            show_choices (bool, optional): Show choices in prompt. Defaults to True.138            stream (TextIO, optional): Optional text file open for reading to get input. Defaults to None.139        """140        _prompt = cls(141            prompt,142            console=console,143            password=password,144            choices=choices,145            case_sensitive=case_sensitive,146            show_default=show_default,147            show_choices=show_choices,148        )149        return _prompt(default=default, stream=stream)150 151    def render_default(self, default: DefaultType) -> Text:152        """Turn the supplied default in to a Text instance.153 154        Args:155            default (DefaultType): Default value.156 157        Returns:158            Text: Text containing rendering of default value.159        """160        return Text(f"({default})", "prompt.default")161 162    def make_prompt(self, default: DefaultType) -> Text:163        """Make prompt text.164 165        Args:166            default (DefaultType): Default value.167 168        Returns:169            Text: Text to display in prompt.170        """171        prompt = self.prompt.copy()172        prompt.end = ""173 174        if self.show_choices and self.choices:175            _choices = "/".join(self.choices)176            choices = f"[{_choices}]"177            prompt.append(" ")178            prompt.append(choices, "prompt.choices")179 180        if (181            default != ...182            and self.show_default183            and isinstance(default, (str, self.response_type))184        ):185            prompt.append(" ")186            _default = self.render_default(default)187            prompt.append(_default)188 189        prompt.append(self.prompt_suffix)190 191        return prompt192 193    @classmethod194    def get_input(195        cls,196        console: Console,197        prompt: TextType,198        password: bool,199        stream: Optional[TextIO] = None,200    ) -> str:201        """Get input from user.202 203        Args:204            console (Console): Console instance.205            prompt (TextType): Prompt text.206            password (bool): Enable password entry.207 208        Returns:209            str: String from user.210        """211        return console.input(prompt, password=password, stream=stream)212 213    def check_choice(self, value: str) -> bool:214        """Check value is in the list of valid choices.215 216        Args:217            value (str): Value entered by user.218 219        Returns:220            bool: True if choice was valid, otherwise False.221        """222        assert self.choices is not None223        if self.case_sensitive:224            return value.strip() in self.choices225        return value.strip().lower() in [choice.lower() for choice in self.choices]226 227    def process_response(self, value: str) -> PromptType:228        """Process response from user, convert to prompt type.229 230        Args:231            value (str): String typed by user.232 233        Raises:234            InvalidResponse: If ``value`` is invalid.235 236        Returns:237            PromptType: The value to be returned from ask method.238        """239        value = value.strip()240        try:241            return_value: PromptType = self.response_type(value)242        except ValueError:243            raise InvalidResponse(self.validate_error_message)244 245        if self.choices is not None:246            if not self.check_choice(value):247                raise InvalidResponse(self.illegal_choice_message)248 249            if not self.case_sensitive:250                # return the original choice, not the lower case version251                return_value = self.response_type(252                    self.choices[253                        [choice.lower() for choice in self.choices].index(value.lower())254                    ]255                )256        return return_value257 258    def on_validate_error(self, value: str, error: InvalidResponse) -> None:259        """Called to handle validation error.260 261        Args:262            value (str): String entered by user.263            error (InvalidResponse): Exception instance the initiated the error.264        """265        self.console.print(error, markup=True)266 267    def pre_prompt(self) -> None:268        """Hook to display something before the prompt."""269 270    @overload271    def __call__(self, *, stream: Optional[TextIO] = None) -> PromptType:272        ...273 274    @overload275    def __call__(276        self, *, default: DefaultType, stream: Optional[TextIO] = None277    ) -> Union[PromptType, DefaultType]:278        ...279 280    def __call__(self, *, default: Any = ..., stream: Optional[TextIO] = None) -> Any:281        """Run the prompt loop.282 283        Args:284            default (Any, optional): Optional default value.285 286        Returns:287            PromptType: Processed value.288        """289        while True:290            self.pre_prompt()291            prompt = self.make_prompt(default)292            value = self.get_input(self.console, prompt, self.password, stream=stream)293            if value == "" and default != ...:294                return default295            try:296                return_value = self.process_response(value)297            except InvalidResponse as error:298                self.on_validate_error(value, error)299                continue300            else:301                return return_value302 303 304class Prompt(PromptBase[str]):305    """A prompt that returns a str.306 307    Example:308        >>> name = Prompt.ask("Enter your name")309 310 311    """312 313    response_type = str314 315 316class IntPrompt(PromptBase[int]):317    """A prompt that returns an integer.318 319    Example:320        >>> burrito_count = IntPrompt.ask("How many burritos do you want to order")321 322    """323 324    response_type = int325    validate_error_message = "[prompt.invalid]Please enter a valid integer number"326 327 328class FloatPrompt(PromptBase[float]):329    """A prompt that returns a float.330 331    Example:332        >>> temperature = FloatPrompt.ask("Enter desired temperature")333 334    """335 336    response_type = float337    validate_error_message = "[prompt.invalid]Please enter a number"338 339 340class Confirm(PromptBase[bool]):341    """A yes / no confirmation prompt.342 343    Example:344        >>> if Confirm.ask("Continue"):345                run_job()346 347    """348 349    response_type = bool350    validate_error_message = "[prompt.invalid]Please enter Y or N"351    choices: List[str] = ["y", "n"]352 353    def render_default(self, default: DefaultType) -> Text:354        """Render the default as (y) or (n) rather than True/False."""355        yes, no = self.choices356        return Text(f"({yes})" if default else f"({no})", style="prompt.default")357 358    def process_response(self, value: str) -> bool:359        """Convert choices to a bool."""360        value = value.strip().lower()361        if value not in self.choices:362            raise InvalidResponse(self.validate_error_message)363        return value == self.choices[0]364 365 366if __name__ == "__main__":  # pragma: no cover367    from rich import print368 369    if Confirm.ask("Run [i]prompt[/i] tests?", default=True):370        while True:371            result = IntPrompt.ask(372                ":rocket: Enter a number between [b]1[/b] and [b]10[/b]", default=5373            )374            if result >= 1 and result <= 10:375                break376            print(":pile_of_poo: [prompt.invalid]Number must be between 1 and 10")377        print(f"number={result}")378 379        while True:380            password = Prompt.ask(381                "Please enter a password [cyan](must be at least 5 characters)",382                password=True,383            )384            if len(password) >= 5:385                break386            print("[prompt.invalid]password too short")387        print(f"password={password!r}")388 389        fruit = Prompt.ask("Enter a fruit", choices=["apple", "orange", "pear"])390        print(f"fruit={fruit!r}")391 392        doggie = Prompt.ask(393            "What's the best Dog? (Case INSENSITIVE)",394            choices=["Border Terrier", "Collie", "Labradoodle"],395            case_sensitive=False,396        )397        print(f"doggie={doggie!r}")398 399    else:400        print("[b]OK :loudly_crying_face:")401 
codekingpro/portable-devtools · Team Ai