Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/validation.py: 49%
67 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1"""
2Input validation for a `Buffer`.
3(Validators will be called before accepting input.)
4"""
5from __future__ import annotations
7from abc import ABCMeta, abstractmethod
8from typing import Callable
10from prompt_toolkit.eventloop import run_in_executor_with_context
12from .document import Document
13from .filters import FilterOrBool, to_filter
15__all__ = [
16 "ConditionalValidator",
17 "ValidationError",
18 "Validator",
19 "ThreadedValidator",
20 "DummyValidator",
21 "DynamicValidator",
22]
25class ValidationError(Exception):
26 """
27 Error raised by :meth:`.Validator.validate`.
29 :param cursor_position: The cursor position where the error occurred.
30 :param message: Text.
31 """
33 def __init__(self, cursor_position: int = 0, message: str = "") -> None:
34 super().__init__(message)
35 self.cursor_position = cursor_position
36 self.message = message
38 def __repr__(self) -> str:
39 return "{}(cursor_position={!r}, message={!r})".format(
40 self.__class__.__name__,
41 self.cursor_position,
42 self.message,
43 )
46class Validator(metaclass=ABCMeta):
47 """
48 Abstract base class for an input validator.
50 A validator is typically created in one of the following two ways:
52 - Either by overriding this class and implementing the `validate` method.
53 - Or by passing a callable to `Validator.from_callable`.
55 If the validation takes some time and needs to happen in a background
56 thread, this can be wrapped in a :class:`.ThreadedValidator`.
57 """
59 @abstractmethod
60 def validate(self, document: Document) -> None:
61 """
62 Validate the input.
63 If invalid, this should raise a :class:`.ValidationError`.
65 :param document: :class:`~prompt_toolkit.document.Document` instance.
66 """
67 pass
69 async def validate_async(self, document: Document) -> None:
70 """
71 Return a `Future` which is set when the validation is ready.
72 This function can be overloaded in order to provide an asynchronous
73 implementation.
74 """
75 try:
76 self.validate(document)
77 except ValidationError:
78 raise
80 @classmethod
81 def from_callable(
82 cls,
83 validate_func: Callable[[str], bool],
84 error_message: str = "Invalid input",
85 move_cursor_to_end: bool = False,
86 ) -> Validator:
87 """
88 Create a validator from a simple validate callable. E.g.:
90 .. code:: python
92 def is_valid(text):
93 return text in ['hello', 'world']
94 Validator.from_callable(is_valid, error_message='Invalid input')
96 :param validate_func: Callable that takes the input string, and returns
97 `True` if the input is valid input.
98 :param error_message: Message to be displayed if the input is invalid.
99 :param move_cursor_to_end: Move the cursor to the end of the input, if
100 the input is invalid.
101 """
102 return _ValidatorFromCallable(validate_func, error_message, move_cursor_to_end)
105class _ValidatorFromCallable(Validator):
106 """
107 Validate input from a simple callable.
108 """
110 def __init__(
111 self, func: Callable[[str], bool], error_message: str, move_cursor_to_end: bool
112 ) -> None:
113 self.func = func
114 self.error_message = error_message
115 self.move_cursor_to_end = move_cursor_to_end
117 def __repr__(self) -> str:
118 return f"Validator.from_callable({self.func!r})"
120 def validate(self, document: Document) -> None:
121 if not self.func(document.text):
122 if self.move_cursor_to_end:
123 index = len(document.text)
124 else:
125 index = 0
127 raise ValidationError(cursor_position=index, message=self.error_message)
130class ThreadedValidator(Validator):
131 """
132 Wrapper that runs input validation in a thread.
133 (Use this to prevent the user interface from becoming unresponsive if the
134 input validation takes too much time.)
135 """
137 def __init__(self, validator: Validator) -> None:
138 self.validator = validator
140 def validate(self, document: Document) -> None:
141 self.validator.validate(document)
143 async def validate_async(self, document: Document) -> None:
144 """
145 Run the `validate` function in a thread.
146 """
148 def run_validation_thread() -> None:
149 return self.validate(document)
151 await run_in_executor_with_context(run_validation_thread)
154class DummyValidator(Validator):
155 """
156 Validator class that accepts any input.
157 """
159 def validate(self, document: Document) -> None:
160 pass # Don't raise any exception.
163class ConditionalValidator(Validator):
164 """
165 Validator that can be switched on/off according to
166 a filter. (This wraps around another validator.)
167 """
169 def __init__(self, validator: Validator, filter: FilterOrBool) -> None:
170 self.validator = validator
171 self.filter = to_filter(filter)
173 def validate(self, document: Document) -> None:
174 # Call the validator only if the filter is active.
175 if self.filter():
176 self.validator.validate(document)
179class DynamicValidator(Validator):
180 """
181 Validator class that can dynamically returns any Validator.
183 :param get_validator: Callable that returns a :class:`.Validator` instance.
184 """
186 def __init__(self, get_validator: Callable[[], Validator | None]) -> None:
187 self.get_validator = get_validator
189 def validate(self, document: Document) -> None:
190 validator = self.get_validator() or DummyValidator()
191 validator.validate(document)
193 async def validate_async(self, document: Document) -> None:
194 validator = self.get_validator() or DummyValidator()
195 await validator.validate_async(document)