Usage

Installation

From cheeseshop

pip install spm

From source

git clone git://github.com/acatton/python-spm spm
pip install -e ./spm

Learn by example

You can reduce spm to two function run() and pipe(). But the objects returned by these two functions have many useful methods.

Run subcommands

>>> stdout, stderr = run('echo', 'Hello, World').wait()
>>> stdout
'Hello, World\n'
>>> stdout, stderr = run('false').wait()
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'false' returned non-zero exit status 1

run() create a subprocess, but it doesn’t spawn it yet. wait() spawn is one way to spawn the said subprocess.

You can also read its output:

>>> run('echo', 'Hello, World').stdout.read()
'Hello, World\n'

Pipe subprocesses together

Piping is done with pipe().

pipe() accepts either an argument list or a Subprocess. pipe() is also a method on Subprocess.

>>> data, _ = pipe(['echo', '-n', 'Hello World'], ['bzip2']).wait()
>>> import bz2
>>> bz2.decompress(data)
'Hello World'

Redirect output to a file

You can set Subprocess.stdout to an open file. spm will be in charge of closing it.

>>> proc = run('echo', 'Hello World')
>>> import os
>>> proc.stdout = open(os.devnull, 'w')
>>> proc.wait()
(None, None)

Propagate the environment

You can use the keyword argument env on run() or pipe() in order to override some variable in the environment of the subprocess.

>>> run('env').stdout.read()
''
>>> run('env', env={'FOO': 'BAR'}).stdout.read()
'FOO=BAR\n'

For security reasons, the environment doesn’t get propagated to the subprocess. See Environment propagation opt-in.

The class propagate_env (but you should think of it as a function) will propagate the environment, and update it if you pass it

>>> run('env', env=propagate_env()).stdout.read().decode() != ''
True
>>> 'a=b' in run('env', env=propagate_env(a='b')).stdout.read().split('\n')
True
>>> 'FOO=BAR' in run('env', env=propagate_env({'FOO': 'BAR'})).stdout.read().split('\n')
True

Debug your subprocesses

You can debug your subprocesses and try to running manually by getting their representation or converting them to strings.

Copying and pasting the string in your terminal should have exact the same result than calling wait().

>>> print(run('echo', 'Hello'))
env - echo Hello
>>> print(run('echo', 'Hello, World'))
env - echo 'Hello, World'
>>> run('echo', '$NAME')
<Subprocess "env - echo '$NAME'">

Create command functions

You can also create a git() function if you wanted. This could be done thanks to functools.partial().

>>> from functools import partial
>>> git = partial(run, 'git')
>>> git('commit')
<Subprocess 'env - git commit'>
>>> git('archive', '--output=/tmp/archive.tar.gz')
<Subprocess 'env - git archive --output=/tmp/archive.tar.gz'>

API

class spm.Subprocess(args, env=None)

Subprocess object used to access properties of the subprocess such as its returncode.

You shouldn’t have to instantiate this class. run() and pipe() will do it for you.

pipe(*args, **kwargs)

Pipe processes together. pipe() can receive an argument list or a subprocess.

>>> print(run('echo', 'foo\nbar').pipe('grep', 'bar').stdout.read()
...                                                  .decode())
bar

>>> noop = run('gzip').pipe('zcat')
>>> print(noop)
env - gzip | env - zcat
>>> # This will be: echo -n foo | gzip | zcat
>>> print(run('echo', '-n', 'foo').pipe(noop).stdout.read()
...                                                 .decode())
foo
returncode

Return the the returncode of the subprocess. If the subprocess isn’t terminated yet, it will return None.

stdin

Set the stdin of the subprocess. It should be a file. If its None it will be a pipe to wich you can write from the main process.

stdout

Set the stdout of the subprocess. It should be a file. If its None it could be read from the main process.

wait()

Waits for the subprocess to finish, and then return a tuple of its stdout and stderr.

If there’s no error, the stderr is None. If the process fails (has a non-zero exit code) it raises a subprocess.CalledProcessError.

spm.run(*args, **kwargs)

Run a simple subcommand:

>>> print(run('echo', '-n', 'hello world').stdout.read().decode())
hello world

Returns a Subprocess.

spm.pipe(cmd, *arguments, **kwargs)

Pipe many commands:

>>> noop = pipe(['gzip'], ['gzip'], ['zcat'], ['zcat'])
>>> _ = noop.stdin.write('foo'.encode())  # Ignore output in Python 3
>>> noop.stdin.close()
>>> print(noop.stdout.read().decode())
foo

Returns a Subprocess.