Home > OS >  How to create multiple CLI options identified my package name in a python?
How to create multiple CLI options identified my package name in a python?

Time:05-15

I want to build a cli interface for my application which has nested functionality. Example:

├── ...
├── setup.py
└── package-name
    ├──__init__.py
    ├──command1.py
    └──command2.py

package-name command1 --arg .. ..
package-name command2 --arg ..

AND

python -m package-name command1 --arg ..

The thing to note here is that command1 and command2 are independent modules that accept different command-line args. So linking them together in a __main__.py might also be another challenge.

I came across similar questions that use entry_points in setup.py to create similar cli functionality but its not exactly what I'm looking for. I found this similar question. How to create a CLI in Python that can be installed with PIP?

CodePudding user response:

If you want access multiple sub-cli's in one entry command, you can implement a sub-command manager at __main__.py, which can parse sub-command from sys.argv and then dispatch to target module.

1️⃣First, i recommend the google fire, which can satify you in most scenarios without extra code.

Here is a example, you can replace the add/multiply function to your sub-command function by using from command1 import xx,and use entry points to expose the main function.

import fire

def add(x, y):
  return x   y

def multiply(x, y):
  return x * y


def main():
  fire.Fire({
      'add': add,
      'multiply': multiply,
  })

if __name__ == '__main__':
  main()

We can debug this in the same way as below:

$ python example.py add 10 20
30
$ python example.py multiply 10 20
200

2️⃣Second, if you need implements by your self for some purpose, such as using argparse to define options for each command. A typical practice is the Django command, the official demo: Writing custom django-admin commands

The core steps is :

  1. define a BaseCommand
  2. implements BaseCommand in sub commands.py, and named it as Command.
  3. __main__.py implements find command and call
# base_command.py
class BaseCommand(object):
    def create_parser(self, prog_name, subcommand, **kwargs):
        """
        Create and return the ``ArgumentParser`` which will be used to
        parse the arguments to this command.
        """
        # TODO: create your ArgumentParser
        return CommandParser(**kwargs) 
        
    def add_arguments(self, parser):
        """
        Entry point for subclassed commands to add custom arguments.
        """
        pass

    def run_from_argv(self, argv):
        """
        Entry point for commands to be run from the command line.
        """
        parser = self.create_parser(argv[0], argv[1])
        options = parser.parse_args(argv[2:])
        cmd_options = vars(options)
        args = cmd_options.pop('args', ())
        self.handle(*args, **cmd_options)
    
    def handle(self, *args, **options):
        """
        The actual logic of the command. Subclasses must implement
        this method.
        """
        raise NotImplementedError('subclasses of BaseCommand must provide a handle() method')
    
    
# command1.py     
class Command(BaseCommand):
    def handle(self, *args, **options):
        print("Hello, it is command1!")
        
   
# command2.py     
class Command(BaseCommand):
    def handle(self, *args, **options):
        print("Hello, it is command2!")
        
    
# __main__.py         
def main():
    # sub command name is the second argument
    command_name = sys.argv[1]
    # build sub command module name, and import
    # package-name is the module name you mentioned
    cmd_package_name = f'package-name.{command_name}'
    instance = importlib.import_module(cmd_package_name)
    # create instance of sub command, the Command must exist
    command = instance.Command()
    command.run_from_argv(sys.argv)

if __name__ == "__main__":
    main()
  • Related