diff --git a/.circleci/config.yml b/.circleci/config.yml
index 4e010b90..72ae21e7 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -68,6 +68,13 @@ install_python_2: &install_python_2
            echo "Miniconda 2 is already installed, continuing to build."
        fi
 
+run_linter: &run_linter
+  name: run linter
+  command: |
+     $HOME/conda/bin/pip install --upgrade pylint
+     cd ~/repo
+     $HOME/conda/bin/pylint spython
+
 test_spython: &test_spython
   name: Test Singularity Python (Singularity Version 2 and 3)
   command: |
@@ -99,6 +106,7 @@ jobs:
       - singularity/debian-install-3:
           singularity-version: 3.1.0
       - run: *install_spython
+      - run: *run_linter
       - save_cache:
           paths:
             - /home/circleci/conda
@@ -121,6 +129,7 @@ jobs:
       - singularity/debian-install-3:
           singularity-version: 3.1.0
       - run: *install_spython
+      - run: *run_linter
       - save_cache:
           paths:
             - /home/circleci/conda
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 00000000..68d70de2
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,511 @@
+[MASTER]
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code.
+extension-pkg-whitelist=
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Add files or directories matching the regex patterns to the blacklist. The
+# regex matches against base names, not paths.
+ignore-patterns=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
+# number of processors available to use.
+jobs=0
+
+# Control the amount of potential inferred values when inferring a single
+# object. This can help the performance when dealing with large functions or
+# complex, nested conditions.
+limit-inference-results=100
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Specify a configuration file.
+#rcfile=
+
+# When enabled, pylint would attempt to guess common misconfiguration and emit
+# user-friendly hints instead of false-positive error messages.
+suggestion-mode=yes
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
+confidence=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once). You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use "--disable=all --enable=classes
+# --disable=W".
+disable=attribute-defined-outside-init,
+        bad-continuation,
+        bad-whitespace,
+        bare-except,
+        blacklisted-name,
+        duplicate-code,
+        fixme,
+        import-error,
+        invalid-name,
+        len-as-condition,
+        line-too-long,
+        missing-docstring,
+        multiple-statements,
+        no-member,
+        protected-access,
+        R,
+        redefined-builtin,
+        redefined-outer-name,
+        trailing-whitespace,
+        unused-argument,
+        wrong-import-order
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=
+
+
+[REPORTS]
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details.
+#msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, json
+# and msvs (visual studio). You can also give a reporter class, e.g.
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Tells whether to display a full report or only the messages.
+reports=no
+
+# Activate the evaluation score.
+score=yes
+
+
+[REFACTORING]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+# Complete name of functions that never returns. When checking for
+# inconsistent-return-statements if a never returning function is called then
+# it will be considered as an explicit return statement and no message will be
+# printed.
+never-returning-functions=sys.exit
+
+
+[BASIC]
+
+# Naming style matching correct argument names.
+argument-naming-style=snake_case
+
+# Regular expression matching correct argument names. Overrides argument-
+# naming-style.
+#argument-rgx=
+
+# Naming style matching correct attribute names.
+attr-naming-style=snake_case
+
+# Regular expression matching correct attribute names. Overrides attr-naming-
+# style.
+#attr-rgx=
+
+# Bad variable names which should always be refused, separated by a comma.
+bad-names=foo,
+          baz,
+          toto,
+          tutu,
+          tata
+
+# Naming style matching correct class attribute names.
+class-attribute-naming-style=any
+
+# Regular expression matching correct class attribute names. Overrides class-
+# attribute-naming-style.
+#class-attribute-rgx=
+
+# Naming style matching correct class names.
+class-naming-style=PascalCase
+
+# Regular expression matching correct class names. Overrides class-naming-
+# style.
+#class-rgx=
+
+# Naming style matching correct constant names.
+const-naming-style=UPPER_CASE
+
+# Regular expression matching correct constant names. Overrides const-naming-
+# style.
+#const-rgx=
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+# Naming style matching correct function names.
+function-naming-style=snake_case
+
+# Regular expression matching correct function names. Overrides function-
+# naming-style.
+#function-rgx=
+
+# Good variable names which should always be accepted, separated by a comma.
+good-names=i,
+           j,
+           k,
+           ex,
+           Run,
+           _
+
+# Include a hint for the correct naming format with invalid-name.
+include-naming-hint=no
+
+# Naming style matching correct inline iteration names.
+inlinevar-naming-style=any
+
+# Regular expression matching correct inline iteration names. Overrides
+# inlinevar-naming-style.
+#inlinevar-rgx=
+
+# Naming style matching correct method names.
+method-naming-style=snake_case
+
+# Regular expression matching correct method names. Overrides method-naming-
+# style.
+#method-rgx=
+
+# Naming style matching correct module names.
+module-naming-style=snake_case
+
+# Regular expression matching correct module names. Overrides module-naming-
+# style.
+#module-rgx=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+# These decorators are taken in consideration only for invalid-name.
+property-classes=abc.abstractproperty
+
+# Naming style matching correct variable names.
+variable-naming-style=snake_case
+
+# Regular expression matching correct variable names. Overrides variable-
+# naming-style.
+#variable-rgx=
+
+
+[STRING]
+
+# This flag controls whether the implicit-str-concat-in-sequence should
+# generate a warning on implicit string concatenation in sequences defined over
+# several lines.
+check-str-concat-over-line-jumps=no
+
+
+[SPELLING]
+
+# Limits count of emitted suggestions for spelling mistakes.
+max-spelling-suggestions=4
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package..
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,
+      XXX,
+      TODO
+
+
+[SIMILARITIES]
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+
+[LOGGING]
+
+# Format style used to check logging format string. `old` means using %
+# formatting, while `new` is for `{}` formatting.
+logging-format-style=old
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format.
+logging-modules=logging
+
+
+[VARIABLES]
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid defining new builtins when possible.
+additional-builtins=
+
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables=yes
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,
+          _cb
+
+# A regular expression matching the name of dummy variables (i.e. expected to
+# not be used).
+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore.
+ignored-argument-names=_.*|^ignored_|^unused_
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# Tells whether to warn about missing members when the owner of the attribute
+# is inferred to be None.
+ignore-none=yes
+
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference
+# can return multiple potential results while evaluating a Python object, but
+# some branches might not be evaluated, which results in partial inference. In
+# that case, it might be useful to still emit no-member and other checks for
+# the rest of the inferred objects.
+ignore-on-opaque-inference=yes
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# Show a hint with possible names when a member name was not found. The aspect
+# of finding the hint is based on edit distance.
+missing-member-hint=yes
+
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance=1
+
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices=1
+
+
+[FORMAT]
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Maximum number of lines in a module.
+max-module-lines=1000
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=trailing-comma,
+               dict-separator
+
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+single-line-class-stmt=no
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method.
+max-args=5
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Maximum number of boolean expressions in an if statement.
+max-bool-expr=5
+
+# Maximum number of branch for function / method body.
+max-branches=12
+
+# Maximum number of locals for function / method body.
+max-locals=15
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of return / yield for function / method body.
+max-returns=6
+
+# Maximum number of statements in function / method body.
+max-statements=50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+
+[IMPORTS]
+
+# Allow wildcard imports from modules that define __all__.
+allow-wildcard-with-all=no
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+# Deprecated modules which should not be used, separated by a comma.
+deprecated-modules=optparse,tkinter.tix
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled).
+ext-import-graph=
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled).
+import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled).
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,
+                      __new__,
+                      setUp
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,
+                  _fields,
+                  _replace,
+                  _source,
+                  _make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=cls
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "BaseException, Exception".
+overgeneral-exceptions=BaseException,
+                       Exception
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6e522b66..55ea0d19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,8 @@ 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)
+ - fix crash in some error conditions (0.0.62)
+   - more OCI commands accept sudo parameter
  - working directory, the last one defined, should be added to runscript (0.0.61)
  - adding deprecation message for image.export (0.0.60)
    - adding --force option to build
diff --git a/setup.py b/setup.py
index 1557e3b0..636d2e8a 100644
--- a/setup.py
+++ b/setup.py
@@ -33,7 +33,7 @@ def get_requirements(lookup=None):
     '''get_requirements reads in requirements and versions from
     the lookup obtained with get_lookup'''
 
-    if lookup == None:
+    if lookup is None:
         lookup = get_lookup()
 
     install_requires = []
@@ -43,7 +43,7 @@ def get_requirements(lookup=None):
         if "exact_version" in module_meta:
             dependency = "%s==%s" %(module_name,module_meta['exact_version'])
         elif "min_version" in module_meta:
-            if module_meta['min_version'] == None:
+            if module_meta['min_version'] is None:
                 dependency = module_name
             else:
                 dependency = "%s>=%s" %(module_name,module_meta['min_version'])
diff --git a/spython/client/__init__.py b/spython/client/__init__.py
index 4da736bb..9ac8e3b7 100644
--- a/spython/client/__init__.py
+++ b/spython/client/__init__.py
@@ -63,23 +63,6 @@ def get_parser():
     return parser
 
 
-
-def get_subparsers(parser):
-    '''get_subparser will get a dictionary of subparsers, to help with printing help
-    '''
-
-    actions = [action for action in parser._actions 
-               if isinstance(action, argparse._SubParsersAction)]
-
-    subparsers = dict()
-    for action in actions:
-        # get all subparsers and print help
-        for choice, subparser in action.choices.items():
-            subparsers[choice] = subparser
-
-    return subparsers
-
-
 def set_verbosity(args):
     '''determine the message level in the environment to set based on args.
     '''
@@ -113,7 +96,6 @@ def version():
 def main():
 
     parser = get_parser()
-    subparsers = get_subparsers(parser)
 
     def help(return_code=0):
         '''print help, including the software version and active client 
diff --git a/spython/client/recipe.py b/spython/client/recipe.py
index 5de55d3c..736cc872 100644
--- a/spython/client/recipe.py
+++ b/spython/client/recipe.py
@@ -53,5 +53,5 @@ def main(args, options, parser):
 
     # Otherwise, convert and print to screen
     else:
-        recipe = parser.convert(runscript=entrypoint, force=True)
+        recipe = parser.convert(runscript=entrypoint, force=force)
         print(recipe)
diff --git a/spython/client/shell.py b/spython/client/shell.py
index ab60c090..b37a088f 100644
--- a/spython/client/shell.py
+++ b/spython/client/shell.py
@@ -6,12 +6,7 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-import sys
-
 def main(args, options, parser):
-
-    from spython.main import Client as cli
-
     # If we have options, first is image
     image = None
     if len(options) > 0:
diff --git a/spython/client/test.py b/spython/client/test.py
index b4e794fb..360215dc 100644
--- a/spython/client/test.py
+++ b/spython/client/test.py
@@ -6,12 +6,6 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-from spython.logger import bot
-from spython.utils import check_install
-import sys
-import os
-
-
 def main(args, options, parser):
     print('TBA, additional tests for Singularity containers.')
     print('What would you like to see? Let us know!')
diff --git a/spython/image/__init__.py b/spython/image/__init__.py
index 0b5a606c..bff996d2 100644
--- a/spython/image/__init__.py
+++ b/spython/image/__init__.py
@@ -8,45 +8,21 @@
 import hashlib
 import os
 import re
-from spython.utils import get_singularity_version
-
+from spython.logger import bot
+from spython.utils import split_uri
 
 class ImageBase(object):
 
     def __str__(self):
-        if hasattr(self, 'uri'):
-            if self.uri:
-                return "%s://%s" %(self.uri, self.image)
+        protocol = getattr(self, 'protocol', None)
+        if protocol:
+            return "%s://%s" %(protocol, self.image)
         return self.image
 
 
     def __repr__(self):
         return self.__str__()
 
-    def get_uri(self, image):
-        '''get the uri of an image, or the string (optional) that appears before
-           ://. Optional. If none found, returns ''
-        '''
-        image = image or ''
-        uri = ''
-
-        match = re.match("^(?P<uri>.+)://", image)
-        if match:
-            uri = match.group('uri')
-
-        return uri
-
-
-    def remove_uri(self, image):
-        '''remove_image_uri will return just the image name.
-           this will also remove all spaces from the uri.
-        '''
-        image = image or ''
-        uri = self.get_uri(image) or ''
-        image = image.replace('%s://' %uri,'', 1)
-        return image.strip('-').rstrip('/')
-
-
     def parse_image_name(self, image):
         '''
             simply split the uri from the image. Singularity handles
@@ -58,25 +34,24 @@ def parse_image_name(self, image):
 
         '''
         self._image = image
-        self.uri = self.get_uri(image)
-        self.image = self.remove_uri(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.
-          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.
+        '''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.
 
-          Parameters
-          ==========
-          image: the image uri to parse (required)
+            Parameters
+            ==========
+            image: the image uri to parse (required)
 
-       '''
-       super(ImageBase, self).__init__()
-       self.parse_image_name(image)
+        '''
+        super().__init__()
+        self.parse_image_name(image)
 
 
     def get_hash(self, image=None):
@@ -99,4 +74,4 @@ def get_hash(self, image=None):
                     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/create.py b/spython/image/cmd/create.py
index 7a9e1ed9..34f8eddf 100644
--- a/spython/image/cmd/create.py
+++ b/spython/image/cmd/create.py
@@ -6,6 +6,7 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
+import os
 from spython.logger import bot
 
 def create(self,image_path, size=1024, sudo=False):
diff --git a/spython/image/cmd/export.py b/spython/image/cmd/export.py
index a247f9da..715f8879 100644
--- a/spython/image/cmd/export.py
+++ b/spython/image/cmd/export.py
@@ -29,5 +29,5 @@ def export(self, image_path, tmptar=None):
         tmptar = "/%s/tmptar.tar" %(tempfile.mkdtemp())
     cmd = ['singularity', 'image.export', '-f', tmptar, image_path]
 
-    output = self.run_command(cmd, sudo=False)
+    self.run_command(cmd, sudo=False)
     return tmptar
diff --git a/spython/image/cmd/importcmd.py b/spython/image/cmd/importcmd.py
index eae8ddb3..449526e6 100644
--- a/spython/image/cmd/importcmd.py
+++ b/spython/image/cmd/importcmd.py
@@ -6,8 +6,6 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-from spython.logger import bot
-
 def importcmd(self, image_path, input_source):
     '''import will import (stdin) to the image
 
@@ -25,4 +23,3 @@ def importcmd(self, image_path, input_source):
     output = self.run_command(cmd, sudo=False)
     self.println(output)        
     return image_path
-
diff --git a/spython/image/cmd/utils.py b/spython/image/cmd/utils.py
index 3f800ab9..a7ae8f41 100644
--- a/spython/image/cmd/utils.py
+++ b/spython/image/cmd/utils.py
@@ -6,6 +6,7 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
+import os
 from spython.logger import bot
 
 def compress(self, image_path):
@@ -26,5 +27,5 @@ def decompress(self, image_path, quiet=True):
         
     extracted_file = image_path.replace('.gz','')
     cmd = ['gzip','-d','-f', image_path]
-    result = self.run_command(cmd, quiet=quiet) # exits if return code != 0
+    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 6cca5dd4..8f888fdd 100644
--- a/spython/instance/__init__.py
+++ b/spython/instance/__init__.py
@@ -11,29 +11,29 @@
 class Instance(ImageBase):
 
     def __init__(self, image, start=True, name=None, **kwargs):
-       '''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.
-
-          Parameters
-          ==========
-          image: the Singularity image uri to parse (required)
-          start: boolean to start the instance (default is True)
-          name: a name for the instance (will generate RobotName 
-                if not provided)
-       '''
-       super(ImageBase, self).__init__()
-       self.parse_image_name(image)
-       self.generate_name(name)
-
-       # Update metadats from arguments
-       self._update_metadata(kwargs)
-       self.options = []
-       self.cmd = []
-
-       # Start the instance
-       if start is True:
-           self.start(**kwargs)
+        '''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.
+
+            Parameters
+            ==========
+            image: the Singularity image uri to parse (required)
+            start: boolean to start the instance (default is True)
+            name: a name for the instance (will generate RobotName 
+                    if not provided)
+        '''
+        super().__init__()
+        self.parse_image_name(image)
+        self.generate_name(name)
+
+        # Update metadats from arguments
+        self._update_metadata(kwargs)
+        self.options = []
+        self.cmd = []
+
+        # Start the instance
+        if start is True:
+            self.start(**kwargs)
 
 # Unique resource identifier
 
@@ -42,7 +42,7 @@ def generate_name(self, name=None):
            supply one.
         '''
         # If no name provided, use robot name
-        if name == None:
+        if name is None:
             name = self.RobotNamer.generate()
         self.name = name.replace('-','_')
 
@@ -58,7 +58,7 @@ def parse_image_name(self, image):
 
         '''
         self._image = image
-        self.uri = 'instance://'
+        self.protocol = 'instance'
 
 
     def get_uri(self):
@@ -74,15 +74,15 @@ def _update_metadata(self, kwargs=None):
         '''
 
         # If not given metadata, use instance.list to get it for container
-        if kwargs == 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']:
 
-           # Skip over non-iterables:
-           if arg in kwargs:
-               setattr(self, arg, kwargs[arg])
+            # Skip over non-iterables:
+            if arg in kwargs:
+                setattr(self, arg, kwargs[arg])
        
         if "image" in kwargs:
             self._image = kwargs['image']
@@ -92,8 +92,8 @@ def _update_metadata(self, kwargs=None):
 
     def __str__(self):
         if hasattr(self, 'name'):
-            if self.uri:
-                return "%s%s" %(self.uri, self.name)
+            if self.protocol:
+                return "%s://%s" %(self.protocol, self.name)
         return os.path.basename(self._image)
 
     def __repr__(self):
diff --git a/spython/instance/cmd/iutils.py b/spython/instance/cmd/iutils.py
index 738acfb3..f6a3cdd7 100644
--- a/spython/instance/cmd/iutils.py
+++ b/spython/instance/cmd/iutils.py
@@ -5,6 +5,8 @@
 # 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.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.
@@ -27,8 +29,8 @@ def parse_table(table_string, header, remove_rows=1):
         item = {}
         # This assumes no white spaces in each entry, which should be the case
         row = [x for x in row.split(' ') if x]
-        for e in range(len(row)):
-            item[header[e]] = row[e]
+        for i, r in enumerate(row):
+            item[header[i]] = r
         parsed.append(item)
     return parsed
 
@@ -49,7 +51,7 @@ def get(self, name, return_json=False, quiet=False):
     cmd = self._init_command(subgroup)
 
     cmd.append(name)
-    output = run_command(cmd, quiet=True)
+    output = self.run_command(cmd, quiet=True)
 
     # Success, we have instances
 
diff --git a/spython/instance/cmd/start.py b/spython/instance/cmd/start.py
index 044e2a92..08e895cd 100644
--- a/spython/instance/cmd/start.py
+++ b/spython/instance/cmd/start.py
@@ -8,7 +8,7 @@
 
 from spython.logger import bot
 
-def start(self, image=None, name=None, args=None, sudo=False, options=[], capture=False):
+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
@@ -30,7 +30,7 @@ def start(self, image=None, name=None, args=None, sudo=False, options=[], captur
     check_install()
 
     # If name provided, over write robot (default)
-    if name != None:
+    if name is not None:
         self.name = name
 
     # If an image isn't provided, we have an initialized instance
@@ -51,13 +51,13 @@ def start(self, image=None, name=None, args=None, sudo=False, options=[], captur
 
     # Add options, if they are provided
     if not isinstance(options, list):
-        options = options.split(' ')
+        options = [] if options is None else options.split(' ')
 
     # Assemble the command!
     cmd = cmd + options + [image, self.name]
 
     # If arguments are provided
-    if args != None:
+    if args is not None:
         if not isinstance(args, list):
             args = [args]
         cmd = cmd + args
diff --git a/spython/logger/message.py b/spython/logger/message.py
index 453bd729..dc7b7f93 100644
--- a/spython/logger/message.py
+++ b/spython/logger/message.py
@@ -206,7 +206,7 @@ def show_progress(
             percent = "%5s" % ("{0:.1f}").format(percent)
             output = '\r' + prefix + \
                 " |%s| %s%s %s" % (bar, percent, '%', suffix)
-            sys.stdout.write(output),
+            sys.stdout.write(output)
             if iteration == total and carriage_return:
                 sys.stdout.write('\n')
             sys.stdout.flush()
diff --git a/spython/logger/progress.py b/spython/logger/progress.py
index d21ce031..666cfc6c 100644
--- a/spython/logger/progress.py
+++ b/spython/logger/progress.py
@@ -54,7 +54,7 @@ def __init__(self, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR,
         self.etadelta =      time.time()
         self.etadisp =       self.format_time(self.eta)
         self.last_progress = 0
-        if (self.expected_size):
+        if self.expected_size:
             self.show(0)
 
     def show(self, progress, count=None):
@@ -104,7 +104,7 @@ def bar(it, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR,
 
     count = len(it) if expected_size is None else expected_size
 
-    with Bar(label=label, width=width, hide=hide, empty_char=BAR_EMPTY_CHAR,
+    with ProgressBar(label=label, width=width, hide=hide, empty_char=BAR_EMPTY_CHAR,
              filled_char=BAR_FILLED_CHAR, expected_size=count, every=every) \
             as bar:
         for i, item in enumerate(it):
diff --git a/spython/logger/spinner.py b/spython/logger/spinner.py
index df1fd1e9..22d40980 100644
--- a/spython/logger/spinner.py
+++ b/spython/logger/spinner.py
@@ -6,9 +6,6 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-import os
-import sys
-
 import sys
 import time
 import threading
@@ -34,7 +31,7 @@ def changing_arrows():
             for cursor in '<^>v': yield cursor
 
     def select_generator(self, generator):
-        if generator == None:
+        if generator is None:
             generator = choice(['cursor',
                                 'arrow',
                                 'balloons'])
diff --git a/spython/main/apps.py b/spython/main/apps.py
index 3f2e26fb..4003a26c 100644
--- a/spython/main/apps.py
+++ b/spython/main/apps.py
@@ -6,8 +6,6 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-from spython.logger import bot
-
 def apps(self, image=None, full_path=False, root=''):
     '''
        return list of SCIF apps in image. The Singularity software serves
diff --git a/spython/main/base/__init__.py b/spython/main/base/__init__.py
index 978d8d6c..b4644c7e 100644
--- a/spython/main/base/__init__.py
+++ b/spython/main/base/__init__.py
@@ -37,10 +37,10 @@ def __repr__(self):
         return self.__str__()
 
     def __init__(self):
-       '''the base client for singularity, will have commands added to it.
-          upon init, store verbosity requested in environment MESSAGELEVEL.
-       '''
-       self._init_level()
+        '''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):
         '''a wrapped to get_singularity_version, takes no arguments.
diff --git a/spython/main/base/command.py b/spython/main/base/command.py
index 103cf408..ca3031e6 100644
--- a/spython/main/base/command.py
+++ b/spython/main/base/command.py
@@ -6,19 +6,15 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-from spython.main.base.logger import println
 from spython.utils import ( 
-    run_command as run_cmd, 
-    check_install 
+    run_command as run_cmd
 )
 
 from spython.logger import bot
 
 import subprocess
-import json
 import sys
 import os
-import re
 
 
 
@@ -129,7 +125,7 @@ def run_command(self, cmd,
 
     '''
     # First preference to function, then to client setting
-    if quiet == None:
+    if quiet is None:
         quiet = self.quiet
 
     result = run_cmd(cmd, sudo=sudo, capture=capture, quiet=quiet)
diff --git a/spython/main/base/flags.py b/spython/main/base/flags.py
index af420364..1a333781 100644
--- a/spython/main/base/flags.py
+++ b/spython/main/base/flags.py
@@ -6,31 +6,6 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-
-def parse_verbosity(self, 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 is True:
-       flags.append('--silent')
-    elif args.quiet is True:
-        flags.append('--quiet')
-    elif args.debug is True:
-       flags.append('--debug')
-    elif args.verbose is True:
-       flags.append('-' + 'v' * args.verbose)
-
-    return flags
-
-
 '''
 GLOBAL OPTIONS:
     -d|--debug    Print debugging information
@@ -69,3 +44,26 @@ def parse_verbosity(self, args):
     instance   Persistent instance command group                                
 
 '''
+
+def parse_verbosity(self, 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 is True:
+        flags.append('--silent')
+    elif args.quiet is True:
+        flags.append('--quiet')
+    elif args.debug is True:
+        flags.append('--debug')
+    elif args.verbose is True:
+        flags.append('-' + 'v' * args.verbose)
+
+    return flags
diff --git a/spython/main/build.py b/spython/main/build.py
index 6e4eef12..f67ba047 100644
--- a/spython/main/build.py
+++ b/spython/main/build.py
@@ -93,7 +93,7 @@ def build(self, recipe=None,
     cmd = cmd + [image, recipe]
 
     if stream is False:
-        output = self._run_command(cmd, sudo=sudo, capture=False)
+        self._run_command(cmd, sudo=sudo, capture=False)
     else:
         # Here we return the expected image, and an iterator! 
         # The caller must iterate over
diff --git a/spython/main/execute.py b/spython/main/execute.py
index 4df805a6..d7883925 100644
--- a/spython/main/execute.py
+++ b/spython/main/execute.py
@@ -8,8 +8,6 @@
 
 from spython.logger import bot
 from spython.utils import stream_command
-import os
-import sys
 
 
 def execute(self, 
@@ -66,6 +64,10 @@ def execute(self,
         if isinstance(image, self.instance):
             image = image.get_uri()
 
+        # 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.')
+
         # Does the user want to use bind paths option?
         if bind is not None:
             cmd += self._generate_bind_list(bind)
diff --git a/spython/main/export.py b/spython/main/export.py
index 49a89442..4a8d655f 100644
--- a/spython/main/export.py
+++ b/spython/main/export.py
@@ -7,7 +7,6 @@
 
 
 from spython.logger import bot
-from spython.utils import stream_command
 import tempfile
 import shutil
 import os
@@ -38,8 +37,8 @@ def export(self,
         # If export is deprecated, we run a build
         bot.warning('Export is not supported for Singularity 3.x. Building to sandbox instead.')
 
-        if output_file == None:
-            basename, ext = os.path.splitext(image_path)
+        if output_file is None:
+            basename, _ = os.path.splitext(image_path)
             output_file = self._get_filename(basename, 'sandbox', pwd=False)
 
         return self.build(recipe=image_path,
@@ -83,7 +82,7 @@ def _export(self,
     cmd = self._init_command('export')
     
     # If the user has specified export to pipe, we don't need a file
-    if pipe == True:
+    if pipe:
         cmd.append(image_path)
 
     else:
@@ -98,7 +97,7 @@ def _export(self,
             return None
 
         # if user has specified output file, move it there, return path
-        if output_file != None:
+        if output_file is not None:
             shutil.copyfile(tmptar, output_file)
             return output_file
         else:
diff --git a/spython/main/help.py b/spython/main/help.py
index 20a676fd..ebf6a1bf 100644
--- a/spython/main/help.py
+++ b/spython/main/help.py
@@ -18,7 +18,7 @@ def help(self, command=None):
     check_install()
 
     cmd = ['singularity','--help']
-    if command != None:
+    if command is not None:
         cmd.append(command)
     help = self._run_command(cmd)
     return help
diff --git a/spython/main/inspect.py b/spython/main/inspect.py
index 796df504..c004da8f 100644
--- a/spython/main/inspect.py
+++ b/spython/main/inspect.py
@@ -7,7 +7,6 @@
 
 import json as jsonp
 
-from spython.logger import bot
 from spython.utils import ( 
     check_install, 
     run_command
@@ -41,7 +40,8 @@ def inspect(self, image=None, json=True, app=None, quiet=True):
     if "version 3" in self.version():
         options = ['e','d','l','r','H','t']
 
-    [cmd.append('-%s' % x) for x in options]
+    for x in options:
+        cmd.append('-%s' % x)
 
     if json is True:
         cmd.append('--json')
@@ -52,8 +52,12 @@ def inspect(self, image=None, json=True, app=None, quiet=True):
     if result['return_code'] == 0:
         result = jsonp.loads(result['message'][0])
 
+        # Unify output to singularity 3 format
+        if "data" in result:
+            result = result['data']
+
         # Fix up labels
-        labels = parse_labels(result)
+        result = parse_labels(result)
 
         if not quiet:
             print(jsonp.dumps(result, indent=4))
@@ -70,22 +74,12 @@ def parse_labels(result):
        result: the json object to parse from inspect
     '''
 
-    if "data" in result:
-        labels = result['data']['attributes'].get('labels') or {}
-
-    elif 'attributes' in result:
-        labels = result['attributes'].get('labels') or {}
-
-    # If labels included, try parsing to json
-
+    labels = result['attributes'].get('labels') or {}
     try:
         labels = jsonp.loads(labels)
     except:
         pass
 
-    if "data" in result:
-        result['data']['attributes']['labels'] = labels
-    else:
-        result['attributes']['labels'] = labels
+    result['attributes']['labels'] = labels
 
     return result
diff --git a/spython/main/instances.py b/spython/main/instances.py
index 987a3d54..a133f0e4 100644
--- a/spython/main/instances.py
+++ b/spython/main/instances.py
@@ -7,7 +7,7 @@
 
 
 from spython.logger import bot
-from spython.utils import ( run_command, check_install )
+from spython.utils import ( run_command )
 
 def instances(self, name=None, return_json=False, quiet=False):
     '''list instances. For Singularity, this is provided as a command sub
@@ -66,7 +66,7 @@ def instances(self, name=None, return_json=False, quiet=False):
             for i in instances:
                 
                 # If the user has provided a name, only add instance matches
-                if name != None:
+                if name is not None:
                     if name != i['daemon_name']:
                         continue
 
@@ -89,7 +89,7 @@ def instances(self, name=None, return_json=False, quiet=False):
         bot.info('No instances found.')
 
     # If we are given a name, return just one
-    if name != None and instances not in [None,[]]:
+    if name is not None and instances not in [None,[]]:
         if len(instances) == 1:
             instances = instances[0]
 
@@ -106,7 +106,7 @@ def stopall(self, sudo=False, quiet=True):
              instances)
 
     '''
-    from spython.utils import run_command, check_install
+    from spython.utils import check_install
     check_install()
 
     subgroup = 'instance.stop'
diff --git a/spython/main/parse/converters.py b/spython/main/parse/converters.py
index 5ad360af..d646778a 100644
--- a/spython/main/parse/converters.py
+++ b/spython/main/parse/converters.py
@@ -6,11 +6,7 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-import json
-import os
 import re
-import sys
-
 from spython.logger import bot
 
 # Singularity to Dockerfile
@@ -84,11 +80,11 @@ def create_runscript(self, default="/bin/bash", force=False):
     entrypoint = default
 
     # Only look at Docker if not enforcing default
-    if force is False:
+    if not force:
         if self.entrypoint is not None:
-            entrypoint = ''.join(self.entrypoint)
-        elif self.cmd is not None:
-            entrypoint = ''.join(self.cmd)
+            entrypoint = ' '.join(self.entrypoint)
+        if self.cmd is not None:
+            entrypoint = entrypoint + ' ' + ' '.join(self.cmd)
 
     # Entrypoint should use exec
     if not entrypoint.startswith('exec'):
@@ -214,8 +210,9 @@ def docker2singularity(self, runscript="/bin/bash", force=False):
     if self.workdir is not None:
         runscript = [self.workdir] + [runscript]
 
-    # Finish the recipe
+    # Finish the recipe, also add as startscript
     recipe += finish_section(runscript, 'runscript')
+    recipe += finish_section(runscript, 'startscript')
 
     if self.test is not None:
         recipe += finish_section(self.test, 'test')
diff --git a/spython/main/parse/docker.py b/spython/main/parse/docker.py
index df403353..ebf744ee 100644
--- a/spython/main/parse/docker.py
+++ b/spython/main/parse/docker.py
@@ -5,14 +5,12 @@
 # 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 json
 import os
 import re
-import sys
 
 from .environment import parse_env
 from .recipe import Recipe
-from spython.utils import read_file
 from spython.logger import bot
 
 class DockerRecipe(Recipe):
@@ -332,14 +330,29 @@ def _cmd(self, line):
         '''_cmd will parse a Dockerfile CMD command
            
            eg: CMD /code/run_uwsgi.sh --> /code/run_uwsgi.sh.
-               The 
+               If a list is provided, it's parsed to a list.
 
            Parameters
            ==========
            line: the line from the recipe file to parse for CMD
 
         '''
-        self.cmd = self._setup('CMD', line)
+        cmd = self._setup('CMD', line)[0]
+        self.cmd = self._load_list(cmd)
+
+
+    def _load_list(self, line):
+        '''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:
+            pass
+        return line
 
 
     def _entry(self, line):
@@ -350,7 +363,8 @@ def _entry(self, line):
            line: the line from the recipe file to parse for CMD
 
         '''
-        self.entrypoint = self._setup('ENTRYPOINT', line)
+        entrypoint = self._setup('ENTRYPOINT', line)[0]
+        self.entrypoint = self._load_list(entrypoint)
 
 
 # Labels
diff --git a/spython/main/parse/recipe.py b/spython/main/parse/recipe.py
index c632da62..f51778bb 100644
--- a/spython/main/parse/recipe.py
+++ b/spython/main/parse/recipe.py
@@ -5,10 +5,8 @@
 # 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 json
 import tempfile
 import os
-import re
 import sys
 
 from spython.logger import bot
@@ -285,7 +283,7 @@ def _clean_line(self, line):
         return line.split('#')[0].strip()
 
 
-    def _write_script(path, lines, chmod=True):
+    def _write_script(self, path, lines, chmod=True):
         '''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.
 
@@ -299,7 +297,7 @@ def _write_script(path, lines, chmod=True):
         if len(lines) > 0:
             lastline = lines.pop()
         for line in lines:
-            self.install.append('echo "%s" >> %s' %path)
+            self.install.append('echo "%s" >> %s' %line %path)
         self.install.append(lastline)     
 
         if chmod is True:
diff --git a/spython/main/parse/singularity.py b/spython/main/parse/singularity.py
index c289c7ec..e001c3f3 100644
--- a/spython/main/parse/singularity.py
+++ b/spython/main/parse/singularity.py
@@ -6,13 +6,10 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-import json
-import os
 import re
 import sys
 
 from spython.logger import bot
-from spython.utils import read_file
 from spython.main.parse.recipe import Recipe
 
 
@@ -88,7 +85,6 @@ def _test(self, lines):
 
         '''
         self._write_script('/tests.sh', lines)
-        testrun = "/bin/bash /tests.sh"
         self.test = "/bin/bash /tests.sh"
         
 
@@ -125,17 +121,17 @@ def _files(self, lines):
 # Comments and Help
 
     def _comments(self, lines):
-         ''' comments is a wrapper for comment, intended to be given a list
-             of comments.
+        ''' comments is a wrapper for comment, intended to be given a list
+            of comments.
 
-             Parameters
-             ==========
-             lines: the list of lines to parse
+            Parameters
+            ==========
+            lines: the list of lines to parse
 
-         ''' 
-         for line in lines:
-             comment = self._comment(line)
-             self.comments.append(comment)
+        ''' 
+        for line in lines:
+            comment = self._comment(line)
+            self.comments.append(comment)
 
 
     def _comment(self, line):
@@ -330,7 +326,6 @@ def load_recipe(self):
         self.config = dict()
      
         section = None
-        name = None
 
         while len(lines) > 0:
 
@@ -382,10 +377,8 @@ def _add_section(self, line, section=None):
         line = line.split('#',1)[0].strip()
 
         # Is there a section name?
-        parts = line.split(' ')
-        if len(parts) > 1:
-            name = ' '.join(parts[1:])          
-        section = re.sub('[%]|(\s+)','',parts[0]).lower()
+        parts = line.split(' ')         
+        section = re.sub(r'[%]|(\s+)','',parts[0]).lower()
 
         if section not in self.config: 
             self.config[section] = []
diff --git a/spython/main/pull.py b/spython/main/pull.py
index 6673a3de..ac2adf6a 100644
--- a/spython/main/pull.py
+++ b/spython/main/pull.py
@@ -10,9 +10,6 @@
 from spython.utils import stream_command
 import os
 import re
-import shutil
-import sys
-import tempfile
 
 def pull(self, 
          image=None,
diff --git a/spython/main/run.py b/spython/main/run.py
index 3ecf0404..69532a30 100644
--- a/spython/main/run.py
+++ b/spython/main/run.py
@@ -60,6 +60,10 @@ def run(self,
     if isinstance(image, self.instance):
         image = image.get_uri()
 
+    # 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.')
+
     # Does the user want to use bind paths option?
     if bind is not None:
         cmd += self._generate_bind_list(bind)
diff --git a/spython/oci/__init__.py b/spython/oci/__init__.py
index 46452eab..6ffbcfa9 100644
--- a/spython/oci/__init__.py
+++ b/spython/oci/__init__.py
@@ -30,15 +30,15 @@ def __init__(self,
             sudo: if init is called with or without sudo, keep a record and use
                   for following commands unless sudo is provided to function.
         '''
-        super(ImageBase, self).__init__()
+        super().__init__()
 
         # Will typically be None, unless used outside of Client
         self.container_id = container_id
-        self.uri = 'oci://'
+        self.protocol = 'oci'
         self.sudo = sudo
 
         # If bundle is provided, create it
-        if bundle != None and container_id != None and create:
+        if bundle is not None and container_id is not None and create:
             self.bundle = bundle
             self.create(bundle, container_id, **kwargs)
 
@@ -56,7 +56,7 @@ def get_container_id(self, container_id=None):
         '''
 
         # The user must provide a container_id, or have one with the client
-        if container_id == None and self.container_id == None:
+        if container_id is None and self.container_id is None:
             bot.exit('You must provide a container_id.')
 
         # Choose whichever is not None, with preference for function provided
@@ -72,7 +72,7 @@ def get_uri(self):
 # Naming
 
     def __str__(self):
-        if self.container_id != None:
+        if self.container_id is not None:
             return "[singularity-python-oci:%s]" % self.container_id
         return "[singularity-python-oci]"
 
@@ -92,7 +92,7 @@ def _get_sudo(self, sudo=None):
            ==========
            sudo: if None, use self.sudo. Otherwise return sudo.
         '''
-        if sudo == None:
+        if sudo is None:
             sudo = self.sudo
         return sudo
 
diff --git a/spython/oci/cmd/actions.py b/spython/oci/cmd/actions.py
index 09f2a0f4..6425d495 100644
--- a/spython/oci/cmd/actions.py
+++ b/spython/oci/cmd/actions.py
@@ -119,11 +119,11 @@ def _run(self, bundle,
     # Additional Logging Files
     cmd = cmd + ['--log-format', log_format]
 
-    if log_path != None:
+    if log_path is not None:
         cmd = cmd + ['--log-path', log_path]
-    if pid_file != None:
+    if pid_file is not None:
         cmd = cmd + ['--pid-file', pid_file]
-    if sync_socket != None:
+    if sync_socket is not None:
         cmd = cmd + ['--sync-socket', sync_socket]
     if empty_process:
         cmd.append('--empty-process')
@@ -132,7 +132,7 @@ def _run(self, bundle,
     cmd.append(container_id)
 
     # Generate the instance
-    result = self._send_command(cmd, sudo=True)
+    self._send_command(cmd, sudo=True)
 
     # Get the status to report to the user!
     # TODO: Singularity seems to create even with error, can we check and
@@ -225,7 +225,7 @@ def execute(self, command=None, container_id=None, sudo=False, stream=False):
     # Add the container_id
     cmd.append(container_id)
 
-    if command != None:
+    if command is not None:
         if not isinstance(command, list):
             command = [command]
 
@@ -236,7 +236,7 @@ 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):
+def update(self, container_id, from_file=None, sudo=False):
     '''update container cgroup resources for a specific container_id,
        The container must have state "running" or "created."
 
@@ -255,7 +255,7 @@ def update(self, container_id, from_file=None):
     # singularity oci delete
     cmd = self._init_command('update')
 
-    if from_file != None:
+    if from_file is not None:
         cmd = cmd + ['--from-file', from_file]
 
     # Add the container_id
diff --git a/spython/oci/cmd/mounts.py b/spython/oci/cmd/mounts.py
index 61fcd73c..659524e7 100644
--- a/spython/oci/cmd/mounts.py
+++ b/spython/oci/cmd/mounts.py
@@ -6,11 +6,6 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-from spython.logger import bot
-from .states import _state_command
-import sys
-
-
 def mount(self, image, sudo=None):
     '''create an OCI bundle from SIF image
 
@@ -21,8 +16,8 @@ def mount(self, image, sudo=None):
     return self._state_command(image, command="mount", sudo=sudo)
 
 
-def umount(self, image):
-    '''delete an OCI bundle createdfrom SIF image
+def umount(self, image, sudo=None):
+    '''delete an OCI bundle created from SIF image
 
        Parameters
        ==========
diff --git a/spython/oci/cmd/states.py b/spython/oci/cmd/states.py
index dfcd36c6..52a9ed34 100644
--- a/spython/oci/cmd/states.py
+++ b/spython/oci/cmd/states.py
@@ -6,7 +6,6 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-from spython.logger import bot
 import json
 
 
@@ -36,7 +35,7 @@ def state(self, container_id=None, sudo=None, sync_socket=None):
     # singularity oci state
     cmd = self._init_command('state')
 
-    if sync_socket != None:
+    if sync_socket is not None:
         cmd = cmd + ['--sync-socket', sync_socket]
 
     # Finally, add the container_id
@@ -45,7 +44,7 @@ def state(self, container_id=None, sudo=None, sync_socket=None):
     # Get the instance state
     result = self._run_command(cmd, sudo=sudo, quiet=True)
 
-    if result != None:
+    if result is not None:
 
         # If successful, a string is returned to parse
         if isinstance(result, str):
@@ -134,7 +133,7 @@ def kill(self, container_id=None, sudo=None, signal=None):
     cmd.append(container_id)
 
     # Add the signal, if defined
-    if signal != None:
+    if signal is not None:
         cmd = cmd + ['--signal', signal]
 
     # Run the command, return return code
diff --git a/spython/tests/test_client.py b/spython/tests/test_client.py
index 9c56c03e..63ceb4fa 100644
--- a/spython/tests/test_client.py
+++ b/spython/tests/test_client.py
@@ -7,12 +7,10 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from spython.utils import get_installdir
-from spython.logger import bot
 from spython.main import Client
 import unittest
 import tempfile
 import shutil
-import json
 import os
 
 
@@ -82,7 +80,9 @@ def test_commands(self):
         self.assertEqual(result['return_code'], 0)
 
         print("Testing client.inspect command")
-        labels = self.cli.inspect(container)
+        result = self.cli.inspect(container)
+        self.assertEqual(result['type'], 'container')
+        self.assertTrue('attributes' in result)
 
         os.remove(container)
 
diff --git a/spython/tests/test_instances.py b/spython/tests/test_instances.py
index 590d9acb..6c3d398a 100644
--- a/spython/tests/test_instances.py
+++ b/spython/tests/test_instances.py
@@ -6,13 +6,10 @@
 # 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_installdir
-from spython.logger import bot
 from spython.main import Client
 import unittest
 import tempfile
 import shutil
-import json
 import os
 
 
@@ -27,6 +24,16 @@ def setUp(self):
     def tearDown(self):
         shutil.rmtree(self.tmpdir)
 
+    def test_instance_class(self):
+        instance = self.cli.instance('docker://ubuntu', start=False)
+        self.assertEqual(instance.get_uri(), 'instance://' + instance.name)
+        self.assertNotEqual(instance.name, '')
+
+        name = 'coolName'
+        instance = self.cli.instance('docker://busybox:1.30.1', start=False, name=name)
+        self.assertEqual(instance.get_uri(), 'instance://' + instance.name)
+        self.assertEqual(instance.name, name)
+
     def test_instances(self):
 
         print('Pulling testing container')
@@ -53,7 +60,7 @@ def test_instances(self):
 
         print("...Case 3: Commands to instances")
         result = self.cli.execute(myinstance, ['echo', 'hello'])
-        self.assertTrue('hello\n' == result)
+        self.assertEqual(result, 'hello\n')
 
         print('...Case 4: Return value from instance')
         result = self.cli.execute(myinstance,'ls /', return_result=True)
@@ -67,6 +74,8 @@ def test_instances(self):
         self.assertEqual(instances, [])
         myinstance1 = self.cli.instance(image)
         myinstance2 = self.cli.instance(image)
+        self.assertTrue(myinstance1 is not None)
+        self.assertTrue(myinstance2 is not None)
         instances = self.cli.instances()
         self.assertEqual(len(instances), 2)
         self.cli.instance_stopall()
diff --git a/spython/tests/test_oci.py b/spython/tests/test_oci.py
index 0cb8844e..355ab165 100644
--- a/spython/tests/test_oci.py
+++ b/spython/tests/test_oci.py
@@ -8,12 +8,10 @@
 
 from spython.utils import get_installdir
 from spython.main.base.generate import RobotNamer
-from spython.logger import bot
 from spython.main import Client
 import unittest
 import tempfile
 import shutil
-import json
 import os
 
 print("############################################################## test_oci")
@@ -42,6 +40,10 @@ def _build_sandbox(self):
         shutil.copyfile(self.config, '%s/config.json' %image)
         return image
 
+    def test_oci_image(self):
+        image=self.cli.oci.OciImage('oci://imagename')
+        self.assertEqual(image.get_uri(), '[singularity-python-oci:oci://imagename]')
+
     def test_oci(self):
 
         image = self._build_sandbox()
diff --git a/spython/tests/test_utils.py b/spython/tests/test_utils.py
index 7294e033..4aba8502 100644
--- a/spython/tests/test_utils.py
+++ b/spython/tests/test_utils.py
@@ -6,11 +6,9 @@
 # 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_installdir
 import unittest
 import tempfile
 import shutil
-import json
 import os
 
 print("############################################################ test_utils")
@@ -46,7 +44,7 @@ def test_write_read_files(self):
         bad_json = {"Wakkawakkawakka'}":[{True},"2",3]}
         tmpfile = tempfile.mkstemp()[1]
         os.remove(tmpfile)        
-        with self.assertRaises(TypeError) as cm:
+        with self.assertRaises(TypeError):
             write_json(bad_json,tmpfile)
 
         print("...Case 2: Providing good json")        
@@ -77,8 +75,14 @@ def test_check_get_singularity_version(self):
         from spython.utils import get_singularity_version
         version = get_singularity_version()
         self.assertTrue(version != "")
+        oldValue = os.environ.get('SPYTHON_SINGULARITY_VERSION')
         os.environ['SPYTHON_SINGULARITY_VERSION'] = "3.0"
         version = get_singularity_version()
+        # Restore for other tests
+        if oldValue is None:
+            del os.environ['SPYTHON_SINGULARITY_VERSION']
+        else:
+            os.environ['SPYTHON_SINGULARITY_VERSION'] = oldValue
         self.assertTrue(version == "3.0")
 
 
@@ -93,11 +97,26 @@ def test_get_installdir(self):
         self.assertTrue(whereami.endswith('spython'))
 
 
+    def test_split_uri(self):
+        from spython.utils import split_uri
+        protocol, image = split_uri('docker://ubuntu')
+        self.assertEqual(protocol, 'docker')
+        self.assertEqual(image, 'ubuntu')
+
+        protocol, image = split_uri('http://image/path/with/slash/')
+        self.assertEqual(protocol, 'http')
+        self.assertEqual(image, 'image/path/with/slash')
+
+        protocol, image = split_uri('no/proto/')
+        self.assertEqual(protocol, '')
+        self.assertEqual(image, 'no/proto')
+
     def test_remove_uri(self):
         print("Testing utils.remove_uri")
         from spython.utils import remove_uri
         self.assertEqual(remove_uri('docker://ubuntu'),'ubuntu')
         self.assertEqual(remove_uri('shub://vanessa/singularity-images'),'vanessa/singularity-images')
+        self.assertEqual(remove_uri('vanessa/singularity-images'),'vanessa/singularity-images')
 
 
 
diff --git a/spython/utils/__init__.py b/spython/utils/__init__.py
index 702fdb3a..46419f35 100644
--- a/spython/utils/__init__.py
+++ b/spython/utils/__init__.py
@@ -13,5 +13,6 @@
     stream_command,
     run_command,
     format_container_name,
+    split_uri,
     remove_uri
 )
diff --git a/spython/utils/fileio.py b/spython/utils/fileio.py
index 3c4519a0..69a638e5 100644
--- a/spython/utils/fileio.py
+++ b/spython/utils/fileio.py
@@ -10,14 +10,8 @@
 
 import errno
 import os
-import re
 import json
 from spython.logger import bot
-from subprocess import (
-    Popen,
-    PIPE,
-    STDOUT
-)
 import sys
 
 
diff --git a/spython/utils/terminal.py b/spython/utils/terminal.py
index 6c79498a..dada60ac 100644
--- a/spython/utils/terminal.py
+++ b/spython/utils/terminal.py
@@ -11,7 +11,6 @@
 import os
 import re
 
-import json
 from spython.logger import bot
 import subprocess
 import sys
@@ -177,7 +176,22 @@ def format_container_name(name, special_characters=None):
                    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
+
+    An empty protocol is returned if none found.
+    A trailing slash is removed from the image part.
+    '''
+    parts = container.split('://', 1)
+    if len(parts) == 2:
+        protocol, image = parts
+    else:
+        protocol = ''
+        image=parts[0]
+    return protocol, image.rstrip('/')
+
+
 def remove_uri(container):
     '''remove_uri will remove docker:// or shub:// from the uri
     '''
-    return container.replace('docker://', '').replace('shub://', '')
+    return split_uri(container)[1]
diff --git a/spython/version.py b/spython/version.py
index efa2f80f..081ea332 100644
--- a/spython/version.py
+++ b/spython/version.py
@@ -6,7 +6,7 @@
 # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-__version__ = "0.0.61"
+__version__ = "0.0.62"
 AUTHOR = 'Vanessa Sochat'
 AUTHOR_EMAIL = 'vsochat@stanford.edu'
 NAME = 'spython'
