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

1""" 

2Input validation for a `Buffer`. 

3(Validators will be called before accepting input.) 

4""" 

5from __future__ import annotations 

6 

7from abc import ABCMeta, abstractmethod 

8from typing import Callable 

9 

10from prompt_toolkit.eventloop import run_in_executor_with_context 

11 

12from .document import Document 

13from .filters import FilterOrBool, to_filter 

14 

15__all__ = [ 

16 "ConditionalValidator", 

17 "ValidationError", 

18 "Validator", 

19 "ThreadedValidator", 

20 "DummyValidator", 

21 "DynamicValidator", 

22] 

23 

24 

25class ValidationError(Exception): 

26 """ 

27 Error raised by :meth:`.Validator.validate`. 

28 

29 :param cursor_position: The cursor position where the error occurred. 

30 :param message: Text. 

31 """ 

32 

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 

37 

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 ) 

44 

45 

46class Validator(metaclass=ABCMeta): 

47 """ 

48 Abstract base class for an input validator. 

49 

50 A validator is typically created in one of the following two ways: 

51 

52 - Either by overriding this class and implementing the `validate` method. 

53 - Or by passing a callable to `Validator.from_callable`. 

54 

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

58 

59 @abstractmethod 

60 def validate(self, document: Document) -> None: 

61 """ 

62 Validate the input. 

63 If invalid, this should raise a :class:`.ValidationError`. 

64 

65 :param document: :class:`~prompt_toolkit.document.Document` instance. 

66 """ 

67 pass 

68 

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 

79 

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

89 

90 .. code:: python 

91 

92 def is_valid(text): 

93 return text in ['hello', 'world'] 

94 Validator.from_callable(is_valid, error_message='Invalid input') 

95 

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) 

103 

104 

105class _ValidatorFromCallable(Validator): 

106 """ 

107 Validate input from a simple callable. 

108 """ 

109 

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 

116 

117 def __repr__(self) -> str: 

118 return f"Validator.from_callable({self.func!r})" 

119 

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 

126 

127 raise ValidationError(cursor_position=index, message=self.error_message) 

128 

129 

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

136 

137 def __init__(self, validator: Validator) -> None: 

138 self.validator = validator 

139 

140 def validate(self, document: Document) -> None: 

141 self.validator.validate(document) 

142 

143 async def validate_async(self, document: Document) -> None: 

144 """ 

145 Run the `validate` function in a thread. 

146 """ 

147 

148 def run_validation_thread() -> None: 

149 return self.validate(document) 

150 

151 await run_in_executor_with_context(run_validation_thread) 

152 

153 

154class DummyValidator(Validator): 

155 """ 

156 Validator class that accepts any input. 

157 """ 

158 

159 def validate(self, document: Document) -> None: 

160 pass # Don't raise any exception. 

161 

162 

163class ConditionalValidator(Validator): 

164 """ 

165 Validator that can be switched on/off according to 

166 a filter. (This wraps around another validator.) 

167 """ 

168 

169 def __init__(self, validator: Validator, filter: FilterOrBool) -> None: 

170 self.validator = validator 

171 self.filter = to_filter(filter) 

172 

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) 

177 

178 

179class DynamicValidator(Validator): 

180 """ 

181 Validator class that can dynamically returns any Validator. 

182 

183 :param get_validator: Callable that returns a :class:`.Validator` instance. 

184 """ 

185 

186 def __init__(self, get_validator: Callable[[], Validator | None]) -> None: 

187 self.get_validator = get_validator 

188 

189 def validate(self, document: Document) -> None: 

190 validator = self.get_validator() or DummyValidator() 

191 validator.validate(document) 

192 

193 async def validate_async(self, document: Document) -> None: 

194 validator = self.get_validator() or DummyValidator() 

195 await validator.validate_async(document)