Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wtforms/fields/numeric.py: 27%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

112 statements  

1import decimal 

2 

3from wtforms import widgets 

4from wtforms.fields.core import Field 

5from wtforms.utils import unset_value 

6 

7__all__ = ( 

8 "IntegerField", 

9 "DecimalField", 

10 "FloatField", 

11 "IntegerRangeField", 

12 "DecimalRangeField", 

13) 

14 

15 

16class LocaleAwareNumberField(Field): 

17 """ 

18 Base class for implementing locale-aware number parsing. 

19 

20 Locale-aware numbers require the 'babel' package to be present. 

21 """ 

22 

23 def __init__( 

24 self, 

25 label=None, 

26 validators=None, 

27 use_locale=False, 

28 number_format=None, 

29 **kwargs, 

30 ): 

31 super().__init__(label, validators, **kwargs) 

32 self.use_locale = use_locale 

33 if use_locale: 

34 self.number_format = number_format 

35 self.locale = kwargs["_form"].meta.locales[0] 

36 self._init_babel() 

37 

38 def _init_babel(self): 

39 try: 

40 from babel import numbers 

41 

42 self.babel_numbers = numbers 

43 except ImportError as exc: 

44 raise ImportError( 

45 "Using locale-aware decimals requires the babel library." 

46 ) from exc 

47 

48 def _parse_decimal(self, value): 

49 return self.babel_numbers.parse_decimal(value, self.locale) 

50 

51 def _format_decimal(self, value): 

52 return self.babel_numbers.format_decimal(value, self.number_format, self.locale) 

53 

54 

55class IntegerField(Field): 

56 """ 

57 A text field, except all input is coerced to an integer. Erroneous input 

58 is ignored and will not be accepted as a value. 

59 """ 

60 

61 widget = widgets.NumberInput() 

62 

63 def __init__( 

64 self, label=None, validators=None, invalid_value_message=None, **kwargs 

65 ): 

66 super().__init__(label, validators, **kwargs) 

67 self.invalid_value_message = invalid_value_message or self.gettext( 

68 "Not a valid integer value." 

69 ) 

70 

71 def _value(self): 

72 if self.raw_data: 

73 return self.raw_data[0] 

74 if self.data is not None: 

75 return str(self.data) 

76 return "" 

77 

78 def process_data(self, value): 

79 if value is None or value is unset_value: 

80 self.data = None 

81 return 

82 

83 try: 

84 self.data = int(value) 

85 except (ValueError, TypeError) as exc: 

86 self.data = None 

87 raise ValueError(self.invalid_value_message) from exc 

88 

89 def process_formdata(self, valuelist): 

90 if not valuelist: 

91 return 

92 

93 try: 

94 self.data = int(valuelist[0]) 

95 except ValueError as exc: 

96 self.data = None 

97 raise ValueError(self.invalid_value_message) from exc 

98 

99 

100class DecimalField(LocaleAwareNumberField): 

101 """ 

102 A text field which displays and coerces data of the `decimal.Decimal` type. 

103 

104 :param places: 

105 How many decimal places to quantize the value to for display on form. 

106 If unset, use 2 decimal places. 

107 If explicitely set to `None`, does not quantize value. 

108 :param rounding: 

109 How to round the value during quantize, for example 

110 `decimal.ROUND_UP`. If unset, uses the rounding value from the 

111 current thread's context. 

112 :param use_locale: 

113 If True, use locale-based number formatting. Locale-based number 

114 formatting requires the 'babel' package. 

115 :param number_format: 

116 Optional number format for locale. If omitted, use the default decimal 

117 format for the locale. 

118 """ 

119 

120 widget = widgets.NumberInput(step="any") 

121 

122 def __init__( 

123 self, 

124 label=None, 

125 validators=None, 

126 places=unset_value, 

127 rounding=None, 

128 invalid_value_message=None, 

129 **kwargs, 

130 ): 

131 super().__init__(label, validators, **kwargs) 

132 if self.use_locale and (places is not unset_value or rounding is not None): 

133 raise TypeError( 

134 "When using locale-aware numbers, 'places' and 'rounding' are ignored." 

135 ) 

136 

137 if places is unset_value: 

138 places = 2 

139 self.places = places 

140 self.rounding = rounding 

141 self.invalid_value_message = invalid_value_message or self.gettext( 

142 "Not a valid decimal value." 

143 ) 

144 

145 def _value(self): 

146 if self.raw_data: 

147 return self.raw_data[0] 

148 

149 if self.data is None: 

150 return "" 

151 

152 if self.use_locale: 

153 return str(self._format_decimal(self.data)) 

154 

155 if self.places is None: 

156 return str(self.data) 

157 

158 if not hasattr(self.data, "quantize"): 

159 # If for some reason, data is a float or int, then format 

160 # as we would for floats using string formatting. 

161 format = f"%.{self.places}f" 

162 return format % self.data 

163 

164 exp = decimal.Decimal(".1") ** self.places 

165 if self.rounding is None: 

166 quantized = self.data.quantize(exp) 

167 else: 

168 quantized = self.data.quantize(exp, rounding=self.rounding) 

169 return str(quantized) 

170 

171 def process_formdata(self, valuelist): 

172 if not valuelist: 

173 return 

174 

175 try: 

176 if self.use_locale: 

177 self.data = self._parse_decimal(valuelist[0]) 

178 else: 

179 self.data = decimal.Decimal(valuelist[0]) 

180 except (decimal.InvalidOperation, ValueError) as exc: 

181 self.data = None 

182 raise ValueError(self.invalid_value_message) from exc 

183 

184 

185class FloatField(Field): 

186 """ 

187 A field that stores floating-point values. 

188 

189 By default, this renders as an ``<input type="number">`` with 

190 ``step="any"`` to allow browser input of any floating-point value. 

191 Erroneous input is ignored and will not be accepted as a value. 

192 """ 

193 

194 widget = widgets.NumberInput(step="any") 

195 

196 def __init__( 

197 self, label=None, validators=None, invalid_value_message=None, **kwargs 

198 ): 

199 super().__init__(label, validators, **kwargs) 

200 self.invalid_value_message = invalid_value_message or self.gettext( 

201 "Not a valid float value." 

202 ) 

203 

204 def _value(self): 

205 if self.raw_data: 

206 return self.raw_data[0] 

207 if self.data is not None: 

208 return str(self.data) 

209 return "" 

210 

211 def process_formdata(self, valuelist): 

212 if not valuelist: 

213 return 

214 

215 try: 

216 self.data = float(valuelist[0]) 

217 except ValueError as exc: 

218 self.data = None 

219 raise ValueError(self.invalid_value_message) from exc 

220 

221 

222class IntegerRangeField(IntegerField): 

223 """ 

224 Represents an :mdn-input:`range`. 

225 """ 

226 

227 widget = widgets.RangeInput() 

228 

229 

230class DecimalRangeField(DecimalField): 

231 """ 

232 Represents an :mdn-input:`range`. 

233 """ 

234 

235 widget = widgets.RangeInput(step="any")