44Use of this source code is governed by an MIT-style license that can be found in the LICENSE file.
55"""
66
7+ import decimal
78import re
89from decimal import Decimal , InvalidOperation
910from typing import Any , Optional , Union
@@ -23,10 +24,18 @@ class DecimalValidator(StringValidator):
2324 Only allows finite numbers in regular decimal notation (e.g. '1.234', '-42', '.00', ...), but no other values that
2425 are accepted by `decimal.Decimal` (e.g. no 'Infinity' or 'NaN' and no scientific notation).
2526
26- Optionally a number range (minimum/maximum value as `Decimal`, integer or decimal string), minimum/maximum number of
27- decimal places and a fixed number of decimal places in the output value can be specified. A fixed number of output
28- places will result in rounding according to the current decimal context (see `decimal.getcontext()`), by default
29- this means that "1.49" will be rounded to "1" and "1.50" to "2".
27+ Optionally a number range (minimum/maximum value as `Decimal`, integer or decimal string) can be specified using the
28+ parameters `min_value` and `max_value`, as well as a minimum/maximum number of decimal places in the input using
29+ `min_places` and `max_places`.
30+
31+ You can also specify how many decimal places the output value should have using `output_places`. If this parameter
32+ is set, the output value will always have the specified amount of decimal places. If rounding is necessary, a
33+ rounding mode as defined by the `decimal` module (https://docs.python.org/3/library/decimal.html#rounding-modes) is
34+ used, which can be specified with the `rounding` parameter.
35+
36+ The rounding mode defaults to `decimal.ROUND_HALF_UP`, which basically means that digits 0 to 4 are rounded down and
37+ digits 5 to 9 are rounded up (e.g. with `output_places=1`, "1.149" would be rounded to "1.1" and "1.150" would be
38+ rounded to "1.2"). Alternatively, set `rounding=None` to use the rounding mode set by the current decimal context.
3039
3140 Examples:
3241
@@ -48,6 +57,9 @@ class DecimalValidator(StringValidator):
4857
4958 # As above, but only allow 2 or less decimal places in input (e.g. '1' -> '1.00', '1.23' -> '1.23' but '1.234' raises an exception)
5059 DecimalValidator(max_places=2, output_places=2)
60+
61+ # Two output places, but always round up (e.g. '1.001' -> '1.01')
62+ DecimalValidator(output_places=2, rounding=decimal.ROUND_UP)
5163 ```
5264
5365 Valid input: `str` in decimal notation
@@ -65,6 +77,9 @@ class DecimalValidator(StringValidator):
6577 # Quantum used in `.quantize()` to set a fixed number of decimal places (from constructor argument output_places)
6678 output_quantum : Optional [Decimal ] = None
6779
80+ # Rounding mode (constant from decimal module)
81+ rounding : Optional [str ] = None
82+
6883 # Precompiled regular expression for decimal values
6984 decimal_regex : re .Pattern = re .compile (r'[+-]?([0-9]+\.[0-9]*|\.?[0-9]+)' )
7085
@@ -75,17 +90,19 @@ def __init__(
7590 min_places : Optional [int ] = None ,
7691 max_places : Optional [int ] = None ,
7792 output_places : Optional [int ] = None ,
93+ rounding : Optional [str ] = decimal .ROUND_HALF_UP ,
7894 ):
7995 """
80- Create a DecimalValidator with optional value range, optional minimum/maximum number of decimal places and optional number
81- of decimal places in output value.
96+ Create a DecimalValidator with optional value range, optional minimum/maximum number of decimal places and
97+ optional number of decimal places in output value.
8298
8399 Parameters:
84100 min_value: Decimal, integer or string, specifies lowest allowed value (default: None, no minimum value)
85101 max_value: Decimal, integer or string, specifies highest allowed value (default: None, no maximum value)
86102 min_places: Integer, minimum number of decimal places an input value must have (default: None, no minimum places)
87103 max_places: Integer, maximum number of decimal places an input value must have (default: None, no maximum places)
88104 output_places: Integer, number of decimal places the output Decimal object shall have (default: None, output equals input)
105+ rounding: Rounding mode for numbers that need to be rounded (default: decimal.ROUND_HALF_UP)
89106 """
90107 # Restrict string length
91108 super ().__init__ (max_length = 40 )
@@ -112,6 +129,7 @@ def __init__(
112129 self .max_value = max_value
113130 self .min_places = min_places
114131 self .max_places = max_places
132+ self .rounding = rounding
115133
116134 # Set output "quantum" (the output decimal will have the same number of decimal places as this value)
117135 if output_places is not None :
@@ -151,6 +169,6 @@ def validate(self, input_data: Any, **kwargs) -> Decimal:
151169
152170 # Set fixed number of decimal places (if wanted)
153171 if self .output_quantum is not None :
154- return decimal_out .quantize (self .output_quantum )
172+ return decimal_out .quantize (self .output_quantum , rounding = self . rounding )
155173 else :
156174 return decimal_out
0 commit comments