Search code examples
pythonsubprocessparameter-passingargskeyword-argument

Pythonic way to wrap a subprocess call that takes a lot of parameters?


I am writing a python script that provides a more user friendly API to a command line tool. Some of the necessary command calls take a lot of parameters (up to around 10 sometimes), but that is not good practice in Python. They can't just be defaults; it has to be possible to set all the parameters for a given call.

My current structure is an API class that has functions such as expose_image(), and then an interface class to handle the construction of the subprocess command and the call. I don't see that adding more classes will help, as the API class still has to generate and pass the parameters in some way.

One solution I have come up with is to fill a dictionary or namedtuple with the parameters and pass it as **kwargs, which makes things look a little nicer, but less explicit.

Is there a better way of handling this?

Thanks!


Solution

  • It is commendable that you want to build a Pythonic API rather than just an API for this command.

    I'm not sure why you disregard default parameters though? If the default is None, you could treat that as a guide to not add things to the command line.

    For example, suppose you want to call the tree command. You could have something like:

    def my_tree(dirs_only=False, full_prefix=False, max_level=None, pattern=None):
       cmd_line = ['tree']
       if dirs_only:
           cmd_line.append('-d')
       if full_prefix:
           cmd_line.append('-f')
       if max_level is not None:
           cmd_line.append('-L')
           cmd_line.append(str(max_level))
       if pattern is not None:
           cmd_line.append('-P')
           cmd_line.append(pattern)
       subprocess.do_something_with(cmd_line)
    

    Callers of my_tree could then interact with it like in the shell:

    my_tree()
    my_tree(dirs_only=True)
    my_tree(pattern='Foo*')
    my_tree(pattern='Foo*', max_level=2, full_prefix=True)
    

    In languages such as Java, C# or Dart, you often see "fluent" APIs, and perhaps those might help. It would result in code such as:

    my_tree().call()
    my_tree().dirs_only().call()
    my_tree().with_pattern('Foo*').call()
    my_tree() \
        .with_pattern('Foo*') \
        .with_max_level(2) \
        .full_prefix() \
        .call()
    

    Though the invocation looks nicer, there is a lot of boilerplate you need to write in order to obtain said niceity, which definitely feels a little bit un-Pythonic.