Package: envargparse
Argparse with environment variables. This module is both a derivedargparseclass supporting environment variables and a framework or set of proposed
enhancements for extendingargparse.The ExampleThe following is taken from the__main__conditional at the end of this module:parser = EnvArgParser\
        ( prog="Test Program"
        , formatter_class=EnvArgDefaultsHelpFormatter
        )

parser.add_argument\
        ( '--bar'
        , required=True
        , env_var="BAR"
        , type=int
        , nargs="+"
        , default=22
        , help="Help message for bar."
        )

parser.add_argument\
        ( 'baz'
        , type=int
        )

args = parser.parse_args()
print(args)Example output:$ BAR="1 2 3 '45  ' 6 7" ./envargparse.py 123
Namespace(bar=[1, 2, 3, 45, 6, 7], baz=123)$ ./envargparse.py -h
usage: Test Program [-h] --bar BAR [BAR ...] baz

positional arguments:
  baz

optional arguments:
  -h, --help           show this help message and exit
  --bar BAR [BAR ...]  Help message for bar. (default: 22) (env_var: BAR)How it WorksThis module tries to be very simple: it may look longer because it is well-commented. There is no code duplication. In fact version 0.1 (tagged) was even simpler: actions were tracked via the namespace argument ofparse_args(), maintaining the precedence:cmd args > env var > preexisting namespace > defaultsBasically it stores within the namespace a unique instance for each action with an environment variable key, if that variable exists. Afterargparsefinishes parsing it executes the action associated with each remaining unique instance. The idea is this: if the action was provided via the command-line, it’ll have been overwritten from the namespace.You can still use that version if you want, but it doesn’t handle actions which fail to set thedestattribute on the namespace. To be fair, such actions are exceedingly unlikely. For a while I considered sandboxing an empty namespace for each action and copying only thedestattribute back to the main namespace, but that would be artificially limiting. Instead…Version 0.2 (tagged) is built around tracking actions. Whether an action has been seen as a command-line argument, and how often it has been called. Believe it or not, there are edge cases in which an action can be seen but not called. See the comments for more information.TheEnvArgParserclass maintains the set of seen actions while every action, environment variable key or not, is embedded within a tracking object. Originally a function-local variable within the parsing function, the set of seen actions is lifted into the instance namespace and reset before each new parsing pass. Luckily there is a 1:1 call correspondence between seeing an action and calling_get_values, which now updates the set of seen actions. Each tracking object increments a counter whenever called and otherwise behaves exactly like the contained action, forwarding (almost) all attribute access.An environment variable record,EnvArgRecord, is attached to each valid environment variable action whether or not that environment variable actually exists - for help-formatting reasons. Such actions will only be executed if the environment variable is available and the action hasn’t already been seen or been called.As for actually parsing the environment variable, that hasn’t changed much between 0.1 and 0.2. Reflecting that the parsing function can be overridden per action, the defaultenv_var_parseis a static method; the first argument is always the parser. The default mimics the parsing ofargparse(counting, converting and checking values) without code duplication by relying on internalArgumentParsermethods. It’s actually quite simple - just two method calls. The arguments are split usingshlexbut your custom parser can useyaml,json, or whatever else you prefer. Whatever the case, the parsing function must return a tuple: the resulting value(s) and a list of extra values. As extra values usually raise an exception you might consider leaving this empty.And that’s basically it, aside the from the value-checking boilerplate. As of python 3.7 the newly introduced intermixed parsing methods perform two-pass parsing, forcingEnvArgParserto track the parsing “depth”. The set of seen actions is only cleared before this two-pass process and the environment variables only parsed afterwards - both at the uppermost depth.There is a help formatter which adds environment variable keys to the argument help, and an example mix-in class withargparse.ArgumentDefaultsHelpFormatterto add both environment variable keys and argument defaults. Custom help formatters can be created simply by inheriting from the appropriate base classes.What comes next is just icing on top of the cake. While it isn’t actually used by this module nor does it change the default behavior it’s a nice showcase for what’s possible and perhaps useful for user-defined derived classes.The originalArgumentParseris relatively monolithic whileEnvArgParseruses cooperative OOP between action and parser to allow per-action overrides of parser methods. Whereas the parser would normally call its own methods it now calls into the action. By default the action calls the matching method from the parser’s base class, so out-of-the-box there is no behavioral change. As a proof-of-concept this is only implemented for_get_valuesandget_valuebut if useful can be extended to all methods via a custom__getattribute__. In the meantime feel free to add additional methods or override the default behavior by deriving fromEnvArgParserandEnvArgAction.The ProposalLet’s step a bit back. Why can’t you just use argument defaults:import argparse
import os

parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("--foo", default=os.environ.get("FOO","bar"), help="FOO!")
parser.parse_args()That creates an artificial dependency between the default and the environment. In other words, it’s not what the user expects. Notice how the default doesn’t stay constant:$ ./test.py -h
usage: test.py [-h] [--foo FOO]

optional arguments:
  -h, --help  show this help message and exit
  --foo FOO   FOO! (default: bar)$ FOO=456 ./test.py -h
usage: test.py [-h] [--foo FOO]

optional arguments:
  -h, --help  show this help message and exit
  --foo FOO   FOO! (default: 456)Unfortunatelyargparseis too much of a black box to cleanly modify and therefore this module serves as a working roadmap for proposed improvements. The important points:Be more transparent about parsing.Maintain parsing state, such as the set of seen actions, within the instance namespace rather than as inaccessible method variables. Track parsing depth during multistage parsing. Have the baseActionclass track how often it has been called. Mark core parsing methods as a part of the public API.Make parsing more modular.Create re-usable entry-points (methods) for:parsing individual optional arguments.parsing individual positional arguments.checking for conflicts based on argument values.handling exceptions (as a decorator).Right now the code is too monolithic.Don’t force each argument to use the same parsing chain.Allow actions to overload important parser methods such as_get_values,_get_valueor_match_argument.The more trivial points:Switch to new-style ({}) string formatting.Old-style (%) string formatting cannot access object attributes. The_get_help_stringmethod is expected to return a format string which would be unable to access attributes ofEnvArgRecord.The ModuleThe code is well-commented, so here is a brief list of the provided classes:EnvArgRecordEnvArgParserEnvArgActionEnvArgHelpFormatterEnvArgDefaultsHelpFormatterContainerRequirementsPython 3.7+module:decorator(@PyPI)LicenseGPLv3+; seeLICENSE.txtAuthorYclept Nemo <pscjtwjdjtAhnbjm/dpn>LinksEnvArgParse@GitHubEnvArgParse@PyPI