Saturday, September 9, 2017

Subprocesses 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.

No comments:

Post a Comment