serhii.net

In the middle of the desert you can say anything you want

27 Nov 2023

Passing booleans to python argparse as str

Needed argparse to accept yes/no decisions, should have been used inside a dockerfile that doesn’t have if/else logic, and all solutions except getting a parameter that accepts string like true/false seemed ugly.

The standard linux --do-thing and --no-do-thing were also impossible to do within Docker, if I want to use an env. variable etc., unless I literally set them to --do-thing which is a mess for many reasons.

I had 40 tabs open because apparently this is not a solved problem, and all ideas I had felt ugly.

How do I convert strings to bools in a good way? (bool alone is not an option because bool('False') etc.)

Basic if value=="true" would work, but maybe let’s support other things as a bonus because why not.

My first thought was to see what YAML does, but then I found the deprecated in 3.12 distutils.util.strtobool: 9. API Reference — Python 3.9.17 documentation

It converts y,yes,t,true,on,1 / n,no,f,false,off,0 into boolean True/False.

The code, the only reason it’s a separate function (and not a lambda inside the type= parameter) was because I wanted a custom ValueError and to add the warning for deprecation, as if Python would let me forget. An one-liner was absolutely possible here as well.

def _str_to_bool(x: str):
    """Converts value to a boolean.

    Currently uses (the rules from) distutils.util.strtobool:
        (https://docs.python.org/3.9/distutils/apiref.html#distutils.util.strtobool)
        True values are y, yes, t, true, on and 1
        False values are n, no, f, false, off and 0
        ValueError otherwise.

    ! distutils.util.strtobool is deprecated in python 3.12
        TODO solve it differently by then

    Args:
        value (str): value
    """
    try:
        res = bool(strtobool(str(x).strip()))
    except ValueError as e:
        logger.error(
            f"Invalid str-to-bool value '{x}'. Valid values are: y,yes,t,true,on,1 / n,no,f,false,off,0."
        )
        raise e
    return res

# inside argparse
    parser.add_argument(
        "--skip-cert-check",
        help="Whether to skip a cert check (%(default)s)",
        type=_str_to_bool,
        default=SKIP_CERT_CHECK,
    )

This allows:

  • sane human-readable default values specified elsewhere
  • use inside Dockerfiles and Rancher configmaps etc. where you just set it to a plaintext value
  • no if/else bits for --no-do-thing flags

distutils is deprecated in 3.12 though :(


YAML is known for it’s bool handling: Boolean Language-Independent Type for YAML™ Version 1.1.

Regexp:

y|Y|yes|Yes|YES|n|N|no|No|NO
|true|True|TRUE|false|False|FALSE
|on|On|ON|off|Off|OFF`

I don’t like it and think it creates more issues than it solves, e.g. the “Norway problem” ([[211020-1304 YAML Norway issues]]), but for CLI I think that’s okay enough.

Nel mezzo del deserto posso dire tutto quello che voglio.