Seashore: A Collection of Shell Abstractions

For many months now, Shopkick has been undergoing an infrastructure revolution. Modern infrastructure means automation, and we find ourselves automating UNIX-like systems a lot. We like UNIX, because you can do amazingly powerful things in just a couple lines:

eval $(docker-machine env default)
docker run --rm quay.io/manylinux1 /opt/python/cp27mu/bin/python --version

While we like UNIX, we love Python. Unfortunately, the equivalent Python code gets quite a bit heavier using only the standard library. First we would have to manually parse the output from docker-machine env, and then write a long subprocess.check_call([...]).

Is it possible to have our UNIX cake and elegantly eat it in Python? We want the formality of Python, with its ability to handle, say, file names with spaces in them and the ease of writing command-lines in shell. And can we do even better, and generate code that is amenable to unit testing, something which the UNIX shell provides no straightforward facility?

We certainly think so!

Just in time for summer, Seashore is a collection of elegant shell abstractions with an eye toward brevity and testability.

Using Seashore, the code above can be rewritten as:

from seashore import Executor, Shell, NO_VALUE
xctr = Executor(Shell())
dm_xctr = xctr.in_docker_machine('default')
version = dm_xctr.docker.run(
    'quay.io/manylinux1', '/opt/python/cp27-cp27mu/bin/python',
    '--version', remove=NO_VALUE).batch()[0].strip()
print("version is {}".format(version))

Since all of the low-level work of running a command line is done in Shell(), it is reasonably easy to convert this into unit-testable code:

# python_version.py
from seashore import Executor, Shell, NO_VALUE

def get_python_27_version(shell=None):
    xctr = Executor(shell or Shell())
    dm_xctr = xctr.in_docker_machine('default')
    version = dm_xctr.docker.run(
        'quay.io/manylinux1', '/opt/python/cp27-cp27mu/bin/python',
        '--version', remove=NO_VALUE).batch()[0].strip()
    return version

Now, we can unit test with something along the lines of

import unittest, mock
import python_version

class TestPythonVersion(unittest.TestCase):
    def test_basic(self):
        shell = mock.Mock()
        def side_effect(cmd, **kwargs):
            if cmd[0] == 'docker-machine':
                return SOMETHING_THAT_LOOKS_LIKE_DOCKER_MACHINE_OUTPUT, ''
            elif cmd[0] == 'docker':
                return '2.7.1242', ''
            shell.batch.side_effect = side_effect
            version = python_version.get_python_27_version(shell)
            self.assertEquals(version, '2.7.1242')
            # Assert that shell.batch was called with the right arguments

Seashore has native support for common Python and Python-adjacent tools, including git, pip, virtualenv, conda, docker, and more! Plus, it can be easily extended to support all your automation needs.

Seashore is available on GitHub and PyPI, where it's released under the MIT license.

Documentation is available on Read the Docs. Development is all public, and very active right now. With seashore already being used in production, all issues and pull requests are more than welcome!

Seashore is just the first of many Shopkick open-source releases we hope to bring you here on tech.shopkick.com. Be sure to subscribe for more!