2012-05-11 13 views
17

Mam skrypt, który ma pewne opcje, które można przekazać w wierszu poleceń lub ze zmiennych środowiskowych. Interfejs CLI powinien mieć pierwszeństwo, jeśli oba są obecne i wystąpi błąd, jeśli żadne z nich nie zostanie ustawione.Ustawianie opcji ze zmiennych środowiskowych podczas korzystania z argparse

Mogłem sprawdzić, czy opcja jest przypisana po analizie, ale wolę, aby argparse wykonywał ciężki lifting i był odpowiedzialny za wyświetlanie instrukcji użycia, jeśli przetwarzanie nie powiedzie się.

Wymyśliłem kilka alternatywnych podejść do tego (które zamieszczę poniżej jako odpowiedzi, aby można je było omówić oddzielnie), ale są dla mnie dość kludgey i myślę, że czegoś mi brakuje.

Czy istnieje zaakceptowany "najlepszy" sposób robienia tego?

(Edit, aby pożądane zachowanie jasne, kiedy zarówno opcja CLI i zmienna jest wyłączony)

Odpowiedz

26

Używam tego wzoru na tyle często, że mam pakowane prostą klasę działania, aby go obsługiwać:

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) 

mogę wtedy nazywamy to z mojego kodu z:

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() 
+1

Czy domyślnie należy to edytować, aby wyszukać w os.environ? "jeśli envvar w os.environ: default = envvar" -> "jeśli envvar w os.environ: default = os.environ [envvar]" – spazm

+1

Dlaczego używasz tylko wartości określonej przez envvar, jeśli nie ma wartości domyślnej? Czy nie powinien przesłonić wartości domyślnej, ponieważ osoba dzwoniąca wyraźnie podała wartość? –

+0

To rozwiązanie jest świetne. Ma jednak efekt uboczny: w zależności od środowiska (bez względu na to, czy zbiór env jest ustawiony), tekst użycia może się różnić. –

2

Jedną z opcji jest, aby sprawdzić, czy zmienna jest ustawiona i zmodyfikować odpowiednio do połączenia np 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() 
31

I ustawiłby zmienną default podczas dodawania argumentu do get OS.environ za pomocą zmiennej, którą chcesz pobrać. Drugi argument w wywołaniu .get() jest wartością domyślną, jeśli .get() nie znajdzie zmiennej środowiskowej o tej nazwie.

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()) 
+0

Brak jest wartością domyślną dla .get(), więc nie trzeba tego wyraźnie określać w ten sposób. Prawdopodobnie powinienem być bardziej jasny w pytaniu - opcja musi znajdować się w co najmniej jednej zmiennej środowiskowej lub w CLI. Jeśli ustawisz wartość domyślną, to args.url może się skończyć jako None, czego właśnie chcę uniknąć ... –

+1

Ah, widzę, czego szukasz. Po prostu użyłbym tego, co napisałem, i po analizie argumentów po prostu sprawdź 'if not args.url: exit (parser.print_usage())' i zakończ. –

+1

Niezły sposób na szybką obsługę. Zapakowałem własny program do obsługi akcji, ponieważ często używam tego wzorca, ale z pewnością byłby to mój powrót do szybkiego i prostego skryptu. –

0

Pomyślałem, że opublikuję moje rozwiązanie, ponieważ oryginalne pytanie/odpowiedź dało mi dużo pomocy.

Mój problem jest trochę inny niż u Russella. Używam OptionParser i zamiast zmiennej środowiskowej dla każdego argumentu mam tylko jeden, który symuluje wiersza polecenia.

tj

MY_ENVIRONMENT_ARGS = --arg1 "maltański" --arg2 "Sokoła" -r "1930" -h

Rozwiązanie:

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:]) 

Tu nie rzucać błąd, jeśli nie znaleziono argumentu. Ale jeśli pragnę I może po prostu zrobić coś jak

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

temat jest dość stary, ale miałem podobny problem i pomyślałem, że podzielę się z wami moją rozwiązanie.Niestety rozwiązanie niestandardowe działania sugerowane przez @Russell Heilling nie działa dla mnie z kilku powodów:

  • uniemożliwia mi korzystanie z predefined actions (jak store_true)
  • Wolałbym jakby awaryjnej do default gdy envvar nie jest w os.environ (który może być łatwo ustalony)
  • chciałbym mieć to zachowanie dla wszystkich moich argumentów bez określania action lub envvar (które zawsze powinny być action.dest.upper())

Oto moje rozwiązanie (w Pythonie 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) 
10

ConfigArgParse dodaje wsparcie dla zmiennych środowiskowych do argparse, więc można robić takie rzeczy jak:

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

zwykle muszę to zrobić dla wiele argumentów (uwierzytelnianie i klucze API). Jest to proste i proste. Używa ** kwargs.

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')) 
Powiązane problemy