Parse arguments to get an argument value, or environment variable value, and raise exception if neither

Question:

I wish to use the argparse module to set the value of a server URL. As well as being set via an argument, the vale can also be in an environment variable, though the argument should always take precedence.

To achieve this, I’ve added an argument to the parser with the environment variable value as the default, and a custom type. However, if the argument is omitted, the custom type=EnvironDefault is not initialised. This means that if default value is None, that value is used regardless, rather than an exception being raised as expected.

I have found a 10 year old question that suggests it is necessary to check the value of the argument after parsing the arguments, but I’m hoping that in the interim a solution has been added to argparse.

Example 1 – Success – Environment variable but no argument

foo@bar:~$ export SERVER_URL="https://some.server.co.uk"
foo@bar:~$ python mycli
Namespace(url='https://some.server.co.uk')

Example 2 – Success – Environment variable and argument

foo@bar:~$ export SERVER_URL="https://some.server.co.uk"
foo@bar:~$ python mycli --url "https://different.server.co.uk"
Namespace(url='https://different.server.co.uk')

Example 3 – Failure – No environment variable or argument

foo@bar:~$ unset SERVER_URL
foo@bar:~$ python mycli
Namespace(url=None)

Expected response

usage: mycli [-h] [--url URL]
mycli: error: argument --url: Server URL must be provided via either the CLI or the environment variable SERVER_URL.

Example Argument Parser

import argparse
import os

class EnvironDefault(str):

    def __init__(self, url: str) -> None:
        err_msg = (
            'Server URL must be provided via either the CLI or the '
            'environment variable SERVER_URL.')
        if not url:
            raise argparse.ArgumentTypeError(err_msg)

parser = argparse.ArgumentParser()
parser.add_argument(
    '--url', type=EnvironDefault, default=os.environ.get('SERVER_URL'),
    help='Server URL. Overrides environment variable SERVER_URL.')

print(parser.parse_args())
Asked By: David Gard

||

Answers:

When the program starts, you can check immediately if SERVER_URL is available, and use that fact to decide if --url is required or not.

parser = argparse.ArgumentParser()
parser.add_argument(
    '--url', required='SERVER_URL' not in os.environ, default=os.environ.get('SERVER_URL'),
    help='Server URL. Overrides environment variable SERVER_URL.')
    
print(parser.parse_args())

If SERVER_URL is not in the environment, the default value of None will be ignored, because --url is required. If it is in the environment, then the default value will be used.

Note that default values are not processed by your type. If you do use a custom type, it should be applied to the default explicitly. For example –

parser.add_argument('--value', 
                    required='VALUE' not in os.environ,                               
                    type=int,
                    default=int(os.environ.get('VALUE')))
Answered By: chepner
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.