2

I have a small program that uses argparse and a positional argument. I'm trying to allow that argument to be set by using an environment variable, but are not getting it to work.

I have seen this post: Setting options from environment variables when using argparse which mentions the same problem, but not for positional arguments.

This is the code so far:

import argparse
import os

class EnvDefault(argparse.Action):
  def __init__(self, envvar, required=True, default=None, **kwargs):
      if not default and envvar:
          if envvar in os.environ:
              default = os.environ[envvar]
      if required and default:
          required = False
      super(EnvDefault, self).__init__(default=default, required=required, **kwargs)

  def __call__(self, parser, namespace, values, option_string=None):
      setattr(namespace, self.dest, values)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('testvar', help="Test variable", action=EnvDefault, envvar='TEST_VAR')
    parser.add_argument('--othervar', help="Other variable", action='store_true')
    args = parser.parse_args()
    if not args.testvar: exit(parser.print_usage())

    print args.testvar

Which returns this:

$ TEST_VAR="bla" ./test.py 
usage: test.py [-h] [--othervar] testvar
test.py: error: too few arguments
Community
  • 1
  • 1
terbolous
  • 173
  • 5

2 Answers2

2

You need to make positional argument optional, try nargs='?':

...
parser.add_argument('testvar', help="Test variable", action=EnvDefault,
        envvar='TEST_VAR', nargs='?')
...

Note that output changes slightly:

$ python test.py
usage: test.py [-h] [--othervar] [testvar]

Note: There's one side effect - it doesn't return error, even if env variable is not set.

Tupteq
  • 2,986
  • 1
  • 21
  • 30
1

The too few error message indicates that you have slightly older version of Python/argparse. Here's the code that generates the message. It occurs at the end of parsing:

    # if we didn't use all the Positional objects, there were too few
    # arg strings supplied.
    if positionals:
        self.error(_('too few arguments'))

    # make sure all required actions were present
    for action in self._actions:
        if action.required:
            if action not in seen_actions:
                name = _get_action_name(action)
                self.error(_('argument %s is required') % name)

positionals starts a list of all the positional arguments, which are removed as they get matched up with argument strings. So it is testing if any were not matched.

Note that the test of the required attribute occurs after this positional test, so changing it, as your Action does, does not help.

The only way to make a positional optional is with nargs - ? or *. An empty list of strings matches those, so such a positional is always consumed. Check the docs for these. The const parameter might be useful.

The latest version drops that if positionals test, using only the required test to generate a list of arguments that were not used. Your special Action might work in that code.

hpaulj
  • 221,503
  • 14
  • 230
  • 353