web-dev-qa-db-fra.com

Argparse: argument requis 'y' si 'x' est présent

J'ai une exigence comme suit:

./xyifier --prox --lport lport --rport rport

pour l'argument prox, j'utilise action = 'store_true' pour vérifier s'il est présent ou non. Je n'ai besoin d'aucun des arguments. Mais, si --prox est défini sur I require rport et lport également. Existe-t-il un moyen simple de faire cela avec argparse sans écrire de codage conditionnel personnalisé?.

Plus de code:

non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', type=int, help='Listen Port.')
non_int.add_argument('--rport', type=int, help='Proxy port.')
89
asudhak

Non, il n'y a aucune option dans argparse pour créer des ensembles d'options mutuellement inclusif.

Le moyen le plus simple de gérer cela serait:

if args.prox and (args.lport is None or args.rport is None):
    parser.error("--prox requires --lport and --rport.")
85
borntyping

Vous parlez d'avoir des arguments requis conditionnellement. Comme @borntyping, vous pouvez vérifier l’erreur et faire parser.error(), ou vous pouvez simplement appliquer une exigence liée à --prox lorsque vous ajoutez un nouvel argument.

Une solution simple pour votre exemple pourrait être:

non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', required='--prox' in sys.argv, type=int)
non_int.add_argument('--rport', required='--prox' in sys.argv, type=int)

De cette façon, required reçoit soit True ou False selon que l'utilisateur a utilisé tel que --prox. Cela garantit également que -lport et -rport ont un comportement indépendant entre eux.

41
Mira

Que diriez-vous d’utiliser la méthode parser.parse_known_args() puis d’ajouter la méthode --lport et --rport args comme argument requis si --prox est présent.

# just add --prox arg now
non_int = argparse.ArgumentParser(description="stackoverflow question", 
                                  usage="%(prog)s [-h] [--prox --lport port --rport port]")
non_int.add_argument('--prox', action='store_true', 
                     help='Flag to turn on proxy, requires additional args lport and rport')
opts, rem_args = non_int.parse_known_args()
if opts.prox:
    non_int.add_argument('--lport', required=True, type=int, help='Listen Port.')
    non_int.add_argument('--rport', required=True, type=int, help='Proxy port.')
    # use options and namespace from first parsing
    non_int.parse_args(rem_args, namespace = opts)

N'oubliez pas non plus que vous pouvez fournir l'espace de noms opts généré après la première analyse, tout en analysant les arguments restants la deuxième fois. Ainsi, à la fin, une fois l’analyse terminée, vous aurez un seul espace de noms avec toutes les options.

Désavantages:

  • Si --prox n'est pas présent, les deux autres options dépendantes ne sont même pas présentes dans l'espace de noms. Bien que basé sur votre cas d'utilisation, si --prox n'est pas présent, ce qui arrive aux autres options est sans importance.
  • Nécessité de modifier le message d'utilisation car l'analyseur ne connaît pas la structure complète
  • --lport et --rport ne s'affiche pas dans le message d'aide
6
Aditya Sriram

Utilisez-vous lport lorsque prox n'est pas défini. Sinon, pourquoi ne pas créer lport et rport arguments de prox? par exemple.

parser.add_argument('--prox', nargs=2, type=int, help='Prox: listen and proxy ports')

Cela évite à vos utilisateurs de taper. Il est tout aussi facile de tester if args.prox is not None: comme if args.prox:.

5
hpaulj

La réponse acceptée a très bien fonctionné pour moi! Puisque tout le code est cassé sans test, voici comment j'ai testé la réponse acceptée. parser.error() ne déclenche pas un argparse.ArgumentError _ error il quitte plutôt le processus. Vous devez tester SystemExit.

avec pytest

import pytest
from . import parse_arguments  # code that rasises parse.error()


def test_args_parsed_raises_error():
    with pytest.raises(SystemExit):
        parse_arguments(["argument that raises error"])

avec unittests

from unittest import TestCase
from . import parse_arguments  # code that rasises parse.error()

class TestArgs(TestCase):

    def test_args_parsed_raises_error():
        with self.assertRaises(SystemExit) as cm:
            parse_arguments(["argument that raises error"])

inspiré de: tilisation de unittest pour tester argparse - erreurs de sortie

0
Daniel Butler