Validators

1. Feature overview

The validators feature allows the programmer to define a validation function that runs after the dataclass is initialized from CLI arguments. The validation functions can raise ValueError’s in cases of invalid configuration being specified which result in calls to argparse.ArgumentParser.error() result in nicely formatted errors for the user including the tools usage message and a non-zero exit code.

An example of the validators feature is shown below:

# price-checker.py

import sys
from cfgclasses import arg, parse_args, validator
from dataclasses import dataclass

ITEMS = {
    "shirt": {
        "red": 10.45,
        "green": 11.45,
        "orange": 12.60,
    },
    "socks": {
        "blue": 5.10,
        "green": 5.10,
    },
}

@dataclass
class Config:
    item: str = arg("Item to check the price for", choices=ITEMS)
    # Note: we can't specify the choices for colour here because it depends
    # on the item
    colour: str = arg("Colour of the item")

    @validator
    def validate_available_color(self):
        if self.colour not in ITEMS[self.item]:
            raise ValueError(f"{self.item} is not available in {self.colour}")

    def run(self):
        print(f{ITEMS[self.item][self.colour]}")

if __name__ == '__main__':
    parse_args(Config, sys.argv[1:]).run()

Example runs:

$ python3 foo.py --item shirt --colour red
£10.45

$ price-checker.py --item shirt --colour blue
usage: foo.py [-h] --item {shirt,socks} --colour COLOUR
foo.py: error: shirt is not available in blue

2. Design ammendments

The following additions are made to the primary design:
  • A new @validator decorator is added to the cfgclasses module which accepts a single argument, the validation function. * The decorator sets a marker attribute on the validation function to indicate that it is a cfgclasses validator.

  • A new step is added to the high-level flow after creating the dataclass instance: * Check all attributes of the instance (exclusing those starting __) * If the attribute has a marker attribute indicating it is a validator, call the attribute. * Repeat this process for all nested dataclass instances. * If any ValueError’s are raised by these methods, use the argparse.ArgumentParser.error() method to output the exception message.