title: How to make a tree command using Python3's argparse library?
body: <p>I do not find a way to create this kind of arguments structure with the <code>argparse</code> package:</p>
  
  <p>$ python3 prog.py [<b>A (a1 VAR | a2 | a3)</b> | B | C c]</p>
  
  <p>I would like A to be a root for a sub-commands family, in my case a blacklist (./prog blacklist [add PATH | rm | ...]), but I also need two other features : <code>$ ./prog push FILE</code> and finally <code>$ ./prog --interactive</code>.</p>
  
  <p>I've already taken a serious look at <a href="https://docs.python.org/3/library/argparse.html" rel="nofollow noreferrer">the argparse documentation</a>.
  I've tried to use <code>add_mutually_exclusive_group()</code> to regroup a1, b1 and c1 but I'm stuck with this :</p>
  
  <p><code>ValueError: mutually exclusive arguments must be optional</code></p>
  
  <p>I did manage to have <code>python3 prog.py (A a1 a2| B | C c1)</code> using groups, but either I miss understood something, or <code>argparse</code> has nothing included for this case. </p>
  
  <p>Any help would be appreciated. Thanks for your time !</p>
  
  <p><strong>EDIT :</strong> I've finally managed to do what I was looking for. However, I am not convinced this is very clean. It could be great to have some packages which does it better than I.
  Thanks to <a href="https://chase-seibert.github.io/blog/2014/03/21/python-multilevel-argparse.html#" rel="nofollow noreferrer">this website</a>, I've found a way to achieve my goal. I attach the code if ever it interests anyone. </p>
  
  <pre class="lang-py prettyprint-override"><code>#!/usr/bin/env python3
  # -*- coding: utf-8 -*-
  
  import argparse
  from sys import argv
  
  
  class CLI:
      """
      Class which represent the CLI's behaviour.
      Every function describes a subcommands tree and
      how the CLI should respond.
      """
      def __init__(self):
          parser = argparse.ArgumentParser(
              description='',
              usage='''cli &lt;command&gt; [&lt;args&gt;]'
  
  Commands list:
  
  cli blacklist     Interact with the blacklist.
  cli interactive   Run an interactive mode for queries.
  cli read &lt;path&gt;   Try to extract credentials.
  '''
          )
          parser.add_argument('command', help='Subcommand to run')
          # only parsing the subcommand
          args = parser.parse_args(argv[1:2])
          if not hasattr(self, args.command):
              print('Unrecognized command')
              parser.print_help()
              exit(1)
          # use dispatch pattern to invoke method with same name
          getattr(self, args.command)()
  
      def blacklist(self):
          """
          Parser for blacklist methods.
          """
          parser = argparse.ArgumentParser(
              description='',
              usage='''cli blacklist &lt;subcommand&gt; [&lt;args&gt;]
  
  Subcommands list:
  
  add &lt;domain&gt;          Add &lt;domain&gt; from the blacklist.
  export &lt;path&gt;         Export blacklist as csv to &lt;path&gt;.
  import &lt;path&gt; [-e]    Import &lt;path&gt; as csv. Erase existing blacklist if -e.
  remove &lt;domain&gt;       Remove &lt;domain&gt; from the blacklist.
  show                  Print the blacklist.
  '''
          )
          parser.add_argument('subcommand')
          args = parser.parse_args(argv[2:3])
          if not hasattr(self, 'bl_%s' % (args.subcommand)):
              print('Unrecognized command')
              parser.print_usage()
              exit(1)
          getattr(self, 'bl_%s' % (args.subcommand))()
  
      def bl_add(self):
          print('add `%s` to the blacklist' % (argv[3:4][0]))
  
      def bl_export(self):
          print('export')
  
      def bl_import(self):
          print('import')
  
      def bl_remove(self):
          print('remove')
  
      def bl_show(self):
          print('show')
  
      def read(self):
          if len(argv) != 3:
              print('Wrong number of arguments')
              print('usage: cli read &lt;path&gt;')
              exit(1)
          print('process %s' % (argv[2:3][0]))
  
      def interactive(self):
          print('run interactive mode')
  
  CLI()
  
  </code></pre>