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 

68 

69 def _value(self): 

70 if self.raw_data: 

71 return self.raw_data[0] 

72 if self.data is not None: 

73 return str(self.data) 

74 return "" 

75 

76 def process_data(self, value): 

77 if value is None or value is unset_value: 

78 self.data = None 

79 return 

80 

81 try: 

82 self.data = int(value) 

83 except (ValueError, TypeError) as exc: 

84 self.data = None 

85 raise ValueError( 

86 self.invalid_value_message or self.gettext("Not a valid integer value.") 

87 ) 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, TypeError) as exc: 

96 self.data = None 

97 raise ValueError( 

98 self.invalid_value_message or self.gettext("Not a valid integer value.") 

99 ) from exc 

100 

101 

102class DecimalField(LocaleAwareNumberField): 

103 """ 

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

105 

106 :param places: 

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

108 If unset, use 2 decimal places. 

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

110 :param rounding: 

111 How to round the value during quantize, for example 

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

113 current thread's context. 

114 :param use_locale: 

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

116 formatting requires the 'babel' package. 

117 :param number_format: 

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

119 format for the locale. 

120 """ 

121 

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

123 

124 def __init__( 

125 self, 

126 label=None, 

127 validators=None, 

128 places=unset_value, 

129 rounding=None, 

130 invalid_value_message=None, 

131 **kwargs, 

132 ): 

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

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

135 raise TypeError( 

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

137 ) 

138 

139 if places is unset_value: 

140 places = 2 

141 self.places = places 

142 self.rounding = rounding 

143 self.invalid_value_message = invalid_value_message 

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, TypeError) as exc: 

181 self.data = None 

182 raise ValueError( 

183 self.invalid_value_message or self.gettext("Not a valid decimal value.") 

184 ) from exc 

185 

186 

187class FloatField(Field): 

188 """ 

189 A field that stores floating-point values. 

190 

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

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

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

194 """ 

195 

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

197 

198 def __init__( 

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

200 ): 

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

202 self.invalid_value_message = invalid_value_message 

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, TypeError) as exc: 

218 self.data = None 

219 raise ValueError( 

220 self.invalid_value_message or self.gettext("Not a valid float value.") 

221 ) from exc 

222 

223 

224class IntegerRangeField(IntegerField): 

225 """ 

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

227 """ 

228 

229 widget = widgets.RangeInput() 

230 

231 

232class DecimalRangeField(DecimalField): 

233 """ 

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

235 """ 

236 

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