September 9, 2017

Subprocesses In Python

  —A look at layering subprocess calls in Python.

In A Poor Use of GitPython, I describe how my layering approach in a project using GitPython proved unsatisfactory. Unsatisfactory because I wasn't using GitPython to the full extent of its power. Unsatisfactory because I didn't want to spend time learning Git internals and how GitPython makes them available.

I revisited my approach without GitPython. In A Poor Use of GitPython, my approach resulted in the spread of Git command-line arguments to other functions and I'm looking for a nice abstraction that doesn't cause this problem.

Let's start with subprocess interaction:
1
2
3
4
5
def execute(command, *args):
""" Use a subprocess to execute a command. Supply the command with any arguments.
"""
assert 0 < len(command)
return subprocess.check_output([ command ] + list(args))

I want the output from the command and I want to know whenever I get a non-zero return code. It provides a nice test point for separating my application from the libraries it uses.

I call git using the following function.
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class GitException(Exception):
""" Throw an exception whenever an error occurs using GIT(1).
"""
def __init__(self, command, output, returncode):
assert 0 < len(command)
self._command = str(command)
assert 0 <= len(output)
self._output = str(output)
assert 0 < returncode
self._returncode = int(returncode)

@property
def command(self):
return self._command

@property
def output(self):
return self._output

@property
def returncode(self):
return self._returncode

def git(command, *args):
""" Execute GIT(1). Supply the git command and any arguments.
"""
assert 0 < len(command)
try:
execute("git", command, *args)
except subprocess.CalledProcessError as e:
raise GitException(e.cmd, e.output, e.returncode)

Too many layers? Perhaps. All I've achieved thus far is a couple of wrappers that provide strong guarantees on the length of the command. In some respects this is worse than the result I achieved in A Poor Use of GitPython.

The advantage lies in the recognition that some git commands (e.g., git-show-ref and git-ls-files.) return with error 1 under specific circumstances that I might want to handle in higher layers.
comments powered by Disqus