关于python:使用argparse时从环境变量设置选项

Setting options from environment variables when using argparse

我有一个脚本,它有一些选项,可以在命令行上传递,也可以从环境变量传递。如果两者都存在,则应优先使用CLI;如果两者都不设置,则会出现错误。

我可以检查在解析之后是否分配了该选项,但是我更愿意让argparse进行繁重的工作,并在解析失败时负责显示用法语句。

我已经想出了一些替代方法(我将在下面发布作为答案,以便单独讨论),但它们对我来说相当笨拙,我认为我遗漏了一些东西。

是否有一种公认的"最佳"方法来做到这一点?

(编辑以在未设置cli选项和环境变量时清除所需的行为)


我只需要在向get of os.environ添加参数时设置default变量,该参数带有您想要获取的变量。如果.get()找不到该名称的环境变量,则.get()调用中的第2个参数是默认值。

1
2
3
4
5
6
7
8
9
import argparse
import os

parser = argparse.ArgumentParser(description='test')
parser.add_argument('--url', default=os.environ.get('URL', None))

args = parser.parse_args()
if not args.url:
    exit(parser.print_usage())


我经常使用这个模式,因此我打包了一个简单的动作类来处理它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

然后,我可以使用以下代码调用此代码:

1
2
3
4
5
6
7
8
import argparse
from envdefault import EnvDefault

parser=argparse.ArgumentParser()
parser.add_argument(
   "-u","--url", action=EnvDefault, envvar='URL',
    help="Specify the URL to process (can also be specified using URL environment variable)")
args=parser.parse_args()


configargparse向argparse添加了对环境变量的支持,因此可以执行以下操作:

1
2
3
p = configargparse.ArgParser()
p.add('-m', '--moo', help='Path of cow', env_var='MOO_PATH')
options = p.parse_args()

我通常需要对多个参数(身份验证和API密钥)执行此操作。这是简单而直接的。使用*kWAGS。

1
2
3
4
5
6
7
def environ_or_required(key):
    if os.environ.get(key):
        return {'default': os.environ.get(key)}
    else:
        return {'required': True}

parser.add_argument('--thing', **environ_or_required('THING'))


这个话题很古老,但我也有类似的问题,我想我会和你分享我的解决方案。不幸的是,@russell heilling建议的自定义操作解决方案不适用于我,原因如下:

  • 它阻止我使用预先定义的操作(如store_true)
  • 我希望在envvar不在os.environ的情况下(这可以很容易地解决),它能退回到default
  • 我希望这种行为适用于我所有的论点,而不指定actionenvvar(应该始终是action.dest.upper())。

这是我的解决方案(在Python3中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CustomArgumentParser(argparse.ArgumentParser):
    class _CustomHelpFormatter(argparse.ArgumentDefaultsHelpFormatter):
        def _get_help_string(self, action):
            help = super()._get_help_string(action)
            if action.dest != 'help':
                help += ' [env: {}]'.format(action.dest.upper())
            return help

    def __init__(self, *, formatter_class=_CustomHelpFormatter, **kwargs):
        super().__init__(formatter_class=formatter_class, **kwargs)

    def _add_action(self, action):
        action.default = os.environ.get(action.dest.upper(), action.default)
        return super()._add_action(action)


一个选项是检查是否设置了环境变量,并修改调用以相应地添加参数例如

1
2
3
4
5
6
7
8
9
10
11
12
import argparse
import os

parser=argparse.ArgumentParser()
if 'CVSWEB_URL' in os.environ:
    cvsopt = { 'default': os.environ['CVSWEB_URL'] }
else:
    cvsopt = { 'required': True }
parser.add_argument(
   "-u","--cvsurl", help="Specify url (overrides CVSWEB_URL environment variable)",
    **cvsopt)
args=parser.parse_args()


ChainMap的一个示例用例中,您将默认值、环境变量和命令行参数合并在一起。

1
2
3
4
5
6
7
8
9
10
11
import os, argparse

defaults = {'color': 'red', 'user': 'guest'}

parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
command_line_args = {k:v for k, v in vars(namespace).items() if v}

combined = ChainMap(command_line_args, os.environ, defaults)

我是从一个关于美丽的、惯用的Python的伟大演讲中得到的。

但是,我不知道如何处理小写和大写字典键的区别。如果两个-u foobar都作为一个参数传递,并且环境设置为USER=bazbaz,那么combined字典将类似于{'user': 'foobar', 'USER': 'bazbaz'}


我想我会把我的答案贴出来,因为最初的问题/答案给了我很多帮助。

我的问题与Russell的有点不同。我使用的是OptionParser,而不是每个参数的环境变量,我只有一个参数来模拟命令行。

我的环境——args=--arg1"马耳他"-arg2"猎鹰"-r"1930"-h

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def set_defaults_from_environment(oparser):

    if 'MY_ENVIRONMENT_ARGS' in os.environ:

        environmental_args = os.environ[ 'MY_ENVIRONMENT_ARGS' ].split()

        opts, _ = oparser.parse_args( environmental_args )

        oparser.defaults = opts.__dict__

oparser = optparse.OptionParser()
oparser.add_option('-a', '--arg1', action='store', default="Consider")
oparser.add_option('-b', '--arg2', action='store', default="Phlebas")
oparser.add_option('-r', '--release', action='store', default='1987')
oparser.add_option('-h', '--hardback', action='store_true', default=False)

set_defaults_from_environment(oparser)

options, _ = oparser.parse_args(sys.argv[1:])

在这里,如果找不到参数,我不会抛出错误。但如果我愿意,我可以做一些像

1
2
3
for key in options.__dict__:
    if options.__dict__[key] is None:
        # raise error/log problem/print to console/etc