{ "metadata": {}, "nbformat": 4, "nbformat_minor": 5, "cells": [ { "id": "metadata", "cell_type": "markdown", "source": "
\n\n# Python - Subprocess\n\nby [Helena Rasche](https://training.galaxyproject.org/hall-of-fame/hexylena/), [Donny Vrins](https://training.galaxyproject.org/hall-of-fame/dirowa/), [Bazante Sanders](https://training.galaxyproject.org/hall-of-fame/bazante1/)\n\nCC-BY licensed content from the [Galaxy Training Network](https://training.galaxyproject.org/)\n\n**Objectives**\n\n- How can I run another program?\n\n**Objectives**\n\n- Run a command in a subprocess.\n- Learn about check_call and check_output and when to use each of these.\n- Read it's output.\n\n**Time Estimation: 45M**\n
\n", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-0", "source": "

Sometimes you need to run other tools in Python, like maybe you want to\nHere we’ll give a quick tutorial on how to read and write files within Python.

\n
\n
Agenda
\n

In this tutorial, we will cover:

\n
    \n
  1. Subprocesses
  2. \n
\n
\n

Subprocesses

\n

Programs can run other programs, and in Python we do this via the subprocess module. It lets you run any other command on the system, just like you could at the terminal.

\n

The first step is importing the module

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-1", "source": [ "import subprocess" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-2", "source": "

You’ll primarily use two functions:

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-3", "source": [ "help(subprocess.check_call)" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-4", "source": "

Which executes a command and checks if it was successful (or it raises an exception), and

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-5", "source": [ "help(subprocess.check_output)" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-6", "source": "

Check Call: Downloading Files

\n

Which executes a command returns the output of that command. This is really useful if you’re running a subprocess that writes something to stdout, like a report you need to parse. We’ll learn how to use these by running two gene callers, augustus and glimmer. You can install both from Conda if you do not have them already.

\n
conda create -n subprocess augustus glimmer3\n
\n

Additionally you’ll need two files, you generally should not do this, but you can use a subprocess to download the file! We’ll use subprocess.check_call for this which simply executes the program, and continues on. If there is an error in the execution, it will raise an exception and stop execution.

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-7", "source": [ "url = \"https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/836/945/GCF_000836945.1_ViralProj14044/GCF_000836945.1_ViralProj14044_genomic.fna.gz\"\n", "genome = 'Escherichia virus T4.fna.gz'\n", "subprocess.check_call(['wget', url, '-O', genome])\n", "subprocess.check_call(['gzip', '-d', genome])" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-8", "source": "
\n
\n
wget https://ftp.ncbi.nlm.nih.gov/.... -O \"Escherichia virus T4.fna.gz\"\ngzip -d \"Escherichia virus T4.fna.gz\"\n
\n
\n

The above segment

\n\n

This list is especially important. When you run commands on the command line, normally you just type in a really bit of text by yourself. It’s one big string, and you’re responsible for making sure quotation marks appear in the right place. For instance, if you have spaces in your filenames, you have to quote the filename. Python requires you specify a list of arguments, and then handles the quoting for you! Which, honestly, is easier and safer.

\n
\n
\n
Code In: Terminal
\n

Here we manually quote the argument

\n
glimmer3 \"bow genome.txt\"\n
\n
\n
\n
Code Out: Python
\n

Here python handles that for us

\n
subprocess.check_call(['glimmer3', 'bow genome.txt'])\n
\n
\n
\n
\n
\n

This is one of the major reasons we don’t use os.system or older Python interfaces for running commands.\nIf you’re processing files, and a user supplies a file with a space, if your program isn’t expecting that space in that filename, then it could do something dangerous!\nLike exploit your system!

\n

So, always use subprocess if you run to commands, never any other module, despite what you see on the internet!

\n
\n

There are more functions in the module, but the vast majority of the time, those are sufficient.

\n

Check Output: Gene Calling with Augustus

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-9", "source": [ "gff3 = subprocess.check_output([\n", " 'augustus', # Our command\n", " '--species=E_coli_K12', # the first argument, the species, we're using a phage so we call genes based on it's host organism\n", " 'Escherichia virus T4.fna', # The path to our genome file, without the .gz because we decompressed it.\n", " '--gff3=on' # We would like gff3 formatted output (it's easier to parse!)\n", "])\n", "\n", "gff3 = gff3.decode('utf-8')\n", "gff3 = gff3.split('\\n')" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-10", "source": "
\n
\n
augustus --species=E_coli_K12 'Escherichia virus T4.fna' --gff3=on\n
\n
\n

If you’re using subprocess.check_output() python doesn’t return plain text str to you, instead it returns a bytes object. We can decode that into text with .decode('utf-8'), a phrase you should memorise as going next to check_output(), for 99% of use cases.

\n

Let’s look at the results!

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-11", "source": [ "print(gff3[0:20])" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-12", "source": "

It’s a lot of comment lines, starting with #. Let’s remove those

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-13", "source": [ "cleaned_gff3 = []\n", "for line in gff3:\n", " if line.startswith('#'):\n", " continue\n", " cleaned_gff3.append(line)\n", "\n", "print(cleaned_gff3[0:20])" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-14", "source": "

And now you’ve got a set of gff3 formatted gene calls! You can use all of your loop processing skills to slice and dice this data into something great!

\n

Aside: stdin, stderr, stdout

\n

All unix processes have three default file handles that are available to them:

\n\n

Pipes

\n

One of the more complicated cases, however, is when you need pipes.

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-15", "source": [ "url = \"https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/001/721/125/GCF_001721125.1_ASM172112v1/GCF_001721125.1_ASM172112v1_cds_from_genomic.fna.gz\"\n", "cds = 'E. Coli CDSs.fna.gz'\n", "subprocess.check_call(['wget', url, '-O', cds])\n", "subprocess.check_call(['gzip', '-d', cds])" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-16", "source": "

With subprocesses, you can control the stdin, and stdout of the process by using file handles.

\n
\n
\n
Code In: Terminal
\n

Here we pipe a file to a process named build-icm which takes one argument, the output name. It reads sequences from stdin.

\n
cat seq.fa | build-icm test.icm\n# OR\nbuild-icm test.icm < seq.fa\n
\n
\n
\n
Code Out: Python
\n

Here we need to do a bit more.

\n
    \n
  1. Open a file handle
  2. \n
  3. Pass that file handle to check_call or check_output. This determines where stdin comes from.\n
    with open('seq.fa', 'r') as handle:\n subprocess.check_call(['build-icm', 'test.icm'], stdin=handle)\n
    \n
  4. \n
\n
\n
\n

We’ll do that now:

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-17", "source": [ "with open('E. Coli CDSs.fna', 'r') as handle:\n", " subprocess.check_call(['build-icm', 'test.icm'], stdin=handle)" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-18", "source": "
\n
\n
build-icm test.icm < 'E. Coli CDSs.fna'\n
\n
\n

Here we build a model, based on the sequences of E. Coli K-12, that Glimmer3 can use.

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-19", "source": [ "output = subprocess.check_output([\n", " 'glimmer3', # Our program\n", " 'Escherichia virus T4.fna', # The input genome\n", " 'test.icm', # The model we just built\n", " 't4-genes' # The base name for output files. It'll produce t4-genes.detail and t4-genes.predict.\n", "]).decode('utf-8') # And of course we decode as utf-8\n", "\n", "print(output)" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-20", "source": "
\n
\n
glimmer3 'Escherichia virus T4.fna' test.icm t4-genes\n
\n
\n

What happened here? The output of the program was written to stderr, not stdout, so Python may print that out to your screen, but output will be empty. To solve this common problem we can re-run the program and collect both stdout and stderr.

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-21", "source": [ "output = subprocess.check_output([\n", " 'glimmer3', # Our program\n", " 'Escherichia virus T4.fna', # The input genome\n", " 'test.icm', # The model we just built\n", " 't4-genes' # The base name for output files. It'll produce t4-genes.detail and t4-genes.predict.\n", "], stderr=subprocess.STDOUT).decode('utf-8') # And of course we decode as utf-8\n", "\n", "print(output)" ], "cell_type": "code", "execution_count": null, "outputs": [], "metadata": { "attributes": { "classes": [ "" ], "id": "" } } }, { "id": "cell-22", "source": "

Here we’ve re-directed the stderr to stdout and mixed both of them together. This isn’t always what we want, but here the program produces no output, and we can do that safely, and now we can parse it or do any other computations we need with it! Our Glimmer3 gene calls are in t4-genes.detail and t4-genes.predict if we want to open and process those as well.

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "cell_type": "markdown", "id": "final-ending-cell", "metadata": { "editable": false, "collapsed": false }, "source": [ "# Key Points\n\n", "- **DO NOT USE `os.system`**\n", "- **DO NOT USE shell=True**\n", "- 👍Use `subprocess.check_call()` if you don't care about the output, just that it succeeds.\n", "- 👍Use `subprocess.check_output()` if you want the output\n", "- Use `.decode('utf-8')` to read the output of `check_output()`\n", "\n# Congratulations on successfully completing this tutorial!\n\n", "Please [fill out the feedback on the GTN website](https://training.galaxyproject.org/training-material/topics/data-science/tutorials/python-subprocess/tutorial.html#feedback) and check there for further resources!\n" ] } ] }