"""Quality control checks for data.""" import re from typing import Optional from .errors import InvalidValue def decimal_places_pattern(mini: int, maxi: Optional[int] = None) -> re.Pattern: """ Generate a regular expression for checking numbers Generates a regular expression that matches: a) Whole numbers, e.g. 2, 54343, 25, etc b) Zeroes e.g. 0, 000, 0.0, 0.000, 0.00000, etc c) Numbers with at least 'mini' decimal places d) If 'maxi' is provided, then numbers with decimal places between 'mini' and 'maxi'. To test for a match of exactly 'n' decimal places, then both 'mini' and 'maxi' should be set to 'n'. ARGUMENTS: - mini [int]: The mini number of decimal places allowed. This is mandatory. - maxi [int]: The maxi number of decimal places allowed. This is an optional argument. If not provided, then an infinite (theoretically) number of decimal places is allowed. """ try: assert isinstance(mini, int), "The argument 'mini' MUST be a integer" assert maxi is None or isinstance(maxi, int), "The argument 'maxi' MUST be a integer" except AssertionError as exc: raise TypeError(*exc.args) from exc try: assert mini > 0, "The argument 'mini' must be greater than zero (0)." if isinstance(maxi, int): assert maxi > 0, "The argument 'maxi' must be greater than zero (0)." assert maxi >= mini, "'maxi' MUST be greater than or equal to 'mini'." except AssertionError as exc: raise ValueError(*exc.args) from exc return re.compile( r"^(" r"0+" # All zeroes, no decimal places + r"|0+\.0+" # 0.00… + r"|[0-9]+\.?0*" # Whole numbers, or all zeroes after decimal point + r"|[0-9]+\.[0-9]{" + str(mini) + r"," + (str(maxi) if maxi is not None else r"") + r"}" + r")$" ) def decimal_points_error(filename: str,# pylint: disable=[too-many-arguments] lineno: int, field: str, value: str, mini: int, maxi: Optional[int] = None) -> Optional[InvalidValue]: """ Check that 'value' in a decimal number with the appropriate decimal places. """ if not bool(decimal_places_pattern(mini, maxi).match(value)): return InvalidValue(filename, lineno, field, value, ( f"Invalid value '{value}'. Expected numerical value " + f"with at least {mini} decimal places" + (f" and at most {maxi} decimal places" if maxi is not None else "") + ".")) return None