Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/filters/base.py: 80%

115 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1from __future__ import annotations 

2 

3from abc import ABCMeta, abstractmethod 

4from typing import Callable, Iterable, Union 

5 

6__all__ = ["Filter", "Never", "Always", "Condition", "FilterOrBool"] 

7 

8 

9class Filter(metaclass=ABCMeta): 

10 """ 

11 Base class for any filter to activate/deactivate a feature, depending on a 

12 condition. 

13 

14 The return value of ``__call__`` will tell if the feature should be active. 

15 """ 

16 

17 def __init__(self) -> None: 

18 self._and_cache: dict[Filter, Filter] = {} 

19 self._or_cache: dict[Filter, Filter] = {} 

20 self._invert_result: Filter | None = None 

21 

22 @abstractmethod 

23 def __call__(self) -> bool: 

24 """ 

25 The actual call to evaluate the filter. 

26 """ 

27 return True 

28 

29 def __and__(self, other: Filter) -> Filter: 

30 """ 

31 Chaining of filters using the & operator. 

32 """ 

33 assert isinstance(other, Filter), "Expecting filter, got %r" % other 

34 

35 if isinstance(other, Always): 

36 return self 

37 if isinstance(other, Never): 

38 return other 

39 

40 if other in self._and_cache: 

41 return self._and_cache[other] 

42 

43 result = _AndList.create([self, other]) 

44 self._and_cache[other] = result 

45 return result 

46 

47 def __or__(self, other: Filter) -> Filter: 

48 """ 

49 Chaining of filters using the | operator. 

50 """ 

51 assert isinstance(other, Filter), "Expecting filter, got %r" % other 

52 

53 if isinstance(other, Always): 

54 return other 

55 if isinstance(other, Never): 

56 return self 

57 

58 if other in self._or_cache: 

59 return self._or_cache[other] 

60 

61 result = _OrList.create([self, other]) 

62 self._or_cache[other] = result 

63 return result 

64 

65 def __invert__(self) -> Filter: 

66 """ 

67 Inverting of filters using the ~ operator. 

68 """ 

69 if self._invert_result is None: 

70 self._invert_result = _Invert(self) 

71 

72 return self._invert_result 

73 

74 def __bool__(self) -> None: 

75 """ 

76 By purpose, we don't allow bool(...) operations directly on a filter, 

77 because the meaning is ambiguous. 

78 

79 Executing a filter has to be done always by calling it. Providing 

80 defaults for `None` values should be done through an `is None` check 

81 instead of for instance ``filter1 or Always()``. 

82 """ 

83 raise ValueError( 

84 "The truth value of a Filter is ambiguous. " 

85 "Instead, call it as a function." 

86 ) 

87 

88 

89def _remove_duplicates(filters: list[Filter]) -> list[Filter]: 

90 result = [] 

91 for f in filters: 

92 if f not in result: 

93 result.append(f) 

94 return result 

95 

96 

97class _AndList(Filter): 

98 """ 

99 Result of &-operation between several filters. 

100 """ 

101 

102 def __init__(self, filters: list[Filter]) -> None: 

103 super().__init__() 

104 self.filters = filters 

105 

106 @classmethod 

107 def create(cls, filters: Iterable[Filter]) -> Filter: 

108 """ 

109 Create a new filter by applying an `&` operator between them. 

110 

111 If there's only one unique filter in the given iterable, it will return 

112 that one filter instead of an `_AndList`. 

113 """ 

114 filters_2: list[Filter] = [] 

115 

116 for f in filters: 

117 if isinstance(f, _AndList): # Turn nested _AndLists into one. 

118 filters_2.extend(f.filters) 

119 else: 

120 filters_2.append(f) 

121 

122 # Remove duplicates. This could speed up execution, and doesn't make a 

123 # difference for the evaluation. 

124 filters = _remove_duplicates(filters_2) 

125 

126 # If only one filter is left, return that without wrapping into an 

127 # `_AndList`. 

128 if len(filters) == 1: 

129 return filters[0] 

130 

131 return cls(filters) 

132 

133 def __call__(self) -> bool: 

134 return all(f() for f in self.filters) 

135 

136 def __repr__(self) -> str: 

137 return "&".join(repr(f) for f in self.filters) 

138 

139 

140class _OrList(Filter): 

141 """ 

142 Result of |-operation between several filters. 

143 """ 

144 

145 def __init__(self, filters: list[Filter]) -> None: 

146 super().__init__() 

147 self.filters = filters 

148 

149 @classmethod 

150 def create(cls, filters: Iterable[Filter]) -> Filter: 

151 """ 

152 Create a new filter by applying an `|` operator between them. 

153 

154 If there's only one unique filter in the given iterable, it will return 

155 that one filter instead of an `_OrList`. 

156 """ 

157 filters_2: list[Filter] = [] 

158 

159 for f in filters: 

160 if isinstance(f, _OrList): # Turn nested _AndLists into one. 

161 filters_2.extend(f.filters) 

162 else: 

163 filters_2.append(f) 

164 

165 # Remove duplicates. This could speed up execution, and doesn't make a 

166 # difference for the evaluation. 

167 filters = _remove_duplicates(filters_2) 

168 

169 # If only one filter is left, return that without wrapping into an 

170 # `_AndList`. 

171 if len(filters) == 1: 

172 return filters[0] 

173 

174 return cls(filters) 

175 

176 def __call__(self) -> bool: 

177 return any(f() for f in self.filters) 

178 

179 def __repr__(self) -> str: 

180 return "|".join(repr(f) for f in self.filters) 

181 

182 

183class _Invert(Filter): 

184 """ 

185 Negation of another filter. 

186 """ 

187 

188 def __init__(self, filter: Filter) -> None: 

189 super().__init__() 

190 self.filter = filter 

191 

192 def __call__(self) -> bool: 

193 return not self.filter() 

194 

195 def __repr__(self) -> str: 

196 return "~%r" % self.filter 

197 

198 

199class Always(Filter): 

200 """ 

201 Always enable feature. 

202 """ 

203 

204 def __call__(self) -> bool: 

205 return True 

206 

207 def __or__(self, other: Filter) -> Filter: 

208 return self 

209 

210 def __invert__(self) -> Never: 

211 return Never() 

212 

213 

214class Never(Filter): 

215 """ 

216 Never enable feature. 

217 """ 

218 

219 def __call__(self) -> bool: 

220 return False 

221 

222 def __and__(self, other: Filter) -> Filter: 

223 return self 

224 

225 def __invert__(self) -> Always: 

226 return Always() 

227 

228 

229class Condition(Filter): 

230 """ 

231 Turn any callable into a Filter. The callable is supposed to not take any 

232 arguments. 

233 

234 This can be used as a decorator:: 

235 

236 @Condition 

237 def feature_is_active(): # `feature_is_active` becomes a Filter. 

238 return True 

239 

240 :param func: Callable which takes no inputs and returns a boolean. 

241 """ 

242 

243 def __init__(self, func: Callable[[], bool]) -> None: 

244 super().__init__() 

245 self.func = func 

246 

247 def __call__(self) -> bool: 

248 return self.func() 

249 

250 def __repr__(self) -> str: 

251 return "Condition(%r)" % self.func 

252 

253 

254# Often used as type annotation. 

255FilterOrBool = Union[Filter, bool]