diff --git a/.circleci/config.yml b/.circleci/config.yml
index d16c1f28..e7404d85 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,7 +2,7 @@
 version: 2.1
 
 orbs:
-  singularity: singularity/singularity@1.0.7
+  singularity: singularity/singularity@1.0.9
 
 workflows:
   version: 2
@@ -14,34 +14,22 @@ workflows:
               ignore: master
           name: "Latest Singularity - Python 3"
           run-linter: false
-          python: 3
-          singularity: 3.2.1
+          singularity: 3.5.0
           singularity-3: true
       - run-spython-ci-tests:
           <<: *base_job
-          name: "Singularity 3.1.0 - Python 3"
-          singularity: 3.1.0
-          run-linter: true # Use any job to run linter once
-      - run-spython-ci-tests:
-          <<: *base_job
-          name: "Singularity 3.2.0 - Python 3"
-          singularity: 3.2.0
+          name: "Singularity 3.2.1 - Python 3"
+          singularity: 3.2.1
+          run-linter: true # run linter once, must be Python 3
       - run-spython-ci-tests:
           <<: *base_job
-          name: "Latest Singularity - Python 2"
-          python: 2
+          name: "Singularity 3.1.0 - Python 3"
+          singularity: 3.1.0
       - run-spython-ci-tests:
           <<: *base_job
           name: "Singularity 2 - Python 3"
           singularity: 2.6.1
           singularity-3: false
-          python: 3
-      - run-spython-ci-tests:
-          <<: *base_job
-          name: "Singularity 2 - Python 2"
-          singularity: 2.6.1
-          singularity-3: false
-          python: 2
 
 waitforapt: &waitforapt
   name: Remove cloud init lock
@@ -63,13 +51,9 @@ install_spython: &install_spython
 
 commands:
   install_dependencies:
-    parameters:
-      python:
-        type: integer
-        description: "Python version: 2 or 3"
     steps:
       - run: |
-          PYTHON_VERSION="<< parameters.python >>"
+          PYTHON_VERSION=3
           CONDA_PATH="$HOME/conda/Python${PYTHON_VERSION}"
           echo 'export PATH="'"$CONDA_PATH"'/bin:$PATH"' >> "$BASH_ENV"
           source "$BASH_ENV"
@@ -83,13 +67,14 @@ commands:
           python --version
           [ $(python -c'import sys;print(sys.version_info.major)') -eq $PYTHON_VERSION ]
 
-          pip install --upgrade pylint pytest
+          pip install --upgrade pytest
+          pip install black || true
 
 run_linter: &run_linter
   name: run linter
   command: |
       cd ~/repo
-      pylint spython
+      black --check spython
 
 test_spython: &test_spython
   name: Test Singularity Python
@@ -101,10 +86,6 @@ jobs:
         run-linter:
           type: boolean
           default: false
-        python:
-          type: integer
-          description: "Python version: 2 or 3"
-          default: 3
         singularity:
           type: string
           description: "Singularity version"
@@ -118,14 +99,13 @@ jobs:
       - checkout
       - restore_cache:
           keys: v2-dependencies
-      - install_dependencies:
-          python: << parameters.python >>
+      - install_dependencies
       - run: *waitforapt
       - when:
           condition: << parameters.singularity-3 >>
           steps:
             - singularity/install-go:
-                go-version: 1.11.5
+                go-version: "1.13"
             - singularity/debian-install-3:
                 singularity-version: << parameters.singularity >>
       - unless:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8288bcc8..80917b29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ The client here will eventually be released as "spython" (and eventually to
 singularity on pypi), and the versions here will coincide with these releases.
 
 ## [master](https://github.com/singularityhub/singularity-cli/tree/master)
+ - instance logging functions for Singularity 3.5 and up (0.0.74)
  - add sudo_options option to spython.main.Client.build (0.0.73)
  - list of options and writable added to shell, execute, and run (0.0.72)
  - client is not honoring quiet for pull (0.0.71)
diff --git a/docs/pages/commands-instances.md b/docs/pages/commands-instances.md
index 24f53fa3..953a8664 100644
--- a/docs/pages/commands-instances.md
+++ b/docs/pages/commands-instances.md
@@ -314,6 +314,22 @@ $ Client.instances(name='creamier', quiet=True)
 No instances found.
 ```
 
+#### Logs
+
+If you are running Singularity 3.5 or later, the instance logs (error and output)
+should be programatically available, and you can return them via these functions:
+
+```bash
+logs = creamy.error_logs()
+logs = creamy.output_logs()
+```
+
+To print them to the screen, set print_logs to True:
+
+```bash
+logs = creamy.error_logs(print_logs=True)
+```
+
 <div>
     <a href="/singularity-cli/commands"><button class="previous-button btn btn-primary"><i class="fa fa-chevron-left"></i> </button></a>
     <a href="/singularity-cli/commands-oci"><button class="next-button btn btn-primary"><i class="fa fa-chevron-right"></i> </button></a>
diff --git a/setup.py b/setup.py
index 3db94764..4cfd7a67 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 '''
 
-Copyright (C) 2017-2019 Vanessa Sochat.
+Copyright (C) 2017-2020 Vanessa Sochat.
 
 This Source Code Form is subject to the terms of the
 Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
diff --git a/spython/__init__.py b/spython/__init__.py
index 8ba3b796..1f630a16 100644
--- a/spython/__init__.py
+++ b/spython/__init__.py
@@ -1,11 +1,11 @@
-'''
+"""
 
-Copyright (C) 2017-2019 Vanessa Sochat.
+Copyright (C) 2017-2020 Vanessa Sochat.
 
 This Source Code Form is subject to the terms of the
 Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-'''
+"""
 
 from spython.version import __version__
diff --git a/spython/client/__init__.py b/spython/client/__init__.py
index 5fc1218c..34eb62c9 100644
--- a/spython/client/__init__.py
+++ b/spython/client/__init__.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -14,71 +14,111 @@
 
 def get_parser():
 
-    parser = argparse.ArgumentParser(description="Singularity Client",
-                                formatter_class=argparse.RawTextHelpFormatter,
-                                add_help=False)
+    parser = argparse.ArgumentParser(
+        description="Singularity Client",
+        formatter_class=argparse.RawTextHelpFormatter,
+        add_help=False,
+    )
 
     # Global Options
-    parser.add_argument('--debug', '-d', dest="debug", 
-                        help="use verbose logging to debug.", 
-                        default=False, action='store_true')
+    parser.add_argument(
+        "--debug",
+        "-d",
+        dest="debug",
+        help="use verbose logging to debug.",
+        default=False,
+        action="store_true",
+    )
+
+    parser.add_argument(
+        "--quiet",
+        "-q",
+        dest="quiet",
+        help="suppress all normal output",
+        default=False,
+        action="store_true",
+    )
+
+    parser.add_argument(
+        "--version",
+        dest="version",
+        help="show singularity and spython version",
+        default=False,
+        action="store_true",
+    )
+
+    subparsers = parser.add_subparsers(
+        help="description",
+        title="actions",
+        description="actions for Singularity",
+        dest="command",
+        metavar="general usage",
+    )
 
-    parser.add_argument('--quiet', '-q', dest="quiet", 
-                        help="suppress all normal output", 
-                        default=False, action='store_true')
-
-    parser.add_argument('--version', dest="version", 
-                        help="show singularity and spython version", 
-                        default=False, action='store_true')
-
-    subparsers = parser.add_subparsers(help='description',
-                                       title='actions',
-                                       description='actions for Singularity',
-                                       dest="command", metavar='general usage')
-
-          
     # Recipes
 
-    recipe = subparsers.add_parser("recipe",
-                                   help="Recipe conversion and parsing")
-
-    recipe.add_argument('--entrypoint', dest="entrypoint",
-                         help="define custom entry point and prevent discovery", 
-                         default=None, type=str)
-
-    recipe.add_argument('--json', dest="json", 
-                        help="dump the (base) recipe content as json to the terminal", 
-                        default=False, action='store_true')
-
-    recipe.add_argument('--force', dest="force", 
-                        help="if the output file exists, overwrite.", 
-                        default=False, action='store_true')
-
-    recipe.add_argument("files", nargs='*',
-                        help="the recipe input file and [optional] output file", 
-                        type=str)
-
-    recipe.add_argument("--parser", type=str, 
-                        default="auto", dest="parser",
-                        choices=["auto", "docker", "singularity"],
-                        help="Is the input a Dockerfile or Singularity recipe?")
-
-    recipe.add_argument("--writer", type=str, 
-                        default="auto", dest="writer",
-                        choices=["auto", "docker", "singularity"],
-                        help="Should we write to Dockerfile or Singularity recipe?")
+    recipe = subparsers.add_parser("recipe", help="Recipe conversion and parsing")
+
+    recipe.add_argument(
+        "--entrypoint",
+        dest="entrypoint",
+        help="define custom entry point and prevent discovery",
+        default=None,
+        type=str,
+    )
+
+    recipe.add_argument(
+        "--json",
+        dest="json",
+        help="dump the (base) recipe content as json to the terminal",
+        default=False,
+        action="store_true",
+    )
+
+    recipe.add_argument(
+        "--force",
+        dest="force",
+        help="if the output file exists, overwrite.",
+        default=False,
+        action="store_true",
+    )
+
+    recipe.add_argument(
+        "files",
+        nargs="*",
+        help="the recipe input file and [optional] output file",
+        type=str,
+    )
+
+    recipe.add_argument(
+        "--parser",
+        type=str,
+        default="auto",
+        dest="parser",
+        choices=["auto", "docker", "singularity"],
+        help="Is the input a Dockerfile or Singularity recipe?",
+    )
+
+    recipe.add_argument(
+        "--writer",
+        type=str,
+        default="auto",
+        dest="writer",
+        choices=["auto", "docker", "singularity"],
+        help="Should we write to Dockerfile or Singularity recipe?",
+    )
 
     # General Commands
 
     subparsers.add_parser("shell", help="Interact with singularity python")
-    subparsers.add_parser("test", help='''Container testing (TBD)''')
+    subparsers.add_parser("test", help="""Container testing (TBD)""")
 
     return parser
 
 
 def set_verbosity(args):
-    '''determine the message level in the environment to set based on args.
-    '''
+    """determine the message level in the environment to set based on args.
+    """
     level = "INFO"
 
     if args.debug:
@@ -86,39 +126,41 @@ def set_verbosity(args):
     elif args.quiet:
         level = "QUIET"
 
-    os.environ['MESSAGELEVEL'] = level
-    os.putenv('MESSAGELEVEL', level)
-    os.environ['SINGULARITY_MESSAGELEVEL'] = level
-    os.putenv('SINGULARITY_MESSAGELEVEL', level)
-    
+    os.environ["MESSAGELEVEL"] = level
+    os.putenv("MESSAGELEVEL", level)
+    os.environ["SINGULARITY_MESSAGELEVEL"] = level
+    os.putenv("SINGULARITY_MESSAGELEVEL", level)
+
     # Import logger to set
     from spython.logger import bot
-    bot.debug('Logging level %s' %level)
+
+    bot.debug("Logging level %s" % level)
     import spython
 
     bot.debug("Singularity Python Version: %s" % spython.__version__)
 
 
 def version():
-    '''version prints the version, both for the user and help output
-    '''
+    """version prints the version, both for the user and help output
+    """
     import spython
+
     return spython.__version__
-    
+
 
 def main():
 
     parser = get_parser()
 
     def print_help(return_code=0):
-        '''print help, including the software version and active client 
+        """print help, including the software version and active client 
            and exit with return code.
-        '''
+        """
         v = version()
-        print("\nSingularity Python [v%s]\n" %(v))
+        print("\nSingularity Python [v%s]\n" % (v))
         parser.print_help()
         sys.exit(return_code)
-    
+
     if len(sys.argv) == 1:
         print_help()
     try:
@@ -139,13 +181,13 @@ def print_help(return_code=0):
     set_verbosity(args)
 
     # Does the user want help for a subcommand?
-    if args.command == 'recipe': 
+    if args.command == "recipe":
         from .recipe import main as func
 
-    elif args.command == 'shell':
+    elif args.command == "shell":
         from .shell import main as func
 
-    elif args.command == 'test': 
+    elif args.command == "test":
         from .test import main as func
 
     else:
@@ -156,5 +198,5 @@ def print_help(return_code=0):
         func(args=args, options=options, parser=parser)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
diff --git a/spython/client/recipe.py b/spython/client/recipe.py
index c8ceeea0..e18588f9 100644
--- a/spython/client/recipe.py
+++ b/spython/client/recipe.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2018 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,28 +8,26 @@
 from spython.main.parse.parsers import get_parser
 
 from spython.logger import bot
-from spython.utils import (
-    write_file, 
-    write_json
-)
+from spython.utils import write_file, write_json
 
 import json
 import sys
 import os
 
+
 def main(args, options, parser):
-    '''This function serves as a wrapper around the DockerParser, 
+    """This function serves as a wrapper around the DockerParser, 
        SingularityParser, DockerWriter, and SingularityParser converters. 
        We can either save to file if args.outfile is defined, or print 
        to the console if not.
-    '''
+    """
     # We need something to work with
     if not args.files:
         parser.print_help()
         sys.exit(1)
 
     # Get the user specified input and output files
-    outfile = None    
+    outfile = None
     if len(args.files) > 1:
         outfile = args.files[1]
 
@@ -41,24 +38,26 @@ def main(args, options, parser):
     # If the user wants to auto-detect the type
     if args.parser == "auto":
         if "dockerfile" in args.files[0].lower():
-            parser = get_parser('docker')
+            parser = get_parser("docker")
         elif "singularity" in args.files[0].lower():
-            parser = get_parser('singularity')
+            parser = get_parser("singularity")
 
     # If the parser still isn't defined, no go.
     if parser is None:
-        bot.exit('Please provide a Dockerfile or Singularity recipe, or define the --parser type.')
+        bot.exit(
+            "Please provide a Dockerfile or Singularity recipe, or define the --parser type."
+        )
 
     # If the writer needs auto-detect
     if args.writer == "auto":
         if parser.name == "docker":
-            writer = get_writer('singularity')
+            writer = get_writer("singularity")
         else:
-            writer = get_writer('docker')
+            writer = get_writer("docker")
 
     # If the writer still isn't defined, no go
     if writer is None:
-        bot.exit('Please define the --writer type.')
+        bot.exit("Please define the --writer type.")
 
     # Initialize the chosen parser
     recipeParser = parser(args.files[0])
@@ -82,12 +81,12 @@ def main(args, options, parser):
                 if force:
                     write_json(outfile, recipeParser.recipe.json())
                 else:
-                    bot.exit('%s exists, set --force to overwrite.' % outfile)
+                    bot.exit("%s exists, set --force to overwrite." % outfile)
         else:
             print(json.dumps(recipeParser.recipe.json(), indent=4))
 
     else:
- 
+
         # Do the conversion
         recipeWriter = writer(recipeParser.recipe)
         result = recipeWriter.convert(runscript=entrypoint, force=force)
diff --git a/spython/client/shell.py b/spython/client/shell.py
index 7baba508..13fca5c3 100644
--- a/spython/client/shell.py
+++ b/spython/client/shell.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2018 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -11,12 +10,10 @@ def main(args, options, parser):
     image = None
     if options:
         image = options.pop(0)
- 
-    lookup = {'ipython': ipython,
-              'python': python,
-              'bpython': run_bpython}
 
-    shells = ['ipython', 'python', 'bpython']
+    lookup = {"ipython": ipython, "python": python, "bpython": run_bpython}
+
+    shells = ["ipython", "python", "bpython"]
 
     # Otherwise present order of liklihood to have on system
     for shell in shells:
@@ -24,27 +21,31 @@ def main(args, options, parser):
             return lookup[shell](image)
         except ImportError:
             pass
-    
+
+
 def prepare_client(image):
-    '''prepare a client to embed in a shell with recipe parsers and writers.
-    '''
+    """prepare a client to embed in a shell with recipe parsers and writers.
+    """
     # The client will announce itself (backend/database) unless it's get
     from spython.main import get_client
     from spython.main.parse import parsers
     from spython.main.parse import writers
 
     client = get_client()
-    client.load(image)
+
+    if image:
+        client.load(image)
 
     # Add recipe parsers
     client.parsers = parsers
     client.writers = writers
     return client
 
+
 def ipython(image):
-    '''give the user an ipython shell
-    '''
-    client = prepare_client(image) # pylint: disable=unused-variable
+    """give the user an ipython shell
+    """
+    client = prepare_client(image)  # pylint: disable=unused-variable
 
     try:
         from IPython import embed
@@ -53,9 +54,10 @@ def ipython(image):
 
     embed()
 
+
 def run_bpython(image):
-    '''give the user a bpython shell
-    '''
+    """give the user a bpython shell
+    """
     client = prepare_client(image)
 
     try:
@@ -63,11 +65,13 @@ def run_bpython(image):
     except ImportError:
         return python(image)
 
-    bpython.embed(locals_={'client': client})
+    bpython.embed(locals_={"client": client})
+
 
 def python(image):
-    '''give the user a python shell
-    '''
+    """give the user a python shell
+    """
     import code
+
     client = prepare_client(image)
-    code.interact(local={"client":client})
+    code.interact(local={"client": client})
diff --git a/spython/client/test.py b/spython/client/test.py
index 360215dc..55552e71 100644
--- a/spython/client/test.py
+++ b/spython/client/test.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2018 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,6 +6,6 @@
 
 
 def main(args, options, parser):
-    print('TBA, additional tests for Singularity containers.')
-    print('What would you like to see? Let us know!')
-    print('https://www.github.com/singularityhub/singularity-cli/issues')
+    print("TBA, additional tests for Singularity containers.")
+    print("What would you like to see? Let us know!")
+    print("https://www.github.com/singularityhub/singularity-cli/issues")
diff --git a/spython/image/__init__.py b/spython/image/__init__.py
index 054c7861..28c25612 100644
--- a/spython/image/__init__.py
+++ b/spython/image/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2018 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -11,20 +10,19 @@
 from spython.logger import bot
 from spython.utils import split_uri
 
-class ImageBase(object):
 
+class ImageBase(object):
     def __str__(self):
-        protocol = getattr(self, 'protocol', None)
+        protocol = getattr(self, "protocol", None)
         if protocol:
-            return "%s://%s" %(protocol, self.image)
+            return "%s://%s" % (protocol, self.image)
         return self.image
 
-
     def __repr__(self):
         return self.__str__()
 
     def parse_image_name(self, image):
-        '''
+        """
             simply split the uri from the image. Singularity handles
             parsing of registry, namespace, image.
             
@@ -32,15 +30,14 @@ def parse_image_name(self, image):
             ==========
             image: the complete image uri to load (e.g., docker://ubuntu) 
 
-        '''
+        """
         self._image = image
         self.protocol, self.image = split_uri(image)
 
 
 class Image(ImageBase):
-
     def __init__(self, image=None):
-        '''An image here is an image file or a record.
+        """An image here is an image file or a record.
             The user can choose to load the image when starting the client, or
             update the main client with an image. The image object is kept
             with the main client to make running additional commands easier.
@@ -49,13 +46,12 @@ def __init__(self, image=None):
             ==========
             image: the image uri to parse (required)
 
-        '''
+        """
         super(Image, self).__init__()
         self.parse_image_name(image)
 
-
     def get_hash(self, image=None):
-        '''return an md5 hash of the file based on a criteria level. This
+        """return an md5 hash of the file based on a criteria level. This
            is intended to give the file a reasonable version. This only is
            useful for actual image files.
     
@@ -64,14 +60,14 @@ def get_hash(self, image=None):
            image: the image path to get hash for (first priority). Second
                   priority is image path saved with image object, if exists.
 
-        '''
+        """
         hasher = hashlib.md5()
         image = image or self.image
- 
+
         if os.path.exists(image):
             with open(image, "rb") as f:
                 for chunk in iter(lambda: f.read(4096), b""):
                     hasher.update(chunk)
                 return hasher.hexdigest()
 
-        bot.warning('%s does not exist.' % image)
+        bot.warning("%s does not exist." % image)
diff --git a/spython/image/cmd/__init__.py b/spython/image/cmd/__init__.py
index 85dc1c14..4d483a88 100644
--- a/spython/image/cmd/__init__.py
+++ b/spython/image/cmd/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,7 +6,7 @@
 
 
 def generate_image_commands():
-    ''' The Image client holds the Singularity image command group, mainly
+    """ The Image client holds the Singularity image command group, mainly
         deprecated commands (image.import) and additional command helpers
         that are commonly use but not provided by Singularity
 
@@ -22,14 +21,14 @@ def generate_image_commands():
             Client.image.decompress
             Client.image.create
 
-    '''
+    """
 
     class ImageClient(object):
         group = "image"
 
     from spython.main.base.logger import println
-    from spython.main.base.command import (init_command, run_command)
-    from .utils import (compress, decompress)
+    from spython.main.base.command import init_command, run_command
+    from .utils import compress, decompress
     from .create import create
     from .importcmd import importcmd
     from .export import export
@@ -42,6 +41,6 @@ class ImageClient(object):
     ImageClient.println = println
     ImageClient.init_command = init_command
     ImageClient.run_command = run_command
- 
+
     cli = ImageClient()
     return cli
diff --git a/spython/image/cmd/create.py b/spython/image/cmd/create.py
index 2b155bec..78c1d33e 100644
--- a/spython/image/cmd/create.py
+++ b/spython/image/cmd/create.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,8 +8,9 @@
 import os
 from spython.logger import bot
 
+
 def create(self, image_path, size=1024, sudo=False):
-    '''create will create a a new image
+    """create will create a a new image
 
         Parameters
         ==========
@@ -18,12 +18,13 @@ def create(self, image_path, size=1024, sudo=False):
         size: image sizein MiB, default is 1024MiB
         filesystem: supported file systems ext3/ext4 (ext[2/3]: default ext3
 
-    '''        
+    """
     from spython.utils import check_install
+
     check_install()
 
-    cmd = self.init_command('image.create')
-    cmd = cmd + ['--size', str(size), image_path]
+    cmd = self.init_command("image.create")
+    cmd = cmd + ["--size", str(size), image_path]
 
     output = self.run_command(cmd, sudo=sudo)
     self.println(output)
diff --git a/spython/image/cmd/export.py b/spython/image/cmd/export.py
index 715f8879..f1930c4b 100644
--- a/spython/image/cmd/export.py
+++ b/spython/image/cmd/export.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,8 +8,9 @@
 from spython.logger import bot
 import tempfile
 
+
 def export(self, image_path, tmptar=None):
-    '''export will export an image, sudo must be used.
+    """export will export an image, sudo must be used.
 
        Parameters
        ==========
@@ -18,16 +18,17 @@ def export(self, image_path, tmptar=None):
        image_path: full path to image
        tmptar: if defined, use custom temporary path for tar export
 
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
-    if 'version 3' in self.version():
-        bot.exit('export is deprecated after Singularity 2.*')
+    if "version 3" in self.version():
+        bot.exit("export is deprecated after Singularity 2.*")
 
     if tmptar is None:
-        tmptar = "/%s/tmptar.tar" %(tempfile.mkdtemp())
-    cmd = ['singularity', 'image.export', '-f', tmptar, image_path]
+        tmptar = "/%s/tmptar.tar" % (tempfile.mkdtemp())
+    cmd = ["singularity", "image.export", "-f", tmptar, image_path]
 
     self.run_command(cmd, sudo=False)
     return tmptar
diff --git a/spython/image/cmd/importcmd.py b/spython/image/cmd/importcmd.py
index 449526e6..ac8c990c 100644
--- a/spython/image/cmd/importcmd.py
+++ b/spython/image/cmd/importcmd.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,7 +6,7 @@
 
 
 def importcmd(self, image_path, input_source):
-    '''import will import (stdin) to the image
+    """import will import (stdin) to the image
 
        Parameters
        ==========
@@ -15,11 +14,12 @@ def importcmd(self, image_path, input_source):
        input_source: input source or file
        import_type: if not specified, imports whatever function is given
        
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
-    cmd = ['singularity', 'image.import', image_path, input_source]
+    cmd = ["singularity", "image.import", image_path, input_source]
     output = self.run_command(cmd, sudo=False)
-    self.println(output)        
+    self.println(output)
     return image_path
diff --git a/spython/image/cmd/utils.py b/spython/image/cmd/utils.py
index d99fbe57..758ef93b 100644
--- a/spython/image/cmd/utils.py
+++ b/spython/image/cmd/utils.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,23 +8,24 @@
 import os
 from spython.logger import bot
 
+
 def compress(self, image_path):
-    '''compress will (properly) compress an image'''
+    """compress will (properly) compress an image"""
     if os.path.exists(image_path):
-        compressed_image = "%s.gz" %image_path
-        os.system('gzip -c -6 %s > %s' %(image_path, compressed_image))
+        compressed_image = "%s.gz" % image_path
+        os.system("gzip -c -6 %s > %s" % (image_path, compressed_image))
         return compressed_image
 
-    bot.exit("Cannot find image %s" %image_path)
+    bot.exit("Cannot find image %s" % image_path)
 
 
 def decompress(self, image_path, quiet=True):
-    '''decompress will (properly) decompress an image'''
+    """decompress will (properly) decompress an image"""
 
     if not os.path.exists(image_path):
-        bot.exit("Cannot find image %s" %image_path)
-        
-    extracted_file = image_path.replace('.gz', '')
-    cmd = ['gzip', '-d', '-f', image_path]
-    self.run_command(cmd, quiet=quiet) # exits if return code != 0
+        bot.exit("Cannot find image %s" % image_path)
+
+    extracted_file = image_path.replace(".gz", "")
+    cmd = ["gzip", "-d", "-f", image_path]
+    self.run_command(cmd, quiet=quiet)  # exits if return code != 0
     return extracted_file
diff --git a/spython/instance/__init__.py b/spython/instance/__init__.py
index 1ed9e062..60026919 100644
--- a/spython/instance/__init__.py
+++ b/spython/instance/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -8,10 +7,10 @@
 from spython.image import ImageBase
 import os
 
-class Instance(ImageBase):
 
+class Instance(ImageBase):
     def __init__(self, image, start=True, name=None, **kwargs):
-        '''An instance is an image running as an instance with services.
+        """An instance is an image running as an instance with services.
             This class has functions appended under cmd/__init__ and is
             instantiated when the user calls Client.
 
@@ -21,7 +20,7 @@ def __init__(self, image, start=True, name=None, **kwargs):
             start: boolean to start the instance (default is True)
             name: a name for the instance (will generate RobotName 
                     if not provided)
-        '''
+        """
         super(Instance, self).__init__()
         self.parse_image_name(image)
         self.generate_name(name)
@@ -35,20 +34,19 @@ def __init__(self, image, start=True, name=None, **kwargs):
         if start:
             self.start(**kwargs)
 
-# Unique resource identifier
+    # Unique resource identifier
 
     def generate_name(self, name=None):
-        '''generate a Robot Name for the instance to use, if the user doesn't
+        """generate a Robot Name for the instance to use, if the user doesn't
            supply one.
-        '''
+        """
         # If no name provided, use robot name
         if name is None:
             name = self.RobotNamer.generate()
-        self.name = name.replace('-', '_')
-
+        self.name = name.replace("-", "_")
 
     def parse_image_name(self, image):
-        '''
+        """
             simply split the uri from the image. Singularity handles
             parsing of registry, namespace, image.
             
@@ -56,44 +54,42 @@ def parse_image_name(self, image):
             ==========
             image: the complete image uri to load (e.g., docker://ubuntu) 
 
-        '''
+        """
         self._image = image
-        self.protocol = 'instance'
-
+        self.protocol = "instance"
 
     def get_uri(self):
-        '''return the image uri (instance://) along with it's name
-        '''
+        """return the image uri (instance://) along with it's name
+        """
         return self.__str__()
 
-# Metadata
+    # Metadata
 
     def _update_metadata(self, kwargs=None):
-        '''Extract any additional attributes to hold with the instance
+        """Extract any additional attributes to hold with the instance
            from kwargs
-        '''
+        """
 
         # If not given metadata, use instance.list to get it for container
-        if kwargs is None and hasattr(self, 'name'):
+        if kwargs is None and hasattr(self, "name"):
             kwargs = self._list(self.name, quiet=True, return_json=True)
 
         # Add acceptable arguments
-        for arg in ['pid', 'name']:
+        for arg in ["pid", "name"]:
 
             # Skip over non-iterables:
             if arg in kwargs:
                 setattr(self, arg, kwargs[arg])
-       
+
         if "image" in kwargs:
-            self._image = kwargs['image']
+            self._image = kwargs["image"]
         elif "container_image" in kwargs:
-            self._image = kwargs['container_image']
-
+            self._image = kwargs["container_image"]
 
     def __str__(self):
-        if hasattr(self, 'name'):
+        if hasattr(self, "name"):
             if self.protocol:
-                return "%s://%s" %(self.protocol, self.name)
+                return "%s://%s" % (self.protocol, self.name)
         return os.path.basename(self._image)
 
     def __repr__(self):
diff --git a/spython/instance/cmd/__init__.py b/spython/instance/cmd/__init__.py
index 26bc1028..e3750281 100644
--- a/spython/instance/cmd/__init__.py
+++ b/spython/instance/cmd/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2018 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,11 +6,11 @@
 
 
 def generate_instance_commands():
-    ''' The Instance client holds the Singularity Instance command group
+    """ The Instance client holds the Singularity Instance command group
         The levels of verbosity (debug and quiet) are passed from the main
         client via the environment variable MESSAGELEVEL.
 
-    '''
+    """
     from spython.instance import Instance
 
     from spython.main.base.logger import println
@@ -19,10 +18,11 @@ def generate_instance_commands():
     from spython.utils import run_command as run_cmd
 
     # run_command uses run_cmd, but wraps to catch error
-    from spython.main.base.command import (init_command, run_command)
+    from spython.main.base.command import init_command, run_command
     from spython.main.base.generate import RobotNamer
     from .start import start
     from .stop import stop
+    from .logs import error_logs, output_logs, _logs
 
     Instance.RobotNamer = RobotNamer()
     Instance._init_command = init_command
@@ -30,10 +30,13 @@ def generate_instance_commands():
     Instance._run_command = run_command
     Instance._list = list_instances  # list command is used to get metadata
     Instance._println = println
-    Instance.start = start     # intended to be called on init, not by user
+    Instance.start = start  # intended to be called on init, not by user
     Instance.stop = stop
+    Instance.error_logs = error_logs
+    Instance.output_logs = output_logs
+    Instance._logs = _logs
 
     # Give an instance the ability to breed :)
     Instance.instance = Instance
- 
+
     return Instance
diff --git a/spython/instance/cmd/iutils.py b/spython/instance/cmd/iutils.py
index 2f2e8491..6d9e7e67 100644
--- a/spython/instance/cmd/iutils.py
+++ b/spython/instance/cmd/iutils.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -8,8 +7,9 @@
 from spython.instance import Instance
 from spython.logger import bot
 
+
 def parse_table(table_string, header, remove_rows=1):
-    '''parse a table to json from a string, where a header is expected by default.
+    """parse a table to json from a string, where a header is expected by default.
        Return a jsonified table.
 
        Parameters
@@ -18,9 +18,9 @@ def parse_table(table_string, header, remove_rows=1):
        header: header of expected table, must match dimension (number columns)
        remove_rows: an integer to indicate a number of rows to remove from top
                     the default is 1 assuming we don't want the header
-    '''
-    rows = [x for x in table_string.split('\n') if x]
-    rows = rows[0+remove_rows:]
+    """
+    rows = [x for x in table_string.split("\n") if x]
+    rows = rows[0 + remove_rows :]
 
     # Parse into json dictionary
     parsed = []
@@ -28,7 +28,7 @@ def parse_table(table_string, header, remove_rows=1):
     for row in rows:
         item = {}
         # This assumes no white spaces in each entry, which should be the case
-        row = [x for x in row.split(' ') if x]
+        row = [x for x in row.split(" ") if x]
         for i, r in enumerate(row):
             item[header[i]] = r
         parsed.append(item)
@@ -36,16 +36,17 @@ def parse_table(table_string, header, remove_rows=1):
 
 
 def get(self, name, return_json=False, quiet=False):
-    '''get is a list for a single instance. It is assumed to be running,
+    """get is a list for a single instance. It is assumed to be running,
        and we need to look up the PID, etc.
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
     # Ensure compatible for singularity prior to 3.0, and after 3.0
     subgroup = "instance.list"
 
-    if 'version 3' in self.version():
+    if "version 3" in self.version():
         subgroup = ["instance", "list"]
 
     cmd = self._init_command(subgroup)
@@ -55,37 +56,39 @@ def get(self, name, return_json=False, quiet=False):
 
     # Success, we have instances
 
-    if output['return_code'] == 0:
+    if output["return_code"] == 0:
 
         # Only print the table if we are returning json
         if not quiet:
-            print(''.join(output['message']))
+            print("".join(output["message"]))
 
         # Prepare json result from table
 
-        header = ['daemon_name', 'pid', 'container_image']
-        instances = parse_table(output['message'][0], header)
+        header = ["daemon_name", "pid", "container_image"]
+        instances = parse_table(output["message"][0], header)
 
         # Does the user want instance objects instead?
         listing = []
         if not return_json:
             for i in instances:
-                new_instance = Instance(pid=i['pid'],
-                                        name=i['daemon_name'],
-                                        image=i['container_image'],
-                                        start=False)
+                new_instance = Instance(
+                    pid=i["pid"],
+                    name=i["daemon_name"],
+                    image=i["container_image"],
+                    start=False,
+                )
 
                 listing.append(new_instance)
             instances = listing
 
     # Couldn't get UID
 
-    elif output['return_code'] == 255:
+    elif output["return_code"] == 255:
         bot.error("Couldn't get UID")
-        
+
     # Return code of 0
     else:
-        bot.info('No instances found.')
+        bot.info("No instances found.")
 
     # If we are given a name, return just one
     if name is not None and len(instances) == 1:
diff --git a/spython/instance/cmd/logs.py b/spython/instance/cmd/logs.py
new file mode 100644
index 00000000..58a0349b
--- /dev/null
+++ b/spython/instance/cmd/logs.py
@@ -0,0 +1,67 @@
+# Copyright (C) 2019-2020 Vanessa Sochat.
+
+# This Source Code Form is subject to the terms of the
+# Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
+# with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+from spython.utils import get_userhome, get_username
+from spython.logger import bot
+import platform
+import os
+
+
+def error_logs(self, print_logs=False):
+    """For Singularity 3.5 and later, we are able to programatically
+       derive the name of the log. In this case, return the content
+       to the user. See 
+       https://github.com/sylabs/singularity/issues/1115#issuecomment-560457918
+       for when this was added.
+
+       Parameters
+       ==========
+       print_logs: boolean to indicate to print to the screen along with
+                   return (defaults to False to just return log string)
+    """
+    return self._logs(print_logs, "err")
+
+
+def output_logs(self, print_logs=False):
+    """Get output logs for the user, if they exist.
+
+       Parameters
+       ==========
+       print_logs: boolean to indicate to print to the screen along with
+                   return (defaults to False to just return log string)
+    """
+    return self._logs(print_logs, "out")
+
+
+def _logs(self, print_logs=False, ext="out"):
+    """A shared function to print log files. The only differing element is
+       the extension (err or out)
+    """
+    from spython.utils import check_install, run_command
+
+    check_install()
+
+    # Formulate the path of the logs
+    hostname = platform.node()
+    logpath = os.path.join(
+        get_userhome(),
+        ".singularity",
+        "instances",
+        "logs",
+        hostname,
+        get_username(),
+        "%s.%s" % (self.name, ext),
+    )
+
+    if os.path.exists(logpath):
+        with open(logpath, "r") as filey:
+            logs = filey.read()
+        if print_logs is True:
+            print(logs)
+    else:
+        bot.warning("No log files have been produced.")
+    return logs
diff --git a/spython/instance/cmd/start.py b/spython/instance/cmd/start.py
index bf12ab1e..f1c5230f 100644
--- a/spython/instance/cmd/start.py
+++ b/spython/instance/cmd/start.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -8,8 +7,11 @@
 
 from spython.logger import bot
 
-def start(self, image=None, name=None, args=None, sudo=False, options=None, capture=False):
-    '''start an instance. This is done by default when an instance is created.
+
+def start(
+    self, image=None, name=None, args=None, sudo=False, options=None, capture=False
+):
+    """start an instance. This is done by default when an instance is created.
 
        Parameters
        ==========
@@ -24,8 +26,9 @@ def start(self, image=None, name=None, args=None, sudo=False, options=None, capt
        USAGE: 
        singularity [...] instance.start [...] <container path> <instance name>
 
-    '''        
-    from spython.utils import (run_command, check_install)
+    """
+    from spython.utils import run_command, check_install
+
     check_install()
 
     # If name provided, over write robot (default)
@@ -37,20 +40,20 @@ def start(self, image=None, name=None, args=None, sudo=False, options=None, capt
 
         # Not having this means it was called as a command, without an image
         if not hasattr(self, "_image"):
-            bot.exit('Please provide an image, or create an Instance first.')
+            bot.exit("Please provide an image, or create an Instance first.")
 
         image = self._image
 
     # Derive subgroup command based on singularity version
-    subgroup = 'instance.start'
-    if 'version 3' in self.version():
+    subgroup = "instance.start"
+    if "version 3" in self.version():
         subgroup = ["instance", "start"]
 
     cmd = self._init_command(subgroup)
 
     # Add options, if they are provided
     if not isinstance(options, list):
-        options = [] if options is None else options.split(' ')
+        options = [] if options is None else options.split(" ")
 
     # Assemble the command!
     cmd = cmd + options + [image, self.name]
@@ -68,12 +71,11 @@ def start(self, image=None, name=None, args=None, sudo=False, options=None, capt
 
     output = run_command(cmd, sudo=sudo, quiet=True, capture=capture)
 
-    if output['return_code'] == 0:
+    if output["return_code"] == 0:
         self._update_metadata()
 
     else:
-        message = '%s : return code %s' %(output['message'], 
-                                          output['return_code'])
+        message = "%s : return code %s" % (output["message"], output["return_code"])
         bot.error(message)
 
     return self
diff --git a/spython/instance/cmd/stop.py b/spython/instance/cmd/stop.py
index 449fa569..da180be4 100644
--- a/spython/instance/cmd/stop.py
+++ b/spython/instance/cmd/stop.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -8,8 +7,9 @@
 
 from spython.logger import bot
 
+
 def stop(self, name=None, sudo=False):
-    '''stop an instance. This is done by default when an instance is created.
+    """stop an instance. This is done by default when an instance is created.
 
        Parameters
        ==========
@@ -19,13 +19,14 @@ def stop(self, name=None, sudo=False):
        USAGE: 
        singularity [...] instance.stop [...] <instance name>
 
-    '''        
-    from spython.utils import (check_install, run_command)
+    """
+    from spython.utils import check_install, run_command
+
     check_install()
 
-    subgroup = 'instance.stop'
+    subgroup = "instance.stop"
 
-    if 'version 3' in self.version():
+    if "version 3" in self.version():
         subgroup = ["instance", "stop"]
 
     cmd = self._init_command(subgroup)
@@ -33,15 +34,14 @@ def stop(self, name=None, sudo=False):
     # If name is provided assume referencing an instance
     instance_name = self.name
     if name is not None:
-        instance_name = name     
+        instance_name = name
     cmd = cmd + [instance_name]
-    
+
     output = run_command(cmd, sudo=sudo, quiet=True)
 
-    if output['return_code'] != 0:
-        message = '%s : return code %s' %(output['message'], 
-                                          output['return_code'])
+    if output["return_code"] != 0:
+        message = "%s : return code %s" % (output["message"], output["return_code"])
         bot.error(message)
-        return output['return_code']
+        return output["return_code"]
 
-    return output['return_code']
+    return output["return_code"]
diff --git a/spython/logger/compatibility.py b/spython/logger/compatibility.py
index aac29584..dc6d5a06 100644
--- a/spython/logger/compatibility.py
+++ b/spython/logger/compatibility.py
@@ -2,10 +2,15 @@
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+
 def decodeUtf8String(inputStr):
-    '''Convert an UTF8 sequence into a string
+    """Convert an UTF8 sequence into a string
 
     Required for compatibility with Python 2 where str==bytes
     inputStr -- Either a str or bytes instance with UTF8 encoding
-    '''
-    return inputStr if isinstance(inputStr, str) or not isinstance(inputStr, bytes) else inputStr.decode('utf8')
+    """
+    return (
+        inputStr
+        if isinstance(inputStr, str) or not isinstance(inputStr, bytes)
+        else inputStr.decode("utf8")
+    )
diff --git a/spython/logger/message.py b/spython/logger/message.py
index f04a304f..8554e53a 100644
--- a/spython/logger/message.py
+++ b/spython/logger/message.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -29,95 +28,95 @@
 DARKRED = "\033[31m"
 CYAN = "\033[36m"
 
-class SingularityMessage:
 
+class SingularityMessage:
     def __init__(self, MESSAGELEVEL=None):
         self.level = get_logging_level()
         self.history = []
         self.errorStream = sys.stderr
         self.outputStream = sys.stdout
         self.colorize = self.useColor()
-        self.colors = {ABORT: DARKRED,
-                       CRITICAL: RED,
-                       ERROR: RED,    
-                       WARNING: YELLOW,  
-                       LOG: PURPLE,      
-                       CUSTOM: PURPLE,       
-                       DEBUG: CYAN,      
-                       'OFF': "\033[0m", # end sequence
-                       'CYAN':CYAN,
-                       'PURPLE':PURPLE,
-                       'RED':RED,
-                       'DARKRED':DARKRED,
-                       'YELLOW':YELLOW}
-                       
+        self.colors = {
+            ABORT: DARKRED,
+            CRITICAL: RED,
+            ERROR: RED,
+            WARNING: YELLOW,
+            LOG: PURPLE,
+            CUSTOM: PURPLE,
+            DEBUG: CYAN,
+            "OFF": "\033[0m",  # end sequence
+            "CYAN": CYAN,
+            "PURPLE": PURPLE,
+            "RED": RED,
+            "DARKRED": DARKRED,
+            "YELLOW": YELLOW,
+        }
 
     # Colors --------------------------------------------
 
     def useColor(self):
-        '''useColor will determine if color should be added
+        """useColor will determine if color should be added
            to a print. Will check if being run in a terminal, and
            if has support for ascii
-        '''
+        """
         COLORIZE = get_user_color_preference()
         if COLORIZE is not None:
             return COLORIZE
         streams = [self.errorStream, self.outputStream]
         for stream in streams:
-            if not hasattr(stream, 'isatty'):
+            if not hasattr(stream, "isatty"):
                 return False
             if not stream.isatty():
                 return False
         return True
 
     def addColor(self, level, text):
-        '''addColor to the prompt (usually prefix) if terminal
+        """addColor to the prompt (usually prefix) if terminal
            supports, and specified to do so
-        '''
+        """
         if self.colorize:
             if level in self.colors:
-                text = "%s%s%s" % (self.colors[level],
-                                   text,
-                                   self.colors["OFF"])
+                text = "%s%s%s" % (self.colors[level], text, self.colors["OFF"])
         return text
 
     def emitError(self, level):
-        '''determine if a level should print to
+        """determine if a level should print to
            stderr, includes all levels but INFO and QUIET
-        '''
-        if level in [ABORT,
-                     ERROR,
-                     WARNING,
-                     VERBOSE,
-                     VERBOSE1,
-                     VERBOSE2,
-                     VERBOSE3,
-                     DEBUG]:
+        """
+        if level in [
+            ABORT,
+            ERROR,
+            WARNING,
+            VERBOSE,
+            VERBOSE1,
+            VERBOSE2,
+            VERBOSE3,
+            DEBUG,
+        ]:
             return True
         return False
 
     def emitOutput(self, level):
-        '''determine if a level should print to stdout
-        only includes INFO'''
-        if level in [LOG,
-                     INFO]:
+        """determine if a level should print to stdout
+        only includes INFO"""
+        if level in [LOG, INFO]:
             return True
         return False
 
     def isEnabledFor(self, messageLevel):
-        '''check if a messageLevel is enabled to emit a level
-        '''
+        """check if a messageLevel is enabled to emit a level
+        """
         if messageLevel <= self.level:
             return True
         return False
 
     def emit(self, level, message, prefix=None, color=None):
-        '''emit is the main function to print the message
+        """emit is the main function to print the message
         optionally with a prefix
         :param level: the level of the message
         :param message: the message to print
         :param prefix: a prefix for the message
-        '''
+        """
         if color is None:
             color = level
 
@@ -130,7 +129,7 @@ def emit(self, level, message, prefix=None, color=None):
         # Add the prefix
         message = "%s%s" % (prefix, message)
 
-        if not message.endswith('\n'):
+        if not message.endswith("\n"):
             message = "%s\n" % message
 
         # If the level is quiet, only print to error
@@ -148,43 +147,43 @@ def emit(self, level, message, prefix=None, color=None):
         self.history.append(message)
 
     def write(self, stream, message):
-        '''write will write a message to a stream,
+        """write will write a message to a stream,
         first checking the encoding
-        '''
+        """
         stream.write(decodeUtf8String(message))
 
     def get_logs(self, join_newline=True):
-        ''''get_logs will return the complete history, joined by newline
+        """'get_logs will return the complete history, joined by newline
         (default) or as is.
-        '''
+        """
         if join_newline:
-            return '\n'.join(self.history)
+            return "\n".join(self.history)
         return self.history
 
-
     def show_progress(
-            self,
-            iteration,
-            total,
-            length=40,
-            min_level=0,
-            prefix=None,
-            carriage_return=True,
-            suffix=None,
-            symbol=None):
-        '''create a terminal progress bar, default bar shows for verbose+
+        self,
+        iteration,
+        total,
+        length=40,
+        min_level=0,
+        prefix=None,
+        carriage_return=True,
+        suffix=None,
+        symbol=None,
+    ):
+        """create a terminal progress bar, default bar shows for verbose+
         :param iteration: current iteration (Int)
         :param total: total iterations (Int)
         :param length: character length of bar (Int)
-        '''
+        """
         percent = 100 * (iteration / float(total))
         progress = int(length * iteration // total)
 
         if suffix is None:
-            suffix = ''
+            suffix = ""
 
         if prefix is None:
-            prefix = 'Progress'
+            prefix = "Progress"
 
         # Download sizes can be imperfect, setting carriage_return to False
         # and writing newline with caller cleans up the UI
@@ -196,41 +195,39 @@ def show_progress(
             symbol = "="
 
         if progress < length:
-            bar = symbol * progress + '|' + '-' * (length - progress - 1)
+            bar = symbol * progress + "|" + "-" * (length - progress - 1)
         else:
-            bar = symbol * progress + '-' * (length - progress)
+            bar = symbol * progress + "-" * (length - progress)
 
         # Only show progress bar for level > min_level
         if self.level > min_level:
             percent = "%5s" % ("{0:.1f}").format(percent)
-            output = '\r' + prefix + \
-                " |%s| %s%s %s" % (bar, percent, '%', suffix)
+            output = "\r" + prefix + " |%s| %s%s %s" % (bar, percent, "%", suffix)
             sys.stdout.write(output)
             if iteration == total and carriage_return:
-                sys.stdout.write('\n')
+                sys.stdout.write("\n")
             sys.stdout.flush()
 
     # Logging ------------------------------------------
 
-
     def abort(self, message):
-        self.emit(ABORT, message, 'ABORT')
+        self.emit(ABORT, message, "ABORT")
 
     def critical(self, message):
-        self.emit(CRITICAL, message, 'CRITICAL')
+        self.emit(CRITICAL, message, "CRITICAL")
 
     def error(self, message):
-        self.emit(ERROR, message, 'ERROR')
+        self.emit(ERROR, message, "ERROR")
 
     def exit(self, message, return_code=1):
-        self.emit(ERROR, message, 'ERROR')
+        self.emit(ERROR, message, "ERROR")
         sys.exit(return_code)
 
     def warning(self, message):
-        self.emit(WARNING, message, 'WARNING')
+        self.emit(WARNING, message, "WARNING")
 
     def log(self, message):
-        self.emit(LOG, message, 'LOG')
+        self.emit(LOG, message, "LOG")
 
     def custom(self, prefix, message, color=PURPLE):
         self.emit(CUSTOM, message, prefix, color)
@@ -251,51 +248,48 @@ def verbose1(self, message):
         self.emit(VERBOSE, message, "VERBOSE1")
 
     def verbose2(self, message):
-        self.emit(VERBOSE2, message, 'VERBOSE2')
+        self.emit(VERBOSE2, message, "VERBOSE2")
 
     def verbose3(self, message):
-        self.emit(VERBOSE3, message, 'VERBOSE3')
+        self.emit(VERBOSE3, message, "VERBOSE3")
 
     def debug(self, message):
-        self.emit(DEBUG, message, 'DEBUG')
+        self.emit(DEBUG, message, "DEBUG")
 
     def is_quiet(self):
-        '''is_quiet returns true if the level is under 1
-        '''
+        """is_quiet returns true if the level is under 1
+        """
         if self.level < 1:
             return False
         return True
 
-
     # Terminal ------------------------------------------
 
     def table(self, rows, col_width=2):
-        '''table will print a table of entries. If the rows is 
+        """table will print a table of entries. If the rows is 
         a dictionary, the keys are interpreted as column names. if
         not, a numbered list is used.
-        '''
+        """
 
         labels = [str(x) for x in range(1, len(rows) + 1)]
         if isinstance(rows, dict):
             labels = list(rows.keys())
             rows = list(rows.values())
 
-        for row in rows: 
+        for row in rows:
             label = labels.pop(0)
             label = label.ljust(col_width)
             message = "\t".join(row)
-            self.custom(prefix=label,
-                        message=message)
-        
+            self.custom(prefix=label, message=message)
 
 
 def get_logging_level():
-    '''configure a logging to standard out based on the user's
+    """configure a logging to standard out based on the user's
     selected level, which should be in an environment variable called
     MESSAGELEVEL. if MESSAGELEVEL is not set, the info level
     (1) is assumed (all informational messages).
-    '''
-    level = os.environ.get("MESSAGELEVEL", "1") # INFO
+    """
+    level = os.environ.get("MESSAGELEVEL", "1")  # INFO
 
     if level == "CRITICAL":
         return CRITICAL
@@ -324,15 +318,15 @@ def get_logging_level():
 
 
 def get_user_color_preference():
-    COLORIZE = os.environ.get('SINGULARITY_COLORIZE', None)
+    COLORIZE = os.environ.get("SINGULARITY_COLORIZE", None)
     if COLORIZE is not None:
         COLORIZE = convert2boolean(COLORIZE)
     return COLORIZE
 
 
 def convert2boolean(arg):
-    '''convert2boolean is used for environmental variables that must be
-    returned as boolean'''
+    """convert2boolean is used for environmental variables that must be
+    returned as boolean"""
     if not isinstance(arg, bool):
         return arg.lower() in ("yes", "true", "t", "1", "y")
     return arg
diff --git a/spython/logger/progress.py b/spython/logger/progress.py
index 17f6efd4..d2f6a2e4 100644
--- a/spython/logger/progress.py
+++ b/spython/logger/progress.py
@@ -14,9 +14,9 @@
 
 STREAM = sys.stderr
 
-BAR_TEMPLATE = '%s[%s%s] %i/%i MB - %s\r'
-BAR_FILLED_CHAR = '-'
-BAR_EMPTY_CHAR = ' '
+BAR_TEMPLATE = "%s[%s%s] %i/%i MB - %s\r"
+BAR_FILLED_CHAR = "-"
+BAR_EMPTY_CHAR = " "
 
 # How long to wait before recalculating the ETA
 ETA_INTERVAL = 1
@@ -33,8 +33,16 @@ def __exit__(self, exc_type, exc_val, exc_tb):
         self.done()
         return False  # we're not suppressing exceptions
 
-    def __init__(self, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR,
-                 filled_char=BAR_FILLED_CHAR, expected_size=None, every=1):
+    def __init__(
+        self,
+        label="",
+        width=32,
+        hide=None,
+        empty_char=BAR_EMPTY_CHAR,
+        filled_char=BAR_FILLED_CHAR,
+        expected_size=None,
+        every=1,
+    ):
         self.label = label
         self.width = width
         self.hide = hide
@@ -65,21 +73,31 @@ def show(self, progress, count=None):
         self.last_progress = progress
         if (time.time() - self.etadelta) > ETA_INTERVAL:
             self.etadelta = time.time()
-            self.ittimes = \
-                self.ittimes[-ETA_SMA_WINDOW:] + \
-                    [-(self.start - time.time()) / (progress+1)]
-            self.eta = \
-                sum(self.ittimes) / float(len(self.ittimes)) * \
-                (self.expected_size - progress)
+            self.ittimes = self.ittimes[-ETA_SMA_WINDOW:] + [
+                -(self.start - time.time()) / (progress + 1)
+            ]
+            self.eta = (
+                sum(self.ittimes)
+                / float(len(self.ittimes))
+                * (self.expected_size - progress)
+            )
             self.etadisp = self.format_time(self.eta)
         x = int(self.width * progress / self.expected_size)
         if not self.hide:
-            if ((progress % self.every) == 0 or      # True every "every" updates
-                (progress == self.expected_size)):   # And when we're done
-                STREAM.write(BAR_TEMPLATE % (
-                    self.label, self.filled_char * x,
-                    self.empty_char * (self.width - x), progress,
-                    self.expected_size, self.etadisp))
+            if (progress % self.every) == 0 or (  # True every "every" updates
+                progress == self.expected_size
+            ):  # And when we're done
+                STREAM.write(
+                    BAR_TEMPLATE
+                    % (
+                        self.label,
+                        self.filled_char * x,
+                        self.empty_char * (self.width - x),
+                        progress,
+                        self.expected_size,
+                        self.etadisp,
+                    )
+                )
                 STREAM.flush()
 
     def done(self):
@@ -87,26 +105,47 @@ def done(self):
         elapsed_disp = self.format_time(self.elapsed)
         if not self.hide:
             # Print completed bar with elapsed time
-            STREAM.write(BAR_TEMPLATE % (
-                self.label, self.filled_char * self.width,
-                self.empty_char * 0, self.last_progress,
-                self.expected_size, elapsed_disp))
-            STREAM.write('\n')
+            STREAM.write(
+                BAR_TEMPLATE
+                % (
+                    self.label,
+                    self.filled_char * self.width,
+                    self.empty_char * 0,
+                    self.last_progress,
+                    self.expected_size,
+                    elapsed_disp,
+                )
+            )
+            STREAM.write("\n")
             STREAM.flush()
 
     def format_time(self, seconds):
-        return time.strftime('%H:%M:%S', time.gmtime(seconds))
-
-
-def bar(it, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR,
-        filled_char=BAR_FILLED_CHAR, expected_size=None, every=1):
+        return time.strftime("%H:%M:%S", time.gmtime(seconds))
+
+
+def bar(
+    it,
+    label="",
+    width=32,
+    hide=None,
+    empty_char=BAR_EMPTY_CHAR,
+    filled_char=BAR_FILLED_CHAR,
+    expected_size=None,
+    every=1,
+):
     """Progress iterator. Wrap your iterables with it."""
 
     count = len(it) if expected_size is None else expected_size
 
-    with ProgressBar(label=label, width=width, hide=hide, empty_char=BAR_EMPTY_CHAR,
-             filled_char=BAR_FILLED_CHAR, expected_size=count, every=every) \
-            as pbar:
+    with ProgressBar(
+        label=label,
+        width=width,
+        hide=hide,
+        empty_char=BAR_EMPTY_CHAR,
+        filled_char=BAR_FILLED_CHAR,
+        expected_size=count,
+        every=every,
+    ) as pbar:
         for i, item in enumerate(it):
             yield item
             pbar.show(i + 1)
diff --git a/spython/logger/spinner.py b/spython/logger/spinner.py
index ad792094..f218ea6c 100644
--- a/spython/logger/spinner.py
+++ b/spython/logger/spinner.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -11,44 +10,43 @@
 import threading
 from random import choice
 
+
 class Spinner:
     spinning = False
     delay = 0.1
 
     @staticmethod
     def spinning_cursor():
-        while 1: 
-            for cursor in '|/-\\':
+        while 1:
+            for cursor in "|/-\\":
                 yield cursor
 
     @staticmethod
     def balloons_cursor():
-        while 1: 
-            for cursor in '. o O @ *':
+        while 1:
+            for cursor in ". o O @ *":
                 yield cursor
 
     @staticmethod
     def changing_arrows():
-        while 1: 
-            for cursor in '<^>v':
+        while 1:
+            for cursor in "<^>v":
                 yield cursor
 
     def select_generator(self, generator):
         if generator is None:
-            generator = choice(['cursor',
-                                'arrow',
-                                'balloons'])
+            generator = choice(["cursor", "arrow", "balloons"])
 
         return generator
 
     def __init__(self, delay=None, generator=None):
         generator = self.select_generator(generator)
 
-        if generator == 'cursor':
+        if generator == "cursor":
             self.spinner_generator = self.spinning_cursor()
-        elif generator == 'arrow':
+        elif generator == "arrow":
             self.spinner_generator = self.changing_arrows()
-        elif generator == 'balloons':
+        elif generator == "balloons":
             self.spinner_generator = self.balloons_cursor()
             if delay is None:
                 delay = 0.2
@@ -63,7 +61,7 @@ def run(self):
             sys.stdout.write(next(self.spinner_generator))
             sys.stdout.flush()
             time.sleep(self.delay)
-            sys.stdout.write('\b')
+            sys.stdout.write("\b")
             sys.stdout.flush()
 
     def start(self):
diff --git a/spython/main/__init__.py b/spython/main/__init__.py
index ea98c221..16334b3e 100644
--- a/spython/main/__init__.py
+++ b/spython/main/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,7 +6,7 @@
 
 
 def get_client(quiet=False, debug=False):
-    '''
+    """
        get the client and perform imports not on init, in case there are any
        initialization or import errors. 
 
@@ -16,7 +15,7 @@ def get_client(quiet=False, debug=False):
        quiet: if True, suppress most output about the client
        debug: turn on debugging mode
 
-    '''
+    """
     from spython.utils import get_singularity_version
     from .base import Client as client
 
@@ -26,13 +25,13 @@ def get_client(quiet=False, debug=False):
     # Do imports here, can be customized
     from .apps import apps
     from .build import build
-    from .execute import (execute, shell) 
+    from .execute import execute, shell
     from .help import helpcmd
     from .inspect import inspect
-    from .instances import (list_instances, stopall) # global instance commands
+    from .instances import list_instances, stopall  # global instance commands
     from .run import run
     from .pull import pull
-    from .export import (export, _export)
+    from .export import export, _export
 
     # Actions
     client.apps = apps
@@ -49,10 +48,14 @@ def get_client(quiet=False, debug=False):
 
     # Command Groups, Images
     from spython.image.cmd import generate_image_commands  # deprecated
+
     client.image = generate_image_commands()
 
     # Commands Groups, Instances
-    from spython.instance.cmd import generate_instance_commands  # instance level commands
+    from spython.instance.cmd import (
+        generate_instance_commands,
+    )  # instance level commands
+
     client.instance = generate_instance_commands()
     client.instance_stopall = stopall
     client.instance.version = client.version
@@ -60,14 +63,14 @@ def get_client(quiet=False, debug=False):
     # Commands Groups, OCI (Singularity version 3 and up)
     if "version 3" in get_singularity_version():
         from spython.oci.cmd import generate_oci_commands
+
         client.oci = generate_oci_commands()()  # first () runs function, second
-                                                # initializes OciImage class
+        # initializes OciImage class
         client.oci.debug = client.debug
         client.oci.quiet = client.quiet
         client.oci.OciImage.quiet = client.quiet
         client.oci.OciImage.debug = client.debug
 
-
     # Initialize
     cli = client()
 
@@ -79,4 +82,5 @@ def get_client(quiet=False, debug=False):
 
     return cli
 
+
 Client = get_client()
diff --git a/spython/main/apps.py b/spython/main/apps.py
index 2592701d..2254f9b7 100644
--- a/spython/main/apps.py
+++ b/spython/main/apps.py
@@ -1,13 +1,12 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-def apps(self, image=None, full_path=False, root=''):
-    '''
+def apps(self, image=None, full_path=False, root=""):
+    """
        return list of SCIF apps in image. The Singularity software serves
        a scientific filesystem integration that will install apps to
        /scif/apps and associated data to /scif/data. For more information 
@@ -18,22 +17,23 @@ def apps(self, image=None, full_path=False, root=''):
        full_path: if True, return relative to scif base folder
        image_path: full path to the image
 
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
     # No image provided, default to use the client's loaded image
     if image is None:
         image = self._get_uri()
 
-    cmd = self._init_command('apps') + [image]
+    cmd = self._init_command("apps") + [image]
     output = self._run_command(cmd)
 
     if full_path:
-        root = '/scif/apps/'
+        root = "/scif/apps/"
 
     if output:
-        output = ''.join(output).split('\n')
-        output = ['%s%s' %(root, x) for x in output if x]
+        output = "".join(output).split("\n")
+        output = ["%s%s" % (root, x) for x in output if x]
 
     return output
diff --git a/spython/main/base/__init__.py b/spython/main/base/__init__.py
index f3ac1155..e6ee3907 100644
--- a/spython/main/base/__init__.py
+++ b/spython/main/base/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,28 +6,16 @@
 
 
 from spython.logger import bot
-from spython.utils import ( 
+from spython.utils import (
     check_install,
     get_singularity_version,
-    get_singularity_version_info
+    get_singularity_version_info,
 )
 
-from .command import (
-    generate_bind_list,
-    init_command,
-    run_command
-)
+from .command import generate_bind_list, init_command, run_command
 from .flags import parse_verbosity
-from .sutils import (
-    get_uri,
-    load,
-    setenv,
-    get_filename
-)
-from .logger import (
-    println,
-    init_level
-)
+from .sutils import get_uri, load, setenv, get_filename
+from .logger import println, init_level
 from .generate import RobotNamer
 
 import json
@@ -36,42 +23,41 @@
 import os
 import re
 
-class Client:
 
+class Client:
     def __str__(self):
         base = "[singularity-python]"
-        if hasattr(self, 'simage'):
-            if self.simage.image not in [None, '']:
-                base = "%s[%s]" %(base, self.simage)
+        if hasattr(self, "simage"):
+            if self.simage.image not in [None, ""]:
+                base = "%s[%s]" % (base, self.simage)
         return base
 
     def __repr__(self):
         return self.__str__()
 
     def __init__(self):
-        '''the base client for singularity, will have commands added to it.
+        """the base client for singularity, will have commands added to it.
            upon init, store verbosity requested in environment MESSAGELEVEL.
-        '''
+        """
         self._init_level()
 
     def version(self):
-        '''Shortcut to get_singularity_version, takes no arguments.
-        '''
+        """Shortcut to get_singularity_version, takes no arguments.
+        """
         return get_singularity_version()
 
     def version_info(self):
-        '''Shortcut to get_singularity_version_info, takes no arguments.
-        '''
+        """Shortcut to get_singularity_version_info, takes no arguments.
+        """
         return get_singularity_version_info()
 
     def _check_install(self):
-        '''ensure that singularity is installed, and exit if not.
-        '''
+        """ensure that singularity is installed, and exit if not.
+        """
         if check_install() is not True:
             bot.exit("Cannot find Singularity! Is it installed?")
 
 
-
 # Image Utils
 Client.load = load
 Client._get_filename = get_filename
diff --git a/spython/main/base/command.py b/spython/main/base/command.py
index 451c4136..fa41f092 100644
--- a/spython/main/base/command.py
+++ b/spython/main/base/command.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -15,31 +14,30 @@
 import os
 
 
-
 def init_command(self, action, flags=None):
-    '''return the initial Singularity command with any added flags.
+    """return the initial Singularity command with any added flags.
         
        Parameters
        ==========
        action: the main action to perform (e.g., build)
        flags: one or more additional flags (e.g, volumes) 
               not implemented yet.
-    '''
+    """
 
     if not isinstance(action, list):
-        action = [action]      
-    cmd = ['singularity'] + action
+        action = [action]
+    cmd = ["singularity"] + action
 
     if self.quiet:
-        cmd.insert(1, '--quiet')
+        cmd.insert(1, "--quiet")
     if self.debug:
-        cmd.insert(1, '--debug')
+        cmd.insert(1, "--debug")
 
     return cmd
 
 
 def generate_bind_list(self, bindlist=None):
-    '''generate bind string will take a single string or list of binds, and
+    """generate bind string will take a single string or list of binds, and
        return a list that can be added to an exec or run command. For example,
        the following map as follows:
 
@@ -55,35 +53,35 @@ def generate_bind_list(self, bindlist=None):
        ==========
        bindlist: a string or list of bind mounts
 
-    '''
+    """
     binds = []
-    
+
     # Case 1: No binds provided
     if not bindlist:
         return binds
 
     # Case 2: provides a long string or non list, and must be split
     if not isinstance(bindlist, list):
-        bindlist = bindlist.split(' ')
+        bindlist = bindlist.split(" ")
 
     for bind in bindlist:
 
         # Still cannot be None
         if bind:
-            bot.debug('Adding bind %s' %bind)
-            binds += ['--bind', bind]
+            bot.debug("Adding bind %s" % bind)
+            binds += ["--bind", bind]
 
             # Check that exists on host
-            host = bind.split(':')[0]
+            host = bind.split(":")[0]
             if not os.path.exists(host):
-                bot.error('%s does not exist on host.' %bind)
+                bot.error("%s does not exist on host." % bind)
                 sys.exit(1)
 
     return binds
 
 
 def send_command(self, cmd, sudo=False, stderr=None, stdout=None):
-    '''send command is a non interactive version of run_command, meaning
+    """send command is a non interactive version of run_command, meaning
        that we execute the command and return the return value, but don't
        attempt to stream any content (text from the screen) back to the
        user. This is useful for commands interacting with OCI bundles.
@@ -92,24 +90,27 @@ def send_command(self, cmd, sudo=False, stderr=None, stdout=None):
        ==========
        cmd: the list of commands to send to the terminal
        sudo: use sudo (or not)
-    '''
-    
+    """
+
     if sudo:
-        cmd = ['sudo'] + cmd
+        cmd = ["sudo"] + cmd
 
     process = subprocess.Popen(cmd, stderr=stderr, stdout=stdout)
     result = process.communicate()
     return result
 
 
-def run_command(self, cmd, 
-                      sudo=False,
-                      capture=True,
-                      quiet=None,
-                      return_result=False,
-                      sudo_options=None):
+def run_command(
+    self,
+    cmd,
+    sudo=False,
+    capture=True,
+    quiet=None,
+    return_result=False,
+    sudo_options=None,
+):
 
-    '''run_command is a wrapper for the global run_command, checking first
+    """run_command is a wrapper for the global run_command, checking first
        for sudo and exiting on error if needed. The message is returned as
        a list of lines for the calling function to parse, and stdout uses
        the parent process so it appears for the user.
@@ -123,23 +124,25 @@ def run_command(self, cmd,
        sudo_options: string or list of strings that will be passed as options to sudo
        On success, returns result.
 
-    '''
+    """
     # First preference to function, then to client setting
     if quiet is None:
         quiet = self.quiet
 
-    result = run_cmd(cmd, sudo=sudo, capture=capture, quiet=quiet, sudo_options=sudo_options)
+    result = run_cmd(
+        cmd, sudo=sudo, capture=capture, quiet=quiet, sudo_options=sudo_options
+    )
 
     # If one line is returned, squash dimension
-    if len(result['message']) == 1:
-        result['message'] = result['message'][0]
+    if len(result["message"]) == 1:
+        result["message"] = result["message"][0]
 
     # If the user wants to return the result, just return it
     if return_result:
         return result
 
     # On success, return result
-    if result['return_code'] == 0:
-        return result['message']
+    if result["return_code"] == 0:
+        return result["message"]
 
     return result
diff --git a/spython/main/base/flags.py b/spython/main/base/flags.py
index 3d649b58..a4ae7a9d 100644
--- a/spython/main/base/flags.py
+++ b/spython/main/base/flags.py
@@ -1,12 +1,11 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-'''
+"""
 GLOBAL OPTIONS:
     -d|--debug    Print debugging information
     -h|--help     Display usage summary
@@ -43,27 +42,28 @@
     image      Container image command group                                    
     instance   Persistent instance command group                                
 
-'''
+"""
+
 
 def parse_verbosity(self, args):
-    '''parse_verbosity will take an argument object, and return the args
+    """parse_verbosity will take an argument object, and return the args
        passed (from a dictionary) to a list
 
        Parameters
        ==========
        args: the argparse argument objects
 
-    '''
+    """
 
     flags = []
 
     if args.silent:
-        flags.append('--silent')
+        flags.append("--silent")
     elif args.quiet:
-        flags.append('--quiet')
+        flags.append("--quiet")
     elif args.debug:
-        flags.append('--debug')
+        flags.append("--debug")
     elif args.verbose:
-        flags.append('-' + 'v' * args.verbose)
+        flags.append("-" + "v" * args.verbose)
 
     return flags
diff --git a/spython/main/base/generate.py b/spython/main/base/generate.py
index 9e68616c..9841b012 100644
--- a/spython/main/base/generate.py
+++ b/spython/main/base/generate.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,43 +9,182 @@
 
 from random import choice
 
+
 class RobotNamer:
 
     _descriptors = [
-        'chunky', 'buttery', 'delicious', 'scruptious', 'dinosaur', 'boopy',
-        'lovely', 'carniverous', 'hanky', 'loopy', 'doopy', 'astute', 'gloopy',
-        'outstanding', 'stinky', 'conspicuous', 'fugly', 'frigid', 'angry',
-        'adorable', 'sticky', 'moolicious', 'cowy', 'spicy', 'grated', 'crusty',
-        'stanky', 'blank', 'bumfuzzled', 'fuzzy', 'hairy', 'peachy', 'tart',
-        'creamy', 'arid', 'strawberry', 'butterscotch', 'wobbly', 'persnickety',
-        'nerdy', 'dirty', 'placid', 'bloated', 'swampy', 'pusheena', 'hello',
-        'goodbye', 'milky', 'purple', 'rainbow', 'bricky', 'muffled', 'anxious',
-        'misunderstood', 'eccentric', 'quirky', 'lovable', 'reclusive', 'faux',
-        'evasive', 'confused', 'crunchy', 'expensive', 'ornery', 'fat', 'phat',
-        'joyous', 'expressive', 'psycho', 'chocolate', 'salted', 'gassy', 'red',
-        'blue'
+        "chunky",
+        "buttery",
+        "delicious",
+        "scruptious",
+        "dinosaur",
+        "boopy",
+        "lovely",
+        "carniverous",
+        "hanky",
+        "loopy",
+        "doopy",
+        "astute",
+        "gloopy",
+        "outstanding",
+        "stinky",
+        "conspicuous",
+        "fugly",
+        "frigid",
+        "angry",
+        "adorable",
+        "sticky",
+        "moolicious",
+        "cowy",
+        "spicy",
+        "grated",
+        "crusty",
+        "stanky",
+        "blank",
+        "bumfuzzled",
+        "fuzzy",
+        "hairy",
+        "peachy",
+        "tart",
+        "creamy",
+        "arid",
+        "strawberry",
+        "butterscotch",
+        "wobbly",
+        "persnickety",
+        "nerdy",
+        "dirty",
+        "placid",
+        "bloated",
+        "swampy",
+        "pusheena",
+        "hello",
+        "goodbye",
+        "milky",
+        "purple",
+        "rainbow",
+        "bricky",
+        "muffled",
+        "anxious",
+        "misunderstood",
+        "eccentric",
+        "quirky",
+        "lovable",
+        "reclusive",
+        "faux",
+        "evasive",
+        "confused",
+        "crunchy",
+        "expensive",
+        "ornery",
+        "fat",
+        "phat",
+        "joyous",
+        "expressive",
+        "psycho",
+        "chocolate",
+        "salted",
+        "gassy",
+        "red",
+        "blue",
     ]
 
     _nouns = [
-        'squidward', 'hippo', 'butter', 'animal', 'peas', 'lettuce', 'carrot',
-        'onion', 'peanut', 'cupcake', 'muffin', 'buttface', 'leopard', 'parrot',
-        'parsnip', 'poodle', 'itch', 'punk', 'kerfuffle', 'soup', 'noodle',
-        'avocado', 'peanut-butter', 'latke', 'milkshake', 'banana', 'lizard',
-        'lemur', 'lentil', 'bits', 'house', 'leader', 'toaster', 'signal',
-        'pancake', 'kitty', 'cat', 'cattywampus', 'poo', 'malarkey',
-        'general', 'rabbit', 'chair', 'staircase', 'underoos', 'snack', 'lamp',
-        'eagle', 'hobbit', 'diablo', 'earthworm', 'pot', 'plant', 'leg', 'arm',
-        'bike', 'citrus', 'dog', 'puppy', 'blackbean', 'ricecake', 'gato',
-        'nalgas', 'lemon', 'caramel', 'fudge', 'cherry', 'sundae', 'truffle',
-        'cinnamonbun', 'pastry', 'egg', 'omelette', 'fork', 'knife', 'spoon',
-        'salad', 'train', 'car', 'motorcycle', 'bicycle', 'platanos', 'mango',
-        'taco', 'pedo', 'nunchucks', 'destiny', 'hope', 'despacito', 'frito',
-        'chip'
+        "squidward",
+        "hippo",
+        "butter",
+        "animal",
+        "peas",
+        "lettuce",
+        "carrot",
+        "onion",
+        "peanut",
+        "cupcake",
+        "muffin",
+        "buttface",
+        "leopard",
+        "parrot",
+        "parsnip",
+        "poodle",
+        "itch",
+        "punk",
+        "kerfuffle",
+        "soup",
+        "noodle",
+        "avocado",
+        "peanut-butter",
+        "latke",
+        "milkshake",
+        "banana",
+        "lizard",
+        "lemur",
+        "lentil",
+        "bits",
+        "house",
+        "leader",
+        "toaster",
+        "signal",
+        "pancake",
+        "kitty",
+        "cat",
+        "cattywampus",
+        "poo",
+        "malarkey",
+        "general",
+        "rabbit",
+        "chair",
+        "staircase",
+        "underoos",
+        "snack",
+        "lamp",
+        "eagle",
+        "hobbit",
+        "diablo",
+        "earthworm",
+        "pot",
+        "plant",
+        "leg",
+        "arm",
+        "bike",
+        "citrus",
+        "dog",
+        "puppy",
+        "blackbean",
+        "ricecake",
+        "gato",
+        "nalgas",
+        "lemon",
+        "caramel",
+        "fudge",
+        "cherry",
+        "sundae",
+        "truffle",
+        "cinnamonbun",
+        "pastry",
+        "egg",
+        "omelette",
+        "fork",
+        "knife",
+        "spoon",
+        "salad",
+        "train",
+        "car",
+        "motorcycle",
+        "bicycle",
+        "platanos",
+        "mango",
+        "taco",
+        "pedo",
+        "nunchucks",
+        "destiny",
+        "hope",
+        "despacito",
+        "frito",
+        "chip",
     ]
 
-
-    def generate(self, delim='-', length=4, chars='0123456789'):
-        '''
+    def generate(self, delim="-", length=4, chars="0123456789"):
+        """
         Generate a robot name. Inspiration from Haikunator, but much more
                  poorly implemented ;)
 
@@ -54,23 +193,22 @@ def generate(self, delim='-', length=4, chars='0123456789'):
         delim: Delimiter
         length: TokenLength
         chars: TokenChars
-        '''
+        """
 
         descriptor = self._select(self._descriptors)
         noun = self._select(self._nouns)
-        numbers = ''.join((self._select(chars) for _ in range(length)))
+        numbers = "".join((self._select(chars) for _ in range(length)))
         return delim.join([descriptor, noun, numbers])
 
-
     def _select(self, select_from):
-        ''' select an element from a list using random.choice
+        """ select an element from a list using random.choice
         
             Parameters
             ==========
             should be a list of things to select from
-        '''
+        """
         if not select_from:
-            return ''
+            return ""
 
         return choice(select_from)
 
@@ -79,5 +217,6 @@ def main():
     bot = RobotNamer()
     print(bot.generate())
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     main()
diff --git a/spython/main/base/logger.py b/spython/main/base/logger.py
index 2971bdd6..e1c60f2f 100644
--- a/spython/main/base/logger.py
+++ b/spython/main/base/logger.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,24 +8,25 @@
 import os
 from spython.logger import decodeUtf8String
 
+
 def init_level(self, quiet=False):
-    '''set the logging level based on the environment
+    """set the logging level based on the environment
         
        Parameters
        ==========
        quiet: boolean if True, set to quiet. Gets overriden by environment
               setting, and only exists to define default
 
-    '''
-        
-    if os.environ.get('MESSAGELEVEL') == "QUIET":
+    """
+
+    if os.environ.get("MESSAGELEVEL") == "QUIET":
         quiet = True
 
     self.quiet = quiet
 
 
 def println(self, output, quiet=False):
-    '''print will print the output, given that quiet is not True. This
+    """print will print the output, given that quiet is not True. This
        function also serves to convert output in bytes to utf-8
 
        Parameters
@@ -34,6 +34,6 @@ def println(self, output, quiet=False):
        output: the string to print
        quiet: a runtime variable to over-ride the default.
 
-    '''
+    """
     if not self.quiet and not quiet:
         print(decodeUtf8String(output))
diff --git a/spython/main/base/sutils.py b/spython/main/base/sutils.py
index 1a2bff26..edc26191 100644
--- a/spython/main/base/sutils.py
+++ b/spython/main/base/sutils.py
@@ -1,8 +1,7 @@
-
-# Singularity Image utils for interacting with the Image/Instance 
+# Singularity Image utils for interacting with the Image/Instance
 #           classes from the client
 
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -13,60 +12,61 @@
 import os
 import re
 
+
 def load(self, image=None):
-    '''load an image, either an actual path on the filesystem or a uri.
+    """load an image, either an actual path on the filesystem or a uri.
 
        Parameters
        ==========
        image: the image path or uri to load (e.g., docker://ubuntu 
 
-    '''
+    """
     from spython.image import Image
     from spython.instance import Instance
 
     self.simage = Image(image)
 
     if image is not None:
-        if image.startswith('instance://'):
+        if image.startswith("instance://"):
             self.simage = Instance(image)
         bot.info(self.simage)
 
 
 def setenv(self, variable, value):
-    '''set an environment variable for Singularity
+    """set an environment variable for Singularity
     
        Parameters
        ==========
        variable: the variable to set
        value: the value to set
-    '''
+    """
     os.environ[variable] = value
     os.putenv(variable, value)
-    bot.debug('%s set to %s' % (variable, value))
+    bot.debug("%s set to %s" % (variable, value))
 
 
-def get_filename(self, image, ext='sif', pwd=True):
-    '''return an image filename based on the image uri.
+def get_filename(self, image, ext="sif", pwd=True):
+    """return an image filename based on the image uri.
  
        Parameters
        ==========
        ext: the extension to use
        pwd: derive a filename for the pwd
-    '''
+    """
     if pwd:
         image = os.path.basename(image)
-    image = re.sub('^.*://', '', image)
+    image = re.sub("^.*://", "", image)
     if not image.endswith(ext):
-        image = "%s.%s" %(image, ext)
+        image = "%s.%s" % (image, ext)
     return image
 
 
 def get_uri(self):
-    ''' check if the loaded image object (self.simage) has an associated uri
+    """ check if the loaded image object (self.simage) has an associated uri
         return if yes, None if not.
-    '''
-    if hasattr(self, 'simage'):
+    """
+    if hasattr(self, "simage"):
         if self.simage is not None:
-            if self.simage.image not in ['', None]:
+            if self.simage.image not in ["", None]:
                 # Concatenates the <uri>://<image>
                 return str(self.simage)
diff --git a/spython/main/build.py b/spython/main/build.py
index b3fd0a71..4529a3be 100644
--- a/spython/main/build.py
+++ b/spython/main/build.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -11,23 +10,27 @@
 import re
 import os
 
-def build(self, recipe=None, 
-                image=None, 
-                isolated=False,
-                sandbox=False,
-                writable=False,
-                build_folder=None,
-                robot_name=False,
-                ext='sif',
-                sudo=True,
-                stream=False,
-                force=False,
-                options=None,
-                quiet=False,
-                return_result=False,
-                sudo_options=None):
-
-    '''build a singularity image, optionally for an isolated build
+
+def build(
+    self,
+    recipe=None,
+    image=None,
+    isolated=False,
+    sandbox=False,
+    writable=False,
+    build_folder=None,
+    robot_name=False,
+    ext="sif",
+    sudo=True,
+    stream=False,
+    force=False,
+    options=None,
+    quiet=False,
+    return_result=False,
+    sudo_options=None,
+):
+
+    """build a singularity image, optionally for an isolated build
        (requires sudo). If you specify to stream, expect the image name
        and an iterator to be returned.
        
@@ -52,22 +55,23 @@ def build(self, recipe=None,
        options: for all other options, specify them in this list.   
        quiet: quiet verbose printing from the client.
        return_result: if True, return complete error code / message dictionary
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
-    cmd = self._init_command('build')
+    cmd = self._init_command("build")
 
     # If no extra options
     if not options:
         options = []
 
-    if 'version 3' in self.version():
-        ext = 'sif'
-        
+    if "version 3" in self.version():
+        ext = "sif"
+
     # Force the build if the image / sandbox exists
     if force:
-        cmd.append('--force')
+        cmd.append("--force")
 
     # No image provided, default to use the client's loaded image
     if recipe is None:
@@ -75,45 +79,47 @@ def build(self, recipe=None,
 
     # If it's still None, try default build recipe
     if recipe is None:
-        recipe = 'Singularity'
+        recipe = "Singularity"
 
         if not os.path.exists(recipe):
-            bot.exit('Cannot find %s, exiting.' %image)
+            bot.exit("Cannot find %s, exiting." % image)
 
     if image is None:
-        if re.search('(docker|shub)://', recipe) and not robot_name:
+        if re.search("(docker|shub)://", recipe) and not robot_name:
             image = self._get_filename(recipe, ext)
         else:
-            image = "%s.%s" %(self.RobotNamer.generate(), ext)
+            image = "%s.%s" % (self.RobotNamer.generate(), ext)
 
     # Does the user want a custom build folder?
     if build_folder is not None:
         if not os.path.exists(build_folder):
-            bot.exit('%s does not exist!' % build_folder)
+            bot.exit("%s does not exist!" % build_folder)
         image = os.path.join(build_folder, image)
-        
+
     # The user wants to run an isolated build
     if isolated:
-        cmd.append('--isolated')
+        cmd.append("--isolated")
 
     if sandbox:
-        cmd.append('--sandbox')
+        cmd.append("--sandbox")
 
     elif writable:
-        cmd.append('--writable')
+        cmd.append("--writable")
 
     cmd = cmd + options + [image, recipe]
 
     if not stream:
-        self._run_command(cmd, 
-                          sudo=sudo, 
-                          sudo_options=sudo_options,
-                          quiet=quiet, 
-                          return_result=return_result, 
-                          capture=False)
+        self._run_command(
+            cmd,
+            sudo=sudo,
+            sudo_options=sudo_options,
+            quiet=quiet,
+            return_result=return_result,
+            capture=False,
+        )
 
     else:
-        # Here we return the expected image, and an iterator! 
+        # Here we return the expected image, and an iterator!
         # The caller must iterate over
         return image, stream_command(cmd, sudo=sudo, sudo_options=sudo_options)
 
diff --git a/spython/main/execute.py b/spython/main/execute.py
index a156ea15..43ad0944 100644
--- a/spython/main/execute.py
+++ b/spython/main/execute.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,27 +6,27 @@
 
 
 from spython.logger import bot
-from spython.utils import (
-    stream_command, 
-    which
-)
+from spython.utils import stream_command, which
 
 import os
 
-def execute(self, 
-            image=None, 
-            command=None,
-            app=None,
-            writable=False,
-            contain=False,
-            bind=None,
-            stream=False,
-            nv=False,
-            return_result=False,
-            options=None,
-            sudo=False,
-            quiet=True):
-    ''' execute: send a command to a container
+
+def execute(
+    self,
+    image=None,
+    command=None,
+    app=None,
+    writable=False,
+    contain=False,
+    bind=None,
+    stream=False,
+    nv=False,
+    return_result=False,
+    options=None,
+    sudo=False,
+    quiet=True,
+):
+    """ execute: send a command to a container
     
         Parameters
         ==========
@@ -45,23 +44,24 @@ def execute(self,
         nv: if True, load Nvidia Drivers in runtime (default False)
         return_result: if True, return entire json object with return code
                        and message result not (default)
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
-    cmd = self._init_command('exec')
+    cmd = self._init_command("exec")
 
     # nv option leverages any GPU cards
     if nv:
-        cmd += ['--nv']
-    
+        cmd += ["--nv"]
+
     # If the image is given as a list, it's probably the command
     if isinstance(image, list):
         command = image
         image = None
 
     if command is not None:
-        
+
         # No image provided, default to use the client's loaded image
         if image is None:
             image = self._get_uri()
@@ -73,7 +73,7 @@ def execute(self,
 
         # If image is still None, not defined by user or previously with client
         if image is None:
-            bot.exit('Please load or provide an image.')
+            bot.exit("Please load or provide an image.")
 
         # Does the user want to use bind paths option?
         if bind is not None:
@@ -81,40 +81,41 @@ def execute(self,
 
         # Does the user want to run an app?
         if app is not None:
-            cmd = cmd + ['--app', app]
+            cmd = cmd + ["--app", app]
 
         if writable:
-            cmd.append('--writable')
+            cmd.append("--writable")
 
         # Add additional options
         if options is not None:
             cmd = cmd + options
 
         if not isinstance(command, list):
-            command = command.split(' ')
+            command = command.split(" ")
 
         cmd = cmd + [image] + command
- 
+
         if not stream:
-            return self._run_command(cmd,
-                                     sudo=sudo,
-                                     return_result=return_result,
-                                     quiet=quiet)
+            return self._run_command(
+                cmd, sudo=sudo, return_result=return_result, quiet=quiet
+            )
         return stream_command(cmd, sudo=sudo)
 
-    bot.exit('Please include a command (list) to execute.')
-
-
-def shell(self, 
-          image, 
-          app=None,
-          writable=False,
-          contain=False,
-          bind=None,
-          nv=False,
-          options=None,
-          sudo=False):
-    ''' shell into a container. A user is advised to use singularity to do
+    bot.exit("Please include a command (list) to execute.")
+
+
+def shell(
+    self,
+    image,
+    app=None,
+    writable=False,
+    contain=False,
+    bind=None,
+    nv=False,
+    options=None,
+    sudo=False,
+):
+    """ shell into a container. A user is advised to use singularity to do
         this directly, however this function is useful for supporting tools.
     
         Parameters
@@ -130,34 +131,35 @@ def shell(self,
              This option allows you to map directories on your host system to
              directories within your container using bind mounts
         nv: if True, load Nvidia Drivers in runtime (default False)
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
-    cmd = self._init_command('shell')
+    cmd = self._init_command("shell")
 
     # nv option leverages any GPU cards
     if nv:
-        cmd += ['--nv']
-    
+        cmd += ["--nv"]
+
     # Does the user want to use bind paths option?
     if bind is not None:
         cmd += self._generate_bind_list(bind)
 
     # Does the user want to run an app?
     if app is not None:
-        cmd = cmd + ['--app', app]
+        cmd = cmd + ["--app", app]
 
     # Add additional options
     if options is not None:
         cmd = cmd + options
 
     if writable:
-        cmd.append('--writable')
+        cmd.append("--writable")
 
     # Finally, add the image or uri
     cmd.append(image)
-    singularity = which('singularity')
+    singularity = which("singularity")
 
     if writable or sudo:
         os.execvp("sudo", ["sudo"] + cmd)
diff --git a/spython/main/export.py b/spython/main/export.py
index 4a8d655f..eb9f28b2 100644
--- a/spython/main/export.py
+++ b/spython/main/export.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -12,14 +11,9 @@
 import os
 
 
-def export(self,
-           image_path,
-           pipe=False,
-           output_file=None,
-           command=None,
-           sudo=False):
+def export(self, image_path, pipe=False, output_file=None, command=None, sudo=False):
 
-    '''export will export an image, sudo must be used. If we have Singularity
+    """export will export an image, sudo must be used. If we have Singularity
        versions after 3, export is replaced with building into a sandbox.
 
        Parameters
@@ -28,41 +22,37 @@ def export(self,
        pipe: export to pipe and not file (default, False)
        output_file: if pipe=False, export tar to this file. If not specified, 
        will generate temporary directory.
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
-    if 'version 3' in self.version() or '2.6' in self.version():
+    if "version 3" in self.version() or "2.6" in self.version():
 
         # If export is deprecated, we run a build
-        bot.warning('Export is not supported for Singularity 3.x. Building to sandbox instead.')
+        bot.warning(
+            "Export is not supported for Singularity 3.x. Building to sandbox instead."
+        )
 
         if output_file is None:
             basename, _ = os.path.splitext(image_path)
-            output_file = self._get_filename(basename, 'sandbox', pwd=False)
+            output_file = self._get_filename(basename, "sandbox", pwd=False)
 
-        return self.build(recipe=image_path,
-                          image=output_file,
-                          sandbox=True,
-                          force=True,
-                          sudo=sudo)
+        return self.build(
+            recipe=image_path, image=output_file, sandbox=True, force=True, sudo=sudo
+        )
 
     # If not version 3, run deprecated command
-    elif '2.5' in self.version():
-        return self._export(image_path=image_path,
-                            pipe=pipe,
-                            output_file=output_file,
-                            command=command)
+    elif "2.5" in self.version():
+        return self._export(
+            image_path=image_path, pipe=pipe, output_file=output_file, command=command
+        )
 
-    bot.warning('Unsupported version of Singularity, %s' % self.version())
+    bot.warning("Unsupported version of Singularity, %s" % self.version())
 
 
-def _export(self,
-            image_path,
-            pipe=False,
-            output_file=None,
-            command=None):
-    ''' the older deprecated function, running export for previous
+def _export(self, image_path, pipe=False, output_file=None, command=None):
+    """ the older deprecated function, running export for previous
                versions of Singularity that support it
 
            USAGE: singularity [...] export [export options...] <container path>
@@ -77,10 +67,10 @@ def _export(self,
                $ sudo singularity export /tmp/Debian.img | gzip -9 > /tmp/Debian.tar.gz
                $ sudo singularity export -f Debian.tar /tmp/Debian.img
 
-    '''
+    """
     sudo = True
-    cmd = self._init_command('export')
-    
+    cmd = self._init_command("export")
+
     # If the user has specified export to pipe, we don't need a file
     if pipe:
         cmd.append(image_path)
@@ -91,9 +81,9 @@ def _export(self,
         cmd = cmd + ["-f", tmptar, image_path]
         self._run_command(cmd, sudo=sudo)
 
-        # Was there an error?            
+        # Was there an error?
         if not os.path.exists(tmptar):
-            print('Error generating image tar')
+            print("Error generating image tar")
             return None
 
         # if user has specified output file, move it there, return path
@@ -103,5 +93,5 @@ def _export(self,
         else:
             return tmptar
 
-    # Otherwise, return output of pipe    
+    # Otherwise, return output of pipe
     return self._run_command(cmd, sudo=sudo)
diff --git a/spython/main/help.py b/spython/main/help.py
index 6aab7fed..5b83e4ee 100644
--- a/spython/main/help.py
+++ b/spython/main/help.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,17 +6,18 @@
 
 
 def helpcmd(self, command=None):
-    '''help prints the general function help, or help for a specific command
+    """help prints the general function help, or help for a specific command
 
         Parameters
         ==========   
         command: the command to get help for, if none, prints general help
 
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
-    cmd = ['singularity', '--help']
+    cmd = ["singularity", "--help"]
     if command is not None:
         cmd.append(command)
     return self._run_command(cmd)
diff --git a/spython/main/inspect.py b/spython/main/inspect.py
index dbec9ab4..f4c3a0c0 100644
--- a/spython/main/inspect.py
+++ b/spython/main/inspect.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -8,13 +7,11 @@
 import json as jsonp
 from spython.logger import bot
 
-from spython.utils import ( 
-    check_install, 
-    run_command
-)
+from spython.utils import check_install, run_command
+
 
 def inspect(self, image=None, json=True, app=None, quiet=True):
-    '''inspect will show labels, defile, runscript, and tests for an image
+    """inspect will show labels, defile, runscript, and tests for an image
     
        Parameters
        ==========
@@ -23,7 +20,7 @@ def inspect(self, image=None, json=True, app=None, quiet=True):
        quiet: Don't print result to the screen (default True)
        app: if defined, return help in context of an app
 
-    '''
+    """
     check_install()
 
     # No image provided, default to use the client's loaded image
@@ -32,34 +29,34 @@ def inspect(self, image=None, json=True, app=None, quiet=True):
 
     # If there still isn't an image, exit on error
     if not image:
-        bot.exit('Please provide an image to inspect.')
+        bot.exit("Please provide an image to inspect.")
 
-    cmd = self._init_command('inspect')
+    cmd = self._init_command("inspect")
     if app:
-        cmd = cmd + ['--app', app]
+        cmd = cmd + ["--app", app]
 
-    options = ['e', 'd', 'l', 'r', 'hf', 't']
+    options = ["e", "d", "l", "r", "hf", "t"]
 
     # After Singularity 3.0, helpfile was changed to H from
 
     if "version 3" in self.version():
-        options = ['e', 'd', 'l', 'r', 'H', 't']
+        options = ["e", "d", "l", "r", "H", "t"]
 
     for x in options:
-        cmd.append('-%s' % x)
+        cmd.append("-%s" % x)
 
     if json:
-        cmd.append('--json')
+        cmd.append("--json")
 
     cmd.append(image)
     result = run_command(cmd, quiet=quiet)
 
-    if result['return_code'] == 0:
-        result = jsonp.loads(result['message'][0])
+    if result["return_code"] == 0:
+        result = jsonp.loads(result["message"][0])
 
         # Unify output to singularity 3 format
         if "data" in result:
-            result = result['data']
+            result = result["data"]
 
         # Fix up labels
         result = parse_labels(result)
@@ -71,20 +68,20 @@ def inspect(self, image=None, json=True, app=None, quiet=True):
 
 
 def parse_labels(result):
-    '''fix up the labels, meaning parse to json if needed, and return
+    """fix up the labels, meaning parse to json if needed, and return
        original updated object
 
        Parameters
        ==========
        result: the json object to parse from inspect
-    '''
+    """
 
-    labels = result['attributes'].get('labels') or {}
+    labels = result["attributes"].get("labels") or {}
     try:
         labels = jsonp.loads(labels)
     except:
         pass
 
-    result['attributes']['labels'] = labels
+    result["attributes"]["labels"] = labels
 
     return result
diff --git a/spython/main/instances.py b/spython/main/instances.py
index e7b54df3..c27f3ebd 100644
--- a/spython/main/instances.py
+++ b/spython/main/instances.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,8 +8,9 @@
 from spython.logger import bot
 from spython.utils import run_command
 
+
 def list_instances(self, name=None, return_json=False, quiet=False, sudo=False):
-    '''list instances. For Singularity, this is provided as a command sub
+    """list instances. For Singularity, this is provided as a command sub
        group.
 
        singularity instance.list
@@ -28,14 +28,15 @@ def list_instances(self, name=None, return_json=False, quiet=False, sudo=False):
        1 -- No Instances, libexecdir value not found, functions file not found
        255 -- Couldn't get UID
 
-    '''
+    """
     from spython.instance.cmd.iutils import parse_table
     from spython.utils import check_install
+
     check_install()
 
-    subgroup = 'instance.list'
+    subgroup = "instance.list"
 
-    if 'version 3' in self.version():
+    if "version 3" in self.version():
         subgroup = ["instance", "list"]
 
     cmd = self._init_command(subgroup)
@@ -49,44 +50,46 @@ def list_instances(self, name=None, return_json=False, quiet=False, sudo=False):
 
     # Success, we have instances
 
-    if output['return_code'] == 0:
+    if output["return_code"] == 0:
 
         # Only print the table if we are returning json
         if not quiet:
-            print(''.join(output['message']))
+            print("".join(output["message"]))
 
         # Prepare json result from table
 
-        header = ['daemon_name', 'pid', 'container_image']
-        instances = parse_table(output['message'][0], header)
+        header = ["daemon_name", "pid", "container_image"]
+        instances = parse_table(output["message"][0], header)
 
         # Does the user want instance objects instead?
         listing = []
         if not return_json:
             for i in instances:
-                
+
                 # If the user has provided a name, only add instance matches
                 if name is not None:
-                    if name != i['daemon_name']:
+                    if name != i["daemon_name"]:
                         continue
 
                 # Otherwise, add instances to the listing
-                new_instance = self.instance(pid=i['pid'],
-                                             name=i['daemon_name'],
-                                             image=i['container_image'],
-                                             start=False)
+                new_instance = self.instance(
+                    pid=i["pid"],
+                    name=i["daemon_name"],
+                    image=i["container_image"],
+                    start=False,
+                )
 
                 listing.append(new_instance)
             instances = listing
 
     # Couldn't get UID
 
-    elif output['return_code'] == 255:
+    elif output["return_code"] == 255:
         bot.error("Couldn't get UID")
-        
+
     # Return code of 0
     else:
-        bot.info('No instances found.')
+        bot.info("No instances found.")
 
     # If we are given a name, return just one
     if name is not None and instances not in [None, []]:
@@ -97,7 +100,7 @@ def list_instances(self, name=None, return_json=False, quiet=False, sudo=False):
 
 
 def stopall(self, sudo=False, quiet=True):
-    '''stop ALL instances. This command is only added to the command group
+    """stop ALL instances. This command is only added to the command group
        as it doesn't make sense to call from a single instance
 
        Parameters
@@ -105,23 +108,23 @@ def stopall(self, sudo=False, quiet=True):
        sudo: if the command should be done with sudo (exposes different set of
              instances)
 
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
-    subgroup = 'instance.stop'
+    subgroup = "instance.stop"
 
-    if 'version 3' in self.version():
+    if "version 3" in self.version():
         subgroup = ["instance", "stop"]
 
     cmd = self._init_command(subgroup)
-    cmd = cmd + ['--all']
+    cmd = cmd + ["--all"]
     output = run_command(cmd, sudo=sudo, quiet=quiet)
 
-    if output['return_code'] != 0:
-        message = '%s : return code %s' %(output['message'], 
-                                          output['return_code'])
+    if output["return_code"] != 0:
+        message = "%s : return code %s" % (output["message"], output["return_code"])
         bot.error(message)
-        return output['return_code']
+        return output["return_code"]
 
-    return output['return_code']
+    return output["return_code"]
diff --git a/spython/main/parse/__init__.py b/spython/main/parse/__init__.py
index 16f1d581..f8e7a615 100644
--- a/spython/main/parse/__init__.py
+++ b/spython/main/parse/__init__.py
@@ -1,9 +1,9 @@
-'''
+"""
 
-Copyright (C) 2017-2019 Vanessa Sochat.
+Copyright (C) 2017-2020 Vanessa Sochat.
 
 This Source Code Form is subject to the terms of the
 Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-'''
+"""
diff --git a/spython/main/parse/parsers/__init__.py b/spython/main/parse/parsers/__init__.py
index c1362da4..0468735f 100644
--- a/spython/main/parse/parsers/__init__.py
+++ b/spython/main/parse/parsers/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -8,18 +7,21 @@
 from .docker import DockerParser
 from .singularity import SingularityParser
 
+
 def get_parser(name):
-    '''get_parser is a simple helper function to return a parser based on it's
+    """get_parser is a simple helper function to return a parser based on it's
        name, if it exists. If there is no writer defined, we return None.
 
        Parameters
        ==========
        name: the name of the parser to return.
-    '''
+    """
     name = name.lower()
-    parsers = {'docker': DockerParser,
-               'singularity': SingularityParser,
-               'dockerfile': DockerParser}
+    parsers = {
+        "docker": DockerParser,
+        "singularity": SingularityParser,
+        "dockerfile": DockerParser,
+    }
 
     if name in parsers:
         return parsers[name]
diff --git a/spython/main/parse/parsers/base.py b/spython/main/parse/parsers/base.py
index 57e76e0a..f727d745 100644
--- a/spython/main/parse/parsers/base.py
+++ b/spython/main/parse/parsers/base.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -12,16 +11,17 @@
 from spython.utils import read_file
 from ..recipe import Recipe
 
+
 class ParserBase(object):
-    '''a parser Base is intended to provide helper functions for a parser,
+    """a parser Base is intended to provide helper functions for a parser,
        namely to read lines in files, and otherwise interact with outputs.
        Input should be some recipe (text file to describe a container build)
        and output of parse() is a Recipe (spython.main.parse.recipe.Recipe)
        object, which can be used to write to file, etc.
-    '''
+    """
 
     def __init__(self, filename, load=True):
-        '''a generic recipe parser holds the original file, and provides 
+        """a generic recipe parser holds the original file, and provides 
            shared functions for interacting with files. If the subclass has
            a parse function defined, we parse the filename
 
@@ -31,7 +31,7 @@ def __init__(self, filename, load=True):
            load: if True, load the filename into the Recipe. If not loaded,
                  the user can call self.parse() at a later time.
 
-        '''
+        """
         self.filename = filename
         self._run_checks()
         self.lines = []
@@ -46,20 +46,18 @@ def __init__(self, filename, load=True):
             if load:
                 self.parse()
 
-
     @abc.abstractmethod
     def parse(self):
-        '''parse is the base function for parsing an input filename, and
+        """parse is the base function for parsing an input filename, and
            extracting elements into the correct Recipe sections. The exact 
            logic and supporting functions will vary based on the recipe type. 
-        '''
+        """
         return
 
-
     def _run_checks(self):
-        '''basic sanity checks for the file name (and others if needed) before
+        """basic sanity checks for the file name (and others if needed) before
            attempting parsing.
-        '''
+        """
         if self.filename is not None:
 
             # Does the recipe provided exist?
@@ -69,29 +67,27 @@ def _run_checks(self):
             # Ensure we carry fullpath
             self.filename = os.path.abspath(self.filename)
 
-
-# Printing
+    # Printing
 
     def __str__(self):
-        ''' show the user the recipe object, along with the type. E.g.,
+        """ show the user the recipe object, along with the type. E.g.,
        
             [spython-parser][docker]
             [spython-parser][singularity]
 
-        '''
+        """
         base = "[spython-parser]"
-        if hasattr(self, 'name'):
-            base = "%s[%s]" %(base, self.name)
+        if hasattr(self, "name"):
+            base = "%s[%s]" % (base, self.name)
         return base
 
     def __repr__(self):
         return self.__str__()
 
-
-# Lines
+    # Lines
 
     def _split_line(self, line):
-        '''clean a line to prepare it for parsing, meaning separation
+        """clean a line to prepare it for parsing, meaning separation
            of commands. We remove newlines (from ends) along with extra spaces.
 
            Parameters
@@ -102,5 +98,5 @@ def _split_line(self, line):
            =======
            parts: a list of line pieces, the command is likely first
 
-        '''
-        return [x.strip() for x in line.split(' ', 1)]
+        """
+        return [x.strip() for x in line.split(" ", 1)]
diff --git a/spython/main/parse/parsers/docker.py b/spython/main/parse/parsers/docker.py
index 3f4a3e99..0026bee4 100644
--- a/spython/main/parse/parsers/docker.py
+++ b/spython/main/parse/parsers/docker.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -15,10 +14,10 @@
 
 class DockerParser(ParserBase):
 
-    name = 'docker'
+    name = "docker"
 
-    def __init__(self, filename='Dockerfile', load=True):
-        '''a docker parser will read in a Dockerfile and put it into a Recipe
+    def __init__(self, filename="Dockerfile", load=True):
+        """a docker parser will read in a Dockerfile and put it into a Recipe
            object.
 
            Parameters
@@ -27,12 +26,11 @@ def __init__(self, filename='Dockerfile', load=True):
                      Dockerfile assumed to be in the $PWD.
            load: whether to load the recipe file (default True)
 
-        '''
+        """
         super(DockerParser, self).__init__(filename, load)
 
-
     def parse(self):
-        '''parse is the base function for parsing the Dockerfile, and extracting
+        """parse is the base function for parsing the Dockerfile, and extracting
            elements into the correct data structures. Everything is parsed into
            lists or dictionaries that can be assembled again on demand. 
 
@@ -45,7 +43,7 @@ def parse(self):
 
            Add/Copy: are treated the same
 
-        '''
+        """
         parser = None
         previous = None
 
@@ -62,74 +60,69 @@ def parse(self):
         # Instantiated by ParserBase
         return self.recipe
 
-
-# Setup for each Parser
+    # Setup for each Parser
 
     def _setup(self, action, line):
-        ''' replace the command name from the group, alert the user of content,
+        """ replace the command name from the group, alert the user of content,
             and clean up empty spaces
-        '''
-        bot.debug('[in]  %s' % line)
+        """
+        bot.debug("[in]  %s" % line)
 
         # Replace ACTION at beginning
-        line = re.sub('^%s' % action, '', line)
+        line = re.sub("^%s" % action, "", line)
 
         # Handle continuation lines without ACTION by padding with leading space
         line = " " + line
 
         # Split into components
-        return [x for x in self._split_line(line) if x not in ['', None]]
+        return [x for x in self._split_line(line) if x not in ["", None]]
 
-
-# From Parser
+    # From Parser
 
     def _from(self, line):
-        ''' get the FROM container image name from a FROM line!
+        """ get the FROM container image name from a FROM line!
 
            Parameters
            ==========
            line: the line from the recipe file to parse for FROM
            recipe: the recipe object to populate.
-        '''
-        fromHeader = self._setup('FROM', line)
+        """
+        fromHeader = self._setup("FROM", line)
 
         # Singularity does not support AS level
         self.recipe.fromHeader = re.sub("AS .+", "", fromHeader[0], flags=re.I)
 
         if "scratch" in self.recipe.fromHeader:
-            bot.warning('scratch is no longer available on Docker Hub.')
-        bot.debug('FROM %s' % self.recipe.fromHeader) 
-
+            bot.warning("scratch is no longer available on Docker Hub.")
+        bot.debug("FROM %s" % self.recipe.fromHeader)
 
-# Run and Test Parser
+    # Run and Test Parser
 
     def _run(self, line):
-        ''' everything from RUN goes into the install list
+        """ everything from RUN goes into the install list
 
            Parameters
            ==========
            line: the line from the recipe file to parse for FROM
 
-        '''
-        line = self._setup('RUN', line)
+        """
+        line = self._setup("RUN", line)
         self.recipe.install += line
 
-
     def _test(self, line):
-        ''' A healthcheck is generally a test command
+        """ A healthcheck is generally a test command
 
            Parameters
            ==========
            line: the line from the recipe file to parse for FROM
 
-        '''
-        self.recipe.test = self._setup('HEALTHCHECK', line)
+        """
+        self.recipe.test = self._setup("HEALTHCHECK", line)
 
-
-# Arg Parser
+    # Arg Parser
 
     def _arg(self, line):
-        '''singularity doesn't have support for ARG, so instead will issue
+        """singularity doesn't have support for ARG, so instead will issue
            a warning to the console for the user to export the variable
            with SINGULARITY prefixed at build.
  
@@ -137,23 +130,23 @@ def _arg(self, line):
            ==========
            line: the line from the recipe file to parse for ARG
    
-        '''
-        line = self._setup('ARG', line)
-        bot.warning("ARG is not supported for Singularity! To get %s" %line[0])
-        bot.warning("in the container, on host export SINGULARITY_%s" %line[0])
+        """
+        line = self._setup("ARG", line)
+        bot.warning("ARG is not supported for Singularity! To get %s" % line[0])
+        bot.warning("in the container, on host export SINGULARITY_%s" % line[0])
 
-# Env Parser
+    # Env Parser
 
     def _env(self, line):
-        '''env will parse a line that beings with ENV, indicative of one or
+        """env will parse a line that beings with ENV, indicative of one or
            more environment variables.
  
            Parameters
            ==========
            line: the line from the recipe file to parse for ADD
    
-        '''
-        line = self._setup('ENV', line)
+        """
+        line = self._setup("ENV", line)
 
         # Extract environment (list) from the line
         environ = self.parse_env(line)
@@ -164,18 +157,17 @@ def _env(self, line):
         # Also define for global environment
         self.recipe.environ += environ
 
-
     def parse_env(self, envlist):
-        '''parse_env will parse a single line (with prefix like ENV removed) to
+        """parse_env will parse a single line (with prefix like ENV removed) to
             a list of commands in the format KEY=VALUE For example:
 
             ENV PYTHONBUFFER 1 --> [PYTHONBUFFER=1]
             Docker: https://docs.docker.com/engine/reference/builder/#env
-        '''
+        """
         if not isinstance(envlist, list):
             envlist = [envlist]
 
-        exports = [] 
+        exports = []
 
         for env in envlist:
 
@@ -185,7 +177,7 @@ def parse_env(self, envlist):
             while pieces:
                 current = pieces.pop(0)
 
-                if current.endswith('='):
+                if current.endswith("="):
 
                     # Case 1: ['A='] --> A=
                     nextone = ""
@@ -193,54 +185,51 @@ def parse_env(self, envlist):
                     # Case 2: ['A=', '"1 2"'] --> A=1 2
                     if pieces:
                         nextone = pieces.pop(0)
-                    exports.append("%s%s" %(current, nextone))
+                    exports.append("%s%s" % (current, nextone))
 
                 # Case 3: ['A=B']     --> A=B
-                elif '=' in current:
+                elif "=" in current:
                     exports.append(current)
 
                 # Case 4: ENV \\
-                elif current.endswith('\\'):
+                elif current.endswith("\\"):
                     continue
 
                 # Case 5: ['A', 'B']  --> A=B
                 else:
                     nextone = pieces.pop(0)
-                    exports.append("%s=%s" %(current, nextone))
+                    exports.append("%s=%s" % (current, nextone))
 
         return exports
 
-
-# Add and Copy Parser
-
+    # Add and Copy Parser
 
     def _copy(self, lines):
-        '''parse_add will copy multiple files from one location to another. 
+        """parse_add will copy multiple files from one location to another. 
            This likely will need tweaking, as the files might need to be 
            mounted from some location before adding to the image. 
            The add command is done for an entire directory. It is also
            possible to have more than one file copied to a destination:
            https://docs.docker.com/engine/reference/builder/#copy
            e.g.: <src> <src> <dest>/
-        '''
-        lines = self._setup('COPY', lines)
+        """
+        lines = self._setup("COPY", lines)
 
         for line in lines:
             values = line.split(" ")
             topath = values.pop()
             for frompath in values:
                 self._add_files(frompath, topath)
-        
 
     def _add(self, lines):
-        '''Add can also handle https, and compressed files.
+        """Add can also handle https, and compressed files.
 
            Parameters
            ==========
            line: the line from the recipe file to parse for ADD
 
-        '''
-        lines = self._setup('ADD', lines)
+        """
+        lines = self._setup("ADD", lines)
 
         for line in lines:
             values = line.split(" ")
@@ -249,7 +238,7 @@ def _add(self, lines):
             # Custom parsing for frompath
 
             # If it's a web address, add to install routine to get
-            if frompath.startswith('http'):
+            if frompath.startswith("http"):
                 for topath in values:
                     self._parse_http(frompath, topath)
 
@@ -262,12 +251,11 @@ def _add(self, lines):
             else:
                 for topath in values:
                     self._add_files(frompath, topath)
-        
 
-# File Handling
+    # File Handling
 
     def _add_files(self, source, dest):
-        '''add files is the underlying function called to add files to the
+        """add files is the underlying function called to add files to the
            list, whether originally called from the functions to parse archives,
            or https. We make sure that any local references are changed to
            actual file locations before adding to the files list.
@@ -276,22 +264,21 @@ def _add_files(self, source, dest):
            ==========
            source: the source
            dest: the destiation
-        '''
+        """
 
         # Warn the user Singularity doesn't support expansion
-        if '*' in source:
+        if "*" in source:
             bot.warning("Singularity doesn't support expansion, * found in %s" % source)
-        
+
         # Warning if file/folder (src) doesn't exist
         if not os.path.exists(source):
             bot.warning("%s doesn't exist, ensure exists for build" % source)
-        
+
         # The pair is added to the files as a list
         self.recipe.files.append([source, dest])
 
-
     def _parse_http(self, url, dest):
-        '''will get the filename of an http address, and return a statement
+        """will get the filename of an http address, and return a statement
            to download it to some location
 
            Parameters
@@ -299,15 +286,14 @@ def _parse_http(self, url, dest):
            url: the source url to retrieve with curl
            dest: the destination folder to put it in the image
 
-        '''
+        """
         file_name = os.path.basename(url)
-        download_path = "%s/%s" %(dest, file_name)
-        command = "curl %s -o %s" %(url, download_path)
+        download_path = "%s/%s" % (dest, file_name)
+        command = "curl %s -o %s" % (url, download_path)
         self.recipe.install.append(command)
 
-
     def _parse_archive(self, targz, dest):
-        '''parse_targz will add a line to the install script to extract a 
+        """parse_targz will add a line to the install script to extract a 
            targz to a location, and also add it to the files.
 
            Parameters
@@ -315,19 +301,18 @@ def _parse_archive(self, targz, dest):
            targz: the targz to extract
            dest: the location to extract it to
 
-        '''
+        """
 
         # Add command to extract it
-        self.recipe.install.append("tar -zvf %s %s" %(targz, dest))
+        self.recipe.install.append("tar -zvf %s %s" % (targz, dest))
 
         # Ensure added to container files
         return self._add_files(targz, dest)
 
-
-# Comments and Default
+    # Comments and Default
 
     def _comment(self, line):
-        '''Simply add the line to the install as a comment. This function is
+        """Simply add the line to the install as a comment. This function is
            equivalent to default, but added in the case we need future custom
            parsing (meaning a comment is different from a line. 
 
@@ -335,27 +320,25 @@ def _comment(self, line):
            ==========
            line: the line from the recipe file to parse to INSTALL
 
-        '''
+        """
         self.recipe.install.append(line)
 
-
     def _default(self, line):
-        '''the default action assumes a line that is either a command (a 
+        """the default action assumes a line that is either a command (a 
            continuation of a previous, for example) or a comment.
           
            Parameters
            ==========
            line: the line from the recipe file to parse to INSTALL
-        '''
-        if line.strip().startswith('#'):
+        """
+        if line.strip().startswith("#"):
             return self._comment(line)
         self.recipe.install.append(line)
-        
 
-# Ports and Volumes
+    # Ports and Volumes
 
     def _volume(self, line):
-        '''We don't have logic for volume for Singularity, so we add as
+        """We don't have logic for volume for Singularity, so we add as
            a comment in the install, and a metadata value for the recipe 
            object
   
@@ -363,47 +346,45 @@ def _volume(self, line):
            ==========
            line: the line from the recipe file to parse to INSTALL
 
-        '''
-        volumes = self._setup('VOLUME', line)
+        """
+        volumes = self._setup("VOLUME", line)
         if volumes:
             self.recipe.volumes += volumes
-        return self._comment("# %s" %line)
-
+        return self._comment("# %s" % line)
 
     def _expose(self, line):
-        '''Again, just add to metadata, and comment in install.
+        """Again, just add to metadata, and comment in install.
   
            Parameters
            ==========
            line: the line from the recipe file to parse to INSTALL
 
-        '''
-        ports = self._setup('EXPOSE', line)
+        """
+        ports = self._setup("EXPOSE", line)
         if ports:
             self.recipe.ports += ports
-        return self._comment("# %s" %line)
-
+        return self._comment("# %s" % line)
 
-# Working Directory
+    # Working Directory
 
     def _workdir(self, line):
-        '''A Docker WORKDIR command simply implies to cd to that location
+        """A Docker WORKDIR command simply implies to cd to that location
 
            Parameters
            ==========
            line: the line from the recipe file to parse for WORKDIR
 
-        '''
+        """
         # Save the last working directory to add to the runscript
-        workdir = self._setup('WORKDIR', line)
-        workdir_cd = "cd %s" %(''.join(workdir))
+        workdir = self._setup("WORKDIR", line)
+        workdir_cd = "cd %s" % ("".join(workdir))
         self.recipe.install.append(workdir_cd)
         self.recipe.workdir = workdir[0]
 
-# Entrypoint and Command
+    # Entrypoint and Command
 
     def _cmd(self, line):
-        '''_cmd will parse a Dockerfile CMD command
+        """_cmd will parse a Dockerfile CMD command
            
            eg: CMD /code/run_uwsgi.sh --> /code/run_uwsgi.sh.
                If a list is provided, it's parsed to a list.
@@ -412,56 +393,51 @@ def _cmd(self, line):
            ==========
            line: the line from the recipe file to parse for CMD
 
-        '''
-        cmd = self._setup('CMD', line)[0]
+        """
+        cmd = self._setup("CMD", line)[0]
         self.recipe.cmd = self._load_list(cmd)
 
-
     def _load_list(self, line):
-        '''load an entrypoint or command, meaning it can be wrapped in a list
+        """load an entrypoint or command, meaning it can be wrapped in a list
            or a regular string. We try loading as json to return an actual
            list. E.g., after _setup, we might go from 'ENTRYPOINT ["one", "two"]'
            to '["one", "two"]', and this function loads as json and returns
            ["one", "two"]
-        '''
+        """
         try:
             line = json.loads(line)
-        except: # json.JSONDecodeError
+        except:  # json.JSONDecodeError
             pass
         return line
 
-
     def _entry(self, line):
-        '''_entrypoint will parse a Dockerfile ENTRYPOINT command
+        """_entrypoint will parse a Dockerfile ENTRYPOINT command
            
            Parameters
            ==========
            line: the line from the recipe file to parse for CMD
 
-        '''
-        entrypoint = self._setup('ENTRYPOINT', line)[0]
+        """
+        entrypoint = self._setup("ENTRYPOINT", line)[0]
         self.recipe.entrypoint = self._load_list(entrypoint)
 
-
-# Labels
+    # Labels
 
     def _label(self, line):
-        '''_label will parse a Dockerfile label
+        """_label will parse a Dockerfile label
            
            Parameters
            ==========
            line: the line from the recipe file to parse for CMD
 
-        '''
-        label = self._setup('LABEL', line)
+        """
+        label = self._setup("LABEL", line)
         self.recipe.labels += [label]
 
-
-# Main Parsing Functions        
-
+    # Main Parsing Functions
 
     def _get_mapping(self, line, parser=None, previous=None):
-        '''mapping will take the command from a Dockerfile and return a map
+        """mapping will take the command from a Dockerfile and return a map
            function to add it to the appropriate place. Any lines that don't
            cleanly map are assumed to be comments.
 
@@ -474,7 +450,7 @@ def _get_mapping(self, line, parser=None, previous=None):
            =======
            function: to map a line to its command group
 
-        '''
+        """
 
         # Split the command into cleanly the command and rest
         if not isinstance(line, list):
@@ -486,20 +462,22 @@ def _get_mapping(self, line, parser=None, previous=None):
 
         cmd = line[0].upper()
 
-        mapping = {"ADD": self._add,
-                   "ARG": self._arg,
-                   "COPY": self._copy,
-                   "CMD": self._cmd,
-                   "ENTRYPOINT": self._entry,
-                   "ENV": self._env,
-                   "EXPOSE": self._expose,
-                   "FROM": self._from,
-                   "HEALTHCHECK": self._test,
-                   "RUN": self._run,
-                   "WORKDIR": self._workdir,
-                   "MAINTAINER": self._label,
-                   "VOLUME": self._volume,
-                   "LABEL": self._label}
+        mapping = {
+            "ADD": self._add,
+            "ARG": self._arg,
+            "COPY": self._copy,
+            "CMD": self._cmd,
+            "ENTRYPOINT": self._entry,
+            "ENV": self._env,
+            "EXPOSE": self._expose,
+            "FROM": self._from,
+            "HEALTHCHECK": self._test,
+            "RUN": self._run,
+            "WORKDIR": self._workdir,
+            "MAINTAINER": self._label,
+            "VOLUME": self._volume,
+            "LABEL": self._label,
+        }
 
         # If it's a command line, return correct function
         if cmd in mapping:
@@ -510,14 +488,13 @@ def _get_mapping(self, line, parser=None, previous=None):
         previous = self._clean_line(previous)
 
         # if we are continuing from last
-        if cleaned.endswith('\\') and parser or previous.endswith('\\'):
+        if cleaned.endswith("\\") and parser or previous.endswith("\\"):
             return parser
 
         return self._default
- 
 
     def _clean_line(self, line):
-        '''clean line will remove comments, and strip the line of newlines 
+        """clean line will remove comments, and strip the line of newlines 
            or spaces.
 
            Parameters
@@ -528,7 +505,7 @@ def _clean_line(self, line):
            =======
            line: a cleaned line
 
-        '''
+        """
         # A line that is None should return empty string
-        line = line or ''
-        return line.split('#')[0].strip()
+        line = line or ""
+        return line.split("#")[0].strip()
diff --git a/spython/main/parse/parsers/singularity.py b/spython/main/parse/parsers/singularity.py
index 0b443826..2e16bb5e 100644
--- a/spython/main/parse/parsers/singularity.py
+++ b/spython/main/parse/parsers/singularity.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -14,10 +13,10 @@
 
 class SingularityParser(ParserBase):
 
-    name = 'singularity'
+    name = "singularity"
 
     def __init__(self, filename="Singularity", load=True):
-        '''a SingularityParser parses a Singularity file into expected fields of
+        """a SingularityParser parses a Singularity file into expected fields of
            labels, environment, and install/runtime commands. The base class
            ParserBase will instantiate an empty Recipe() object to populate,
            and call parse() here on the recipe.
@@ -27,21 +26,20 @@ def __init__(self, filename="Singularity", load=True):
            filename: the recipe file (Singularity) to parse
            load: load and parse the recipe (defaults to True)
 
-        '''
+        """
         super(SingularityParser, self).__init__(filename, load)
 
-
     def parse(self):
-        '''parse is the base function for parsing the recipe, and extracting
+        """parse is the base function for parsing the recipe, and extracting
            elements into the correct data structures. Everything is parsed into
            lists or dictionaries that can be assembled again on demand. 
     
            Singularity: we parse files/labels first, then install. 
                         cd first in a line is parsed as WORKDIR
 
-        '''
+        """
         # If the recipe isn't loaded, load it
-        if not hasattr(self, 'config'):
+        if not hasattr(self, "config"):
             self.load_recipe()
 
         # Parse each section
@@ -50,74 +48,69 @@ def parse(self):
 
             # Get the correct parsing function
             parser = self._get_mapping(section)
-           
+
             # Parse it, if appropriate
             if parser:
                 parser(lines)
 
         return self.recipe
 
-
-# Setup for each Parser
+    # Setup for each Parser
 
     def _setup(self, lines):
-        '''setup required adding content from the host to the rootfs,
+        """setup required adding content from the host to the rootfs,
            so we try to capture with with ADD.
-        '''
-        bot.warning('SETUP is error prone, please check output.')
+        """
+        bot.warning("SETUP is error prone, please check output.")
 
         for line in lines:
 
             # For all lines, replace rootfs with actual root /
-            line = re.sub('[$]{?SINGULARITY_ROOTFS}?', 
-                          '', '$SINGULARITY_ROOTFS')
+            line = re.sub("[$]{?SINGULARITY_ROOTFS}?", "", "$SINGULARITY_ROOTFS")
 
             # If we have nothing left, don't continue
-            if line in ['', None]:
+            if line in ["", None]:
                 continue
 
             # If the line starts with copy or move, assume is file from host
-            if re.search('(^cp|^mv)', line):
-                line = re.sub('(^cp|^mv)', '', line)
+            if re.search("(^cp|^mv)", line):
+                line = re.sub("(^cp|^mv)", "", line)
                 self.files.append(line)
 
             # If it's a general command, add to install routine
             else:
-                self.install.append(line)            
-
+                self.install.append(line)
 
-# From Parser
+    # From Parser
 
     def _from(self, line):
-        ''' get the FROM container image name from a FROM line!
+        """ get the FROM container image name from a FROM line!
 
            Parameters
            ==========
            line: the line from the recipe file to parse for FROM
 
-        '''
+        """
         self.recipe.fromHeader = line
-        bot.debug('FROM %s' % self.recipe.fromHeader) 
+        bot.debug("FROM %s" % self.recipe.fromHeader)
 
-
-# Run and Test Parser
+    # Run and Test Parser
 
     def _test(self, lines):
-        ''' A healthcheck is generally a test command
+        """ A healthcheck is generally a test command
 
            Parameters
            ==========
            line: the line from the recipe file to parse for FROM
 
-        '''
-        self._write_script('/tests.sh', lines)
+        """
+        self._write_script("/tests.sh", lines)
         self.recipe.test = "/bin/bash /tests.sh"
-        
 
-# Env Parser
+    # Env Parser
 
     def _env(self, lines):
-        '''env will parse a list of environment lines and simply remove any
+        """env will parse a list of environment lines and simply remove any
            blank lines, or those with export. Dockerfiles don't usually
            have exports.
  
@@ -125,65 +118,60 @@ def _env(self, lines):
            ==========
            lines: A list of environment pair lines.
 
-        '''
-        environ = [x for x in lines if not x.startswith('export')]        
+        """
+        environ = [x for x in lines if not x.startswith("export")]
         self.recipe.environ += environ
 
-
-# Files for container
-
+    # Files for container
 
     def _files(self, lines):
-        '''parse_files will simply add the list of files to the correct object
+        """parse_files will simply add the list of files to the correct object
  
            Parameters
            ==========
            lines: pairs of files, one pair per line
    
-        '''
+        """
         self.recipe.files += lines
 
-
-# Comments and Help
+    # Comments and Help
 
     def _comments(self, lines):
-        ''' comments is a wrapper for comment, intended to be given a list
+        """ comments is a wrapper for comment, intended to be given a list
             of comments.
 
             Parameters
             ==========
             lines: the list of lines to parse
 
-        ''' 
+        """
         for line in lines:
             comment = self._comment(line)
             self.recipe.comments.append(comment)
 
-
     def _comment(self, line):
-        '''Simply add the line to the install as a comment. Add an extra # to be
+        """Simply add the line to the install as a comment. Add an extra # to be
            extra careful. 
 
            Parameters
            ==========
            line: the line from the recipe file to parse to INSTALL
 
-        '''
-        return "# %s" % line.strip().strip('#')
-
+        """
+        return "# %s" % line.strip().strip("#")
 
-# Runscript Command
+    # Runscript Command
 
     def _run(self, lines):
-        '''_parse the runscript to be the Docker CMD. If we have one line,
+        """_parse the runscript to be the Docker CMD. If we have one line,
            call it directly. If not, write the entrypoint into a script. 
 
            Parameters
            ==========
            lines: the line from the recipe file to parse for CMD
 
-        '''
-        lines = [x for x in lines if x not in ['', None]]
+        """
+        lines = [x for x in lines if x not in ["", None]]
 
         # Default runscript is first index
         runscript = lines[0]
@@ -191,43 +179,39 @@ def _run(self, lines):
         # Multiple line runscript needs multiple lines written to script
         if len(lines) > 1:
 
-            bot.warning('More than one line detected for runscript!')
-            bot.warning('These will be echoed into a single script to call.')
-            self._write_script('/entrypoint.sh', lines)
+            bot.warning("More than one line detected for runscript!")
+            bot.warning("These will be echoed into a single script to call.")
+            self._write_script("/entrypoint.sh", lines)
             runscript = "/bin/bash /entrypoint.sh"
 
         self.recipe.cmd = runscript
 
-
-# Labels
+    # Labels
 
     def _labels(self, lines):
-        '''_labels simply adds the labels to the list to save.
+        """_labels simply adds the labels to the list to save.
            
            Parameters
            ==========
            lines: the lines from the recipe with key,value pairs
 
-        '''
+        """
         self.recipe.labels += lines
 
-
     def _post(self, lines):
-        '''the main core of commands, to be added to the install section
+        """the main core of commands, to be added to the install section
 
            Parameters
            ==========
            lines: the lines from the recipe with install commands
 
-        '''
+        """
         self.recipe.install += lines
 
-
-# Main Parsing Functions
-
+    # Main Parsing Functions
 
     def _get_mapping(self, section):
-        '''mapping will take the section name from a Singularity recipe 
+        """mapping will take the section name from a Singularity recipe 
            and return a map function to add it to the appropriate place. 
            Any lines that don't cleanly map are assumed to be comments.
 
@@ -239,56 +223,55 @@ def _get_mapping(self, section):
            =======
            function: to map a line to its command group (e.g., install)
 
-        '''
+        """
 
         # Ensure section is lowercase
         section = section.lower()
 
-        mapping = {"environment": self._env,
-                   "comments": self._comments,
-                   "runscript": self._run,
-                   "labels": self._labels,
-                   "setup": self._setup,
-                   "files": self._files,
-                   "from": self._from,
-                   "post": self._post,
-                   "test": self._test,
-                   "help": self._comments}
+        mapping = {
+            "environment": self._env,
+            "comments": self._comments,
+            "runscript": self._run,
+            "labels": self._labels,
+            "setup": self._setup,
+            "files": self._files,
+            "from": self._from,
+            "post": self._post,
+            "test": self._test,
+            "help": self._comments,
+        }
 
         if section in mapping:
             return mapping[section]
         return self._comments
- 
 
-# Loading Functions
+    # Loading Functions
 
     def _load_from(self, line):
-        '''load the From section of the recipe for the Dockerfile.
-        '''
+        """load the From section of the recipe for the Dockerfile.
+        """
         # Remove any comments
-        line = line.split('#', 1)[0] 
-        line = re.sub('(F|f)(R|r)(O|o)(M|m):', '', line).strip()
-        self.config['from'] = line
-
+        line = line.split("#", 1)[0]
+        line = re.sub("(F|f)(R|r)(O|o)(M|m):", "", line).strip()
+        self.config["from"] = line
 
     def _load_bootstrap(self, line):
-        '''load bootstrap checks that the bootstrap is Docker, otherwise we
+        """load bootstrap checks that the bootstrap is Docker, otherwise we
            exit on fail (there is no other option to convert to Dockerfile!
-        '''
-        if 'docker' not in line.lower():
-            raise NotImplementedError('docker not detected as Bootstrap!')
-
+        """
+        if "docker" not in line.lower():
+            raise NotImplementedError("docker not detected as Bootstrap!")
 
     def _load_section(self, lines, section):
-        '''read in a section to a list, and stop when we hit the next section
-        '''
+        """read in a section to a list, and stop when we hit the next section
+        """
         members = []
 
         while True:
 
             if not lines:
                 break
-            next_line = lines[0]                
+            next_line = lines[0]
 
             # The end of a section
             if next_line.strip().startswith("%"):
@@ -297,7 +280,7 @@ def _load_section(self, lines, section):
             # Still in current section!
             else:
                 new_member = lines.pop(0).strip()
-                if new_member not in ['', None]:
+                if new_member not in ["", None]:
                     members.append(new_member)
 
         # Add the list to the config
@@ -305,16 +288,15 @@ def _load_section(self, lines, section):
             if section is not None:
                 self.config[section] += members
 
-
     def load_recipe(self):
-        '''load_recipe will return a loaded in singularity recipe. The idea
+        """load_recipe will return a loaded in singularity recipe. The idea
            is that these sections can then be parsed into a Dockerfile,
            or printed back into their original form.
 
            Returns
            =======
            config: a parsed recipe Singularity recipe
-        '''
+        """
 
         # Comments between sections, add to top of file
         lines = self.lines[:]
@@ -322,7 +304,7 @@ def load_recipe(self):
 
         # Start with a fresh config!
         self.config = dict()
-     
+
         section = None
 
         while lines:
@@ -332,11 +314,11 @@ def load_recipe(self):
             stripped = line.strip()
 
             # Bootstrap Line
-            if re.search('(b|B)(o|O){2}(t|T)(s|S)(t|T)(r|R)(a|A)(p|P)', line):
+            if re.search("(b|B)(o|O){2}(t|T)(s|S)(t|T)(r|R)(a|A)(p|P)", line):
                 self._load_bootstrap(stripped)
 
             # From Line
-            if re.search('(f|F)(r|R)(O|o)(m|M)', stripped):
+            if re.search("(f|F)(r|R)(O|o)(m|M)", stripped):
                 self._load_from(stripped)
 
             # Comment
@@ -345,21 +327,19 @@ def load_recipe(self):
                 continue
 
             # Section
-            elif stripped.startswith('%'):
+            elif stripped.startswith("%"):
                 section = self._add_section(stripped)
-                bot.debug("Adding section title %s" %section)
+                bot.debug("Adding section title %s" % section)
 
             # If we have a section, and are adding it
             elif section is not None:
                 lines = [line] + lines
-                self._load_section(lines=lines,
-                                   section=section)
-
-            self.config['comments'] = comments
+                self._load_section(lines=lines, section=section)
 
+            self.config["comments"] = comments
 
     def _add_section(self, line, section=None):
-        '''parse a line for a section, and return the parsed section (if not
+        """parse a line for a section, and return the parsed section (if not
            None)
 
            Parameters
@@ -370,23 +350,22 @@ def _add_section(self, line, section=None):
            Resulting data structure is:
            config['post'] (in lowercase)
 
-        '''
+        """
         # Remove any comments
-        line = line.split('#', 1)[0].strip()
+        line = line.split("#", 1)[0].strip()
 
         # Is there a section name?
-        parts = line.split(' ')         
-        section = re.sub(r'[%]|(\s+)', '', parts[0]).lower()
+        parts = line.split(" ")
+        section = re.sub(r"[%]|(\s+)", "", parts[0]).lower()
 
-        if section not in self.config: 
+        if section not in self.config:
             self.config[section] = []
             bot.debug("Adding section %s" % section)
 
         return section
 
-
     def _write_script(self, path, lines, chmod=True):
-        '''write a script with some lines content to path in the image. This
+        """write a script with some lines content to path in the image. This
            is done by way of adding echo statements to the install section.
 
            Parameters
@@ -395,9 +374,9 @@ def _write_script(self, path, lines, chmod=True):
            lines: the lines to echo to the file
            chmod: If true, change permission to make u+x
 
-        '''
+        """
         for line in lines:
             self.recipe.install.append('echo "%s" >> %s' % (line, path))
 
         if chmod:
-            self.recipe.install.append('chmod u+x %s' % path)
+            self.recipe.install.append("chmod u+x %s" % path)
diff --git a/spython/main/parse/recipe.py b/spython/main/parse/recipe.py
index a049de2b..22d58378 100644
--- a/spython/main/parse/recipe.py
+++ b/spython/main/parse/recipe.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,7 +6,7 @@
 
 
 class Recipe(object):
-    '''a recipe includes an environment, labels, runscript or command,
+    """a recipe includes an environment, labels, runscript or command,
        and install sequence. This object is interacted with by a Parser 
        (intended to popualte the recipe with content) and a Writer (intended
        to write a recipe to file). The parsers and writers are located in
@@ -19,7 +18,7 @@ class Recipe(object):
        recipe: the original recipe file, parsed by the subclass either
                DockerParser or SingularityParser
 
-    '''
+    """
 
     def __init__(self, recipe=None):
 
@@ -38,36 +37,38 @@ def __init__(self, recipe=None):
         self.source = recipe
 
     def __str__(self):
-        ''' show the user the recipe object, along with the type. E.g.,
+        """ show the user the recipe object, along with the type. E.g.,
        
             [spython-recipe][source:Singularity]
             [spython-recipe][source:Dockerfile]
 
-        '''
+        """
         base = "[spython-recipe]"
         if self.source:
-            base = "%s[source:%s]" %(base, self.source)
+            base = "%s[source:%s]" % (base, self.source)
         return base
 
     def json(self):
-        '''return a dictionary version of the recipe, intended to be parsed
+        """return a dictionary version of the recipe, intended to be parsed
            or printed as json.
 
            Returns: a dictionary of attributes including cmd, comments,
                     entrypoint, environ, files, install, labels, ports,
                     test, volumes, and workdir.
-        '''
-        attributes = ['cmd',
-                      'comments',
-                      'entrypoint',
-                      'environ',
-                      'files',
-                      'install',
-                      'labels',
-                      'ports',
-                      'test',
-                      'volumes',
-                      'workdir']
+        """
+        attributes = [
+            "cmd",
+            "comments",
+            "entrypoint",
+            "environ",
+            "files",
+            "install",
+            "labels",
+            "ports",
+            "test",
+            "volumes",
+            "workdir",
+        ]
 
         result = {}
 
diff --git a/spython/main/parse/writers/__init__.py b/spython/main/parse/writers/__init__.py
index 346eb5d3..cf7c522e 100644
--- a/spython/main/parse/writers/__init__.py
+++ b/spython/main/parse/writers/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -11,17 +10,19 @@
 
 
 def get_writer(name):
-    '''get_writer is a simple helper function to return a writer based on it's
+    """get_writer is a simple helper function to return a writer based on it's
        name, if it exists. If there is no writer defined, we return None.
 
        Parameters
        ==========
        name: the name of the writer to return.
-    '''
+    """
     name = name.lower()
-    writers = {'docker': DockerWriter,
-               'singularity': SingularityWriter,
-               'dockerfile': DockerWriter}
+    writers = {
+        "docker": DockerWriter,
+        "singularity": SingularityWriter,
+        "dockerfile": DockerWriter,
+    }
 
     if name in writers:
         return writers[name]
diff --git a/spython/main/parse/writers/base.py b/spython/main/parse/writers/base.py
index bf5e06a8..f0a4fb35 100644
--- a/spython/main/parse/writers/base.py
+++ b/spython/main/parse/writers/base.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -13,21 +12,19 @@
 
 
 class WriterBase(object):
-
     def __init__(self, recipe=None):
-        '''a writer base will take a recipe object (parser.base.Recipe) and
+        """a writer base will take a recipe object (parser.base.Recipe) and
            provide helpers for writing to file.
 
            Parameters
            ==========
            recipe: the recipe instance to parse
 
-        '''
+        """
         self.recipe = recipe
 
-
     def write(self, output_file=None, force=False):
-        '''convert a recipe to a specified format, and write to file, meaning
+        """convert a recipe to a specified format, and write to file, meaning
            we use the loaded recipe to write to an output file.
            If the output file is not specified, a temporary file is used.
 
@@ -36,48 +33,47 @@ def write(self, output_file=None, force=False):
            output_file: the file to save to, not required (estimates default)
            force: if True, if file exists, over-write existing file
 
-        '''
+        """
         if output_file is None:
             output_file = self._get_conversion_outfile()
 
         # Cut out early if file exists and we aren't overwriting
         if os.path.exists(output_file) and not force:
-            bot.exit('%s exists, and force is False.' % output_file)
+            bot.exit("%s exists, and force is False." % output_file)
 
         # Do the conversion if function is provided by subclass
-        if hasattr(self, 'convert'):
+        if hasattr(self, "convert"):
             converted = self.convert()
-            bot.info('Saving to %s' % output_file)
+            bot.info("Saving to %s" % output_file)
             write_file(output_file, converted)
-        
 
     def _get_conversion_outfile(self):
-        '''a helper function to return a conversion temporary output file
+        """a helper function to return a conversion temporary output file
            based on kind of conversion
 
            Parameters
            ==========
            convert_to: a string either docker or singularity, if a different
 
-        '''
-        prefix = 'spythonRecipe'
-        if hasattr(self, 'name'):
+        """
+        prefix = "spythonRecipe"
+        if hasattr(self, "name"):
             prefix = self.name
         suffix = next(tempfile._get_candidate_names())
-        return "%s.%s" %(prefix, suffix)
+        return "%s.%s" % (prefix, suffix)
 
-# Printing
+    # Printing
 
     def __str__(self):
-        ''' show the user the recipe object, along with the type. E.g.,
+        """ show the user the recipe object, along with the type. E.g.,
        
             [spython-writer][docker]
             [spython-writer][singularity]
 
-        '''
+        """
         base = "[spython-writer]"
-        if hasattr(self, 'name'):
-            base = "%s[%s]" %(base, self.name)
+        if hasattr(self, "name"):
+            base = "%s[%s]" % (base, self.name)
         return base
 
     def __repr__(self):
diff --git a/spython/main/parse/writers/docker.py b/spython/main/parse/writers/docker.py
index 8297328b..ce7701ee 100644
--- a/spython/main/parse/writers/docker.py
+++ b/spython/main/parse/writers/docker.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -20,7 +19,8 @@
     "(?P<repo>[^:@/]+)"
     "(?::(?P<tag>[^:@]+))?"
     "(?:@(?P<version>.+))?"
-    "$")
+    "$"
+)
 
 # Reduced to match registry:port/repo or registry.com/repo
 _reduced_uri = re.compile(
@@ -29,7 +29,8 @@
     "(?::(?P<tag>[^:@]+))?"
     "(?:@(?P<version>.+))?"
     "$"
-    "(?P<collection>.)?")
+    "(?P<collection>.)?"
+)
 
 # Default
 _default_uri = re.compile(
@@ -38,40 +39,38 @@
     "(?P<repo>[^:@/]+)"
     "(?::(?P<tag>[^:@]+))?"
     "(?:@(?P<version>.+))?"
-    "$")
+    "$"
+)
 
 
 class DockerWriter(WriterBase):
 
-    name = 'docker'
+    name = "docker"
 
-    def __init__(self, recipe=None): # pylint: disable=useless-super-delegation
-        '''a DockerWriter will take a Recipe as input, and write
+    def __init__(self, recipe=None):  # pylint: disable=useless-super-delegation
+        """a DockerWriter will take a Recipe as input, and write
            to a Dockerfile.
 
            Parameters
            ==========
            recipe: the Recipe object to write to file.
 
-        '''
+        """
         super(DockerWriter, self).__init__(recipe)
 
-
     def validate(self):
-        '''validate that all (required) fields are included for the Docker
+        """validate that all (required) fields are included for the Docker
            recipe. We minimimally just need a FROM image, and must ensure
            it's in a valid format. If anything is missing, we exit with error.
-        '''
+        """
         if self.recipe is None:
-            bot.exit('Please provide a Recipe() to the writer first.')
+            bot.exit("Please provide a Recipe() to the writer first.")
 
         if self.recipe.fromHeader is None:
             bot.exit("Dockerfile requires a fromHeader.")
-            
+
             # Parse the provided name
-            uri_regexes = [_reduced_uri,
-                           _default_uri,
-                           _docker_uri]
+            uri_regexes = [_reduced_uri, _default_uri, _docker_uri]
 
             for r in uri_regexes:
                 match = r.match(self.recipe.fromHeader)
@@ -79,72 +78,73 @@ def validate(self):
                     break
 
             if not match:
-                bot.exit('FROM header %s not valid.' % self.recipe.fromHeader)
+                bot.exit("FROM header %s not valid." % self.recipe.fromHeader)
 
     def convert(self, runscript="/bin/bash", force=False):
-        '''convert is called by the parent class to convert the recipe object
+        """convert is called by the parent class to convert the recipe object
            (at self.recipe) to the output file content to write to file.
-        '''
+        """
         self.validate()
 
         recipe = ["FROM %s" % self.recipe.fromHeader]
 
         # Comments go up front!
-        recipe += self.recipe.comments  
+        recipe += self.recipe.comments
 
         # First add files, labels, environment
-        recipe += write_files('ADD', self.recipe.files)
-        recipe += write_lines('LABEL', self.recipe.labels)
-        recipe += write_lines('ENV', self.recipe.environ)
+        recipe += write_files("ADD", self.recipe.files)
+        recipe += write_lines("LABEL", self.recipe.labels)
+        recipe += write_lines("ENV", self.recipe.environ)
 
         # Install routine is added as RUN commands
-        recipe += write_lines('RUN', self.recipe.install)
+        recipe += write_lines("RUN", self.recipe.install)
 
         # Expose ports
-        recipe += write_lines('EXPOSE', self.recipe.ports)
+        recipe += write_lines("EXPOSE", self.recipe.ports)
 
         if self.recipe.workdir is not None:
-            recipe.append('WORKDIR %s' % self.recipe.workdir)
+            recipe.append("WORKDIR %s" % self.recipe.workdir)
 
         # write the command, and entrypoint as is
         if self.recipe.cmd is not None:
-            recipe.append('CMD %s' % self.recipe.cmd)
+            recipe.append("CMD %s" % self.recipe.cmd)
 
         if self.recipe.entrypoint is not None:
-            recipe.append('ENTRYPOINT %s' % self.recipe.entrypoint)
+            recipe.append("ENTRYPOINT %s" % self.recipe.entrypoint)
 
         if self.recipe.test is not None:
-            recipe += write_lines('HEALTHCHECK', self.recipe.test)
+            recipe += write_lines("HEALTHCHECK", self.recipe.test)
 
         # Clean up extra white spaces
-        recipe = '\n'.join(recipe).replace('\n\n', '\n')
+        recipe = "\n".join(recipe).replace("\n\n", "\n")
         return recipe.rstrip()
 
 
 def write_files(label, lines):
-    '''write a list of lines with a header for a section.
+    """write a list of lines with a header for a section.
     
        Parameters
        ==========
        lines: one or more lines to write, with header appended
 
-    '''
+    """
     result = []
     for line in lines:
         if isinstance(line, list):
-            result.append('%s %s %s' %(label, line[0], line[1]))            
+            result.append("%s %s %s" % (label, line[0], line[1]))
         else:
-            result.append('%s %s' %(label, line))            
+            result.append("%s %s" % (label, line))
     return result
 
+
 def write_lines(label, lines):
-    '''write a list of lines with a header for a section.
+    """write a list of lines with a header for a section.
     
        Parameters
        ==========
        lines: one or more lines to write, with header appended
 
-    '''
+    """
     if not isinstance(lines, list):
         lines = [lines]
 
@@ -153,16 +153,16 @@ def write_lines(label, lines):
     for line in lines:
 
         # Skip comments and empty lines
-        if line.strip() == "" or line.strip().startswith('#'):
+        if line.strip() == "" or line.strip().startswith("#"):
             continue
 
         if continued or "USER" in line:
             result.append(line)
         else:
-            result.append('%s %s' %(label, line))
+            result.append("%s %s" % (label, line))
 
         continued = False
-        if line.endswith('\\'):
+        if line.endswith("\\"):
             continued = True
-            
+
     return result
diff --git a/spython/main/parse/writers/singularity.py b/spython/main/parse/writers/singularity.py
index ad6974e7..89808f51 100644
--- a/spython/main/parse/writers/singularity.py
+++ b/spython/main/parse/writers/singularity.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -14,49 +13,46 @@
 
 class SingularityWriter(WriterBase):
 
-    name = 'singularity'
+    name = "singularity"
 
-    def __init__(self, recipe=None): # pylint: disable=useless-super-delegation
-        '''a SingularityWriter will take a Recipe as input, and write
+    def __init__(self, recipe=None):  # pylint: disable=useless-super-delegation
+        """a SingularityWriter will take a Recipe as input, and write
            to a Singularity recipe file.
 
            Parameters
            ==========
            recipe: the Recipe object to write to file.
 
-        '''
+        """
         super(SingularityWriter, self).__init__(recipe)
 
-
     def validate(self):
-        '''validate that all (required) fields are included for the Docker
+        """validate that all (required) fields are included for the Docker
            recipe. We minimimally just need a FROM image, and must ensure
            it's in a valid format. If anything is missing, we exit with error.
-        '''
+        """
         if self.recipe is None:
-            bot.exit('Please provide a Recipe() to the writer first.')
+            bot.exit("Please provide a Recipe() to the writer first.")
 
         if self.recipe.fromHeader is None:
             bot.exit("Singularity recipe requires a from header.")
-                        
-
 
     def convert(self, runscript="/bin/bash", force=False):
-        '''docker2singularity will return a Singularity build recipe based on
+        """docker2singularity will return a Singularity build recipe based on
            a the loaded recipe object. It doesn't take any arguments as the
            recipe object contains the sections, and the calling function 
            determines saving / output logic.
-        '''
+        """
         self.validate()
 
-        recipe = ['Bootstrap: docker']
+        recipe = ["Bootstrap: docker"]
         recipe += ["From: %s" % self.recipe.fromHeader]
-  
+
         # Sections with key value pairs
-        recipe += self._create_section('files')
-        recipe += self._create_section('labels')
-        recipe += self._create_section('install', 'post')
-        recipe += self._create_section('environ', 'environment')    
+        recipe += self._create_section("files")
+        recipe += self._create_section("labels")
+        recipe += self._create_section("install", "post")
+        recipe += self._create_section("environ", "environment")
 
         # Take preference for user, entrypoint, command, then default
         runscript = self._create_runscript(runscript, force)
@@ -66,19 +62,18 @@ def convert(self, runscript="/bin/bash", force=False):
             runscript = ["cd " + self.recipe.workdir] + [runscript]
 
         # Finish the recipe, also add as startscript
-        recipe += finish_section(runscript, 'runscript')
-        recipe += finish_section(runscript, 'startscript')
+        recipe += finish_section(runscript, "runscript")
+        recipe += finish_section(runscript, "startscript")
 
         if self.recipe.test is not None:
-            recipe += finish_section(self.recipe.test, 'test')
+            recipe += finish_section(self.recipe.test, "test")
 
         # Clean up extra white spaces
-        recipe = '\n'.join(recipe).replace('\n\n', '\n')
+        recipe = "\n".join(recipe).replace("\n\n", "\n")
         return recipe.rstrip()
 
-
     def _create_runscript(self, default="/bin/bash", force=False):
-        '''create_entrypoint is intended to create a singularity runscript
+        """create_entrypoint is intended to create a singularity runscript
            based on a Docker entrypoint or command. We first use the Docker
            ENTRYPOINT, if defined. If not, we use the CMD. If neither is found,
            we use function default.
@@ -88,7 +83,7 @@ def _create_runscript(self, default="/bin/bash", force=False):
            default: set a default entrypoint, if the container does not have
                     an entrypoint or cmd.
            force: If true, use default and ignore Dockerfile settings
-        '''
+        """
         entrypoint = default
 
         # Only look at Docker if not enforcing default
@@ -98,18 +93,18 @@ def _create_runscript(self, default="/bin/bash", force=False):
 
                 # The provided entrypoint can be a string or a list
                 if isinstance(self.recipe.entrypoint, list):
-                    entrypoint = ' '.join(self.recipe.entrypoint)
+                    entrypoint = " ".join(self.recipe.entrypoint)
                 else:
-                    entrypoint = ''.join(self.recipe.entrypoint)
+                    entrypoint = "".join(self.recipe.entrypoint)
 
             if self.recipe.cmd is not None:
                 if isinstance(self.recipe.cmd, list):
-                    entrypoint = entrypoint + ' ' + ' '.join(self.recipe.cmd)
+                    entrypoint = entrypoint + " " + " ".join(self.recipe.cmd)
                 else:
-                    entrypoint = entrypoint + ' ' + ''.join(self.recipe.cmd)
+                    entrypoint = entrypoint + " " + "".join(self.recipe.cmd)
 
         # Entrypoint should use exec
-        if not entrypoint.startswith('exec'):
+        if not entrypoint.startswith("exec"):
             entrypoint = "exec %s" % entrypoint
 
         # Should take input arguments into account
@@ -117,9 +112,8 @@ def _create_runscript(self, default="/bin/bash", force=False):
             entrypoint = '%s "$@"' % entrypoint
         return entrypoint
 
-
     def _create_section(self, attribute, name=None):
-        '''create a section based on key, value recipe pairs, 
+        """create a section based on key, value recipe pairs, 
            This is used for files or label
 
           Parameters
@@ -128,20 +122,20 @@ def _create_section(self, attribute, name=None):
           name: the name to write to the recipe file (e.g., %name).
                 if not defined, the attribute name is used.
 
-        '''
+        """
 
         # Default section name is the same as attribute
         if name is None:
-            name = attribute 
+            name = attribute
 
         # Put a space between sections
-        section = ['\n']
+        section = ["\n"]
 
         # Only continue if we have the section and it's not empty
         try:
             section = getattr(self.recipe, attribute)
         except AttributeError:
-            bot.debug('Recipe does not have section for %s' % attribute)
+            bot.debug("Recipe does not have section for %s" % attribute)
             return section
 
         # if the section is empty, don't print it
@@ -149,11 +143,11 @@ def _create_section(self, attribute, name=None):
             return section
 
         # Files
-        if attribute in ['files', 'labels']:
+        if attribute in ["files", "labels"]:
             return create_keyval_section(section, name)
 
         # An environment section needs exports
-        if attribute in ['environ']:
+        if attribute in ["environ"]:
             return create_env_section(section, name)
 
         # Post, Setup
@@ -161,7 +155,7 @@ def _create_section(self, attribute, name=None):
 
 
 def finish_section(section, name):
-    '''finish_section will add the header to a section, to finish the recipe
+    """finish_section will add the header to a section, to finish the recipe
        take a custom command or list and return a section.
 
        Parameters
@@ -169,8 +163,8 @@ def finish_section(section, name):
        section: the section content, without a header
        name: the name of the section for the header
 
-    '''    
-    
+    """
+
     if not isinstance(section, list):
         section = [section]
 
@@ -178,16 +172,16 @@ def finish_section(section, name):
     lines = []
     for line in section:
         if "USER" in line:
-            username = line.replace('USER', '').rstrip()
-            line = "su - %s" % username + ' # ' + line
+            username = line.replace("USER", "").rstrip()
+            line = "su - %s" % username + " # " + line
         lines.append(line)
 
-    header = ['%' + name]
+    header = ["%" + name]
     return header + lines
 
 
 def create_keyval_section(pairs, name):
-    '''create a section based on key, value recipe pairs, 
+    """create a section based on key, value recipe pairs, 
        This is used for files or label
 
       Parameters
@@ -195,15 +189,15 @@ def create_keyval_section(pairs, name):
       section: the list of values to return as a parsed list of lines
       name: the name of the section to write (e.g., files)
 
-    '''
-    section = ['%' + name]
+    """
+    section = ["%" + name]
     for pair in pairs:
-        section.append(' '.join(pair).strip().strip('\\'))
+        section.append(" ".join(pair).strip().strip("\\"))
     return section
 
 
 def create_env_section(pairs, name):
-    '''environment key value pairs need to be joined by an equal, and 
+    """environment key value pairs need to be joined by an equal, and 
        exported at the end.
 
       Parameters
@@ -211,8 +205,8 @@ def create_env_section(pairs, name):
       section: the list of values to return as a parsed list of lines
       name: the name of the section to write (e.g., files)
 
-    '''
-    section = ['%' + name]
+    """
+    section = ["%" + name]
     for pair in pairs:
         section.append("export %s" % pair)
     return section
diff --git a/spython/main/pull.py b/spython/main/pull.py
index 52526caf..255ff9e3 100644
--- a/spython/main/pull.py
+++ b/spython/main/pull.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -11,17 +10,20 @@
 import os
 import re
 
-def pull(self, 
-         image=None,
-         name=None,
-         pull_folder='',
-         ext=None,
-         force=False,
-         capture=False,
-         stream=False,
-         quiet=False):
-
-    '''pull will pull a singularity hub or Docker image
+
+def pull(
+    self,
+    image=None,
+    name=None,
+    pull_folder="",
+    ext=None,
+    force=False,
+    capture=False,
+    stream=False,
+    quiet=False,
+):
+
+    """pull will pull a singularity hub or Docker image
         
        Parameters
        ==========
@@ -34,17 +36,18 @@ def pull(self,
        name: a custom name to use, to override default
        ext: if no name specified, the default extension to use.
 
-    ''' 
+    """
     from spython.utils import check_install
+
     check_install()
 
-    cmd = self._init_command('pull')
+    cmd = self._init_command("pull")
 
     # Quiet is honored if set by the client, or user
     quiet = quiet or self.quiet
 
     if not ext:
-        ext = 'sif' if 'version 3' in self.version() else 'simg'
+        ext = "sif" if "version 3" in self.version() else "simg"
 
     # No image provided, default to use the client's loaded image
     if image is None:
@@ -52,10 +55,10 @@ def pull(self,
 
     # If it's still None, no go!
     if image is None:
-        bot.exit('You must provide an image uri, or use client.load() first.')
+        bot.exit("You must provide an image uri, or use client.load() first.")
 
     # Singularity Only supports shub and Docker pull
-    if not re.search('^(shub|docker)://', image):
+    if not re.search("^(shub|docker)://", image):
         bot.exit("pull only valid for docker and shub. Use sregistry client.")
 
     # If we still don't have a custom name, base off of image uri.
@@ -64,12 +67,12 @@ def pull(self,
 
     if pull_folder:
         final_image = os.path.join(pull_folder, os.path.basename(name))
-    
+
         # Regression Singularity 3.* onward, PULLFOLDER not honored
         # https://github.com/sylabs/singularity/issues/2788
-        if 'version 3' in self.version():
+        if "version 3" in self.version():
             name = final_image
-            pull_folder = None # Don't use pull_folder
+            pull_folder = None  # Don't use pull_folder
     else:
         final_image = name
 
@@ -81,14 +84,12 @@ def pull(self,
     cmd.append(image)
 
     if not quiet:
-        bot.info(' '.join(cmd))
+        bot.info(" ".join(cmd))
 
-    with ScopedEnvVar('SINGULARITY_PULLFOLDER', pull_folder):
+    with ScopedEnvVar("SINGULARITY_PULLFOLDER", pull_folder):
         # Option 1: Streaming we just run to show user
         if not stream:
-            self._run_command(cmd,
-                              capture=capture,
-                              quiet=quiet)
+            self._run_command(cmd, capture=capture, quiet=quiet)
 
         # Option 3: A custom name we can predict (not commit/hash) and can also show
         else:
diff --git a/spython/main/run.py b/spython/main/run.py
index 9ea3490a..7b6d8fc3 100644
--- a/spython/main/run.py
+++ b/spython/main/run.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -10,19 +9,22 @@
 from spython.utils import stream_command
 import json
 
-def run(self, 
-        image=None,
-        args=None,
-        app=None,
-        sudo=False,
-        writable=False,
-        contain=False,
-        bind=None,
-        stream=False,
-        nv=False,
-        options=None,
-        return_result=False):
-    '''
+
+def run(
+    self,
+    image=None,
+    args=None,
+    app=None,
+    sudo=False,
+    writable=False,
+    contain=False,
+    bind=None,
+    stream=False,
+    nv=False,
+    options=None,
+    return_result=False,
+):
+    """
         run will run the container, with or withour arguments (which
         should be provided in a list)
     
@@ -43,15 +45,16 @@ def run(self,
         return_result: if True, return entire json object with return code
              and message result (default is False)
 
-    '''
+    """
     from spython.utils import check_install
+
     check_install()
 
-    cmd = self._init_command('run')
-   
+    cmd = self._init_command("run")
+
     # nv option leverages any GPU cards
     if nv:
-        cmd += ['--nv']
+        cmd += ["--nv"]
 
     # No image provided, default to use the client's loaded image
     if image is None:
@@ -63,7 +66,7 @@ def run(self,
 
     # If image is still None, not defined by user or previously with client
     if image is None:
-        bot.exit('Please load or provide an image.')
+        bot.exit("Please load or provide an image.")
 
     # Does the user want to use bind paths option?
     if bind is not None:
@@ -71,27 +74,25 @@ def run(self,
 
     # Does the user want to run an app?
     if app is not None:
-        cmd = cmd + ['--app', app]
+        cmd = cmd + ["--app", app]
 
     # Does the user want writable?
     if writable:
-        cmd.append('--writable')
+        cmd.append("--writable")
 
     # Add options
     if options is not None:
         cmd = cmd + options
 
     cmd = cmd + [image]
-        
-    if args is not None:        
+
+    if args is not None:
         if not isinstance(args, list):
-            args = args.split(' ')
+            args = args.split(" ")
         cmd = cmd + args
 
     if not stream:
-        result = self._run_command(cmd, 
-                                   sudo=sudo, 
-                                   return_result=return_result)
+        result = self._run_command(cmd, sudo=sudo, return_result=return_result)
     else:
         return stream_command(cmd, sudo=sudo)
 
@@ -101,7 +102,7 @@ def run(self,
 
     # Otherwise, we parse the result if it was successful
     if result:
-        result = result.strip('\n')
+        result = result.strip("\n")
 
         try:
             result = json.loads(result)
diff --git a/spython/oci/__init__.py b/spython/oci/__init__.py
index a902f309..b33812c8 100644
--- a/spython/oci/__init__.py
+++ b/spython/oci/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,17 +8,16 @@
 from spython.logger import bot
 import os
 
+
 class OciImage(ImageBase):
 
     # Default functions of client don't use sudo
     sudo = False
 
-    def __init__(self, 
-                 container_id=None, 
-                 bundle=None,
-                 create=True, 
-                 sudo=True, **kwargs):
-        ''' An Oci Image is an Image Base with OCI functions appended
+    def __init__(
+        self, container_id=None, bundle=None, create=True, sudo=True, **kwargs
+    ):
+        """ An Oci Image is an Image Base with OCI functions appended
 
             Parameters
             ==========
@@ -29,12 +27,12 @@ def __init__(self,
             create: if the bundle is provided, create a container (default True)
             sudo: if init is called with or without sudo, keep a record and use
                   for following commands unless sudo is provided to function.
-        '''
+        """
         super(OciImage, self).__init__()
 
         # Will typically be None, unless used outside of Client
         self.container_id = container_id
-        self.protocol = 'oci'
+        self.protocol = "oci"
         self.sudo = sudo
 
         # If bundle is provided, create it
@@ -42,10 +40,10 @@ def __init__(self,
             self.bundle = bundle
             self.create(bundle, container_id, **kwargs)
 
-# Unique resource identifier
+    # Unique resource identifier
 
     def get_container_id(self, container_id=None):
-        ''' a helper function shared between functions that will return a 
+        """ a helper function shared between functions that will return a 
             container_id. First preference goes to a container_id provided by
             the user at runtime. Second preference goes to the container_id
             instantiated with the client.
@@ -53,23 +51,22 @@ def get_container_id(self, container_id=None):
             Parameters
             ==========
             container_id: image uri to parse (required)
-        '''
+        """
 
         # The user must provide a container_id, or have one with the client
         if container_id is None and self.container_id is None:
-            bot.exit('You must provide a container_id.')
+            bot.exit("You must provide a container_id.")
 
         # Choose whichever is not None, with preference for function provided
         container_id = container_id or self.container_id
         return container_id
 
-
     def get_uri(self):
-        '''return the image uri (oci://) along with it's name
-        '''
+        """return the image uri (oci://) along with it's name
+        """
         return self.__str__()
 
-# Naming
+    # Naming
 
     def __str__(self):
         if self.container_id is not None:
@@ -79,11 +76,10 @@ def __str__(self):
     def __repr__(self):
         return self.__str__()
 
-
-# Commands
+    # Commands
 
     def _get_sudo(self, sudo=None):
-        '''if the client was initialized with sudo, remember this choice for
+        """if the client was initialized with sudo, remember this choice for
            later communication with the Oci Images. However, if the user provides
            a sudo argument (True or False) and not the default None, take
            preference to this argument.
@@ -91,14 +87,13 @@ def _get_sudo(self, sudo=None):
            Parameters
            ==========
            sudo: if None, use self.sudo. Otherwise return sudo.
-        '''
+        """
         if sudo is None:
             sudo = self.sudo
         return sudo
 
-
     def _run_and_return(self, cmd, sudo=None):
-        ''' Run a command, show the message to the user if quiet isn't set,
+        """ Run a command, show the message to the user if quiet isn't set,
             and return the return code. This is a wrapper for the OCI client
             to run a command and easily return the return code value (what
             the user is ultimately interested in).
@@ -108,27 +103,23 @@ def _run_and_return(self, cmd, sudo=None):
             cmd: the command (list) to run.
             sudo: whether to add sudo or not.         
 
-        '''
+        """
         sudo = self._get_sudo(sudo)
-        result = self._run_command(cmd, 
-                                   sudo=sudo, 
-                                   quiet=True, 
-                                   return_result=True)
+        result = self._run_command(cmd, sudo=sudo, quiet=True, return_result=True)
 
         # Successful return with no output
         if not result:
-            return 
+            return
 
         # Show the response to the user, only if not quiet.
         elif not self.quiet:
-            bot.println(result['message'])
+            bot.println(result["message"])
 
         # Return the state object to the user
-        return result['return_code']
-
+        return result["return_code"]
 
     def _init_command(self, action, flags=None):
-        ''' a wrapper to the base init_command, ensuring that "oci" is added
+        """ a wrapper to the base init_command, ensuring that "oci" is added
             to each command
 
             Parameters
@@ -137,9 +128,10 @@ def _init_command(self, action, flags=None):
             flags: one or more additional flags (e.g, volumes) 
                    not implemented yet.
 
-        '''
+        """
         from spython.main.base.command import init_command
+
         if not isinstance(action, list):
             action = [action]
-        cmd = ['oci'] + action
+        cmd = ["oci"] + action
         return init_command(self, cmd, flags)
diff --git a/spython/oci/cmd/__init__.py b/spython/oci/cmd/__init__.py
index 489887a3..1ce15b23 100644
--- a/spython/oci/cmd/__init__.py
+++ b/spython/oci/cmd/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,21 +6,21 @@
 
 
 def generate_oci_commands():
-    ''' The oci command group will allow interaction with an image using
+    """ The oci command group will allow interaction with an image using
         OCI commands.
-    '''
+    """
     from spython.oci import OciImage
 
     from spython.main.base.logger import println
 
     # run_command uses run_cmd, but wraps to catch error
-    from spython.main.base.command import (run_command, send_command)
+    from spython.main.base.command import run_command, send_command
     from spython.main.base.generate import RobotNamer
 
     # Oci Command Groups
-    from .mounts import (mount, umount)
-    from .states import (kill, state, start, pause, resume, _state_command)
-    from .actions import (attach, create, delete, execute, run, _run, update)
+    from .mounts import mount, umount
+    from .states import kill, state, start, pause, resume, _state_command
+    from .actions import attach, create, delete, execute, run, _run, update
 
     # Oci Commands
     OciImage.start = start
@@ -41,7 +40,7 @@ def generate_oci_commands():
     OciImage._state_command = _state_command
 
     OciImage.RobotNamer = RobotNamer()
-    OciImage._send_command = send_command # send and disregard stderr, stdout
+    OciImage._send_command = send_command  # send and disregard stderr, stdout
     OciImage._run_command = run_command
     OciImage._println = println
     OciImage.OciImage = OciImage
diff --git a/spython/oci/cmd/actions.py b/spython/oci/cmd/actions.py
index 6425d495..55c23eab 100644
--- a/spython/oci/cmd/actions.py
+++ b/spython/oci/cmd/actions.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2019 Vanessa Sochat.
+# Copyright (C) 2019-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,13 +8,17 @@
 from spython.utils import stream_command
 import os
 
-def run(self, bundle,
-              container_id=None,
-              log_path=None,
-              pid_file=None,
-              log_format="kubernetes"):
 
-    ''' run is a wrapper to create, start, attach, and delete a container.
+def run(
+    self,
+    bundle,
+    container_id=None,
+    log_path=None,
+    pid_file=None,
+    log_format="kubernetes",
+):
+
+    """ run is a wrapper to create, start, attach, and delete a container.
 
         Equivalent command line example:      
           singularity oci run -b ~/bundle mycontainer
@@ -28,24 +31,29 @@ def run(self, bundle,
         log_path: the path to store the log.
         pid_file: specify the pid file path to use
         log_format: defaults to kubernetes. Can also be "basic" or "json"
-    '''
-    return self._run(bundle,
-                     container_id=container_id,
-                     log_path=log_path,
-                     pid_file=pid_file,
-                     command="run",
-                     log_format=log_format)
-
-
-def create(self, bundle,
-                 container_id=None,
-                 empty_process=False,
-                 log_path=None,
-                 pid_file=None,
-                 sync_socket=None,
-                 log_format="kubernetes"): 
-
-    ''' use the client to create a container from a bundle directory. The bundle
+    """
+    return self._run(
+        bundle,
+        container_id=container_id,
+        log_path=log_path,
+        pid_file=pid_file,
+        command="run",
+        log_format=log_format,
+    )
+
+
+def create(
+    self,
+    bundle,
+    container_id=None,
+    empty_process=False,
+    log_path=None,
+    pid_file=None,
+    sync_socket=None,
+    log_format="kubernetes",
+):
+
+    """ use the client to create a container from a bundle directory. The bundle
         directory should have a config.json. You must be the root user to
         create a runtime.
 
@@ -64,27 +72,32 @@ def create(self, bundle,
         pid_file: specify the pid file path to use
         sync_socket: the path to the unix socket for state synchronization.
         log_format: defaults to kubernetes. Can also be "basic" or "json"
-    '''
-    return self._run(bundle,
-                     container_id=container_id,
-                     empty_process=empty_process,
-                     log_path=log_path,
-                     pid_file=pid_file,
-                     sync_socket=sync_socket,
-                     command="create",
-                     log_format=log_format)
-
-
-def _run(self, bundle,
-               container_id=None,
-               empty_process=False,
-               log_path=None,
-               pid_file=None,
-               sync_socket=None,
-               command="run",
-               log_format="kubernetes"): 
-
-    ''' _run is the base function for run and create, the only difference
+    """
+    return self._run(
+        bundle,
+        container_id=container_id,
+        empty_process=empty_process,
+        log_path=log_path,
+        pid_file=pid_file,
+        sync_socket=sync_socket,
+        command="create",
+        log_format=log_format,
+    )
+
+
+def _run(
+    self,
+    bundle,
+    container_id=None,
+    empty_process=False,
+    log_path=None,
+    pid_file=None,
+    sync_socket=None,
+    command="run",
+    log_format="kubernetes",
+):
+
+    """ _run is the base function for run and create, the only difference
         between the two being that run does not have an option for sync_socket.
 
         Equivalent command line example:      
@@ -103,7 +116,7 @@ def _run(self, bundle,
         sync_socket: the path to the unix socket for state synchronization.
         command: the command (run or create) to use (default is run)
         log_format: defaults to kubernetes. Can also be "basic" or "json"
-    '''
+    """
     container_id = self.get_container_id(container_id)
 
     # singularity oci create
@@ -111,22 +124,22 @@ def _run(self, bundle,
 
     # Check that the bundle exists
     if not os.path.exists(bundle):
-        bot.exit('Bundle not found at %s' % bundle)
+        bot.exit("Bundle not found at %s" % bundle)
 
     # Add the bundle
-    cmd = cmd + ['--bundle', bundle]
+    cmd = cmd + ["--bundle", bundle]
 
     # Additional Logging Files
-    cmd = cmd + ['--log-format', log_format]
+    cmd = cmd + ["--log-format", log_format]
 
     if log_path is not None:
-        cmd = cmd + ['--log-path', log_path]
+        cmd = cmd + ["--log-path", log_path]
     if pid_file is not None:
-        cmd = cmd + ['--pid-file', pid_file]
+        cmd = cmd + ["--pid-file", pid_file]
     if sync_socket is not None:
-        cmd = cmd + ['--sync-socket', sync_socket]
+        cmd = cmd + ["--sync-socket", sync_socket]
     if empty_process:
-        cmd.append('--empty-process')
+        cmd.append("--empty-process")
 
     # Finally, add the container_id
     cmd.append(container_id)
@@ -141,7 +154,7 @@ def _run(self, bundle,
 
 
 def delete(self, container_id=None, sudo=None):
-    '''delete an instance based on container_id.
+    """delete an instance based on container_id.
 
        Parameters
        ==========
@@ -155,12 +168,12 @@ def delete(self, container_id=None, sudo=None):
        =======
        return_code: the return code from the delete command. 0 indicates a
                     successful delete, 255 indicates not.
-    '''
+    """
     sudo = self._get_sudo(sudo)
     container_id = self.get_container_id(container_id)
 
     # singularity oci delete
-    cmd = self._init_command('delete')
+    cmd = self._init_command("delete")
 
     # Add the container_id
     cmd.append(container_id)
@@ -169,9 +182,8 @@ def delete(self, container_id=None, sudo=None):
     return self._run_and_return(cmd, sudo=sudo)
 
 
-
 def attach(self, container_id=None, sudo=False):
-    '''attach to a container instance based on container_id
+    """attach to a container instance based on container_id
 
        Parameters
        ==========
@@ -184,12 +196,12 @@ def attach(self, container_id=None, sudo=False):
        =======
        return_code: the return code from the delete command. 0 indicates a
                     successful delete, 255 indicates not.
-    '''
+    """
     sudo = self._get_sudo(sudo)
     container_id = self.get_container_id(container_id)
 
     # singularity oci delete
-    cmd = self._init_command('attach')
+    cmd = self._init_command("attach")
 
     # Add the container_id
     cmd.append(container_id)
@@ -199,7 +211,7 @@ def attach(self, container_id=None, sudo=False):
 
 
 def execute(self, command=None, container_id=None, sudo=False, stream=False):
-    '''execute a command to a container instance based on container_id
+    """execute a command to a container instance based on container_id
 
        Parameters
        ==========
@@ -215,12 +227,12 @@ def execute(self, command=None, container_id=None, sudo=False, stream=False):
        =======
        return_code: the return code from the delete command. 0 indicates a
                     successful delete, 255 indicates not.
-    '''
+    """
     sudo = self._get_sudo(sudo)
     container_id = self.get_container_id(container_id)
 
     # singularity oci delete
-    cmd = self._init_command('exec')
+    cmd = self._init_command("exec")
 
     # Add the container_id
     cmd.append(container_id)
@@ -236,8 +248,9 @@ def execute(self, command=None, container_id=None, sudo=False, stream=False):
             return stream_command(cmd, sudo=sudo)
         return self._run_command(cmd, sudo=sudo, quiet=True)
 
+
 def update(self, container_id, from_file=None, sudo=False):
-    '''update container cgroup resources for a specific container_id,
+    """update container cgroup resources for a specific container_id,
        The container must have state "running" or "created."
 
        Singularity Example:
@@ -248,15 +261,15 @@ def update(self, container_id, from_file=None, sudo=False):
        ==========
        container_id: the container_id to update cgroups for
        from_file: a path to an OCI JSON resource file to update from.
-    '''
+    """
     sudo = self._get_sudo(sudo)
     container_id = self.get_container_id(container_id)
 
     # singularity oci delete
-    cmd = self._init_command('update')
+    cmd = self._init_command("update")
 
     if from_file is not None:
-        cmd = cmd + ['--from-file', from_file]
+        cmd = cmd + ["--from-file", from_file]
 
     # Add the container_id
     cmd.append(container_id)
diff --git a/spython/oci/cmd/mounts.py b/spython/oci/cmd/mounts.py
index 659524e7..296093cf 100644
--- a/spython/oci/cmd/mounts.py
+++ b/spython/oci/cmd/mounts.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2019 Vanessa Sochat.
+# Copyright (C) 2019-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -7,20 +6,20 @@
 
 
 def mount(self, image, sudo=None):
-    '''create an OCI bundle from SIF image
+    """create an OCI bundle from SIF image
 
        Parameters
        ==========
        image: the container (sif) to mount
-    '''
+    """
     return self._state_command(image, command="mount", sudo=sudo)
 
 
 def umount(self, image, sudo=None):
-    '''delete an OCI bundle created from SIF image
+    """delete an OCI bundle created from SIF image
 
        Parameters
        ==========
        image: the container (sif) to mount
-    '''
+    """
     return self._state_command(image, command="umount", sudo=sudo)
diff --git a/spython/oci/cmd/states.py b/spython/oci/cmd/states.py
index 52a9ed34..ededbbb2 100644
--- a/spython/oci/cmd/states.py
+++ b/spython/oci/cmd/states.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2019 Vanessa Sochat.
+# Copyright (C) 2019-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -11,7 +10,7 @@
 
 def state(self, container_id=None, sudo=None, sync_socket=None):
 
-    ''' get the state of an OciImage, if it exists. The optional states that
+    """ get the state of an OciImage, if it exists. The optional states that
         can be returned are created, running, stopped or (not existing).
 
         Equivalent command line example:      
@@ -28,15 +27,15 @@ def state(self, container_id=None, sudo=None, sync_socket=None):
         =======
         state: a parsed json of the container state, if exists. If the
                container is not found, None is returned.
-    '''
+    """
     sudo = self._get_sudo(sudo)
     container_id = self.get_container_id(container_id)
 
     # singularity oci state
-    cmd = self._init_command('state')
+    cmd = self._init_command("state")
 
     if sync_socket is not None:
-        cmd = cmd + ['--sync-socket', sync_socket]
+        cmd = cmd + ["--sync-socket", sync_socket]
 
     # Finally, add the container_id
     cmd.append(container_id)
@@ -49,11 +48,11 @@ def state(self, container_id=None, sudo=None, sync_socket=None):
         # If successful, a string is returned to parse
         if isinstance(result, str):
             return json.loads(result)
-    
 
-def _state_command(self, container_id=None, command='start', sudo=None):
 
-    ''' A generic state command to wrap pause, resume, kill, etc., where the
+def _state_command(self, container_id=None, command="start", sudo=None):
+
+    """ A generic state command to wrap pause, resume, kill, etc., where the
         only difference is the command. This function will be unwrapped if the
         child functions get more complicated (with additional arguments).
 
@@ -70,7 +69,7 @@ def _state_command(self, container_id=None, command='start', sudo=None):
         Returns
         =======
         return_code: the return code to indicate if the container was started.
-    '''
+    """
     sudo = self._get_sudo(sudo)
     container_id = self.get_container_id(container_id)
 
@@ -84,10 +83,9 @@ def _state_command(self, container_id=None, command='start', sudo=None):
     return self._run_and_return(cmd, sudo)
 
 
-
 def start(self, container_id=None, sudo=None):
 
-    ''' start a previously invoked OciImage, if it exists.
+    """ start a previously invoked OciImage, if it exists.
 
         Equivalent command line example:      
            singularity oci start <container_ID>
@@ -101,13 +99,13 @@ def start(self, container_id=None, sudo=None):
         Returns
         =======
         return_code: the return code to indicate if the container was started.
-    '''
+    """
     return self._state_command(container_id, sudo=sudo)
 
 
 def kill(self, container_id=None, sudo=None, signal=None):
 
-    ''' stop (kill) a started OciImage container, if it exists
+    """ stop (kill) a started OciImage container, if it exists
 
         Equivalent command line example:      
            singularity oci kill <container_ID>
@@ -122,26 +120,26 @@ def kill(self, container_id=None, sudo=None, signal=None):
         Returns
         =======
         return_code: the return code to indicate if the container was killed.
-    '''
+    """
     sudo = self._get_sudo(sudo)
     container_id = self.get_container_id(container_id)
 
     # singularity oci state
-    cmd = self._init_command('kill')
+    cmd = self._init_command("kill")
 
     # Finally, add the container_id
     cmd.append(container_id)
 
     # Add the signal, if defined
     if signal is not None:
-        cmd = cmd + ['--signal', signal]
+        cmd = cmd + ["--signal", signal]
 
     # Run the command, return return code
     return self._run_and_return(cmd, sudo)
 
 
 def resume(self, container_id=None, sudo=None):
-    ''' resume a stopped OciImage container, if it exists
+    """ resume a stopped OciImage container, if it exists
 
         Equivalent command line example:      
            singularity oci resume <container_ID>
@@ -155,12 +153,12 @@ def resume(self, container_id=None, sudo=None):
         Returns
         =======
         return_code: the return code to indicate if the container was resumed.
-    '''
-    return self._state_command(container_id, command='resume', sudo=sudo)
+    """
+    return self._state_command(container_id, command="resume", sudo=sudo)
 
 
 def pause(self, container_id=None, sudo=None):
-    ''' pause a running OciImage container, if it exists
+    """ pause a running OciImage container, if it exists
 
         Equivalent command line example:      
            singularity oci pause <container_ID>
@@ -174,5 +172,5 @@ def pause(self, container_id=None, sudo=None):
         Returns
         =======
         return_code: the return code to indicate if the container was paused.
-    '''
-    return self._state_command(container_id, command='pause', sudo=sudo)
+    """
+    return self._state_command(container_id, command="pause", sudo=sudo)
diff --git a/spython/tests/conftest.py b/spython/tests/conftest.py
index 2114e50e..5c212751 100644
--- a/spython/tests/conftest.py
+++ b/spython/tests/conftest.py
@@ -4,23 +4,27 @@
 from spython.main import Client
 from spython.utils import get_installdir
 
+
 @pytest.fixture
 def installdir():
     return get_installdir()
 
+
 @pytest.fixture
-def test_data(installdir): # pylint: disable=redefined-outer-name
-    root = os.path.join(installdir, 'tests', 'testdata')
-    dockerFiles = glob(os.path.join(root, 'docker2singularity', '*.docker'))
-    singularityFiles = glob(os.path.join(root, 'singularity2docker', '*.def'))
+def test_data(installdir):  # pylint: disable=redefined-outer-name
+    root = os.path.join(installdir, "tests", "testdata")
+    dockerFiles = glob(os.path.join(root, "docker2singularity", "*.docker"))
+    singularityFiles = glob(os.path.join(root, "singularity2docker", "*.def"))
     return {
-        'root': root,
-        'd2s': [(file, os.path.splitext(file)[0] + '.def') for file in dockerFiles],
-        's2d': [(file, os.path.splitext(file)[0] + '.docker') for file in singularityFiles],
+        "root": root,
+        "d2s": [(file, os.path.splitext(file)[0] + ".def") for file in dockerFiles],
+        "s2d": [
+            (file, os.path.splitext(file)[0] + ".docker") for file in singularityFiles
+        ],
     }
 
+
 @pytest.fixture(scope="session")
 def docker_container(tmp_path_factory):
     folder = tmp_path_factory.mktemp("docker-img")
-    return folder, Client.pull("docker://busybox:1.30.1",
-                               pull_folder=str(folder))
+    return folder, Client.pull("docker://busybox:1.30.1", pull_folder=str(folder))
diff --git a/spython/tests/test_base.py b/spython/tests/test_base.py
index 07bcebcf..26f5e535 100644
--- a/spython/tests/test_base.py
+++ b/spython/tests/test_base.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -10,7 +10,7 @@
 
 
 def test_image():
-    image = Image('docker://ubuntu')
-    assert str(image) == 'docker://ubuntu'
-    assert image.protocol == 'docker'
-    assert image.image == 'ubuntu'
+    image = Image("docker://ubuntu")
+    assert str(image) == "docker://ubuntu"
+    assert image.protocol == "docker"
+    assert image.image == "ubuntu"
diff --git a/spython/tests/test_client.py b/spython/tests/test_client.py
index 1b49d02e..6b5b2468 100644
--- a/spython/tests/test_client.py
+++ b/spython/tests/test_client.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -14,51 +14,55 @@
 def test_build_from_docker(tmp_path):
     container = str(tmp_path / "container.sif")
 
-    created_container = Client.build('docker://busybox:1.30.1', 
-                                     image=container,
-                                     sudo=False)
+    created_container = Client.build(
+        "docker://busybox:1.30.1", image=container, sudo=False
+    )
     assert created_container == container
     assert os.path.exists(created_container)
 
+
 def test_export():
     sandbox = "busybox:1.30.sandbox"
-    created_sandbox = Client.export('docker://busybox:1.30.1')
+    created_sandbox = Client.export("docker://busybox:1.30.1")
     assert created_sandbox == sandbox
     assert os.path.exists(created_sandbox)
     shutil.rmtree(created_sandbox)
 
+
 def test_pull_and_run(tmp_path):
     pass
-    #image = Client.pull("shub://vsoch/singularity-images", 
-    #                    pull_folder=str(tmp_path))
-    #print(image)
-    #assert os.path.exists(image)
-    #ext = 'sif' if Client.version_info().major >= 3 else 'simg'
-    #assert image == str(tmp_path / ('singularity-images.' + ext))
+    image = Client.pull("shub://vsoch/singularity-images", pull_folder=str(tmp_path))
+    print(image)
+    assert os.path.exists(image)
+    ext = "sif" if Client.version_info().major >= 3 else "simg"
+    assert image == str(tmp_path / ("singularity-images." + ext))
+
+    result = Client.run(image)
+    print(result)
+    assert "You say please, but all I see is pizza.." in result
 
-    #result = Client.run(image)
-    #print(result)
-    #assert 'You say please, but all I see is pizza..' in result
 
 def test_docker_pull(docker_container):
     tmp_path, container = docker_container
     print(container)
-    ext = 'sif' if Client.version_info().major >= 3 else 'simg'
+    ext = "sif" if Client.version_info().major >= 3 else "simg"
     assert container == str(tmp_path / ("busybox:1.30.1." + ext))
     assert os.path.exists(container)
 
+
 def test_execute(docker_container):
-    result = Client.execute(docker_container[1], 'ls /')
+    result = Client.execute(docker_container[1], "ls /")
     print(result)
-    assert 'tmp\nusr\nvar' in result
+    assert "tmp\nusr\nvar" in result
+
 
 def test_execute_with_return_code(docker_container):
-    result = Client.execute(docker_container[1], 'ls /', return_result=True)
+    result = Client.execute(docker_container[1], "ls /", return_result=True)
     print(result)
-    assert 'tmp\nusr\nvar' in result['message']
-    assert result['return_code'] == 0
+    assert "tmp\nusr\nvar" in result["message"]
+    assert result["return_code"] == 0
+
 
 def test_inspect(docker_container):
     result = Client.inspect(docker_container[1])
-    assert result['type'] == 'container'
-    assert 'attributes' in result
+    assert "attributes" in result or "data" in result
diff --git a/spython/tests/test_conversion.py b/spython/tests/test_conversion.py
index 83b75df6..e84cf276 100644
--- a/spython/tests/test_conversion.py
+++ b/spython/tests/test_conversion.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-# Copyright (C) 2019 Vanessa Sochat.
+# Copyright (C) 2019-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,43 +9,51 @@
 from glob import glob
 import os
 
+
 def read_file(file):
     with open(file) as f:
-        return [line.rstrip('\n') for line in f] 
-        
+        return [line.rstrip("\n") for line in f]
+
+
 def test_other_recipe_exists(test_data):
     # Have any example
-    assert test_data['d2s']
-    assert test_data['s2d']
-
-    for _, outFile in test_data['d2s'] + test_data['s2d']:
-        assert os.path.exists(outFile), outFile + ' is missing'
-    
-    dockerfiles = glob(os.path.join(os.path.dirname(test_data['s2d'][0][0]), '*.docker'))
-    singularityfiles = glob(os.path.join(os.path.dirname(test_data['d2s'][0][0]), '*.def'))
+    assert test_data["d2s"]
+    assert test_data["s2d"]
+
+    for _, outFile in test_data["d2s"] + test_data["s2d"]:
+        assert os.path.exists(outFile), outFile + " is missing"
+
+    dockerfiles = glob(
+        os.path.join(os.path.dirname(test_data["s2d"][0][0]), "*.docker")
+    )
+    singularityfiles = glob(
+        os.path.join(os.path.dirname(test_data["d2s"][0][0]), "*.def")
+    )
     for file in dockerfiles:
-        assert file in [out for _, out in test_data['s2d']]
+        assert file in [out for _, out in test_data["s2d"]]
     for file in singularityfiles:
-        assert file in [out for _, out in test_data['d2s']]
+        assert file in [out for _, out in test_data["d2s"]]
+
 
 def test_docker2singularity(test_data, tmp_path):
     from spython.main.parse.parsers import DockerParser
     from spython.main.parse.writers import SingularityWriter
 
-    for dockerfile, recipe in test_data['d2s']:
+    for dockerfile, recipe in test_data["d2s"]:
         parser = DockerParser(dockerfile)
         writer = SingularityWriter(parser.recipe)
 
-        assert writer.convert().split('\n') == read_file(recipe)
+        assert writer.convert().split("\n") == read_file(recipe)
+
 
 def test_singularity2docker(test_data, tmp_path):
 
-    print('Testing spython conversion from singularity2docker')
+    print("Testing spython conversion from singularity2docker")
     from spython.main.parse.parsers import SingularityParser
     from spython.main.parse.writers import DockerWriter
 
-    for recipe, dockerfile in test_data['s2d']:
+    for recipe, dockerfile in test_data["s2d"]:
         parser = SingularityParser(recipe)
         writer = DockerWriter(parser.recipe)
 
-        assert writer.convert().split('\n') == read_file(dockerfile)
+        assert writer.convert().split("\n") == read_file(dockerfile)
diff --git a/spython/tests/test_instances.py b/spython/tests/test_instances.py
index 9c661279..dc66517a 100644
--- a/spython/tests/test_instances.py
+++ b/spython/tests/test_instances.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -11,19 +11,21 @@
 
 
 def test_instance_class():
-    instance = Client.instance('docker://ubuntu', start=False)
-    assert instance.get_uri() == 'instance://' + instance.name
-    assert instance.name != ''
+    instance = Client.instance("docker://ubuntu", start=False)
+    assert instance.get_uri() == "instance://" + instance.name
+    assert instance.name != ""
 
-    name = 'coolName'
-    instance = Client.instance('docker://busybox:1.30.1', start=False, name=name)
-    assert instance.get_uri() == 'instance://' + instance.name
+    name = "coolName"
+    instance = Client.instance("docker://busybox:1.30.1", start=False, name=name)
+    assert instance.get_uri() == "instance://" + instance.name
     assert instance.name == name
 
+
 def test_has_no_instances():
     instances = Client.instances()
     assert instances == []
 
+
 class TestInstanceFuncs(object):
     @pytest.fixture(autouse=True)
     def cleanup(self):
@@ -33,7 +35,7 @@ def cleanup(self):
     def test_instance_cmds(self, docker_container):
         image = docker_container[1]
         myinstance = Client.instance(image)
-        assert myinstance.get_uri().startswith('instance://')
+        assert myinstance.get_uri().startswith("instance://")
 
         print("...Case 2: List instances")
         instances = Client.instances()
@@ -43,14 +45,14 @@ def test_instance_cmds(self, docker_container):
         assert isinstance(instances[0], dict)
 
         print("...Case 3: Commands to instances")
-        result = Client.execute(myinstance, ['echo', 'hello'])
-        assert result == 'hello\n'
+        result = Client.execute(myinstance, ["echo", "hello"])
+        assert result == "hello\n"
 
-        print('...Case 4: Return value from instance')
-        result = Client.execute(myinstance, 'ls /', return_result=True)
+        print("...Case 4: Return value from instance")
+        result = Client.execute(myinstance, "ls /", return_result=True)
         print(result)
-        assert 'tmp\nusr\nvar' in result['message']
-        assert result['return_code'] == 0
+        assert "tmp\nusr\nvar" in result["message"]
+        assert result["return_code"] == 0
 
         print("...Case 5: Stop instances")
         myinstance.stop()
diff --git a/spython/tests/test_oci.py b/spython/tests/test_oci.py
index 2c6900c2..228de635 100644
--- a/spython/tests/test_oci.py
+++ b/spython/tests/test_oci.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-# Copyright (C) 2019 Vanessa Sochat.
+# Copyright (C) 2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -17,96 +17,100 @@
 
 pytestmark = pytest.mark.skipif(
     Client.version_info() < VersionInfo(3, 0, 0),
-    reason='OCI command group introduced in singularity 3'
+    reason="OCI command group introduced in singularity 3",
 )
 
+
 @pytest.fixture
 def sandbox(tmp_path):
-    image = Client.build("docker://busybox:1.30.1", 
-                            image=str(tmp_path / 'sandbox'),
-                            sandbox=True,
-                            sudo=False)
+    image = Client.build(
+        "docker://busybox:1.30.1",
+        image=str(tmp_path / "sandbox"),
+        sandbox=True,
+        sudo=False,
+    )
 
     assert os.path.exists(image)
 
-    config = os.path.join(get_installdir(), 'oci', 'config.json')
-    shutil.copyfile(config, os.path.join(image, 'config.json'))
+    config = os.path.join(get_installdir(), "oci", "config.json")
+    shutil.copyfile(config, os.path.join(image, "config.json"))
     return image
 
+
 def test_oci_image():
-    image = Client.oci.OciImage('oci://imagename')
-    assert image.get_uri() == '[singularity-python-oci:oci://imagename]'
+    image = Client.oci.OciImage("oci://imagename")
+    assert image.get_uri() == "[singularity-python-oci:oci://imagename]"
 
-def test_oci(sandbox): # pylint: disable=redefined-outer-name
+
+def test_oci(sandbox):  # pylint: disable=redefined-outer-name
     image = sandbox
     container_id = RobotNamer().generate()
 
     # A non existing process should not have a state
-    print('...Case 1. Check status of non-existing bundle.')
-    state = Client.oci.state('mycontainer')
+    print("...Case 1. Check status of non-existing bundle.")
+    state = Client.oci.state("mycontainer")
     assert state is None
-        
+
     # This will use sudo
     print("...Case 2: Create OCI image from bundle")
-    result = Client.oci.create(bundle=image,
-                               container_id=container_id)
+    result = Client.oci.create(bundle=image, container_id=container_id)
 
     print(result)
-    assert result['status'] == 'created'
+    assert result["status"] == "created"
 
-    print('...Case 3. Execute command to non running bundle.')
-    result = Client.oci.execute(container_id=container_id, 
-                                sudo=True,
-                                command=['ls', '/'])
+    print("...Case 3. Execute command to non running bundle.")
+    result = Client.oci.execute(
+        container_id=container_id, sudo=True, command=["ls", "/"]
+    )
 
     print(result)
     print(Client.version_info())
 
-    if Client.version_info() >= VersionInfo(3, 2, 0, '1'):
-        assert result['return_code'] == 255
+    if Client.version_info() >= VersionInfo(3, 2, 0, "1"):
+        assert result["return_code"] == 255
     else:
-        assert 'bin' in result
+        assert "bin" in result
 
-    print('...Case 4. Start container return value 0.')
+    print("...Case 4. Start container return value 0.")
     state = Client.oci.start(container_id, sudo=True)
     assert state == 0
 
-    print('...Case 5. Execute command to running bundle.')
-    result = Client.oci.execute(container_id=container_id, 
-                                sudo=True, 
-                                command=['ls', '/'])
+    print("...Case 5. Execute command to running bundle.")
+    result = Client.oci.execute(
+        container_id=container_id, sudo=True, command=["ls", "/"]
+    )
 
     print(result)
-    assert 'bin' in result
+    assert "bin" in result
 
-    print('...Case 6. Check status of existing bundle.')
+    print("...Case 6. Check status of existing bundle.")
     state = Client.oci.state(container_id, sudo=True)
-    assert state['status'] == 'running'
+    assert state["status"] == "running"
 
-    print('...Case 7. Pause running container return value 0.')
+    print("...Case 7. Pause running container return value 0.")
     state = Client.oci.pause(container_id, sudo=True)
     assert state == 0
 
     # State was still reported as running
-    if Client.version_info() >= VersionInfo(3, 2, 0, '1'):
-        print('...check status of paused bundle.')
+    if Client.version_info() >= VersionInfo(3, 2, 0, "1"):
+        print("...check status of paused bundle.")
         state = Client.oci.state(container_id, sudo=True)
-        assert state['status'] == 'paused'
+        assert state["status"] == "paused"
 
-    print('...Case 8. Resume paused container return value 0.')
+    print("...Case 8. Resume paused container return value 0.")
     state = Client.oci.resume(container_id, sudo=True)
     assert state == 0
 
-    print('...check status of resumed bundle.')
+    print("...check status of resumed bundle.")
     state = Client.oci.state(container_id, sudo=True)
-    assert state['status'] == 'running'
+    assert state["status"] == "running"
 
-    print('...Case 9. Kill container.')
+    print("...Case 9. Kill container.")
     state = Client.oci.kill(container_id, sudo=True)
     assert state == 0
 
     # Clean up the image (should still use sudo)
-    # Bug in singularity that kill doesn't kill completely - this returns 
+    # Bug in singularity that kill doesn't kill completely - this returns
     # 255. When testsupdated to 3.1.* add signal=K to run
     result = Client.oci.delete(container_id, sudo=True)
     assert result in [0, 255]
diff --git a/spython/tests/test_parsers.py b/spython/tests/test_parsers.py
index 9c677fc5..e9e5cc2c 100644
--- a/spython/tests/test_parsers.py
+++ b/spython/tests/test_parsers.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -13,41 +13,42 @@
 def test_get_parser():
     from spython.main.parse.parsers import get_parser
 
-    parser = get_parser('docker')
+    parser = get_parser("docker")
     assert parser == DockerParser
 
-    parser = get_parser('Dockerfile')
+    parser = get_parser("Dockerfile")
     assert parser == DockerParser
 
-    parser = get_parser('Singularity')
+    parser = get_parser("Singularity")
     assert parser == SingularityParser
 
 
 def test_docker_parser(test_data):
-    dockerfile = os.path.join(test_data['root'], 'Dockerfile')        
+    dockerfile = os.path.join(test_data["root"], "Dockerfile")
     parser = DockerParser(dockerfile)
 
-    assert str(parser) == '[spython-parser][docker]'
+    assert str(parser) == "[spython-parser][docker]"
 
     # Test all fields from recipe
-    assert parser.recipe.fromHeader == 'python:3.5.1'
-    assert parser.recipe.cmd == '/code/run_uwsgi.sh'
+    assert parser.recipe.fromHeader == "python:3.5.1"
+    assert parser.recipe.cmd == "/code/run_uwsgi.sh"
     assert parser.recipe.entrypoint is None
-    assert parser.recipe.workdir == '/code'
+    assert parser.recipe.workdir == "/code"
     assert parser.recipe.volumes == []
-    assert parser.recipe.ports == ['3031']
-    assert parser.recipe.files[0] == ['requirements.txt', '/tmp/requirements.txt']
-    assert parser.recipe.environ == ['PYTHONUNBUFFERED=1']
+    assert parser.recipe.ports == ["3031"]
+    assert parser.recipe.files[0] == ["requirements.txt", "/tmp/requirements.txt"]
+    assert parser.recipe.environ == ["PYTHONUNBUFFERED=1"]
     assert parser.recipe.source == dockerfile
 
+
 def test_singularity_parser(test_data):
-    recipe = os.path.join(test_data['root'], 'Singularity')  
+    recipe = os.path.join(test_data["root"], "Singularity")
     parser = SingularityParser(recipe)
 
-    assert str(parser) == '[spython-parser][singularity]'
+    assert str(parser) == "[spython-parser][singularity]"
 
     # Test all fields from recipe
-    assert parser.recipe.fromHeader == 'continuumio/miniconda3'
+    assert parser.recipe.fromHeader == "continuumio/miniconda3"
     assert parser.recipe.cmd == 'exec /opt/conda/bin/spython "$@"'
     assert parser.recipe.entrypoint is None
     assert parser.recipe.workdir is None
diff --git a/spython/tests/test_recipe.py b/spython/tests/test_recipe.py
index 99c70b2b..23d2c4f5 100644
--- a/spython/tests/test_recipe.py
+++ b/spython/tests/test_recipe.py
@@ -1,40 +1,51 @@
 #!/usr/bin/python
 
-# Copyright (C) 2019 Vanessa Sochat.
+# Copyright (C) 2019-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-        
+
 def test_recipe_base():
     from spython.main.parse.recipe import Recipe
-    recipe = Recipe()
-    assert str(recipe) == '[spython-recipe]'
 
-    attributes = ['cmd', 'comments', 'entrypoint', 'environ', 'files',
-                    'install', 'labels', 'ports', 'test',
-                    'volumes', 'workdir']
+    recipe = Recipe()
+    assert str(recipe) == "[spython-recipe]"
+
+    attributes = [
+        "cmd",
+        "comments",
+        "entrypoint",
+        "environ",
+        "files",
+        "install",
+        "labels",
+        "ports",
+        "test",
+        "volumes",
+        "workdir",
+    ]
 
     for att in attributes:
         assert hasattr(recipe, att)
 
-    print('Checking that empty recipe returns empty')
+    print("Checking that empty recipe returns empty")
     result = recipe.json()
     assert not result
 
-    print('Checking that non-empty recipe returns values')
-    recipe.cmd = ['echo', 'hello']
-    recipe.entrypoint = '/bin/bash'
-    recipe.comments = ['This recipe is great', 'Yes it is!']
-    recipe.environ = ['PANCAKES=WITHSYRUP']
-    recipe.files = [['one', 'two']]
-    recipe.test = ['true']
-    recipe.install = ['apt-get update']
-    recipe.labels = ['Maintainer vanessasaur']
-    recipe.ports = ['3031']
-    recipe.volumes = ['/data']
-    recipe.workdir = '/code'
+    print("Checking that non-empty recipe returns values")
+    recipe.cmd = ["echo", "hello"]
+    recipe.entrypoint = "/bin/bash"
+    recipe.comments = ["This recipe is great", "Yes it is!"]
+    recipe.environ = ["PANCAKES=WITHSYRUP"]
+    recipe.files = [["one", "two"]]
+    recipe.test = ["true"]
+    recipe.install = ["apt-get update"]
+    recipe.labels = ["Maintainer vanessasaur"]
+    recipe.ports = ["3031"]
+    recipe.volumes = ["/data"]
+    recipe.workdir = "/code"
 
     result = recipe.json()
     for att in attributes:
diff --git a/spython/tests/test_utils.py b/spython/tests/test_utils.py
index 28e58bfc..1ee1ea5a 100644
--- a/spython/tests/test_utils.py
+++ b/spython/tests/test_utils.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -10,74 +10,84 @@
 import pytest
 from semver import VersionInfo
 from spython.utils import ScopedEnvVar
-        
+
 
 def test_write_read_files(tmp_path):
-    '''test_write_read_files will test the functions write_file and read_file
-    '''
+    """test_write_read_files will test the functions write_file and read_file
+    """
     print("Testing utils.write_file...")
     from spython.utils import write_file
-    tmpfile = str(tmp_path / 'written_file.txt')
+
+    tmpfile = str(tmp_path / "written_file.txt")
     assert not os.path.exists(tmpfile)
     write_file(tmpfile, "hello!")
     assert os.path.exists(tmpfile)
 
     print("Testing utils.read_file...")
     from spython.utils import read_file
+
     content = read_file(tmpfile)[0]
     assert content == "hello!"
 
+
 def test_write_bad_json(tmp_path):
     from spython.utils import write_json
+
     bad_json = {"Wakkawakkawakka'}": [{True}, "2", 3]}
-    tmpfile = str(tmp_path / 'json_file.txt')
-    assert not os.path.exists(tmpfile)   
+    tmpfile = str(tmp_path / "json_file.txt")
+    assert not os.path.exists(tmpfile)
     with pytest.raises(TypeError):
         write_json(bad_json, tmpfile)
 
+
 def test_write_json(tmp_path):
     import json
     from spython.utils import write_json
+
     good_json = {"Wakkawakkawakka": [True, "2", 3]}
-    tmpfile = str(tmp_path / 'good_json_file.txt')
+    tmpfile = str(tmp_path / "good_json_file.txt")
     assert not os.path.exists(tmpfile)
     write_json(good_json, tmpfile)
-    with open(tmpfile, 'r') as f:
+    with open(tmpfile, "r") as f:
         content = json.loads(f.read())
     assert isinstance(content, dict)
     assert "Wakkawakkawakka" in content
 
 
 def test_check_install():
-    '''check install is used to check if a particular software is installed.
-    If no command is provided, singularity is assumed to be the test case'''
+    """check install is used to check if a particular software is installed.
+    If no command is provided, singularity is assumed to be the test case"""
     print("Testing utils.check_install")
     from spython.utils import check_install
+
     is_installed = check_install()
     assert is_installed
-    is_not_installed = check_install('fakesoftwarename')
+    is_not_installed = check_install("fakesoftwarename")
     assert not is_not_installed
 
 
 def test_check_get_singularity_version():
-    '''check that the singularity version is found to be that installed'''
+    """check that the singularity version is found to be that installed"""
     from spython.utils import get_singularity_version
+
     version = get_singularity_version()
     assert version != ""
-    with ScopedEnvVar('SPYTHON_SINGULARITY_VERSION', "3.0"):
+    with ScopedEnvVar("SPYTHON_SINGULARITY_VERSION", "3.0"):
         version = get_singularity_version()
     assert version == "3.0"
 
+
 def test_check_get_singularity_version_info():
-    '''Check that the version_info is correct'''
+    """Check that the version_info is correct"""
     from spython.utils import get_singularity_version_info
-    with ScopedEnvVar('SPYTHON_SINGULARITY_VERSION', "2.3.1"):
+
+    with ScopedEnvVar("SPYTHON_SINGULARITY_VERSION", "2.3.1"):
         version = get_singularity_version_info()
     assert version == VersionInfo(2, 3, 1)
     assert version > VersionInfo(2, 3, 0)
     assert version < VersionInfo(3, 0, 0)
 
-    with ScopedEnvVar('SPYTHON_SINGULARITY_VERSION', "singularity version 3.2.1-1"):
+    with ScopedEnvVar("SPYTHON_SINGULARITY_VERSION", "singularity version 3.2.1-1"):
         version = get_singularity_version_info()
     assert version == VersionInfo(3, 2, 1, "1")
     assert version > VersionInfo(2, 0, 0)
@@ -85,7 +95,7 @@ def test_check_get_singularity_version_info():
     assert version > VersionInfo(3, 2, 0)
     assert version < VersionInfo(3, 2, 1)
 
-    with ScopedEnvVar('SPYTHON_SINGULARITY_VERSION', "2.6.1-pull/124.1d068a7"):
+    with ScopedEnvVar("SPYTHON_SINGULARITY_VERSION", "2.6.1-pull/124.1d068a7"):
         version = get_singularity_version_info()
     assert version == VersionInfo(2, 6, 1, "pull", "124.1d068a7")
     assert version > VersionInfo(2, 6, 0)
@@ -93,39 +103,47 @@ def test_check_get_singularity_version_info():
 
 
 def test_get_installdir():
-    '''get install directory should return the base of where singularity
+    """get install directory should return the base of where singularity
     is installed
-    '''
+    """
     print("Testing utils.get_installdir")
     from spython.utils import get_installdir
+
     whereami = get_installdir()
     print(whereami)
-    assert whereami.endswith('spython')
+    assert whereami.endswith("spython")
 
 
 def test_split_uri():
     from spython.utils import split_uri
-    protocol, image = split_uri('docker://ubuntu')
-    assert protocol == 'docker'
-    assert image == 'ubuntu'
 
-    protocol, image = split_uri('http://image/path/with/slash/')
-    assert protocol == 'http'
-    assert image == 'image/path/with/slash'
+    protocol, image = split_uri("docker://ubuntu")
+    assert protocol == "docker"
+    assert image == "ubuntu"
+
+    protocol, image = split_uri("http://image/path/with/slash/")
+    assert protocol == "http"
+    assert image == "image/path/with/slash"
+
+    protocol, image = split_uri("no/proto/")
+    assert protocol == ""
+    assert image == "no/proto"
 
-    protocol, image = split_uri('no/proto/')
-    assert protocol == ''
-    assert image == 'no/proto'
 
 def test_remove_uri():
     print("Testing utils.remove_uri")
     from spython.utils import remove_uri
-    assert remove_uri('docker://ubuntu') == 'ubuntu'
-    assert remove_uri('shub://vanessa/singularity-images') == 'vanessa/singularity-images'
-    assert remove_uri('vanessa/singularity-images') == 'vanessa/singularity-images'
+
+    assert remove_uri("docker://ubuntu") == "ubuntu"
+    assert (
+        remove_uri("shub://vanessa/singularity-images") == "vanessa/singularity-images"
+    )
+    assert remove_uri("vanessa/singularity-images") == "vanessa/singularity-images"
+
 
 def test_decode():
     from spython.logger import decodeUtf8String
+
     out = decodeUtf8String(str("Hello"))
     assert isinstance(out, str)
     assert out == "Hello"
@@ -133,24 +151,25 @@ def test_decode():
     assert isinstance(out, str)
     assert out == "Hello"
 
+
 def test_ScopedEnvVar():
-    assert 'FOO' not in os.environ
-    with ScopedEnvVar('FOO', 'bar') as e:
-        assert e.name == 'FOO'
-        assert e.value == 'bar'
-        assert os.environ['FOO'] == 'bar'
-        with ScopedEnvVar('FOO', 'baz'):
-            assert os.environ['FOO'] == 'baz'
-        assert os.environ['FOO'] == 'bar'
+    assert "FOO" not in os.environ
+    with ScopedEnvVar("FOO", "bar") as e:
+        assert e.name == "FOO"
+        assert e.value == "bar"
+        assert os.environ["FOO"] == "bar"
+        with ScopedEnvVar("FOO", "baz"):
+            assert os.environ["FOO"] == "baz"
+        assert os.environ["FOO"] == "bar"
         # None removes it
-        with ScopedEnvVar('FOO', None):
-            assert 'FOO' not in os.environ
+        with ScopedEnvVar("FOO", None):
+            assert "FOO" not in os.environ
         # But empty string is allowed
-        with ScopedEnvVar('FOO', ''):
-            assert os.environ['FOO'] == ''
-        assert os.environ['FOO'] == 'bar'
-    assert 'FOO' not in os.environ
+        with ScopedEnvVar("FOO", ""):
+            assert os.environ["FOO"] == ""
+        assert os.environ["FOO"] == "bar"
+    assert "FOO" not in os.environ
     # Unset a non-existing variable
-    with ScopedEnvVar('FOO', None):
-        assert 'FOO' not in os.environ
-    assert 'FOO' not in os.environ
+    with ScopedEnvVar("FOO", None):
+        assert "FOO" not in os.environ
+    assert "FOO" not in os.environ
diff --git a/spython/tests/test_writers.py b/spython/tests/test_writers.py
index 51839c43..6e3491e7 100644
--- a/spython/tests/test_writers.py
+++ b/spython/tests/test_writers.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-# Copyright (C) 2019 Vanessa Sochat.
+# Copyright (C) 2019-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
@@ -9,35 +9,37 @@
 import os
 from spython.main.parse.writers import DockerWriter, SingularityWriter
 
+
 def test_writers():
     from spython.main.parse.writers import get_writer
 
-    writer = get_writer('docker')
+    writer = get_writer("docker")
     assert writer == DockerWriter
 
-    writer = get_writer('Dockerfile')
+    writer = get_writer("Dockerfile")
     assert writer == DockerWriter
 
-    writer = get_writer('Singularity')
+    writer = get_writer("Singularity")
     assert writer == SingularityWriter
 
+
 def test_docker_writer(test_data):
     from spython.main.parse.parsers import DockerParser
 
-    dockerfile = os.path.join(test_data['root'], 'Dockerfile')        
+    dockerfile = os.path.join(test_data["root"], "Dockerfile")
     parser = DockerParser(dockerfile)
     writer = DockerWriter(parser.recipe)
 
-    assert str(writer) == '[spython-writer][docker]'
+    assert str(writer) == "[spython-writer][docker]"
     print(writer.convert())
 
 
 def test_singularity_writer(test_data):
     from spython.main.parse.parsers import SingularityParser
 
-    recipe = os.path.join(test_data['root'], 'Singularity')        
+    recipe = os.path.join(test_data["root"], "Singularity")
     parser = SingularityParser(recipe)
     writer = SingularityWriter(parser.recipe)
 
-    assert str(writer) == '[spython-writer][singularity]'
+    assert str(writer) == "[spython-writer][singularity]"
     print(writer.convert())
diff --git a/spython/utils/__init__.py b/spython/utils/__init__.py
index 19868898..623e8a2a 100644
--- a/spython/utils/__init__.py
+++ b/spython/utils/__init__.py
@@ -1,22 +1,18 @@
-from .fileio import ( 
-    mkdir_p, 
-    write_file, 
-    write_json,
-    read_file, 
-    read_json
-)
+from .fileio import mkdir_p, write_file, write_json, read_file, read_json
 
 from .misc import ScopedEnvVar
 
 from .terminal import (
-    check_install, 
+    check_install,
     format_container_name,
     get_installdir,
     get_singularity_version,
     get_singularity_version_info,
+    get_userhome,
+    get_username,
     remove_uri,
     run_command,
     stream_command,
     split_uri,
-    which
+    which,
 )
diff --git a/spython/utils/fileio.py b/spython/utils/fileio.py
index 1bf8a1a4..2b940707 100644
--- a/spython/utils/fileio.py
+++ b/spython/utils/fileio.py
@@ -1,12 +1,12 @@
-'''
+"""
 
-Copyright (C) 2017-2019 Vanessa Sochat.
+Copyright (C) 2017-2020 Vanessa Sochat.
 
 This Source Code Form is subject to the terms of the
 Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-'''
+"""
 
 import errno
 import os
@@ -21,9 +21,9 @@
 
 
 def mkdir_p(path):
-    '''mkdir_p attempts to get the same functionality as mkdir -p
+    """mkdir_p attempts to get the same functionality as mkdir -p
     :param path: the path to create.
-    '''
+    """
     try:
         os.makedirs(path)
     except OSError as e:
@@ -40,38 +40,32 @@ def mkdir_p(path):
 
 
 def write_file(filename, content, mode="w"):
-    '''write_file will open a file, "filename" and write content, "content"
+    """write_file will open a file, "filename" and write content, "content"
     and properly close the file
-    '''
+    """
     with open(filename, mode) as filey:
         filey.writelines(content)
     return filename
 
 
 def write_json(json_obj, filename, mode="w", print_pretty=True):
-    '''write_json will (optionally,pretty print) a json object to file
+    """write_json will (optionally,pretty print) a json object to file
     :param json_obj: the dict to print to json
     :param filename: the output file to write to
     :param pretty_print: if True, will use nicer formatting
-    '''
+    """
     with open(filename, mode) as filey:
         if print_pretty:
-            filey.writelines(
-                json.dumps(
-                    json_obj,
-                    indent=4,
-                    separators=(
-                        ',',
-                        ': ')))
+            filey.writelines(json.dumps(json_obj, indent=4, separators=(",", ": ")))
         else:
             filey.writelines(json.dumps(json_obj))
     return filename
 
 
 def read_file(filename, mode="r", readlines=True):
-    '''write_file will open a file, "filename" and write content, "content"
+    """write_file will open a file, "filename" and write content, "content"
     and properly close the file
-    '''
+    """
     with open(filename, mode) as filey:
         if readlines:
             content = filey.readlines()
@@ -80,10 +74,10 @@ def read_file(filename, mode="r", readlines=True):
     return content
 
 
-def read_json(filename, mode='r'):
-    '''read_json reads in a json file and returns
+def read_json(filename, mode="r"):
+    """read_json reads in a json file and returns
     the data structure as dict.
-    '''
+    """
     with open(filename, mode) as filey:
         data = json.load(filey)
     return data
diff --git a/spython/utils/misc.py b/spython/utils/misc.py
index 75fff7b8..0b264a9b 100644
--- a/spython/utils/misc.py
+++ b/spython/utils/misc.py
@@ -1,44 +1,48 @@
+# Copyright (C) 2019-2020 Vanessa Sochat.
+
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os
 
+
 def setEnvVar(name, value):
-    '''Set or unset an environment variable
+    """Set or unset an environment variable
 
     name -- Name of the variable to set
     value -- Value to use or None to clear
-    '''
+    """
     if value is None:
         if name in os.environ:
             del os.environ[name]
     else:
         os.environ[name] = value
 
+
 class ScopedEnvVar(object):
-    '''Temporarly change an environment variable
+    """Temporarly change an environment variable
 
     Usage:
         with ScopedEnvVar("FOO", "bar"):
             print(os.environ["FOO"]) # "bar"
         print(os.environ["FOO"]) # <oldvalue>
-    '''
-    
+    """
+
     def __init__(self, name, value):
-        '''Create the scoped environment variable object
+        """Create the scoped environment variable object
 
         name -- Name of the variable to set
         value -- Value to use or None to clear
-        '''
+        """
         self.name = name
         self.value = value
         self.oldValue = None
-    
+
     def __enter__(self):
         self.oldValue = os.environ.get(self.name)
         setEnvVar(self.name, self.value)
         return self
-    
+
     def __exit__(self, ex_type, ex_value, traceback):
         setEnvVar(self.name, self.oldValue)
diff --git a/spython/utils/terminal.py b/spython/utils/terminal.py
index c76a960e..8ce14613 100644
--- a/spython/utils/terminal.py
+++ b/spython/utils/terminal.py
@@ -1,14 +1,15 @@
-'''
+"""
 
-Copyright (C) 2017-2019 Vanessa Sochat.
+Copyright (C) 2017-2020 Vanessa Sochat.
 
 This Source Code Form is subject to the terms of the
 Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-'''
+"""
 
 import os
+import pwd
 import re
 import semver
 from spython.logger import bot
@@ -21,86 +22,101 @@
 # Local commands and requests
 ################################################################################
 
+
 def _process_sudo_cmd(cmd, sudo, sudo_options):
     if sudo and sudo_options is not None:
         if isinstance(sudo_options, str):
             sudo_options = shlex.split(sudo_options)
-        cmd = ['sudo'] + sudo_options + cmd
+        cmd = ["sudo"] + sudo_options + cmd
     elif sudo:
-        cmd = ['sudo'] + cmd
+        cmd = ["sudo"] + cmd
     return cmd
 
 
-def check_install(software='singularity', quiet=True):
-    '''check_install will attempt to run the singularity command, and
+def check_install(software="singularity", quiet=True):
+    """check_install will attempt to run the singularity command, and
        return True if installed. The command line utils will not run 
        without this check.
-    '''
+    """
 
-    cmd = [software, '--version']
+    cmd = [software, "--version"]
     found = False
 
     try:
         version = run_command(cmd, quiet=True)
-    except: # FileNotFoundError
+    except:  # FileNotFoundError
         return found
 
     if version is not None:
-        if version['return_code'] == 0:
+        if version["return_code"] == 0:
             found = True
 
         if not quiet:
-            version = version['message']
+            version = version["message"]
             bot.info("Found %s version %s" % (software.upper(), version))
 
     return found
 
 
-def which(software='singularity'):
-    '''which returns the full path to where software is installed.
-    '''
-    cmd = ['which', software]
-    result = run_command(cmd, quiet=True)['message'][0]
-    return result.strip('\n')
-    
+def which(software="singularity"):
+    """which returns the full path to where software is installed.
+    """
+    cmd = ["which", software]
+    result = run_command(cmd, quiet=True)["message"][0]
+    return result.strip("\n")
+
 
 def get_singularity_version():
-    '''get the full singularity client version as reported by 
+    """get the full singularity client version as reported by 
        singularity --version [...]. For Singularity 3.x, this means:
        "singularity version 3.0.1-1"
-    '''
-    version = os.environ.get('SPYTHON_SINGULARITY_VERSION', "")
+    """
+    version = os.environ.get("SPYTHON_SINGULARITY_VERSION", "")
     if version == "":
         try:
-            version = run_command(["singularity", '--version'], quiet=True)
-        except: # FileNotFoundError
+            version = run_command(["singularity", "--version"], quiet=True)
+        except:  # FileNotFoundError
             return version
 
-        if version['return_code'] == 0:
-            if version['message']:
-                version = version['message'][0].strip('\n')
+        if version["return_code"] == 0:
+            if version["message"]:
+                version = version["message"][0].strip("\n")
 
     return version
 
+
+def get_userhome():
+    """get the user home based on the effective uid
+    """
+    return pwd.getpwuid(os.getuid())[5]
+
+
+def get_username():
+    """get the user name based on the effective uid
+    """
+    return pwd.getpwuid(os.getuid())[0]
+
+
 def get_singularity_version_info():
-    '''get the full singularity client version as a semantic version"
-    '''
+    """get the full singularity client version as a semantic version"
+    """
     version_string = get_singularity_version()
-    prefix = 'singularity version '
+    prefix = "singularity version "
     if version_string.startswith(prefix):
-        version_string = version_string[len(prefix):]
-    elif '/' in version_string: # Handle old stuff like "x.y.z-pull/123-0a5d"
-        version_string = version_string.replace('/', '+', 1)
+        version_string = version_string[len(prefix) :]
+    elif "/" in version_string:  # Handle old stuff like "x.y.z-pull/123-0a5d"
+        version_string = version_string.replace("/", "+", 1)
     return semver.parse_version_info(version_string)
 
+
 def get_installdir():
-    '''get_installdir returns the installation directory of the application
-    '''
+    """get_installdir returns the installation directory of the application
+    """
     return os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
 
 
 def stream_command(cmd, no_newline_regexp="Progess", sudo=False, sudo_options=None):
-    '''stream a command (yield) back to the user, as each line is available.
+    """stream a command (yield) back to the user, as each line is available.
 
        # Example usage:
        results = []
@@ -115,12 +131,10 @@ def stream_command(cmd, no_newline_regexp="Progess", sudo=False, sudo_options=No
                           newline. Defaults to finding Progress
        sudo_options: string or list of strings that will be passed as options to sudo
 
-    '''
+    """
     cmd = _process_sudo_cmd(cmd, sudo, sudo_options)
 
-    process = subprocess.Popen(cmd,
-                               stdout=subprocess.PIPE,
-                               universal_newlines=True)
+    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
     for line in iter(process.stdout.readline, ""):
         if not re.search(no_newline_regexp, line):
             yield line
@@ -130,14 +144,16 @@ def stream_command(cmd, no_newline_regexp="Progess", sudo=False, sudo_options=No
         raise subprocess.CalledProcessError(return_code, cmd)
 
 
-def run_command(cmd, 
-                sudo=False,
-                capture=True,
-                no_newline_regexp="Progess",
-                quiet=False,
-                sudo_options=None):
+def run_command(
+    cmd,
+    sudo=False,
+    capture=True,
+    no_newline_regexp="Progess",
+    quiet=False,
+    sudo_options=None,
+):
 
-    '''run_command uses subprocess to send a command to the terminal. If
+    """run_command uses subprocess to send a command to the terminal. If
        capture is True, we use the parent stdout, so the progress bar (and
        other commands of interest) are piped to the user. This means we 
        don't return the output to parse.
@@ -152,7 +168,7 @@ def run_command(cmd,
                 option can print a progress bar, but won't return the lines
                 as output.
        sudo_options: string or list of strings that will be passed as options to sudo
-    '''
+    """
     cmd = _process_sudo_cmd(cmd, sudo, sudo_options)
 
     stdout = None
@@ -160,9 +176,7 @@ def run_command(cmd,
         stdout = subprocess.PIPE
 
     # Use the parent stdout and stderr
-    process = subprocess.Popen(cmd,
-                               stderr=subprocess.PIPE,
-                               stdout=stdout)
+    process = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=stdout)
     lines = []
     found_match = False
 
@@ -180,8 +194,7 @@ def run_command(cmd,
                     print(line.rstrip())
                 found_match = False
 
-    output = {'message': lines,
-              'return_code': process.returncode}
+    output = {"message": lines, "return_code": process.returncode}
 
     return output
 
@@ -192,32 +205,31 @@ def run_command(cmd,
 
 
 def format_container_name(name, special_characters=None):
-    '''format_container_name will take a name supplied by the user,
+    """format_container_name will take a name supplied by the user,
     remove all special characters (except for those defined by "special-characters"
     and return the new image name.
-    '''
+    """
     if special_characters is None:
         special_characters = []
-    return ''.join(e.lower()
-                   for e in name if e.isalnum() or e in special_characters)
+    return "".join(e.lower() for e in name if e.isalnum() or e in special_characters)
 
 
 def split_uri(container):
-    '''Split the uri of a container into the protocol and image part
+    """Split the uri of a container into the protocol and image part
 
     An empty protocol is returned if none found.
     A trailing slash is removed from the image part.
-    '''
-    parts = container.split('://', 1)
+    """
+    parts = container.split("://", 1)
     if len(parts) == 2:
         protocol, image = parts
     else:
-        protocol = ''
+        protocol = ""
         image = parts[0]
-    return protocol, image.rstrip('/')
+    return protocol, image.rstrip("/")
 
 
 def remove_uri(container):
-    '''remove_uri will remove docker:// or shub:// from the uri
-    '''
+    """remove_uri will remove docker:// or shub:// from the uri
+    """
     return split_uri(container)[1]
diff --git a/spython/version.py b/spython/version.py
index 59db0d50..b50fbda9 100644
--- a/spython/version.py
+++ b/spython/version.py
@@ -1,24 +1,19 @@
-
-# Copyright (C) 2017-2019 Vanessa Sochat.
+# Copyright (C) 2017-2020 Vanessa Sochat.
 
 # This Source Code Form is subject to the terms of the
 # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-__version__ = "0.0.73"
-AUTHOR = 'Vanessa Sochat'
-AUTHOR_EMAIL = 'vsochat@stanford.edu'
-NAME = 'spython'
+__version__ = "0.0.74"
+AUTHOR = "Vanessa Sochat"
+AUTHOR_EMAIL = "vsochat@stanford.edu"
+NAME = "spython"
 PACKAGE_URL = "http://www.github.com/singularityhub/singularity-cli"
-KEYWORDS = 'singularity python client (spython)'
+KEYWORDS = "singularity python client (spython)"
 DESCRIPTION = "Command line python tool for working with singularity."
 LICENSE = "LICENSE"
 
-INSTALL_REQUIRES = (
-    ('semver', {'min_version': '2.8.0'}),
-)
+INSTALL_REQUIRES = (("semver", {"min_version": "2.8.0"}),)
 
-TESTS_REQUIRES = (
-    ('pytest', {'min_version': '4.6.2'}),
-)
+TESTS_REQUIRES = (("pytest", {"min_version": "4.6.2"}),)
