使用argparse时从环境变量设置选项

Rus*_*ing 37 python argparse

我有一个脚本,它具有某些选项,可以在命令行上传递,也可以从环境变量传递.如果两者都存在,则CLI应该优先,如果两者都没有,则会发生错误.

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

我已经提出了几种替代方法(我将在下面作为答案发布,以便可以单独讨论),但他们觉得我很笨,我认为我错过了一些东西.

是否有一种被接受的"最佳"方式?

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

Chr*_*tts 52

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

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())
Run Code Online (Sandbox Code Playgroud)

  • 这个答案提出的方式是糟糕的用户体验."default"表示"应用程序的默认值",而不是"当前环境中的默认值".人们可能会看--help,看到一个特定的默认值,假设它是永久的默认值,转到另一台机器/会话,获取kaboom. (12认同)
  • None 是 .get() 的默认值,因此不需要像这样明确声明。我可能应该在问题中更清楚 - 该选项至少需要在环境变量或 CLI 之一中。如果您像这样设置默认值,那么 args.url 很可能会以 None 结束,这是我想要避免的... (2认同)

Rus*_*ing 42

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

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)
Run Code Online (Sandbox Code Playgroud)

然后我可以从我的代码中调用它:

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()
Run Code Online (Sandbox Code Playgroud)

  • 如果没有默认值,为什么只使用 envvar 指定的值?它不应该覆盖默认值吗,因为调用者已经明确提供了一个值? (2认同)
  • 这个解决方案很棒。但是,它有一个副作用:根据您的环境(无论是否设置了 env var),使用文本可能会有所不同。 (2认同)

use*_*965 21

ConfigArgParse将对环境变量的支持添加到argparse中,因此您可以执行以下操作:

p = configargparse.ArgParser()
p.add('-m', '--moo', help='Path of cow', env_var='MOO_PATH') 
options = p.parse_args()
Run Code Online (Sandbox Code Playgroud)

  • @minexew,为什么你需要 ENV 变量作为位置参数??? (2认同)

wha*_*ier 18

我通常必须为多个参数(身份验证和API密钥)执行此操作..这很简单直接.使用**kwargs.

def environ_or_required(key):
    return (
        {'default': os.environ.get(key)} if os.environ.get(key)
        else {'required': True}
    )

parser.add_argument('--thing', **environ_or_required('THING'))
Run Code Online (Sandbox Code Playgroud)

  • @TheGodfather:如果你想指定一个“真正的”“默认”,那么将参数设为必需是没有意义的! (4认同)

Tom*_*ndt 6

这个话题很老了,但我也遇到了类似的问题,我想我会与你分享我的解决方案。不幸的是,@Russell Heilling 建议的自定义操作解决方案对我不起作用,原因如下:

  • 它阻止我使用预定义的操作(例如store_true
  • 我宁愿它回退到不在的default时候(这可以很容易修复)envvaros.environ
  • 我希望我的所有参数都具有这种行为,而不指定actionenvvar(应该始终是action.dest.upper()

这是我的解决方案(Python 3):

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)
Run Code Online (Sandbox Code Playgroud)


Rus*_*ing 5

一种选择是检查是否设置了环境变量,并相应地修改对 add_argument 的调用,例如

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()
Run Code Online (Sandbox Code Playgroud)