Parameter and configuration validation

Parameter and configuration validation

The validation of a parameter value checks requirements that are not part of the parsing process itself. For example, we may require that a numerical parameter is within a certain range.

There are two places where the validation may occur.

  • The validation may be done right after parsing, if the parameter value can be checked on its own. This is the recommended place to perform validation, if at all possible. The validation function is then tacked on to the parser.

    More interestingly, errors about this kind of validation will be reported early in the processing along with other parse errors. This is the most user-friendly option.

  • If the validation is about a relationship between parameter values, it must be done after the configuration has been processed and constructed. The validation function needs to be a method in the configuration subclass.

We demonstrate the two options in the example below.

from dataclasses import dataclass
from configpile import *
from typing_extensions import Annotated
from typing import Optional


def int_is_not_negative(x: int) -> bool:
    return x >= 0

@dataclass(frozen=True)
class TestConfig(Config):

    a: Annotated[int, Param.store(
        parsers.int_parser.validated(int_is_not_negative, "The value cannot be negative")
    )]
    
    b: Annotated[int, Param.store(
        parsers.int_parser.validated(int_is_not_negative, "The value cannot be negative")
        )]

    def validate_a_is_greater_than_b(self) -> Optional[Err]:
        if not self.a > self.b:
            return Err.make(f"The value a={self.a} needs to be greater than the value b={self.b}")
        else:
            return None

Validation during parsing

This example has the validation during parsing fail.

res = TestConfig.parse_command_line_(args=["--a", "-2", "--b", "-3"])
res
ManyErr(errs=[Err1(msg='The value cannot be negative', contexts=[('flag', '--a'), ('param', 'a')]), Err1(msg='The value cannot be negative', contexts=[('flag', '--b'), ('param', 'b')])])
res.pretty_print()
 0 In flag: --a                                                                              
   In param: a                                                                               
   The value cannot be negative                                                              
 1 In flag: --b                                                                              
   In param: b                                                                               
   The value cannot be negative                                                              

Validation during construction

This example has the validation post-construction fail.

res = TestConfig.parse_command_line_(args=["--a", "2", "--b", "3"])
res
Err1(msg='The value a=2 needs to be greater than the value b=3', contexts=[])
res.pretty_print()
The value a=2 needs to be greater than the value b=3