From 6e60b71df07234c329fcf448ec67e26e5d84d525 Mon Sep 17 00:00:00 2001
From: Gabriel Studer <gabriel.studer@unibas.ch>
Date: Wed, 13 Nov 2019 19:12:20 +0100
Subject: [PATCH] Initial Python 3 port commit

This commit doesn't make OpenStructure work with Python 3. The goal of this
commit was to perform an automated port of the Python code and make it compile.
The performed steps:

- Edited CMakeLists.txt to search for Python with 3.6 as min version 3.6 is the
  Python version shipped by default with Ubuntu 18.04 LTS
- Add version 3.6 to cmake_support/FindPython.cmake
- Adapt setup_boost macro in cmake_support/OST.cmake to prefer versioned
  libraries and not first check for boost_python.so. In the example of
  Ubuntu 18.04, libboost_python.so is specific for Python 2 but
  libboost_python3.so is the one we want.
- apply the following command: 2to3-2.7 -n -w <OST_DIR>
- adapt base/pymod/wrap_base.cc, gui/pymod/wrap_gui.cc and
  gui/pymod/export_message_widget.cc as PyString functionalities do not exist
  anymore in the Python 3 interpreter (replaced by PyUnicode)
- adapt gui/src/python_shell/python_interpreter_worker.hh to resolve issue
  discussed in https://stackoverflow.com/questions/23068700/embedding-python3-in-qt-5
  Long story short: Qt does a typedef for "slots" which causes trouble with
  other headers that are pulled in from the Python interpreter
---
 CMakeLists.txt                                |   2 +-
 cmake_support/FindPython.cmake                |   2 +-
 cmake_support/OST.cmake                       |   8 +-
 deployment/macos/deps.py                      |  46 +--
 deployment/macos/pack.py                      |  36 +--
 doc/conf/conf.py                              |   8 +-
 doc/make.py                                   |   4 +-
 doc/to_sphinx.py                              |  12 +-
 docker/test_docker.py                         |   2 +-
 examples/demos/charmm_trj_blur.py             |   2 +-
 examples/demos/correlate.py                   |   2 +-
 examples/demos/gfx_mapslab.py                 |   4 +-
 examples/demos/load_and_display.py            |   2 +-
 examples/demos/the_hammer.py                  |   2 +-
 modules/base/pymod/table.py                   |  36 +--
 modules/base/pymod/testutils.py               |   8 +-
 modules/base/pymod/wrap_base.cc               |  12 +-
 modules/base/pymod/xmlrunner.py               |   4 +-
 modules/base/tests/test_table.py              | 292 +++++++++---------
 modules/bindings/pymod/align_3dcomb.py        |   8 +-
 modules/bindings/pymod/blast.py               |   6 +-
 modules/bindings/pymod/cadscore.py            |   6 +-
 modules/bindings/pymod/dssp.py                |   8 +-
 modules/bindings/pymod/hbplus.py              |   4 +-
 modules/bindings/pymod/hhblits.py             |  24 +-
 modules/bindings/pymod/ialign.py              |   2 +-
 modules/bindings/pymod/kclust.py              |   2 +-
 modules/bindings/pymod/lga.py                 |   4 +-
 modules/bindings/pymod/msms.py                |   6 +-
 modules/bindings/pymod/naccess.py             |   6 +-
 modules/bindings/tests/test_blast.py          |   2 +-
 modules/bindings/tests/test_clustalw.py       |   2 +-
 modules/bindings/tests/test_hhblits.py        |   4 +-
 modules/bindings/tests/test_kclust.py         |   2 +-
 modules/bindings/tests/test_msms.py           |   4 +-
 modules/bindings/tests/test_naccess.py        |   4 +-
 modules/conop/pymod/cleanup.py                |   2 +-
 modules/conop/tests/test_cleanup.py           |   2 +-
 modules/conop/tests/test_compound.py          |   2 +-
 modules/geom/tests/test_geom.py               |   2 +-
 modules/gfx/pymod/__init__.py                 |   4 +-
 modules/gfx/tests/test_gfx.py                 |   2 +-
 modules/gfx/tests/test_gost_import.py         |   2 +-
 modules/gui/pymod/__init__.py                 |   4 +-
 modules/gui/pymod/dng/init.py                 |  22 +-
 modules/gui/pymod/dng/menu.py                 |   4 +-
 modules/gui/pymod/export_message_widget.cc    |   6 +-
 modules/gui/pymod/helpwidget.py               |   2 +-
 .../gui/pymod/scene/color_options_widget.py   |   8 +-
 .../gui/pymod/scene/combo_options_widget.py   |   2 +-
 modules/gui/pymod/scene/cpk_widget.py         |   2 +-
 modules/gui/pymod/scene/custom_widget.py      |   2 +-
 .../gui/pymod/scene/gradient_editor_widget.py |   4 +-
 .../gui/pymod/scene/gradient_info_handler.py  |   2 +-
 .../gui/pymod/scene/gradient_list_model.py    |   8 +-
 .../gui/pymod/scene/gradient_preset_widget.py |   4 +-
 modules/gui/pymod/scene/hsc_widget.py         |   2 +-
 modules/gui/pymod/scene/inspector_widget.py   |  10 +-
 modules/gui/pymod/scene/line_trace_widget.py  |   2 +-
 modules/gui/pymod/scene/map_level_widget.py   |   2 +-
 modules/gui/pymod/scene/preset.py             |   2 +-
 .../pymod/scene/preset_editor_list_model.py   |   2 +-
 .../gui/pymod/scene/preset_editor_widget.py   |  20 +-
 .../gui/pymod/scene/preset_info_handler.py    |   2 +-
 modules/gui/pymod/scene/preset_list_model.py  |   8 +-
 modules/gui/pymod/scene/preset_widget.py      |   8 +-
 modules/gui/pymod/scene/remote.py             |   6 +-
 modules/gui/pymod/scene/render_mode_widget.py |   4 +-
 .../gui/pymod/scene/render_options_widget.py  |  22 +-
 modules/gui/pymod/scene/simple_widget.py      |   2 +-
 modules/gui/pymod/scene/sline_widget.py       |   2 +-
 .../gui/pymod/scene/toolbar_options_widget.py |   2 +-
 modules/gui/pymod/scene/trace_widget.py       |   2 +-
 modules/gui/pymod/scene/tube_widget.py        |   2 +-
 .../gui/pymod/scene/uniform_color_widget.py   |   2 +-
 modules/gui/pymod/scene/wireframe_widget.py   |   2 +-
 modules/gui/pymod/table.py                    |   2 +-
 modules/gui/pymod/trajectory_viewer.py        |   8 +-
 modules/gui/pymod/wrap_gui.cc                 |  13 +-
 .../python_shell/python_interpreter_worker.hh |   4 +
 modules/io/pymod/__init__.py                  |   2 +-
 modules/io/pymod/remote.py                    |   6 +-
 modules/io/tests/test_io_mmcif.py             | 172 +++++------
 modules/io/tests/test_io_pdb.py               |   4 +-
 modules/mol/alg/pymod/hbond.py                |  34 +-
 modules/mol/alg/pymod/helix_kinks.py          |  18 +-
 modules/mol/alg/pymod/qsscoring.py            |  80 ++---
 modules/mol/alg/pymod/structure_analysis.py   |  24 +-
 modules/mol/alg/pymod/superpose.py            |  10 +-
 modules/mol/alg/pymod/trajectory_analysis.py  |   2 +-
 modules/mol/alg/pymod/views.py                |   8 +-
 modules/mol/alg/tests/test_accessibility.py   |   4 +-
 .../alg/tests/test_convenient_superpose.py    |   4 +-
 modules/mol/alg/tests/test_nonstandard.py     |   2 +-
 modules/mol/alg/tests/test_pdbize.py          |  14 +-
 modules/mol/alg/tests/test_qsscoring.py       |  16 +-
 modules/mol/alg/tests/test_sec_struct.py      |   2 +-
 modules/mol/base/tests/test_numpy.py          |   2 +-
 modules/mol/mm/pymod/__init__.py              |   2 +-
 modules/mol/mm/pymod/antechamber.py           |   4 +-
 modules/seq/alg/pymod/__init__.py             |   2 +-
 modules/seq/alg/tests/test_aligntoseqres.py   |   2 +-
 modules/seq/alg/tests/test_renumber.py        |   2 +-
 scripts/bump-version.py                       |   6 +-
 scripts/code_coverage.py                      |  10 +-
 105 files changed, 622 insertions(+), 605 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7b76c4eea..3908e11f6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -177,7 +177,7 @@ if (ENABLE_STATIC AND UNIX AND NOT APPLE)
 endif()
 
 # Python needed before Boost
-find_package(Python 2.4 REQUIRED)
+find_package(Python 3.6 REQUIRED)
 # Split version string
 string(REPLACE "." ";" _python_version_list ${PYTHON_VERSION})
 list(GET _python_version_list 0 PYTHON_VERSION_MAJOR)
diff --git a/cmake_support/FindPython.cmake b/cmake_support/FindPython.cmake
index 673a80219..3a2e690de 100644
--- a/cmake_support/FindPython.cmake
+++ b/cmake_support/FindPython.cmake
@@ -17,7 +17,7 @@
 # Author: Marco Biasini
 #-------------------------------------------------------------------------------
 
-set(PYTHON_VERSIONS 2.7 2.6 2.5 2.4 2.3 2.2 )
+set(PYTHON_VERSIONS 3.6 2.7 2.6 2.5 2.4 2.3 2.2 )
 set(PYTHON_MIN_VERSION 2.2.1)
 
 #-------------------------------------------------------------------------------
diff --git a/cmake_support/OST.cmake b/cmake_support/OST.cmake
index 31c304804..46c511d58 100644
--- a/cmake_support/OST.cmake
+++ b/cmake_support/OST.cmake
@@ -917,10 +917,10 @@ macro(setup_boost)
   #              python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR} REQUIRED)
   # set(BOOST_PYTHON_LIBRARIES ${Boost_LIBRARIES})
   # see https://cmake.org/cmake/help/v3.11/module/FindBoost.html
-  foreach(_python_lib_name python
-                           python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}
+  foreach(_python_lib_name python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}
                            python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}
-                           python${PYTHON_VERSION_MAJOR})
+                           python${PYTHON_VERSION_MAJOR}
+                           python)
     find_package(Boost ${_BOOST_MIN_VERSION} COMPONENTS ${_python_lib_name} QUIET)
     if(Boost_FOUND)
       message(STATUS "Found Boost package: " ${_python_lib_name})
@@ -1004,4 +1004,4 @@ macro(ost_action ACTION TARGET)
     set(_ACTION_NAMES "${stripped_action}")
   endif()
   set(OST_ACTION_NAMES "${_ACTION_NAMES}" CACHE INTERNAL "" FORCE)
-endmacro(ost_action)
\ No newline at end of file
+endmacro(ost_action)
diff --git a/deployment/macos/deps.py b/deployment/macos/deps.py
index 91ef55c9a..63fb51cf7 100644
--- a/deployment/macos/deps.py
+++ b/deployment/macos/deps.py
@@ -37,7 +37,7 @@ def collect_deps(stage_dir, components, binaries, libexec_binaries,
     lib_name=os.path.abspath(os.path.join(stage_dir, 'lib', 
                                           _lib_name(component)))  
     if not os.path.exists(lib_name):
-      print 'WARNING:', lib_name, 'does not exist'
+      print('WARNING:', lib_name, 'does not exist')
     if lib_name not in pool:
       _deps_for_lib(lib_name, pool)
       pool.add(lib_name)    
@@ -45,7 +45,7 @@ def collect_deps(stage_dir, components, binaries, libexec_binaries,
     bin_name=os.path.abspath(os.path.join(stage_dir, 'bin', 
                                           bin))  
     if not os.path.exists(bin_name):
-      print 'WARNING:', bin_name, 'does not exist'
+      print('WARNING:', bin_name, 'does not exist')
       continue
     if bin_name not in pool:
       _deps_for_lib(bin_name, pool)
@@ -53,15 +53,15 @@ def collect_deps(stage_dir, components, binaries, libexec_binaries,
     bin_name=os.path.abspath(os.path.join(stage_dir, 'libexec/openstructure',
                                           bin))
     if not os.path.exists(bin_name):
-      print 'WARNING:', bin_name, 'does not exist'
+      print('WARNING:', bin_name, 'does not exist')
       continue
     if bin_name not in pool:
       _deps_for_lib(bin_name, pool)
   for site_package in site_packages:
     full_path=get_python_module_path(site_package)
-    print full_path
+    print(full_path)
     if not os.path.exists(full_path):
-      print 'WARNING:', site_package, 'does not exists'
+      print('WARNING:', site_package, 'does not exists')
       continue
     if os.path.isdir(full_path):
       for so_file in glob.glob(os.path.join(full_path, '*.so')):
@@ -99,7 +99,7 @@ def copy_binaries(stage_dir, outdir, binary_names, scripts, bin_dir,
     else:
       bin_name=os.path.join(stage_dir, binary_name)
     if not os.path.exists(bin_name):
-      print 'WARNING:', binary_name, 'does not exist'
+      print('WARNING:', binary_name, 'does not exist')
       continue
     dst_name=os.path.join(outdir, bin_dir, os.path.basename(bin_name))
     shutil.copy(bin_name, dst_name)
@@ -123,14 +123,14 @@ def split_framework_components(abs_path):
         return lead, trail
 
 def change_id(id, lib):
-  os.chmod(lib, 0666)
+  os.chmod(lib, 0o666)
   os.system(CHANGE_ID_RPATH % (id,lib))
-  os.chmod(lib, 0444)
+  os.chmod(lib, 0o444)
 
 def update_load_commands(lib, exe, exe_path):
   direct_deps=set()
   _deps_for_lib(lib, direct_deps, recursive=False)
-  os.chmod(lib, 0666)
+  os.chmod(lib, 0o666)
   for direct_dep in direct_deps:
     if direct_dep.endswith('.dylib'):
       new_name=os.path.basename(direct_dep)
@@ -142,9 +142,9 @@ def update_load_commands(lib, exe, exe_path):
       new_name=os.path.join(framework_name, rel_path)
       os.system(CHANGE_LOAD_CMD_RPATH % (direct_dep, new_name, lib))
   if exe:
-    os.chmod(lib, 0555)
+    os.chmod(lib, 0o555)
   else:
-    os.chmod(lib, 0444)
+    os.chmod(lib, 0o444)
 
 def copy_deps(dependencies, outdir):
   exe_path=os.path.join(outdir, 'bin')
@@ -218,7 +218,7 @@ def make_standalone(stage_dir, outdir, no_includes, force_no_rpath=False,
   os.system('mkdir -p "%s/lib"' % outdir)
   os.system('mkdir -p "%s/bin"' % outdir)
   os.system('mkdir -p "%s/libexec/openstructure"' % outdir)
-  print 'copying shared datafiles'
+  print('copying shared datafiles')
   shutil.copytree(os.path.join(stage_dir, 'share'), 
                   os.path.join(outdir, 'share'))
   scripts=SCRIPTS
@@ -233,7 +233,7 @@ def make_standalone(stage_dir, outdir, no_includes, force_no_rpath=False,
     components+=GUI_COMPONENTS
     libexec_binaries+=GUI_LIBEXEC_BINARIES
     site_packages+=GUI_SITE_PACKAGES
-  print 'collecting dependencies'
+  print('collecting dependencies')
   deps=collect_deps(stage_dir, components, binaries, libexec_binaries,
                     site_packages, site_packages_dir)
   # when running in non-gui mode, we are most likely missing the boost
@@ -242,14 +242,14 @@ def make_standalone(stage_dir, outdir, no_includes, force_no_rpath=False,
   pymod_dir='lib/python%d.%d/site-packages' % sys.version_info[0:2]
   _deps_for_lib(os.path.join(stage_dir, pymod_dir, 'ost/_ost_base.so'),
                 deps, recursive=False)
-  print 'copying dependencies'
+  print('copying dependencies')
   copy_deps(deps, outdir)
-  print 'copying libexec binaries'
+  print('copying libexec binaries')
   copy_binaries(stage_dir, outdir, libexec_binaries, libexec_scripts,
                 'libexec/openstructure')
-  print 'copying binaries'
+  print('copying binaries')
   copy_binaries(stage_dir, outdir, binaries, scripts, 'bin')
-  print 'copying pymod'
+  print('copying pymod')
   shutil.copytree(os.path.join(stage_dir,pymod_dir),
                   os.path.join(outdir, pymod_dir))
   copied_py_framework=False
@@ -261,10 +261,10 @@ def make_standalone(stage_dir, outdir, no_includes, force_no_rpath=False,
   if len(glob.glob(os.path.join(outdir, 'lib', 'libpython*')))>0:
     non_std_python=True
   if non_std_python:
-    print 'looks like we are using a non-standard python.'
+    print('looks like we are using a non-standard python.')
     python_home=get_python_home()    
     if not copied_py_framework:
-      print 'also copying python modules from %s' % python_home
+      print('also copying python modules from %s' % python_home)
       modules_dst=os.path.join(outdir, 'lib', os.path.basename(python_home))
       shutil.copytree(python_home, modules_dst)
       if os.path.exists(os.path.join(modules_dst, 'site-packages')):
@@ -298,23 +298,23 @@ def make_standalone(stage_dir, outdir, no_includes, force_no_rpath=False,
           shutil.rmtree(directory)
     # replace the python executable
     ost_script=os.path.join(outdir, 'libexec', 'openstructure', 'ost_config')
-    os.chmod(ost_script, 0666)
+    os.chmod(ost_script, 0o666)
     script=''.join(open(ost_script, 'r').readlines())
     script=script.replace(python_bin, '$BIN_DIR/python')
     open(ost_script, 'w').write(script)
-    os.chmod(ost_script, 0555)
+    os.chmod(ost_script, 0o555)
 
   if no_includes:
     os.system(REMOVE_HEADERS % outdir)
     os.system(REMOVE_CURRENT % outdir)  
-  print 'copying site-packages'
+  print('copying site-packages')
   for sp in SITE_PACKAGES:
     src=get_python_module_path(sp)
     if os.path.isdir(src):
       shutil.copytree(src, os.path.join(outdir, pymod_dir, sp))
     else:
       shutil.copy(src, os.path.join(outdir, pymod_dir, sp))
-  print 'updating link commands of python shared objects'
+  print('updating link commands of python shared objects')
   os.path.walk(os.path.join(outdir, 'lib'), 
                update_pymod_shared_objects, 
                os.path.join(outdir, 'lib'))
diff --git a/deployment/macos/pack.py b/deployment/macos/pack.py
index beb7d7d74..16b516577 100644
--- a/deployment/macos/pack.py
+++ b/deployment/macos/pack.py
@@ -42,7 +42,7 @@ def collect_deps(stage_dir, components, binaries, libexec_binaries,
     lib_name=os.path.abspath(os.path.join(stage_dir, 'lib', 
                                           _lib_name(component)))  
     if not os.path.exists(lib_name):
-      print 'WARNING:', lib_name, 'does not exist'
+      print('WARNING:', lib_name, 'does not exist')
     if lib_name not in pool:
       _deps_for_lib(lib_name, pool)
       pool.add(lib_name)    
@@ -50,7 +50,7 @@ def collect_deps(stage_dir, components, binaries, libexec_binaries,
     bin_name=os.path.abspath(os.path.join(stage_dir, 'bin', 
                                           bin))  
     if not os.path.exists(bin_name):
-      print 'WARNING:', bin_name, 'does not exist'
+      print('WARNING:', bin_name, 'does not exist')
       continue
     if bin_name not in pool:
       _deps_for_lib(bin_name, pool)
@@ -58,15 +58,15 @@ def collect_deps(stage_dir, components, binaries, libexec_binaries,
     bin_name=os.path.abspath(os.path.join(stage_dir, 'libexec', libexec_path,
                                           bin))
     if not os.path.exists(bin_name):
-      print 'WARNING:', bin_name, 'does not exist'
+      print('WARNING:', bin_name, 'does not exist')
       continue
     if bin_name not in pool:
       _deps_for_lib(bin_name, pool)
   for site_package in site_packages:
     full_path=get_python_module_path(site_package)
-    print full_path
+    print(full_path)
     if not os.path.exists(full_path):
-      print 'WARNING:', site_package, 'does not exists'
+      print('WARNING:', site_package, 'does not exists')
       continue
     if os.path.isdir(full_path):
       for so_file in glob.glob(os.path.join(full_path, '*.so')):
@@ -104,7 +104,7 @@ def copy_binaries(stage_dir, outdir, binary_names, scripts, bin_dir,
     else:
       bin_name=os.path.join(stage_dir, binary_name)
     if not os.path.exists(bin_name):
-      print 'WARNING:', binary_name, 'does not exist'
+      print('WARNING:', binary_name, 'does not exist')
       continue
     dst_name=os.path.join(outdir, bin_dir, os.path.basename(bin_name))
     shutil.copy(bin_name, dst_name)
@@ -128,14 +128,14 @@ def split_framework_components(abs_path):
         return lead, trail
 
 def change_id(id, lib):
-  os.chmod(lib, 0666)
+  os.chmod(lib, 0o666)
   os.system(CHANGE_ID_RPATH % (id,lib))
-  os.chmod(lib, 0444)
+  os.chmod(lib, 0o444)
 
 def update_load_commands(lib, exe, exe_path):
   direct_deps=set()
   _deps_for_lib(lib, direct_deps, recursive=False)
-  os.chmod(lib, 0666)
+  os.chmod(lib, 0o666)
   for direct_dep in direct_deps:
     if direct_dep.endswith('.dylib'):
       new_name=os.path.basename(direct_dep)
@@ -147,9 +147,9 @@ def update_load_commands(lib, exe, exe_path):
       new_name=os.path.join(framework_name, rel_path)
       os.system(CHANGE_LOAD_CMD_RPATH % (direct_dep, new_name, lib))
   if exe:
-    os.chmod(lib, 0555)
+    os.chmod(lib, 0o555)
   else:
-    os.chmod(lib, 0444)
+    os.chmod(lib, 0o444)
 
 def copy_deps(dependencies, outdir):
   exe_path=os.path.join(outdir, 'bin')
@@ -209,20 +209,20 @@ def merge_tree(src, dst):
             merge_tree(srcname, dstname)
         else:
             shutil.copy2(srcname, dstname)
-    except (IOError, os.error), why:
+    except (IOError, os.error) as why:
         errors.append((srcname, dstname, str(why)))
-    except shutil.Error, err:
+    except shutil.Error as err:
         errors.extend(err.args[0])
   try:
       shutil.copystat(src, dst)
-  except OSError, why:
+  except OSError as why:
       if WindowsError is not None and isinstance(why, WindowsError):
           # Copying file access times may fail on Windows
           pass
       else:
           errors.extend((src, dst, str(why)))
   if errors:
-      raise shutil.Error, errors
+      raise shutil.Error(errors)
 
 def get_site_package_dir():
   """
@@ -269,7 +269,7 @@ class Package(object):
     self.site_packages=[]
     self.site_packages_dir=''
   def status(self, message):
-    print '%s: %s' % (self.name, message)
+    print('%s: %s' % (self.name, message))
 
   def _prepare_output_dir(self, output_dir):
     """
@@ -287,7 +287,7 @@ class Package(object):
     if self.libexec_dir:
       out_exec_dir=os.path.join(output_dir, 'libexec', self.libexec_dir)
       if not os.path.exists(out_exec_dir):
-        print 'making...', out_exec_dir
+        print('making...', out_exec_dir)
         os.makedirs(out_exec_dir)
   def _copy_site_packages(self, output_dir):
     for sp in SITE_PACKAGES:
@@ -296,7 +296,7 @@ class Package(object):
         merge_tree(src, os.path.joini(output_dir, self.pymod_dir, sp))
       else:
         shutil.copy(src, os.path.join(output_dir, self.pymod_dir, sp))
-    print 'updating link commands of python shared objects'
+    print('updating link commands of python shared objects')
     os.path.walk(os.path.join(output_dir, 'lib'), 
                  update_pymod_shared_objects, 
                  os.path.join(output_dir, 'lib'))
diff --git a/doc/conf/conf.py b/doc/conf/conf.py
index 7a7fa741f..8da3b5569 100644
--- a/doc/conf/conf.py
+++ b/doc/conf/conf.py
@@ -48,8 +48,8 @@ source_suffix = '.rst'
 master_doc = 'index'
 
 # General information about the project.
-project = u'OpenStructure'
-copyright = u'2019, OpenStructure authors'
+project = 'OpenStructure'
+copyright = '2019, OpenStructure authors'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -189,8 +189,8 @@ htmlhelp_basename = 'openstructure-doc'
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title, author, documentclass [howto/manual]).
 latex_documents = [
-  ('index', 'OpenStructure.tex', u'OpenStructure Documentation',
-   u'OpenStructure authors', 'manual'),
+  ('index', 'OpenStructure.tex', 'OpenStructure Documentation',
+   'OpenStructure authors', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
diff --git a/doc/make.py b/doc/make.py
index 57da9479e..1b9f43a1d 100644
--- a/doc/make.py
+++ b/doc/make.py
@@ -48,7 +48,7 @@ def _CreateAndCopy(in_name, outdir):
   if not os.path.exists(out_dir):
     _MkDir(out_dir)
   if _RequireCopy(in_name, out_name):
-    print 'cp %s %s' % (in_name, out_name)
+    print('cp %s %s' % (in_name, out_name))
     os.system('cp %s %s' % (in_name, out_name))
 
 def _MkDir(dirname):
@@ -99,7 +99,7 @@ sphinx_bin=settings.Locate(['sphinx-build', 'sphinx-build-2.6','sphinx-build-2.7
 if opts.html:
   cmd='%s %s -b html -c %s %s %s' % (sphinx_bin, opt_str, 
                                      'doc/conf', 'doc/source', 'doc/build/html')
-  print cmd
+  print(cmd)
   _CheckCall(cmd, shell=True)
 
 if opts.doctest:
diff --git a/doc/to_sphinx.py b/doc/to_sphinx.py
index 09809d235..eab09effc 100644
--- a/doc/to_sphinx.py
+++ b/doc/to_sphinx.py
@@ -6,9 +6,9 @@ class_name=parts[-1]
 __import__(module, globals(), locals())
 mod=sys.modules[module]
 the_class=getattr(mod, class_name)
-print '.. currentmodule:: %s' % module
-print ''
-print '.. class:: %s' % class_name
+print('.. currentmodule:: %s' % module)
+print('')
+print('.. class:: %s' % class_name)
 
 def _arg_type(arg_type):
   if arg_type not in ('str', 'int', 'bool', 'float', 'None'):
@@ -63,10 +63,10 @@ def parse_signature(signature):
     return Method(method_match.groupdict()['name'], 
                   _arg_type(method_match.groupdict()['rtype']), 
                   args[1:], opt_args)
-  print signature, 'not matched'
+  print(signature, 'not matched')
 
 if '--no-derived' in sys.argv:
-  members=the_class.__dict__.keys()
+  members=list(the_class.__dict__.keys())
 else:
   members=dir(the_class)
 for m in members:
@@ -75,4 +75,4 @@ for m in members:
   member_doc=getattr(the_class, m).__doc__
   if member_doc:
     method=parse_signature(member_doc.split('\n')[1])
-    print method.to_sphinx()
\ No newline at end of file
+    print(method.to_sphinx())
\ No newline at end of file
diff --git a/docker/test_docker.py b/docker/test_docker.py
index e191217e6..38fae4a60 100644
--- a/docker/test_docker.py
+++ b/docker/test_docker.py
@@ -16,4 +16,4 @@ except qsscoring.QSscoreError as ex:
     ost.LogError('QSscore failed:', str(ex))
     qs_score = 0
 
-print "OST is working!"
+print("OST is working!")
diff --git a/examples/demos/charmm_trj_blur.py b/examples/demos/charmm_trj_blur.py
index f65d0dbcc..b6f848ffb 100644
--- a/examples/demos/charmm_trj_blur.py
+++ b/examples/demos/charmm_trj_blur.py
@@ -41,5 +41,5 @@ scene.AutoAutoslab(True)
 
 # create an animation timer and start it
 anim=Anim(cg,go)
-print 'Demo 6: Import of a CHARMM trajectory. Type anim.stop() to halt animation, anim.start(100) to start it again with stepsize 100!Starting animation now....'
+print('Demo 6: Import of a CHARMM trajectory. Type anim.stop() to halt animation, anim.start(100) to start it again with stepsize 100!Starting animation now....')
 anim.start(50)
diff --git a/examples/demos/correlate.py b/examples/demos/correlate.py
index 55bb4d068..1b65722d6 100644
--- a/examples/demos/correlate.py
+++ b/examples/demos/correlate.py
@@ -42,7 +42,7 @@ scene.CenterOn(map_go)
 candidates=io.LoadPDB('data/loop-candidates.pdb', 
                       load_multi=True)
 
-print len(candidates)
+print(len(candidates))
 #-------------------------------------------------------------------------------
 def Correlate(candidates):
   # this function converts the candidate loop into a density and calculates the 
diff --git a/examples/demos/gfx_mapslab.py b/examples/demos/gfx_mapslab.py
index 8cc8ca334..d7ca61c30 100644
--- a/examples/demos/gfx_mapslab.py
+++ b/examples/demos/gfx_mapslab.py
@@ -12,7 +12,7 @@ for p in img.ExtentIterator(mh.GetExtent()):
   mh.SetReal(p,val)
   vmin=min(vmin,val)
   vmax=max(vmax,val)
-print vmin, vmax
+print(vmin, vmax)
 for p in img.ExtentIterator(mh.GetExtent()):
   mh.SetReal(p,(mh.GetReal(p)-vmin)/(vmax-vmin))
 
@@ -55,4 +55,4 @@ scene.Add(go2)
 
 go2.ColorBy('HEAT_MAP',0.2,0.8)
 
-print 'Demo 4: Projecting the density of a map onto a plane...' 
+print('Demo 4: Projecting the density of a map onto a plane...') 
diff --git a/examples/demos/load_and_display.py b/examples/demos/load_and_display.py
index 6ce59673c..b4e140520 100644
--- a/examples/demos/load_and_display.py
+++ b/examples/demos/load_and_display.py
@@ -18,4 +18,4 @@ scene.Add(go)
 # center the scene on the geometric center of the SDH
 scene.CenterOn(go)
 
-print 'Demo 1: loading and displaying a pdb file'
+print('Demo 1: loading and displaying a pdb file')
diff --git a/examples/demos/the_hammer.py b/examples/demos/the_hammer.py
index 1c308b2d7..114e2baa2 100644
--- a/examples/demos/the_hammer.py
+++ b/examples/demos/the_hammer.py
@@ -82,4 +82,4 @@ grad.SetColorAt(0.5, gfx.Color(1.0, 0.0, 1.0))
 grad.SetColorAt(1.0, gfx.Color(1.0, 0.0, 0.0))
 b_go.ColorBy('clash', gfx.Color(0,1,0), gfx.Color(1,0,0), 0.0, 10.0, mol.Prop.Level.ATOM)
 scene.CenterOn(b_go)
-print 'Demo 7: Illustrating a clash score'
+print('Demo 7: Illustrating a clash score')
diff --git a/modules/base/pymod/table.py b/modules/base/pymod/table.py
index 994f01000..4a4a55934 100644
--- a/modules/base/pymod/table.py
+++ b/modules/base/pymod/table.py
@@ -4,7 +4,7 @@ import math
 from ost import stutil
 import itertools
 import operator
-import cPickle
+import pickle
 import weakref
 from ost import LogError, LogWarning, LogInfo, LogVerbose
 
@@ -228,7 +228,7 @@ class Table(object):
     self.rows=[]    
     if len(kwargs)>=0:
       if not col_names:
-        self.col_names=[v for v in kwargs.keys()]
+        self.col_names=[v for v in list(kwargs.keys())]
       if not self.col_types:
         self.col_types=['string' for u in range(len(self.col_names))]
       if len(kwargs)>0:
@@ -249,8 +249,8 @@ class Table(object):
       return None
     
     short2long = {'s' : 'string', 'i': 'int', 'b' : 'bool', 'f' : 'float'}
-    allowed_short = short2long.keys()
-    allowed_long = short2long.values()
+    allowed_short = list(short2long.keys())
+    allowed_long = list(short2long.values())
     
     type_list = []
     
@@ -359,7 +359,7 @@ class Table(object):
     if ty=='string':
       return str(value)
     if ty=='bool':
-      if isinstance(value, str) or isinstance(value, unicode):
+      if isinstance(value, str) or isinstance(value, str):
         if value.upper() in ('FALSE', 'NO',):
           return False
         return True
@@ -525,11 +525,11 @@ Statistics for column %(col)s
              data items is different for different columns.
     '''
     # get column indices
-    idxs = [self.GetColIndex(k) for k in d.keys()]
+    idxs = [self.GetColIndex(k) for k in list(d.keys())]
     
     # convert scalar values to list
     old_len = None
-    for k,v in d.iteritems():
+    for k,v in d.items():
       if IsScalar(v):
         v = [v]
         d[k] = v
@@ -540,7 +540,7 @@ Statistics for column %(col)s
                          "for all columns in %s"%str(d))
     
     # convert column based dict to row based dict and create row and add data
-    for i,data in enumerate(zip(*d.values())):
+    for i,data in enumerate(zip(*list(d.values()))):
       new_row = [None for a in range(len(self.col_names))]
       for idx,v in zip(idxs,data):
         new_row[idx] = self._Coerce(v, self.col_types[idx])
@@ -815,7 +815,7 @@ Statistics for column %(col)s
         if not func(row):
           matches=False
           break
-      for key, val in kwargs.iteritems():
+      for key, val in kwargs.items():
         if row[self.GetColIndex(key)]!=val:
           matches=False
           break
@@ -947,13 +947,13 @@ Statistics for column %(col)s
       stream=open(stream_or_filename, 'rb')
     else:
       stream=stream_or_filename
-    return cPickle.load(stream)
+    return pickle.load(stream)
 
   @staticmethod
   def _GuessFormat(filename):
     try:
       filename = filename.name
-    except AttributeError, e:
+    except AttributeError as e:
       pass
     if filename.endswith('.csv'):
       return 'csv'
@@ -1085,7 +1085,7 @@ Statistics for column %(col)s
       for col1, col2 in zip(tab['col1'], tab['col2']):
         print col1, col2
     """
-    return zip(*[self[arg] for arg in args])
+    return list(zip(*[self[arg] for arg in args]))
 
   def Plot(self, x, y=None, z=None, style='.', x_title=None, y_title=None,
            z_title=None, x_range=None, y_range=None, z_range=None,
@@ -1561,7 +1561,7 @@ Statistics for column %(col)s
                          'specify the parameter rows with a list of row indices '\
                          '(max 7)')
       else:
-        rows=range(len(self.rows))
+        rows=list(range(len(self.rows)))
     else:
       if not isinstance(rows,list):
         rows=[rows]
@@ -2197,7 +2197,7 @@ Statistics for column %(col)s
   def _SavePickle(self, stream):
     if not hasattr(stream, 'write'):
       stream=open(stream, 'wb')
-    cPickle.dump(self, stream, cPickle.HIGHEST_PROTOCOL)
+    pickle.dump(self, stream, pickle.HIGHEST_PROTOCOL)
 
   def _SaveHTML(self, stream_or_filename):
     def _escape(s):
@@ -2459,7 +2459,7 @@ Statistics for column %(col)s
       a = self.GetNumpyMatrix(*args)
       
       if len(kwargs)!=0:
-        if kwargs.has_key('weights'):
+        if 'weights' in kwargs:
           w = self.GetNumpyMatrix(kwargs['weights'])
           b = np.multiply(b,w)
           a = np.multiply(a,w)
@@ -3048,7 +3048,7 @@ Statistics for column %(col)s
     num_rows = len(tab.rows)
     for i in range(0,num_rows):
       row = tab.rows[i]
-      data = dict(zip(tab.col_names,row))
+      data = dict(list(zip(tab.col_names,row)))
       self.AddRow(data, overwrite)
     
 
@@ -3133,7 +3133,7 @@ def Merge(table1, table2, by, only_matching=False):
       raise ValueError('duplicate key "%s" in second table' % (str(key)))
     common2[key]=row
   new_tab=Table(table1.col_names+col_names, table1.col_types+col_types)
-  for k, v in common1.iteritems():
+  for k, v in common1.items():
     row=v+[None for i in range(len(table2.col_names)-len(common2_indices))]
     matched=False
     if k in common2:
@@ -3146,7 +3146,7 @@ def Merge(table1, table2, by, only_matching=False):
     new_tab.AddRow(row)
   if only_matching:
     return new_tab
-  for k, v in common2.iteritems():
+  for k, v in common2.items():
     if not k in common1:
       v2=[v[i] for i in new_index]
       row=[None for i in range(len(table1.col_names))]+v2
diff --git a/modules/base/pymod/testutils.py b/modules/base/pymod/testutils.py
index 5dd8762e2..4b701fe08 100644
--- a/modules/base/pymod/testutils.py
+++ b/modules/base/pymod/testutils.py
@@ -41,7 +41,7 @@ def RunTests():
       import __main__
       from ost import xmlrunner
       for name, obj in inspect.getmembers(__main__):
-        if (isinstance(obj, (type, types.ClassType)) and
+        if (isinstance(obj, type) and
                             issubclass(obj, unittest.TestCase)):
           suite = unittest.TestLoader().loadTestsFromTestCase(obj)
           stream = open('PYTEST-%s.xml'%name, 'w')
@@ -50,8 +50,8 @@ def RunTests():
 
     else:
       unittest.main()
-  except Exception, e:
-    print e
+  except Exception as e:
+    print(e)
 
 
 def SetDefaultCompoundLib():
@@ -92,7 +92,7 @@ def SetDefaultCompoundLib():
     # try to get the shared data path?
     try:
       shared_data_path = GetSharedDataPath()
-    except Exception, e:
+    except Exception as e:
       SetPrefixPath(os.path.abspath(os.path.join(conop.__path__[0], os.pardir,
                                                  os.pardir, os.pardir,
                                                  os.pardir, os.pardir)))
diff --git a/modules/base/pymod/wrap_base.cc b/modules/base/pymod/wrap_base.cc
index b0cfa8465..2b43f842e 100644
--- a/modules/base/pymod/wrap_base.cc
+++ b/modules/base/pymod/wrap_base.cc
@@ -58,15 +58,21 @@ struct stringref_from_python_string
   
   static void* convertible(PyObject* obj_ptr)
   {
-    if (!PyString_Check(obj_ptr)) return 0;
+    if (!PyUnicode_Check(obj_ptr)) return 0;
     return obj_ptr;
   }
   
   static void construct(PyObject* obj_ptr,
                  boost::python::converter::rvalue_from_python_stage1_data* data)
   {
-    const char* value = PyString_AsString(obj_ptr);
-    if (value == 0) boost::python::throw_error_already_set();
+    PyObject* temp_bytes = PyUnicode_AsEncodedString(obj_ptr, "UTF-8", "strict");
+    char* value = NULL;
+    if (temp_bytes != NULL) {
+      value = PyBytes_AS_STRING(temp_bytes);
+      value = strdup(value);
+      Py_DECREF(temp_bytes);
+    }
+    if (value == NULL) boost::python::throw_error_already_set();
     void* storage = (
          (boost::python::converter::rvalue_from_python_storage<ost::StringRef>*)
                      data)->storage.bytes;
diff --git a/modules/base/pymod/xmlrunner.py b/modules/base/pymod/xmlrunner.py
index e2bcae76d..0ea333262 100644
--- a/modules/base/pymod/xmlrunner.py
+++ b/modules/base/pymod/xmlrunner.py
@@ -16,7 +16,7 @@ import unittest
 from xml.sax.saxutils import escape
 
 try:
-    from StringIO import StringIO
+    from io import StringIO
 except ImportError:
     from io import StringIO
 
@@ -297,7 +297,7 @@ class XMLTestRunnerTest(unittest.TestCase):
         """
         class TestTest(unittest.TestCase):
             def test_foo(self):
-                self.assert_(False)
+                self.assertTrue(False)
         self._try_test_run(TestTest, """<testsuite errors="0" failures="1" name="unittest.TestSuite" tests="1" time="0.000">
   <testcase classname="__main__.TestTest" name="test_foo" time="0.000">
     <failure type="exceptions.AssertionError">Foobar</failure>
diff --git a/modules/base/tests/test_table.py b/modules/base/tests/test_table.py
index 347183262..ec2e5fcf0 100644
--- a/modules/base/tests/test_table.py
+++ b/modules/base/tests/test_table.py
@@ -19,19 +19,19 @@ try:
   import numpy as np
 except ImportError:
   HAS_NUMPY=False
-  print "Could not find numpy: ignoring some table class unit tests"
+  print("Could not find numpy: ignoring some table class unit tests")
 
 try:
   import scipy.stats.mstats
 except ImportError:
   HAS_SCIPY_STATS=False
-  print "Could not find scipy.stats.mstats: ignoring some table class unit tests"
+  print("Could not find scipy.stats.mstats: ignoring some table class unit tests")
   
 try:
   import scipy.ndimage
 except ImportError:
   HAS_SCIPY_NDIMG=False
-  print "Could not find scipy.ndimage: ignoring some table class unit tests"
+  print("Could not find scipy.ndimage: ignoring some table class unit tests")
   
 
 try:
@@ -39,14 +39,14 @@ try:
   matplotlib.use('Agg')
 except ImportError:
   HAS_MPL=False
-  print "Could not find matplotlib: ignoring some table class unit tests"
+  print("Could not find matplotlib: ignoring some table class unit tests")
   
 try:
   from PIL import Image
   from PIL import ImageChops
 except ImportError:
   HAS_PIL=False
-  print "Could not find python imagine library: ignoring some table class unit tests"
+  print("Could not find python imagine library: ignoring some table class unit tests")
 
 # setting up an OST LogSink to capture error messages
 class _FetchLog(ost.LogSink):
@@ -57,7 +57,7 @@ class _FetchLog(ost.LogSink):
   def LogMessage(self, message, severity):
     levels=['ERROR', 'WARNING', 'INFO', 'VERBOSE', 'DEBUG', 'TRACE']
     level=levels[severity]
-    if not level in self.messages.keys():
+    if not level in list(self.messages.keys()):
       self.messages[level] = list()
     self.messages[level].append(message.strip())
   
@@ -128,7 +128,7 @@ class TestTable(unittest.TestCase):
     dictionary containing a list of values for each column.
     '''
     self.CompareColCount(t, len(data_dict))
-    for k, v in data_dict.iteritems():
+    for k, v in data_dict.items():
       self.CompareDataForCol(t, k, v)
       
   def CompareDataForCol(self, t, col_name, ref_data):
@@ -186,8 +186,8 @@ class TestTable(unittest.TestCase):
 
   def testAllowsToSearchColNames(self):
     tab = self.CreateTestTable()
-    self.assertEquals(tab.SearchColNames('d$'), ['second', 'third'])
-    self.assertEquals(tab.SearchColNames('(first|third)'), ['first','third'])
+    self.assertEqual(tab.SearchColNames('d$'), ['second', 'third'])
+    self.assertEqual(tab.SearchColNames('(first|third)'), ['first','third'])
 
   def testProvidesDirectAccessToColumns(self):
     tab = Table(['x', 'two'], 'ii')
@@ -218,10 +218,10 @@ class TestTable(unittest.TestCase):
     self.assertEqual(z[1][0], 'x')
     self.assertEqual(z[1][1], 'x')
     z=tab.Zip('col1', 'col4')
-    self.assertEquals(type(z[0][0]),str)
-    self.assertEquals(type(z[1][0]),str)
-    self.assertEquals(type(z[0][1]),int)
-    self.assertEquals(type(z[1][1]),int)
+    self.assertEqual(type(z[0][0]),str)
+    self.assertEqual(type(z[1][0]),str)
+    self.assertEqual(type(z[0][1]),int)
+    self.assertEqual(type(z[1][1]),int)
     self.assertRaises(ValueError, tab.Zip, 'col5', 'col3')
   def testPercentiles(self):
     tab = Table(['nums'], 'i')
@@ -340,7 +340,7 @@ class TestTable(unittest.TestCase):
       4.000
 
     '''
-    tab = Table(['x'], 'f', x=range(5))
+    tab = Table(['x'], 'f', x=list(range(5)))
     self.CompareColCount(tab, 1)
     self.CompareRowCount(tab, 5)
     self.CompareColNames(tab, ['x'])
@@ -377,7 +377,7 @@ class TestTable(unittest.TestCase):
 
     '''
     
-    tab = Table(['foo', 'bar'], 'si', bar=range(10,14), foo=['i','love','unit','tests'])
+    tab = Table(['foo', 'bar'], 'si', bar=list(range(10,14)), foo=['i','love','unit','tests'])
     self.CompareColCount(tab, 2)
     self.CompareRowCount(tab, 4)
     self.CompareColNames(tab, ['foo','bar'])
@@ -390,7 +390,7 @@ class TestTable(unittest.TestCase):
     '''
     
     self.assertRaises(ValueError, Table, ['foo', 'bar'], 'si',
-                      bar=range(10,14), foo=['i','love','tests'])
+                      bar=list(range(10,14)), foo=['i','love','tests'])
     
     
   def testTableInitMultiColMultiValueAndNoneNonEmpty(self):
@@ -672,40 +672,40 @@ class TestTable(unittest.TestCase):
     
   def testParseColumnTypes(self):
     types = Table._ParseColTypes(['i','f','s','b'])
-    self.assertEquals(types, ['int','float','string','bool'])
+    self.assertEqual(types, ['int','float','string','bool'])
     
     types = Table._ParseColTypes(['int','float','string','bool'])
-    self.assertEquals(types, ['int','float','string','bool'])
+    self.assertEqual(types, ['int','float','string','bool'])
     
     types = Table._ParseColTypes(['i','float','s','bool'])
-    self.assertEquals(types, ['int','float','string','bool'])
+    self.assertEqual(types, ['int','float','string','bool'])
 
     types = Table._ParseColTypes(['i','fLOAT','S','bool'])
-    self.assertEquals(types, ['int','float','string','bool'])
+    self.assertEqual(types, ['int','float','string','bool'])
     
     types = Table._ParseColTypes('ifsb')
-    self.assertEquals(types, ['int','float','string','bool'])
+    self.assertEqual(types, ['int','float','string','bool'])
     
     types = Table._ParseColTypes('int,float,string,bool')
-    self.assertEquals(types, ['int','float','string','bool'])
+    self.assertEqual(types, ['int','float','string','bool'])
     
     types = Table._ParseColTypes('int,f,s,bool')
-    self.assertEquals(types, ['int','float','string','bool'])
+    self.assertEqual(types, ['int','float','string','bool'])
     
     types = Table._ParseColTypes('INT,F,s,bOOL')
-    self.assertEquals(types, ['int','float','string','bool'])
+    self.assertEqual(types, ['int','float','string','bool'])
 
     types = Table._ParseColTypes('boOl')
-    self.assertEquals(types, ['bool'])
+    self.assertEqual(types, ['bool'])
     
     types = Table._ParseColTypes('S')
-    self.assertEquals(types, ['string'])
+    self.assertEqual(types, ['string'])
     
     types = Table._ParseColTypes(['i'])
-    self.assertEquals(types, ['int'])
+    self.assertEqual(types, ['int'])
     
     types = Table._ParseColTypes(['FLOAT'])
-    self.assertEquals(types, ['float'])
+    self.assertEqual(types, ['float'])
 
     self.assertRaises(ValueError, Table._ParseColTypes, 'bfstring')
     self.assertRaises(ValueError, Table._ParseColTypes, ['b,f,string'])
@@ -765,23 +765,23 @@ class TestTable(unittest.TestCase):
     tab = Table()
     
     # None values
-    self.assertEquals(tab._Coerce('NA', 'x'), None)
-    self.assertEquals(tab._Coerce(None, 'x'), None)
+    self.assertEqual(tab._Coerce('NA', 'x'), None)
+    self.assertEqual(tab._Coerce(None, 'x'), None)
     
     # int type
     self.assertTrue(isinstance(tab._Coerce(2 ,'int'), int))
-    self.assertEquals(tab._Coerce(2 ,'int'), 2)
+    self.assertEqual(tab._Coerce(2 ,'int'), 2)
     self.assertTrue(isinstance(tab._Coerce(2.2 ,'int'), int))
-    self.assertEquals(tab._Coerce(2.2 ,'int'), 2)
-    self.assertEquals(tab._Coerce(True ,'int'), 1)
-    self.assertEquals(tab._Coerce(False ,'int'), 0)
+    self.assertEqual(tab._Coerce(2.2 ,'int'), 2)
+    self.assertEqual(tab._Coerce(True ,'int'), 1)
+    self.assertEqual(tab._Coerce(False ,'int'), 0)
     self.assertRaises(ValueError, tab._Coerce, "foo" , 'int')
     
     # float type
     self.assertTrue(isinstance(tab._Coerce(2 ,'float'), float))
-    self.assertEquals(tab._Coerce(2 ,'float'), 2.000)
+    self.assertEqual(tab._Coerce(2 ,'float'), 2.000)
     self.assertTrue(isinstance(tab._Coerce(3.141 ,'float'), float))
-    self.assertEquals(tab._Coerce(3.141 ,'float'), 3.141)
+    self.assertEqual(tab._Coerce(3.141 ,'float'), 3.141)
     self.assertRaises(ValueError, tab._Coerce, "foo" , 'float')
     
     # string type
@@ -793,13 +793,13 @@ class TestTable(unittest.TestCase):
     self.assertTrue(isinstance(tab._Coerce(False ,'string'), str))
     
     # bool type
-    self.assertEquals(tab._Coerce(True ,'bool'), True)
-    self.assertEquals(tab._Coerce(False ,'bool'), False)
-    self.assertEquals(tab._Coerce('falSE' ,'bool'), False)
-    self.assertEquals(tab._Coerce('no' ,'bool'), False)
-    self.assertEquals(tab._Coerce('not false and not no','bool'), True)
-    self.assertEquals(tab._Coerce(0, 'bool'), False)
-    self.assertEquals(tab._Coerce(1, 'bool'), True)
+    self.assertEqual(tab._Coerce(True ,'bool'), True)
+    self.assertEqual(tab._Coerce(False ,'bool'), False)
+    self.assertEqual(tab._Coerce('falSE' ,'bool'), False)
+    self.assertEqual(tab._Coerce('no' ,'bool'), False)
+    self.assertEqual(tab._Coerce('not false and not no','bool'), True)
+    self.assertEqual(tab._Coerce(0, 'bool'), False)
+    self.assertEqual(tab._Coerce(1, 'bool'), True)
     
     # unknown type
     self.assertRaises(ValueError, tab._Coerce, 'bla', 'abc')
@@ -880,7 +880,7 @@ class TestTable(unittest.TestCase):
     
   def testLoadOSTDifficultHeaders(self):
     tab = Table.Load(os.path.join('testfiles','ost-table-difficult-headers.tab'))
-    self.assertEquals(tab.col_types, ['float','float','float','float','float'])
+    self.assertEqual(tab.col_types, ['float','float','float','float','float'])
 
   def testSaveLoadTableOST(self):
     tab = self.CreateTestTable()
@@ -920,15 +920,15 @@ class TestTable(unittest.TestCase):
     tab_loaded_fname = Table.Load('saveloadtable_withspaces_filename_out.tab')
     self.CompareDataFromDict(tab_loaded_fname, {'first': ['x','foo',None,'hello spaces'], 'second': [3,None,9,10], 'third': [None,2.2,3.3,10.1]})
   def testSaveTableHTML(self):
-    import StringIO
+    import io
     tab = self.CreateTestTable()
-    stream = StringIO.StringIO()
+    stream = io.StringIO()
     tab.Save(stream, format='html')
     self.assertEqual(stream.getvalue(), '<table><tr><th>first</th><th>second</th><th>third</th></tr><tr><td>x</td><td>3</td><td></td></tr><tr><td>foo</td><td></td><td>2.200</td></tr><tr><td></td><td>9</td><td>3.300</td></tr></table>')
   def testSaveTableContext(self):
-    import StringIO
+    import io
     tab = self.CreateTestTable()
-    stream = StringIO.StringIO()
+    stream = io.StringIO()
     tab.Save(stream, format='context')
     self.assertEqual(stream.getvalue(), 
                      '\\starttable[l|r|i3r|]\n\\HL\n\\NC \\bf first\\NC \\bf second\\NC \\bf third \\AR\\HL\n\\NC x\\NC 3\\NC --- \\AR\n\\NC foo\NC ---\NC 2.200 \\AR\n\\NC ---\\NC 9\\NC 3.300 \\AR\n\\HL\n\\stoptable')
@@ -1042,44 +1042,44 @@ class TestTable(unittest.TestCase):
     tab = self.CreateTestTable()
     tab.AddCol('fourth','bool',[True,True,False])
 
-    self.assertEquals(tab.Min('first'),'foo')
-    self.assertEquals(tab.Min('second'),3)
-    self.assertAlmostEquals(tab.Min('third'),2.2)
-    self.assertEquals(tab.Min('fourth'),False)
+    self.assertEqual(tab.Min('first'),'foo')
+    self.assertEqual(tab.Min('second'),3)
+    self.assertAlmostEqual(tab.Min('third'),2.2)
+    self.assertEqual(tab.Min('fourth'),False)
     self.assertRaises(ValueError,tab.Min,'fifth')
     
-    self.assertEquals(tab.MinIdx('first'),1)
-    self.assertEquals(tab.MinIdx('second'),0)
-    self.assertAlmostEquals(tab.MinIdx('third'),1)
-    self.assertEquals(tab.MinIdx('fourth'),2)
+    self.assertEqual(tab.MinIdx('first'),1)
+    self.assertEqual(tab.MinIdx('second'),0)
+    self.assertAlmostEqual(tab.MinIdx('third'),1)
+    self.assertEqual(tab.MinIdx('fourth'),2)
     self.assertRaises(ValueError,tab.MinIdx,'fifth')
     
-    self.assertEquals(tab.MinRow('first'),['foo', None, 2.20, True])
-    self.assertEquals(tab.MinRow('second'),['x', 3, None, True])
-    self.assertEquals(tab.MinRow('third'),['foo', None, 2.20, True])
-    self.assertEquals(tab.MinRow('fourth'),[None, 9, 3.3, False])
+    self.assertEqual(tab.MinRow('first'),['foo', None, 2.20, True])
+    self.assertEqual(tab.MinRow('second'),['x', 3, None, True])
+    self.assertEqual(tab.MinRow('third'),['foo', None, 2.20, True])
+    self.assertEqual(tab.MinRow('fourth'),[None, 9, 3.3, False])
     self.assertRaises(ValueError,tab.MinRow,'fifth')
     
   def testMaxTable(self):
     tab = self.CreateTestTable()
     tab.AddCol('fourth','bool',[False,True,True])
     
-    self.assertEquals(tab.Max('first'),'x')
-    self.assertEquals(tab.Max('second'),9)
-    self.assertAlmostEquals(tab.Max('third'),3.3)
-    self.assertEquals(tab.Max('fourth'),True)
+    self.assertEqual(tab.Max('first'),'x')
+    self.assertEqual(tab.Max('second'),9)
+    self.assertAlmostEqual(tab.Max('third'),3.3)
+    self.assertEqual(tab.Max('fourth'),True)
     self.assertRaises(ValueError,tab.Max,'fifth')
     
-    self.assertEquals(tab.MaxIdx('first'),0)
-    self.assertEquals(tab.MaxIdx('second'),2)
-    self.assertAlmostEquals(tab.MaxIdx('third'),2)
-    self.assertEquals(tab.MaxIdx('fourth'),1)
+    self.assertEqual(tab.MaxIdx('first'),0)
+    self.assertEqual(tab.MaxIdx('second'),2)
+    self.assertAlmostEqual(tab.MaxIdx('third'),2)
+    self.assertEqual(tab.MaxIdx('fourth'),1)
     self.assertRaises(ValueError,tab.MaxIdx,'fifth')
     
-    self.assertEquals(tab.MaxRow('first'),['x', 3, None, False])
-    self.assertEquals(tab.MaxRow('second'),[None, 9, 3.3, True])
-    self.assertEquals(tab.MaxRow('third'),[None, 9, 3.3, True])
-    self.assertEquals(tab.MaxRow('fourth'),['foo', None, 2.2, True])
+    self.assertEqual(tab.MaxRow('first'),['x', 3, None, False])
+    self.assertEqual(tab.MaxRow('second'),[None, 9, 3.3, True])
+    self.assertEqual(tab.MaxRow('third'),[None, 9, 3.3, True])
+    self.assertEqual(tab.MaxRow('fourth'),['foo', None, 2.2, True])
     self.assertRaises(ValueError,tab.MaxRow,'fifth')
     
   def testSumTable(self):
@@ -1088,9 +1088,9 @@ class TestTable(unittest.TestCase):
     tab.AddCol('fifth','string',['foo','bar',None])
     
     self.assertRaises(TypeError,tab.Sum,'first')
-    self.assertEquals(tab.Sum('second'),12)
-    self.assertAlmostEquals(tab.Sum('third'),5.5)
-    self.assertEquals(tab.Sum('fourth'),1)
+    self.assertEqual(tab.Sum('second'),12)
+    self.assertAlmostEqual(tab.Sum('third'),5.5)
+    self.assertEqual(tab.Sum('fourth'),1)
     self.assertRaises(TypeError,tab.Sum,'fifth')
     self.assertRaises(ValueError,tab.Sum,'sixth')
     
@@ -1100,9 +1100,9 @@ class TestTable(unittest.TestCase):
     tab.AddCol('fifth','string',['foo','bar',None])
     
     self.assertRaises(TypeError,tab.Median,'first')
-    self.assertEquals(tab.Median('second'),6.0)
-    self.assertAlmostEquals(tab.Median('third'),2.75)
-    self.assertEquals(tab.Median('fourth'),False)
+    self.assertEqual(tab.Median('second'),6.0)
+    self.assertAlmostEqual(tab.Median('third'),2.75)
+    self.assertEqual(tab.Median('fourth'),False)
     self.assertRaises(TypeError,tab.Median,'fifth')
     self.assertRaises(ValueError,tab.Median,'sixth')
     
@@ -1112,9 +1112,9 @@ class TestTable(unittest.TestCase):
     tab.AddCol('fifth','string',['foo','bar',None])
     
     self.assertRaises(TypeError,tab.Mean,'first')
-    self.assertAlmostEquals(tab.Mean('second'),6.0)
-    self.assertAlmostEquals(tab.Mean('third'),2.75)
-    self.assertAlmostEquals(tab.Mean('fourth'),0.33333333)
+    self.assertAlmostEqual(tab.Mean('second'),6.0)
+    self.assertAlmostEqual(tab.Mean('third'),2.75)
+    self.assertAlmostEqual(tab.Mean('fourth'),0.33333333)
     self.assertRaises(TypeError,tab.Mean,'fifth')
     self.assertRaises(ValueError,tab.Mean,'sixth')
     
@@ -1146,9 +1146,9 @@ class TestTable(unittest.TestCase):
     tab.AddCol('fifth','string',['foo','bar',None])
     
     self.assertRaises(TypeError,tab.StdDev,'first')
-    self.assertAlmostEquals(tab.StdDev('second'),3.0)
-    self.assertAlmostEquals(tab.StdDev('third'),0.55)
-    self.assertAlmostEquals(tab.StdDev('fourth'),0.47140452079)
+    self.assertAlmostEqual(tab.StdDev('second'),3.0)
+    self.assertAlmostEqual(tab.StdDev('third'),0.55)
+    self.assertAlmostEqual(tab.StdDev('fourth'),0.47140452079)
     self.assertRaises(TypeError,tab.StdDev,'fifth')
     self.assertRaises(ValueError,tab.StdDev,'sixth')
     
@@ -1156,14 +1156,14 @@ class TestTable(unittest.TestCase):
     tab = self.CreateTestTable()
     tab.AddCol('fourth','bool',[False,True,False])
     
-    self.assertEquals(tab.Count('first'),2)
-    self.assertEquals(tab.Count('second'),2)
-    self.assertEquals(tab.Count('third'),2)
-    self.assertEquals(tab.Count('fourth'),3)
-    self.assertEquals(tab.Count('first', ignore_nan=False),3)
-    self.assertEquals(tab.Count('second', ignore_nan=False),3)
-    self.assertEquals(tab.Count('third', ignore_nan=False),3)
-    self.assertEquals(tab.Count('fourth', ignore_nan=False),3)
+    self.assertEqual(tab.Count('first'),2)
+    self.assertEqual(tab.Count('second'),2)
+    self.assertEqual(tab.Count('third'),2)
+    self.assertEqual(tab.Count('fourth'),3)
+    self.assertEqual(tab.Count('first', ignore_nan=False),3)
+    self.assertEqual(tab.Count('second', ignore_nan=False),3)
+    self.assertEqual(tab.Count('third', ignore_nan=False),3)
+    self.assertEqual(tab.Count('fourth', ignore_nan=False),3)
     self.assertRaises(ValueError,tab.Count,'fifth')
     
   def testCalcEnrichment(self):
@@ -1180,15 +1180,15 @@ class TestTable(unittest.TestCase):
                                       class_dir='-')
     
     for x,y,refx,refy in zip(enrx,enry,enrx_ref,enry_ref):
-      self.assertAlmostEquals(x,refx)
-      self.assertAlmostEquals(y,refy)
+      self.assertAlmostEqual(x,refx)
+      self.assertAlmostEqual(y,refy)
     
     enrx,enry = tab.ComputeEnrichment(score_col='score', score_dir='-',
                                       class_col='classific')
     
     for x,y,refx,refy in zip(enrx,enry,enrx_ref,enry_ref):
-      self.assertAlmostEquals(x,refx)
-      self.assertAlmostEquals(y,refy)
+      self.assertAlmostEqual(x,refx)
+      self.assertAlmostEqual(y,refy)
     
     tab.AddCol('bad','string','col')
     
@@ -1275,7 +1275,7 @@ class TestTable(unittest.TestCase):
                                    class_col='rmsd', class_cutoff=2.0,
                                    class_dir='-')
     
-    self.assertAlmostEquals(auc, auc_ref)
+    self.assertAlmostEqual(auc, auc_ref)
 
   def testPlotROC(self):
     if not HAS_MPL or not HAS_PIL:
@@ -1297,7 +1297,7 @@ class TestTable(unittest.TestCase):
     pl = tab.PlotROC(score_col='score', score_dir='+',
                      class_col='classific',
                      save=os.path.join("testfiles","roc-out.png"))
-    self.assertEquals(pl, None)
+    self.assertEqual(pl, None)
 
   def testPlotLogROC(self):
     if not HAS_MPL or not HAS_PIL:
@@ -1319,7 +1319,7 @@ class TestTable(unittest.TestCase):
     pl = tab.PlotLogROC(score_col='score', score_dir='+',
                         class_col='classific',
                         save=os.path.join("testfiles","logroc-out.png"))
-    self.assertEquals(pl, None)
+    self.assertEqual(pl, None)
 
   def testPlotROCSameValues(self):
     if not HAS_MPL or not HAS_PIL:
@@ -1361,14 +1361,14 @@ class TestTable(unittest.TestCase):
                 classific=[True, True, False, True, True, True, False, False, True, False, True, False, True, False, False, False, True, False, True, False],
                 score=[0.9, 0.8, 0.7, 0.6, 0.55, 0.54, 0.53, 0.52, 0.51, 0.505, 0.4, 0.39, 0.38, 0.37, 0.36, 0.35, 0.34, 0.33, 0.30, 0.1])
     auc = tab.ComputeROCAUC(score_col='score', score_dir='+', class_col='classific')
-    self.assertAlmostEquals(auc, auc_ref)
+    self.assertAlmostEqual(auc, auc_ref)
 
     # no true positives
     tab = Table(['classific', 'score'], 'bf',
                 classific=[False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False],
                 score=[0.9, 0.8, 0.7, 0.6, 0.55, 0.54, 0.53, 0.52, 0.51, 0.505, 0.4, 0.39, 0.38, 0.37, 0.36, 0.35, 0.34, 0.33, 0.30, 0.1])
     auc = tab.ComputeROCAUC(score_col='score', score_dir='+', class_col='classific')
-    self.assertEquals(auc, None)
+    self.assertEqual(auc, None)
     
   def testLogROCAUCforPerfectCurve(self):
     if not HAS_NUMPY:
@@ -1380,11 +1380,11 @@ class TestTable(unittest.TestCase):
     
     # test logAUC
     auc = tab.ComputeLogROCAUC(score_col='score', score_dir='+', class_col='classific')
-    self.assertAlmostEquals(auc, auc_ref)
+    self.assertAlmostEqual(auc, auc_ref)
     
     # test linear AUC
     auc = tab.ComputeROCAUC(score_col='score', score_dir='+', class_col='classific')
-    self.assertAlmostEquals(auc, auc_ref)
+    self.assertAlmostEqual(auc, auc_ref)
       
   def testCalcLogROCAUCRandomCurve(self):
     if not HAS_NUMPY:
@@ -1396,12 +1396,12 @@ class TestTable(unittest.TestCase):
     # test logAUC
     auc_ref = 0.1440197405305
     auc = tab.ComputeLogROCAUC(score_col='score', score_dir='+', class_col='classific')
-    self.assertAlmostEquals(auc, auc_ref)
+    self.assertAlmostEqual(auc, auc_ref)
     
     # test linear AUC    
     auc_ref = 0.5
     auc = tab.ComputeROCAUC(score_col='score', score_dir='+', class_col='classific')
-    self.assertAlmostEquals(auc, auc_ref)
+    self.assertAlmostEqual(auc, auc_ref)
 
 
   def testCalcROCAUCWithCutoff(self):
@@ -1411,18 +1411,18 @@ class TestTable(unittest.TestCase):
                 classific=[0.9, 0.8, 0.7, 0.6, 0.55, 0.54, 0.53, 0.52, 0.51, 0.505, 0.4, 0.39, 0.38, 0.37, 0.36, 0.35, 0.34, 0.33, 0.30, 0.1],
                 score=[0.9, 0.8, 0.7, 0.6, 0.55, 0.54, 0.53, 0.52, 0.51, 0.505, 0.4, 0.39, 0.38, 0.37, 0.36, 0.35, 0.34, 0.33, 0.30, 0.1])
     auc = tab.ComputeROCAUC(score_col='score', class_col='classific', class_cutoff=0.5)
-    self.assertEquals(auc, 1.0)
+    self.assertEqual(auc, 1.0)
 
     # no true positives
     auc = tab.ComputeROCAUC(score_col='score', class_col='classific', class_cutoff=1.0)
-    self.assertEquals(auc, None)
+    self.assertEqual(auc, None)
 
   def testCalcROCFromFile(self):
     if not HAS_NUMPY:
       return
     tab = Table.Load(os.path.join('testfiles','roc_table.dat'))
     auc = tab.ComputeROCAUC(score_col='prediction', class_col='reference', class_cutoff=0.4)
-    self.assertEquals(auc, 1.0)
+    self.assertEqual(auc, 1.0)
       
 
   def testCalcROCAUCSameValues(self):
@@ -1433,7 +1433,7 @@ class TestTable(unittest.TestCase):
                 classific=[True, True, False, True, True, True, False, False, True, False, True, False, True, False, False, False, True, False, True, False],
                 score=[0.9, 0.8, 0.7, 0.7, 0.7, 0.7, 0.53, 0.52, 0.51, 0.505, 0.4, 0.4, 0.4, 0.4, 0.36, 0.35, 0.34, 0.33, 0.30, 0.1])
     auc = tab.ComputeROCAUC(score_col='score', score_dir='+', class_col='classific')
-    self.assertAlmostEquals(auc, auc_ref)
+    self.assertAlmostEqual(auc, auc_ref)
 
   def testCalcMCC(self):
     log = _FetchLog()
@@ -1447,18 +1447,18 @@ class TestTable(unittest.TestCase):
                 class_wrong=[False,False,False,False,False,False, False,False,False, False,False,False,False,False,False, False,False,False,False, False,False,False,False,False])
     
     mcc = tab.ComputeMCC(score_col='score', score_dir='-', class_col='rmsd', class_dir='-', score_cutoff=1.0, class_cutoff=2.0)
-    self.assertAlmostEquals(mcc, 0.1490711984)
+    self.assertAlmostEqual(mcc, 0.1490711984)
     mcc = tab.ComputeMCC(score_col='class_score', class_col='class_rmsd')
-    self.assertAlmostEquals(mcc, 0.1490711984)
+    self.assertAlmostEqual(mcc, 0.1490711984)
     mcc = tab.ComputeMCC(score_col='score', score_dir='+', class_col='rmsd', class_dir='+', score_cutoff=1.0, class_cutoff=2.0)
-    self.assertAlmostEquals(mcc, 0.1490711984)
+    self.assertAlmostEqual(mcc, 0.1490711984)
     mcc = tab.ComputeMCC(score_col='score', score_dir='-', class_col='rmsd', class_dir='+', score_cutoff=1.0, class_cutoff=2.0)
-    self.assertAlmostEquals(mcc, -0.1490711984)
+    self.assertAlmostEqual(mcc, -0.1490711984)
     mcc = tab.ComputeMCC(score_col='score', score_dir='+', class_col='rmsd', class_dir='-', score_cutoff=1.0, class_cutoff=2.0)
-    self.assertAlmostEquals(mcc, -0.1490711984)
+    self.assertAlmostEqual(mcc, -0.1490711984)
     mcc = tab.ComputeMCC(score_col='class_wrong', class_col='class_rmsd')
-    self.assertEquals(mcc,None)
-    self.assertEquals(log.messages['WARNING'][0],
+    self.assertEqual(mcc,None)
+    self.assertEqual(log.messages['WARNING'][0],
     'Could not compute MCC: MCC is not defined since factor (tp + fp) is zero')
 
   def testCalcMCCPreclassified(self):
@@ -1467,9 +1467,9 @@ class TestTable(unittest.TestCase):
                 prediction1=[False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True,  False, True, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, True, False, True,  False, False, False, False, False, False],
                 prediction2=[False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True,  False, False, True,  False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, True, False, False, True,  False, True,  True,  False, False, False, False, False, False, False, False, False, False, True, False, False, True, False, False, False, True,  False, False, False, False])
     mcc = tab.ComputeMCC(score_col='prediction1', class_col='reference')
-    self.assertAlmostEquals(mcc, 0.538389277)
+    self.assertAlmostEqual(mcc, 0.538389277)
     mcc = tab.ComputeMCC(score_col='prediction2', class_col='reference')
-    self.assertAlmostEquals(mcc, 0.882089673321)
+    self.assertAlmostEqual(mcc, 0.882089673321)
 
   def testTableAsNumpyMatrix(self):
     if not HAS_NUMPY:
@@ -1519,24 +1519,24 @@ class TestTable(unittest.TestCase):
                 f=[9,9,9,9,9,9,9,9,9])
     
     pref = tab.GetOptimalPrefactors('c','a','b')
-    self.assertAlmostEquals(pref[0],0.799999999)
-    self.assertAlmostEquals(pref[1],0.166666666666)
+    self.assertAlmostEqual(pref[0],0.799999999)
+    self.assertAlmostEqual(pref[1],0.166666666666)
     
     pref = tab.GetOptimalPrefactors('c','b','a')
-    self.assertAlmostEquals(pref[0],0.166666666666)
-    self.assertAlmostEquals(pref[1],0.799999999)
+    self.assertAlmostEqual(pref[0],0.166666666666)
+    self.assertAlmostEqual(pref[1],0.799999999)
     
     pref = tab.GetOptimalPrefactors('c','b','a',weights='e')
-    self.assertAlmostEquals(pref[0],0.166666666666)
-    self.assertAlmostEquals(pref[1],0.799999999)
+    self.assertAlmostEqual(pref[0],0.166666666666)
+    self.assertAlmostEqual(pref[1],0.799999999)
     
     pref = tab.GetOptimalPrefactors('c','b','a',weights='f')
-    self.assertAlmostEquals(pref[0],0.166666666666)
-    self.assertAlmostEquals(pref[1],0.799999999)
+    self.assertAlmostEqual(pref[0],0.166666666666)
+    self.assertAlmostEqual(pref[1],0.799999999)
     
     pref = tab.GetOptimalPrefactors('c','a','b',weights='d')
-    self.assertAlmostEquals(pref[0],0.6078825445851)
-    self.assertAlmostEquals(pref[1],0.3394613806088)
+    self.assertAlmostEqual(pref[0],0.6078825445851)
+    self.assertAlmostEqual(pref[1],0.3394613806088)
     
     self.assertRaises(RuntimeError, tab.GetOptimalPrefactors, 'c','a','b',weight='d')
     self.assertRaises(RuntimeError, tab.GetOptimalPrefactors, 'c',weights='d')
@@ -1580,12 +1580,12 @@ class TestTable(unittest.TestCase):
         tab_list[i].append(v)
 
     for i in range(len(ref_list[0])):
-      self.assertAlmostEquals(tab_list[0][i],ref_list[0][i])
-      self.assertAlmostEquals(tab_list[1][i],ref_list[1][i])
-      self.assertAlmostEquals(tab_list[2][i],ref_list[2][i])
-      self.assertAlmostEquals(tab_list[3][i],ref_list[3][i])
-      self.assertAlmostEquals(tab_list[4][i],ref_list[4][i])
-      self.assertAlmostEquals(tab_list[5][i],ref_list[5][i])
+      self.assertAlmostEqual(tab_list[0][i],ref_list[0][i])
+      self.assertAlmostEqual(tab_list[1][i],ref_list[1][i])
+      self.assertAlmostEqual(tab_list[2][i],ref_list[2][i])
+      self.assertAlmostEqual(tab_list[3][i],ref_list[3][i])
+      self.assertAlmostEqual(tab_list[4][i],ref_list[4][i])
+      self.assertAlmostEqual(tab_list[5][i],ref_list[5][i])
      
 
   def testIsEmpty(self):
@@ -1641,30 +1641,30 @@ class TestTable(unittest.TestCase):
     tab = self.CreateTestTable()
     tab.AddRow(['foo',4, 3.3])
     tab.AddRow([None,5, 6.3])
-    self.assertEquals(tab.GetUnique('first'), ['x','foo'])
-    self.assertEquals(tab.GetUnique('first', ignore_nan=False), ['x','foo', None])
-    self.assertEquals(tab.GetUnique('second'), [3,9,4,5])
-    self.assertEquals(tab.GetUnique('second', ignore_nan=False), [3,None,9,4,5])
-    self.assertEquals(tab.GetUnique('third'), [2.2, 3.3, 6.3])
-    self.assertEquals(tab.GetUnique('third', ignore_nan=False), [None, 2.2, 3.3, 6.3])
+    self.assertEqual(tab.GetUnique('first'), ['x','foo'])
+    self.assertEqual(tab.GetUnique('first', ignore_nan=False), ['x','foo', None])
+    self.assertEqual(tab.GetUnique('second'), [3,9,4,5])
+    self.assertEqual(tab.GetUnique('second', ignore_nan=False), [3,None,9,4,5])
+    self.assertEqual(tab.GetUnique('third'), [2.2, 3.3, 6.3])
+    self.assertEqual(tab.GetUnique('third', ignore_nan=False), [None, 2.2, 3.3, 6.3])
     
   def testCorrel(self):
     tab = self.CreateTestTable()
-    self.assertEquals(tab.Correl('second','third'), None)
+    self.assertEqual(tab.Correl('second','third'), None)
     tab.AddRow(['foo',4, 3.3])
     tab.AddRow([None,5, 6.3])
     tab.AddRow([None,8, 2])
-    self.assertAlmostEquals(tab.Correl('second','third'), -0.4954982578)
+    self.assertAlmostEqual(tab.Correl('second','third'), -0.4954982578)
     
   def testSpearmanCorrel(self):
     if not HAS_SCIPY_STATS:
       return
     tab = self.CreateTestTable()
-    self.assertEquals(tab.SpearmanCorrel('second','third'), None)
+    self.assertEqual(tab.SpearmanCorrel('second','third'), None)
     tab.AddRow(['foo',4, 3.3])
     tab.AddRow([None,5, 6.3])
     tab.AddRow([None,8, 2])
-    self.assertAlmostEquals(tab.SpearmanCorrel('second','third'), -0.316227766)
+    self.assertAlmostEqual(tab.SpearmanCorrel('second','third'), -0.316227766)
     
   def testExtend(self):
     '''
@@ -1804,8 +1804,8 @@ class TestTable(unittest.TestCase):
     split_exp_two=selector._ExpressionLexer(query_two)
     parsed_exp_two=selector._ParseExpression(split_exp_two)
     rpn_two=['a', 1, '=', 'a', 2, '=', 'or', 'b', 5.0, 'a', '+', '>', 'b', 2.0, '<', 'or', 'or']
-    self.assertEquals(selector._ShuntingYard(parsed_exp_one),rpn_one)
-    self.assertEquals(selector._ShuntingYard(parsed_exp_two),rpn_two)
+    self.assertEqual(selector._ShuntingYard(parsed_exp_one),rpn_one)
+    self.assertEqual(selector._ShuntingYard(parsed_exp_two),rpn_two)
 
     #check operator evaluations
     self.assertTrue(selector._EvaluateOperator('=',False,False))
diff --git a/modules/bindings/pymod/align_3dcomb.py b/modules/bindings/pymod/align_3dcomb.py
index ca709fc15..f6a4b49ef 100644
--- a/modules/bindings/pymod/align_3dcomb.py
+++ b/modules/bindings/pymod/align_3dcomb.py
@@ -44,7 +44,7 @@ def _SetupFiles(structure_list):
   
   #write out temporary pdb files
   if not all([ev.IsValid() for ev in structure_list]):
-    raise RuntimeError, "Invalid EntityView in structure_list"
+    raise RuntimeError("Invalid EntityView in structure_list")
   tpd=utils.TempDirWithFiles(structure_list)
   
   #Write out the file containing the list of all structures
@@ -71,7 +71,7 @@ def _Run3DCOMB(command,tpd):
   outfile.close()
   #check for successful completion of 3DCOMB
   if returncode!=0:
-    print "WARNING: 3DCOMB error\n"
+    print("WARNING: 3DCOMB error\n")
     raise subprocess.CalledProcessError
   return returncode
 
@@ -87,7 +87,7 @@ def _ParseOutput(tpd):
     if l.startswith('>'):
       fl=ost.FloatList()
       for i in range(3):
-        l=f.next()
+        l=next(f)
         sl=l.split(',')
         fl.extend([float(el) for el in sl[0].split()+[sl[1]]])
       fl.extend([0,0,0,1])
@@ -115,7 +115,7 @@ def AlignStructures(structure_list,apply_transform=False,name_list=None,comb_exe
   returncode=_Run3DCOMB(command,tpd)
   try:ali,Tl,results=_ParseOutput(tpd)
   except:
-    print 'could not parse output'
+    print('could not parse output')
     raise RuntimeError
   if apply_transform:
     for T,ev in zip(Tl,structure_list):
diff --git a/modules/bindings/pymod/blast.py b/modules/bindings/pymod/blast.py
index e63007f40..788d46025 100644
--- a/modules/bindings/pymod/blast.py
+++ b/modules/bindings/pymod/blast.py
@@ -103,12 +103,12 @@ def ParseBlastOutput(string, seqid_thres=0, evalue_thres=float("infinity")):
         aln=seq.CreateAlignment(query_seq, hit_seq)
         return AlignedPatch(aln, bit_score, score, evalue, seqid)
 
-    except Exception, e:
-      print str(e), query_seq, hit_seq
+    except Exception as e:
+      print(str(e), query_seq, hit_seq)
 
   try:
     doc=minidom.parseString(string)
-  except Exception, e:
+  except Exception as e:
     ost.LogError('Error while parsing BLAST output: %s' % str(e))
     return None
   hits=[]
diff --git a/modules/bindings/pymod/cadscore.py b/modules/bindings/pymod/cadscore.py
index 3b0fd0a0c..6d5bb0b9a 100644
--- a/modules/bindings/pymod/cadscore.py
+++ b/modules/bindings/pymod/cadscore.py
@@ -246,16 +246,16 @@ def _RunCAD(tmp_dir, mode, cad_bin_path, old_regime):
 def _HasInsertionCodes(model, reference):
   for r in model.residues:
     if r.GetNumber().GetInsCode() != "\0":
-      print r
+      print(r)
       return True
   for r in reference.residues:
     if r.GetNumber().GetInsCode() != "\0":
-      print r
+      print(r)
       return True
   return False
 
 def _MapLabels(model, cad_results, label):
-  for k,v in cad_results.localAA.iteritems():
+  for k,v in cad_results.localAA.items():
     r = model.FindResidue(k[0], k[1])
     if not r.IsValid():
       raise RuntimeError("Failed to map cadscore on residues: " +
diff --git a/modules/bindings/pymod/dssp.py b/modules/bindings/pymod/dssp.py
index 7fe206d0a..a43696c4e 100644
--- a/modules/bindings/pymod/dssp.py
+++ b/modules/bindings/pymod/dssp.py
@@ -116,9 +116,9 @@ def AssignDSSP(ent, pdb_path="", extract_burial_status=False, tmp_dir=None,
   try:
     LoadDSSP(temp_dssp_path, ent, extract_burial_status,
              entity_saved)
-  except Exception, e:
+  except Exception as e:
     # clean up
-    print "Exception in DSSP:", e
+    print("Exception in DSSP:", e)
     _Cleanup(pdb_path, temp_dssp_path, entity_saved)
     raise RuntimeError(e)
 
@@ -203,8 +203,8 @@ def LoadDSSP(file_name, model, extract_burial_status=False,
               residue.SetStringProp("burial_status", 'b')
             else:
               residue.SetStringProp("burial_status", 'e')
-      except Exception, e:
-        print "ERROR:",e
+      except Exception as e:
+        print("ERROR:",e)
         continue
 
       rtype=line[16:17]
diff --git a/modules/bindings/pymod/hbplus.py b/modules/bindings/pymod/hbplus.py
index 5a97df2c9..39b903359 100644
--- a/modules/bindings/pymod/hbplus.py
+++ b/modules/bindings/pymod/hbplus.py
@@ -108,5 +108,5 @@ def HBondScore(ent1, ent2, hbplus_bin=None):
     return 0.0
 
 if __name__=='__main__':
-  print 'HBond Score:', HBondScore(io.LoadPDB(sys.argv[1]), 
-                                   io.LoadPDB(sys.argv[2]))
\ No newline at end of file
+  print('HBond Score:', HBondScore(io.LoadPDB(sys.argv[1]), 
+                                   io.LoadPDB(sys.argv[2])))
\ No newline at end of file
diff --git a/modules/bindings/pymod/hhblits.py b/modules/bindings/pymod/hhblits.py
index 617784798..94c1a6435 100644
--- a/modules/bindings/pymod/hhblits.py
+++ b/modules/bindings/pymod/hhblits.py
@@ -161,34 +161,34 @@ def ParseHHblitsOutput(output):
         value_start_column = 14
         date_pattern = '%a %b %d %H:%M:%S %Y'
         header = HHblitsHeader()
-        line = lines.next()
+        line = next(lines)
         assert line.startswith('Query')
         header.query = line[value_start_column:].strip()
-        line = lines.next()
+        line = next(lines)
         assert line.startswith('Match_columns')
         header.match_columns = int(line[value_start_column:].strip())
 
-        line = lines.next()
+        line = next(lines)
         assert line.startswith('No_of_seqs')
 
-        line = lines.next()
+        line = next(lines)
         assert line.startswith('Neff')
         header.n_eff = float(line[value_start_column:].strip())
 
-        line = lines.next()
+        line = next(lines)
         assert line.startswith('Searched_HMMs')
         header.searched_hmms = int(line[value_start_column:].strip())
 
-        line = lines.next()
+        line = next(lines)
         assert line.startswith('Date')
         value = line[value_start_column:].strip()
         header.date = datetime.datetime.strptime(value, date_pattern)
 
-        line = lines.next()
+        line = next(lines)
         assert line.startswith('Command')
         header.command = line[value_start_column:].strip()
 
-        line = lines.next()
+        line = next(lines)
         assert len(line.strip()) == 0
         return header
 
@@ -196,7 +196,7 @@ def ParseHHblitsOutput(output):
         assert lines.next().startswith(' No Hit')
         hits = []
         while True:
-            line = lines.next()
+            line = next(lines)
             if len(line.strip()) == 0:
                 return hits
             hits.append(ParseHeaderLine(line))
@@ -221,7 +221,7 @@ def ParseHHblitsOutput(output):
                 # - "T <hit_id> <start> <data> <end>"
                 # - "Q <query_id> <start> <data> <end>"
                 # -> rest is to be skipped
-                line = lines.next()
+                line = next(lines)
                 if len(line.strip()) == 0:
                     continue
                 if line.startswith('Done!'):
@@ -241,7 +241,7 @@ def ParseHHblitsOutput(output):
                     templ_str = ''
                     # skip the next line. It doesn't contain information we
                     # don't already know
-                    lines.next()
+                    next(lines)
                     continue
                 assert entry_index != None
                 # Skip all "T ..." and "Q ..." lines besides the one we want
@@ -758,7 +758,7 @@ def _ParseOptions(opts):
     """
     opt_cmd = list()
     opt_str = list()
-    for k, val in opts.iteritems():
+    for k, val in opts.items():
         if type(val) == type(True):
             if val == True:
                 opt_cmd.append('-%s' % str(k))
diff --git a/modules/bindings/pymod/ialign.py b/modules/bindings/pymod/ialign.py
index d5c9f570d..2c6090102 100644
--- a/modules/bindings/pymod/ialign.py
+++ b/modules/bindings/pymod/ialign.py
@@ -125,7 +125,7 @@ def _RuniAlign(ialign, tmp_dir, options={}):
          } 
   opts.update(options)
   cmd_opts = []
-  for k, v in opts.iteritems():
+  for k, v in opts.items():
     if type(v) == type(True):
       if v == True:
         cmd_opts.append('-%s' % str(k))
diff --git a/modules/bindings/pymod/kclust.py b/modules/bindings/pymod/kclust.py
index 5ecdf11d0..edcbc4797 100644
--- a/modules/bindings/pymod/kclust.py
+++ b/modules/bindings/pymod/kclust.py
@@ -84,7 +84,7 @@ def _ParseOutput(tmp_dir_name):
   #translate into final output
 
   res=list()
-  for k, v in clusters.iteritems():
+  for k, v in clusters.items():
     res.append(cluster(v, header_mapper[k]))
 
   return res
diff --git a/modules/bindings/pymod/lga.py b/modules/bindings/pymod/lga.py
index 0f5410fe3..7ab23e854 100644
--- a/modules/bindings/pymod/lga.py
+++ b/modules/bindings/pymod/lga.py
@@ -52,8 +52,8 @@ def _ParseGDTSection(section, residue_count):
   cutoffs=[float(e) for e in section[0].split()[2:]]
   num_ca=[int(e) for e in section[1].split()[2:]]
   gdt_ts=[float(e) for e in section[2].split()[2:]]
-  scores=dict(zip(cutoffs, gdt_ts))
-  numbers=dict(zip(cutoffs, num_ca))
+  scores=dict(list(zip(cutoffs, gdt_ts)))
+  numbers=dict(list(zip(cutoffs, num_ca)))
   factor=(1.0/(4*residue_count))*100
   ts_cutoffs=(1.0, 2.0, 4.0, 8.0)
   ha_cutoffs=(0.5, 1.0, 2.0, 4.0)  
diff --git a/modules/bindings/pymod/msms.py b/modules/bindings/pymod/msms.py
index 534cd3d8e..21ccf5125 100644
--- a/modules/bindings/pymod/msms.py
+++ b/modules/bindings/pymod/msms.py
@@ -86,7 +86,7 @@ def _SetupFiles(entity, selection):
   # select only heavy atoms if no_hydrogens is true
   entity_view=entity.Select(selection)
   if not entity_view.IsValid():
-    raise RuntimeError, "Could not create view for selection (%s)"%(selection)
+    raise RuntimeError("Could not create view for selection (%s)"%(selection))
 
   # write entity to tmp file
   tmp_file_name=os.path.join(tmp_dir_name,"entity")
@@ -118,7 +118,7 @@ def _ParseAreaFile(entity, selection, file, asa_prop, esa_prop):
   # shift first line
   area_lines = area_lines[1:]
   if view.GetAtomCount() != len(area_lines):
-      raise RuntimeError, "Atom count (%d) unequeal to number of atoms in area file (%d)" % (view.GetAtomCount(), len(area_lines))
+      raise RuntimeError("Atom count (%d) unequeal to number of atoms in area file (%d)" % (view.GetAtomCount(), len(area_lines)))
   for l in area_lines:
       atom_no, sesa, sasa = l.split()
       a = view.atoms[int(atom_no)]
@@ -154,7 +154,7 @@ def _RunMSMS(command):
 
   #check for successful completion of msms
   if proc.returncode!=0:
-    print "WARNING: msms error\n", stdout_value
+    print("WARNING: msms error\n", stdout_value)
     raise MsmsProcessError(proc.returncode, command)
 
   return stdout_value
diff --git a/modules/bindings/pymod/naccess.py b/modules/bindings/pymod/naccess.py
index 0d5566ea2..dc65ab4ef 100644
--- a/modules/bindings/pymod/naccess.py
+++ b/modules/bindings/pymod/naccess.py
@@ -74,9 +74,9 @@ def _SetupFiles(entity, selection, scratch_dir, max_number_of_atoms):
   else:
     entity_view = entity
   if len(entity_view.atoms) > max_number_of_atoms:
-    raise RuntimeError, "Too much atoms for NACCESS (> %s)" % max_number_of_atoms
+    raise RuntimeError("Too much atoms for NACCESS (> %s)" % max_number_of_atoms)
   if not entity_view.IsValid():
-    raise RuntimeError, "Could not create view for selection (%s)"%(selection)
+    raise RuntimeError("Could not create view for selection (%s)"%(selection))
   
   # write entity to tmp file
   tmp_file_name = "entity.pdb"
@@ -162,7 +162,7 @@ def _ParseRsaFile(entity, file, asa_abs, asa_rel):
         res.SetFloatProp(asa_rel, float(rel_all) )
         res.SetFloatProp(asa_abs, float(abs_all) )
       else:
-        raise RuntimeError, "Residue Names are not the same for ResNumb: %s (%s vs %s)" % (res_number, res.name, res_name)
+        raise RuntimeError("Residue Names are not the same for ResNumb: %s (%s vs %s)" % (res_number, res.name, res_name))
       
 
 def __CleanupFiles(dir_name):
diff --git a/modules/bindings/tests/test_blast.py b/modules/bindings/tests/test_blast.py
index 18311abd9..fbadfccb1 100644
--- a/modules/bindings/tests/test_blast.py
+++ b/modules/bindings/tests/test_blast.py
@@ -102,7 +102,7 @@ if __name__ == "__main__":
   try:
     blastpath=settings.Locate(('blastp','blastall'))
   except(settings.FileNotFound):
-    print "Could not find blast executable: ignoring unit tests"
+    print("Could not find blast executable: ignoring unit tests")
     sys.exit(0)
   from ost import testutils
   testutils.RunTests()
diff --git a/modules/bindings/tests/test_clustalw.py b/modules/bindings/tests/test_clustalw.py
index 8f295088e..222effed8 100644
--- a/modules/bindings/tests/test_clustalw.py
+++ b/modules/bindings/tests/test_clustalw.py
@@ -76,7 +76,7 @@ if __name__ == "__main__":
   try:
     clustalw_path=settings.Locate(('clustalw', 'clustalw2'))
   except(settings.FileNotFound):
-    print "Could not find clustalw executable: ignoring unit tests"
+    print("Could not find clustalw executable: ignoring unit tests")
     sys.exit(0)
   from ost import testutils
   testutils.RunTests()
diff --git a/modules/bindings/tests/test_hhblits.py b/modules/bindings/tests/test_hhblits.py
index 2ba099ced..22a4faf20 100644
--- a/modules/bindings/tests/test_hhblits.py
+++ b/modules/bindings/tests/test_hhblits.py
@@ -399,8 +399,8 @@ class TestHHblitsBindings(unittest.TestCase):
 if __name__ == "__main__":
     hhsuite_root_dir =  os.getenv('EBROOTHHMINSUITE')
     if not hhsuite_root_dir:
-        print "No environment variable 'EBROOTHHMINSUITE'. To enable the "+\
-            "unit test, this needs to point to your HHsuite installation."
+        print("No environment variable 'EBROOTHHMINSUITE'. To enable the "+\
+            "unit test, this needs to point to your HHsuite installation.")
         sys.exit(0)
     from ost import testutils
     testutils.RunTests()
diff --git a/modules/bindings/tests/test_kclust.py b/modules/bindings/tests/test_kclust.py
index fe7251613..707288d07 100644
--- a/modules/bindings/tests/test_kclust.py
+++ b/modules/bindings/tests/test_kclust.py
@@ -38,7 +38,7 @@ if __name__ == "__main__":
   try:
     blastpath=settings.Locate(('kClust'))
   except(settings.FileNotFound):
-    print "Could not find kClust executable: ignoring unit tests"
+    print("Could not find kClust executable: ignoring unit tests")
     sys.exit(0)
   from ost import testutils
   testutils.RunTests()
diff --git a/modules/bindings/tests/test_msms.py b/modules/bindings/tests/test_msms.py
index 9041f94fa..d0cc2c42e 100755
--- a/modules/bindings/tests/test_msms.py
+++ b/modules/bindings/tests/test_msms.py
@@ -37,10 +37,10 @@ if __name__ == "__main__":
   try:
     msms._GetExecutable(msms_exe=None, msms_env='MSMSSERVER')
   except(settings.FileNotFound):
-    print "Could not find msms executable: ignoring unit tests"
+    print("Could not find msms executable: ignoring unit tests")
     exit(0)
   version = msms.GetVersion(msms_exe=None, msms_env='MSMSSERVER')
   if version!=VERSION_REQUIRED:
-    print "MSMS version (%s) does not match required version %s: ignoring unit tests"%(version, VERSION_REQUIRED)
+    print("MSMS version (%s) does not match required version %s: ignoring unit tests"%(version, VERSION_REQUIRED))
   from ost import testutils
   testutils.RunTests()
diff --git a/modules/bindings/tests/test_naccess.py b/modules/bindings/tests/test_naccess.py
index c2b33b063..95949301d 100644
--- a/modules/bindings/tests/test_naccess.py
+++ b/modules/bindings/tests/test_naccess.py
@@ -25,7 +25,7 @@ class TestNaccessBindings(unittest.TestCase):
         except:
             excp_raised = True
             raise
-        self.assertEquals(excp_raised, False, msg="Naccess raised an "+
+        self.assertEqual(excp_raised, False, msg="Naccess raised an "+
                           "exception on a path containing a '.'. This is not "+
                           "supposed to happen.")
 
@@ -33,7 +33,7 @@ if __name__ == "__main__":
     try:
         settings.Locate("naccess")
     except:
-        print "Could not find NACCESS, could not test binding..."
+        print("Could not find NACCESS, could not test binding...")
         sys.exit(0)
     from ost import testutils
     testutils.RunTests()
diff --git a/modules/conop/pymod/cleanup.py b/modules/conop/pymod/cleanup.py
index 1a51673d7..6866a913a 100644
--- a/modules/conop/pymod/cleanup.py
+++ b/modules/conop/pymod/cleanup.py
@@ -79,7 +79,7 @@ def _CanonicalizeResidues(clean_entity, ed, compound_lib) :
           _DeleteSidechain(res, ed)
           for atom in res.atoms:
             atom.is_hetatom = False
-          print "Removing sidechain of %s, beacuse it has not been found in the compound library"% parent_tlc
+          print("Removing sidechain of %s, beacuse it has not been found in the compound library"% parent_tlc)
         else:
           #collect atom's names
           modif_atom_names = set([atom.name for atom in res.atoms
diff --git a/modules/conop/tests/test_cleanup.py b/modules/conop/tests/test_cleanup.py
index b0579127a..54d33a8bc 100644
--- a/modules/conop/tests/test_cleanup.py
+++ b/modules/conop/tests/test_cleanup.py
@@ -163,4 +163,4 @@ if __name__ == "__main__":
   if testutils.SetDefaultCompoundLib():
     testutils.RunTests()
   else:
-    print 'No compound library available. Ignoring test_cleanup.py tests.'
+    print('No compound library available. Ignoring test_cleanup.py tests.')
diff --git a/modules/conop/tests/test_compound.py b/modules/conop/tests/test_compound.py
index bd54b7dfa..01c59a233 100644
--- a/modules/conop/tests/test_compound.py
+++ b/modules/conop/tests/test_compound.py
@@ -28,4 +28,4 @@ if __name__=='__main__':
     if testutils.SetDefaultCompoundLib():
         testutils.RunTests()
     else:
-        print 'No compound library available. Ignoring test_compound.py tests.'
\ No newline at end of file
+        print('No compound library available. Ignoring test_compound.py tests.')
\ No newline at end of file
diff --git a/modules/geom/tests/test_geom.py b/modules/geom/tests/test_geom.py
index 46063eb4f..f84daaa84 100644
--- a/modules/geom/tests/test_geom.py
+++ b/modules/geom/tests/test_geom.py
@@ -30,7 +30,7 @@ import ost.geom as geom
 # which do not convert std::out_of_range to IndexError correctly.
 try:
   geom.Vec2()[3]
-except Exception, e:
+except Exception as e:
   pass
   IndexError = type(e)
 
diff --git a/modules/gfx/pymod/__init__.py b/modules/gfx/pymod/__init__.py
index d1b59f1a7..a0525f126 100644
--- a/modules/gfx/pymod/__init__.py
+++ b/modules/gfx/pymod/__init__.py
@@ -17,7 +17,7 @@
 # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 #------------------------------------------------------------------------------
 from _ost_gfx import *
-from py_gfx_obj import PyGfxObj
+from .py_gfx_obj import PyGfxObj
 
 WHITE=RGB(1.0,1.0,1.0)
 BLACK=RGB(0.0,0.0,0.0)
@@ -282,7 +282,7 @@ def _entity_reset(self,*args,**kwargs):
     else:
       raise TypeError("Reset: unknown option of type '%s' given"%type(a))
 
-  for key,val in kwargs.iteritems():
+  for key,val in kwargs.items():
     if key=="entity":
       if not isinstance(val,mol.EntityHandle):
         raise TypeError("Reset: expected mol.EntityHandle for 'entity' option")
diff --git a/modules/gfx/tests/test_gfx.py b/modules/gfx/tests/test_gfx.py
index fcd0f8510..3889c8e2f 100644
--- a/modules/gfx/tests/test_gfx.py
+++ b/modules/gfx/tests/test_gfx.py
@@ -32,7 +32,7 @@ if ost.WITH_NUMPY:
   has_numpy=True
   try:
     import numpy
-  except ImportError, e:
+  except ImportError as e:
     has_numpy=False
 else:
   has_numpy=False
diff --git a/modules/gfx/tests/test_gost_import.py b/modules/gfx/tests/test_gost_import.py
index deebd2d31..357a0678d 100644
--- a/modules/gfx/tests/test_gost_import.py
+++ b/modules/gfx/tests/test_gost_import.py
@@ -9,7 +9,7 @@ with open("test.gost","rb") as gf:
 
   while raw:
     (type, subtype,size) = struct.unpack("iiL",raw)
-    print "found type=%d, subtype=%d and blocksize=%u"%(type,subtype,size)
+    print("found type=%d, subtype=%d and blocksize=%u"%(type,subtype,size))
     if size>0:
       data = gf.read(size)
     raw = gf.read(16)
diff --git a/modules/gui/pymod/__init__.py b/modules/gui/pymod/__init__.py
index da6b1599a..4343829dc 100644
--- a/modules/gui/pymod/__init__.py
+++ b/modules/gui/pymod/__init__.py
@@ -24,9 +24,9 @@ import sip
 ## \brief Opens a DataViewer 
 # \sa \example fft_li.py "View Fourier Transform Example" \sa \ref modulate_image.py "Modulate Image Example"
 def _close_event_override_(event):
-  print "close event"
+  print("close event")
 def _set_data_override_(data):
-  print "set data"
+  print("set data")
 
 def CreateDataViewer(ih,flag=False):
       viewer=GostyApp.Instance().CreateDataViewer(ih)
diff --git a/modules/gui/pymod/dng/init.py b/modules/gui/pymod/dng/init.py
index 070cace7b..ed274b5d7 100644
--- a/modules/gui/pymod/dng/init.py
+++ b/modules/gui/pymod/dng/init.py
@@ -13,7 +13,7 @@ except ImportError:
   _img_present=False
   pass
 
-import httplib
+import http.client
 
 from PyQt5 import QtGui, QtWidgets, QtCore
 from ost.gui.scene.init_inspector import _InitInspector
@@ -116,7 +116,7 @@ def _execute_script():
   sys_argv_backup=sys.argv
   sys.argv=script_argv
   try:
-    execfile(script, __main__.__dict__)
+    exec(compile(open(script).read(), script, 'exec'), __main__.__dict__)
   finally:
     sys.argv=sys_argv_backup     
 
@@ -150,7 +150,7 @@ class OstOptionParser(optparse.OptionParser):
   def __init__(self, **kwargs):
     optparse.OptionParser.__init__(self, **kwargs)
   def exit(self, status_code, error_message):
-    print error_message,
+    print(error_message, end=' ')
     QtWidgets.QApplication.instance().exit()
     sys.exit(-1)
 
@@ -171,7 +171,7 @@ if len(parser.rargs)!=0:
     if not rargs_string.endswith('.py'):  
       loading_list.append(rargs_string)
     else:
-      print 'Error:  one of the files to load is a Python script, use -s flag to execute it\n'
+      print('Error:  one of the files to load is a Python script, use -s flag to execute it\n')
       QtWidgets.QApplication.instance().exit()
       sys.exit(-1)    
 
@@ -190,15 +190,15 @@ _ostrc=os.path.join(home, '.ostrc')
 if os.path.exists(_ostrc):
   try:
     exec(open(_ostrc))
-  except Exception, e:
-    print e
+  except Exception as e:
+    print(e)
 else:
   rcfile=open(_ostrc,"w")
-  print >> rcfile, '# This python file is parsed by ost and dng at startup'
-  print >> rcfile, '# Its content is made available in the global namespace'
-  print >> rcfile, '# It can be used to define custom variables and functions'
-  print >> rcfile, '# For example:'
-  print >> rcfile, '# IMPORTANT_DIR="path/to/important/dir"'
+  print('# This python file is parsed by ost and dng at startup', file=rcfile)
+  print('# Its content is made available in the global namespace', file=rcfile)
+  print('# It can be used to define custom variables and functions', file=rcfile)
+  print('# For example:', file=rcfile)
+  print('# IMPORTANT_DIR="path/to/important/dir"', file=rcfile)
   rcfile.close()
 
 ost.gui.PushVerbosityLevel(options.vlevel)
diff --git a/modules/gui/pymod/dng/menu.py b/modules/gui/pymod/dng/menu.py
index 425cf6ca7..487eec75c 100644
--- a/modules/gui/pymod/dng/menu.py
+++ b/modules/gui/pymod/dng/menu.py
@@ -52,8 +52,8 @@ class ClipWidget(QWidget):
     self.far_.setMinimum(int(bounds_near.text()))
     self.far_.setMaximum(int(bounds_far.text()))
     far = int(gfx.Scene().far)
-    if far>sys.maxint:
-      far = sys.maxint
+    if far>sys.maxsize:
+      far = sys.maxsize
     self.far_.setValue(far)
     self.auto_ = QCheckBox("Continuous Automatic Clipping")
     self.auto_.setChecked(gfx.Scene().GetAutoAutoslab())
diff --git a/modules/gui/pymod/export_message_widget.cc b/modules/gui/pymod/export_message_widget.cc
index 1893c7e87..69dd6bb38 100644
--- a/modules/gui/pymod/export_message_widget.cc
+++ b/modules/gui/pymod/export_message_widget.cc
@@ -39,9 +39,9 @@ void log_message_a(MessageWidget* message_widget, const QString& message, QMessa
 void log_message_b(MessageWidget * message_widget, object py_object)
 {
   if(py_object.ptr() != Py_None){
-    if(PyObject_IsInstance(py_object.ptr(), (PyObject*)&PyString_Type)){
-    String message = extract < std::string > (py_object);
-    message_widget->LogMessage(QString(message.c_str()));
+    if(PyObject_IsInstance(py_object.ptr(), (PyObject*)&PyUnicode_Type)){
+      String message = extract < std::string > (py_object);
+      message_widget->LogMessage(QString(message.c_str()));
     }
     else if(QStandardItem* item = get_cpp_qobject<QStandardItem>(py_object)){
       message_widget->LogMessage(item);
diff --git a/modules/gui/pymod/helpwidget.py b/modules/gui/pymod/helpwidget.py
index 9ac28e205..aee9b19ac 100644
--- a/modules/gui/pymod/helpwidget.py
+++ b/modules/gui/pymod/helpwidget.py
@@ -30,7 +30,7 @@ class Help:
     else:
       try:
         s=d.document(pydoc.resolve(what)[0])
-      except ImportError, detail:
+      except ImportError as detail:
         s=str(detail)
       doc_widget.doctext.setHtml(s)
       doc_widget.searchbox.setText('')
diff --git a/modules/gui/pymod/scene/color_options_widget.py b/modules/gui/pymod/scene/color_options_widget.py
index c99eb41e2..da2deb806 100644
--- a/modules/gui/pymod/scene/color_options_widget.py
+++ b/modules/gui/pymod/scene/color_options_widget.py
@@ -30,10 +30,10 @@ except ImportError:
   pass
 
 from PyQt5 import QtCore, QtWidgets, QtWidgets
-from scene_selection_helper import SelHelper
-from gradient_editor_widget import GradientEditor
-from uniform_color_widget import UniformColorWidget
-from combo_options_widget import ComboOptionsWidget
+from .scene_selection_helper import SelHelper
+from .gradient_editor_widget import GradientEditor
+from .uniform_color_widget import UniformColorWidget
+from .combo_options_widget import ComboOptionsWidget
 
 class ColorOptionsWidget(ComboOptionsWidget):
   def __init__(self, parent=None):
diff --git a/modules/gui/pymod/scene/combo_options_widget.py b/modules/gui/pymod/scene/combo_options_widget.py
index 28bacce3f..b15a8a490 100644
--- a/modules/gui/pymod/scene/combo_options_widget.py
+++ b/modules/gui/pymod/scene/combo_options_widget.py
@@ -90,7 +90,7 @@ class ComboOptionsWidget(QtWidgets.QWidget):
      This abstract method must be implemented by all subclasses. 
      It can be used to do something ;-) whenever the combobox changes its value.
     """
-    raise NotImplementedError, "Subclasses must define OnComboChange()"
+    raise NotImplementedError("Subclasses must define OnComboChange()")
 
   def OnActivate(self, item):
     return self.OnComboChange(self, item)
diff --git a/modules/gui/pymod/scene/cpk_widget.py b/modules/gui/pymod/scene/cpk_widget.py
index c40819abe..3ce8d05a2 100644
--- a/modules/gui/pymod/scene/cpk_widget.py
+++ b/modules/gui/pymod/scene/cpk_widget.py
@@ -21,7 +21,7 @@
 from ost import gui
 from ost import gfx
 from PyQt5 import QtCore, QtWidgets
-from render_mode_widget import RenderModeWidget
+from .render_mode_widget import RenderModeWidget
 
 #CPK Render Options
 class CPKWidget(RenderModeWidget):
diff --git a/modules/gui/pymod/scene/custom_widget.py b/modules/gui/pymod/scene/custom_widget.py
index e94eabd2d..2319e066c 100644
--- a/modules/gui/pymod/scene/custom_widget.py
+++ b/modules/gui/pymod/scene/custom_widget.py
@@ -21,7 +21,7 @@
 from ost import gui
 from ost import gfx
 from PyQt5 import QtCore, QtWidgets
-from render_mode_widget import RenderModeWidget
+from .render_mode_widget import RenderModeWidget
 
 #Custom Render Options
 class CustomWidget(RenderModeWidget):
diff --git a/modules/gui/pymod/scene/gradient_editor_widget.py b/modules/gui/pymod/scene/gradient_editor_widget.py
index 370bd51cd..17ea07f0d 100644
--- a/modules/gui/pymod/scene/gradient_editor_widget.py
+++ b/modules/gui/pymod/scene/gradient_editor_widget.py
@@ -22,8 +22,8 @@ from ost import gui
 from ost import gfx
 from ost import mol
 from PyQt5 import QtCore, QtGui, QtWidgets
-from color_select_widget import ColorSelectWidget
-from gradient_preset_widget import GradientPresetWidget
+from .color_select_widget import ColorSelectWidget
+from .gradient_preset_widget import GradientPresetWidget
 
 #Gradient Editor
 class GradientEditor(QtWidgets.QWidget):
diff --git a/modules/gui/pymod/scene/gradient_info_handler.py b/modules/gui/pymod/scene/gradient_info_handler.py
index 747b05860..5c7414504 100644
--- a/modules/gui/pymod/scene/gradient_info_handler.py
+++ b/modules/gui/pymod/scene/gradient_info_handler.py
@@ -22,7 +22,7 @@ from ost import gui
 from ost import gfx
 from ost import info
 from PyQt5 import QtCore
-from immutable_gradient_info_handler import ImmutableGradientInfoHandler
+from .immutable_gradient_info_handler import ImmutableGradientInfoHandler
 
 #Gradient Info Handler
 class GradientInfoHandler(ImmutableGradientInfoHandler):
diff --git a/modules/gui/pymod/scene/gradient_list_model.py b/modules/gui/pymod/scene/gradient_list_model.py
index 43f3f95db..5195e7f9f 100644
--- a/modules/gui/pymod/scene/gradient_list_model.py
+++ b/modules/gui/pymod/scene/gradient_list_model.py
@@ -3,8 +3,8 @@ from ost import gfx
 import os
 import ost
 from PyQt5 import QtCore, QtWidgets, QtGui
-from immutable_gradient_info_handler import ImmutableGradientInfoHandler
-from gradient_info_handler import GradientInfoHandler
+from .immutable_gradient_info_handler import ImmutableGradientInfoHandler
+from .gradient_info_handler import GradientInfoHandler
 
 class GradientListModel(QtCore.QAbstractListModel):
   
@@ -57,11 +57,11 @@ class GradientListModel(QtCore.QAbstractListModel):
   def LoadGradientFromInfo(self):
     if self.immutable_infoh_:
       qgradients = self.immutable_infoh_.GetQGradients()
-      for k, v in qgradients.iteritems():
+      for k, v in qgradients.items():
         self.AddItem(k, v, self.GetLastRow(), False, False)
     
     qgradients = self.infoh_.GetQGradients()
-    for k, v in qgradients.iteritems():
+    for k, v in qgradients.items():
       self.AddItem(k, v, self.GetLastRow(), True, False)
 
   def GetGradient(self, model_index):
diff --git a/modules/gui/pymod/scene/gradient_preset_widget.py b/modules/gui/pymod/scene/gradient_preset_widget.py
index 998714bef..e1717c98f 100644
--- a/modules/gui/pymod/scene/gradient_preset_widget.py
+++ b/modules/gui/pymod/scene/gradient_preset_widget.py
@@ -27,8 +27,8 @@ from datetime import datetime
 
 from datetime import datetime
 from PyQt5 import QtCore, QtWidgets, QtGui
-from gradient_info_handler import GradientInfoHandler
-from gradient_list_model import GradientListModel
+from .gradient_info_handler import GradientInfoHandler
+from .gradient_list_model import GradientListModel
 
 
 #Gradient Preset Widget
diff --git a/modules/gui/pymod/scene/hsc_widget.py b/modules/gui/pymod/scene/hsc_widget.py
index 442639a71..8ea2d4ede 100644
--- a/modules/gui/pymod/scene/hsc_widget.py
+++ b/modules/gui/pymod/scene/hsc_widget.py
@@ -21,7 +21,7 @@
 from ost import gui
 from ost import gfx
 from PyQt5 import QtCore, QtWidgets
-from render_mode_widget import RenderModeWidget
+from .render_mode_widget import RenderModeWidget
 
 #Tube Render Options
 class HSCWidget(RenderModeWidget):
diff --git a/modules/gui/pymod/scene/inspector_widget.py b/modules/gui/pymod/scene/inspector_widget.py
index 503d3acdc..d3d78d034 100644
--- a/modules/gui/pymod/scene/inspector_widget.py
+++ b/modules/gui/pymod/scene/inspector_widget.py
@@ -24,12 +24,12 @@ from ost import gfx
 import ost
 import os
 from PyQt5 import QtCore, QtWidgets
-from toolbar_options_widget import ToolBarOptionsWidget 
-from render_options_widget import RenderOptionsWidget
-from color_options_widget import ColorOptionsWidget
+from .toolbar_options_widget import ToolBarOptionsWidget 
+from .render_options_widget import RenderOptionsWidget
+from .color_options_widget import ColorOptionsWidget
 from ost.gui.scene.scene_observer_impl import SceneObserverImpl
-from map_level_widget import AdditionalSettingsWidget
-from scene_selection_helper import SelHelper
+from .map_level_widget import AdditionalSettingsWidget
+from .scene_selection_helper import SelHelper
 
 class InspectorWidget(ToolBarOptionsWidget):
   ICONS_PATH = os.path.join(ost.GetSharedDataPath(), "scene", "icons/")
diff --git a/modules/gui/pymod/scene/line_trace_widget.py b/modules/gui/pymod/scene/line_trace_widget.py
index 569f6dfcc..b9c6d7695 100644
--- a/modules/gui/pymod/scene/line_trace_widget.py
+++ b/modules/gui/pymod/scene/line_trace_widget.py
@@ -21,7 +21,7 @@
 from ost import gui
 from ost import gfx
 from PyQt5 import QtCore, QtWidgets
-from render_mode_widget import RenderModeWidget
+from .render_mode_widget import RenderModeWidget
 
 #Simple Render Options
 class LineTraceWidget(RenderModeWidget):
diff --git a/modules/gui/pymod/scene/map_level_widget.py b/modules/gui/pymod/scene/map_level_widget.py
index b5c0905de..e9a890be8 100644
--- a/modules/gui/pymod/scene/map_level_widget.py
+++ b/modules/gui/pymod/scene/map_level_widget.py
@@ -28,7 +28,7 @@ except ImportError:
   pass
 from PyQt5 import QtCore, QtWidgets
 
-from preset_widget import PresetWidget
+from .preset_widget import PresetWidget
 
 class AdditionalSettingsWidget(QtWidgets.QStackedWidget):
   def __init__(self, parent=None):
diff --git a/modules/gui/pymod/scene/preset.py b/modules/gui/pymod/scene/preset.py
index 279765d7c..f06a477df 100644
--- a/modules/gui/pymod/scene/preset.py
+++ b/modules/gui/pymod/scene/preset.py
@@ -106,7 +106,7 @@ class Preset:
             op = op_class.FromInfo(op_group)
             class_order_dict[index]=op
     for i in range(0, len(class_order_dict)):
-      if(class_order_dict.has_key(i)):
+      if(i in class_order_dict):
         preset.AddOp(class_order_dict[i])
     return preset
     
diff --git a/modules/gui/pymod/scene/preset_editor_list_model.py b/modules/gui/pymod/scene/preset_editor_list_model.py
index 35fac9836..a87800695 100644
--- a/modules/gui/pymod/scene/preset_editor_list_model.py
+++ b/modules/gui/pymod/scene/preset_editor_list_model.py
@@ -3,7 +3,7 @@ from ost import gfx
 import os
 import ost
 from PyQt5 import QtCore
-from preset import Preset
+from .preset import Preset
 
 class PresetEditorListModel(QtCore.QAbstractListModel):
   def __init__(self, preset, parent=None, *args): 
diff --git a/modules/gui/pymod/scene/preset_editor_widget.py b/modules/gui/pymod/scene/preset_editor_widget.py
index c5a314efb..5c3ce6aa6 100644
--- a/modules/gui/pymod/scene/preset_editor_widget.py
+++ b/modules/gui/pymod/scene/preset_editor_widget.py
@@ -24,21 +24,21 @@ from ost import mol
 from datetime import datetime
 from datetime import datetime
 from PyQt5 import QtCore, QtWidgets, QtGui
-from color_select_widget import ColorSelectWidget
-from gradient_preset_widget import GradientPresetWidget
-from gradient_editor_widget import GradientPreview
-from gradient_editor_widget import GradientEdit
-from preset_editor_list_model import PresetEditorListModel
-from immutable_gradient_info_handler import ImmutableGradientInfoHandler
-from query_editor import QueryEditorWidget
+from .color_select_widget import ColorSelectWidget
+from .gradient_preset_widget import GradientPresetWidget
+from .gradient_editor_widget import GradientPreview
+from .gradient_editor_widget import GradientEdit
+from .preset_editor_list_model import PresetEditorListModel
+from .immutable_gradient_info_handler import ImmutableGradientInfoHandler
+from .query_editor import QueryEditorWidget
 from ost.mol import Prop
 from ost.gfx import ByElementColorOp
 from ost.gfx import ByChainColorOp
 from ost.gfx import GradientLevelColorOp
 from ost.gfx import UniformColorOp
-from preset import Preset
-from render_op import RenderOp
-from visibility_op import VisibilityOp
+from .preset import Preset
+from .render_op import RenderOp
+from .visibility_op import VisibilityOp
 
 #Preset Editor
 class PresetEditor(QtWidgets.QDialog):
diff --git a/modules/gui/pymod/scene/preset_info_handler.py b/modules/gui/pymod/scene/preset_info_handler.py
index 25237b3bd..de1331668 100644
--- a/modules/gui/pymod/scene/preset_info_handler.py
+++ b/modules/gui/pymod/scene/preset_info_handler.py
@@ -21,7 +21,7 @@
 from ost import gui
 from ost import gfx
 from ost import info
-from immutable_preset_info_handler import ImmutablePresetInfoHandler
+from .immutable_preset_info_handler import ImmutablePresetInfoHandler
 
 #Preset Info Handler
 class PresetInfoHandler(ImmutablePresetInfoHandler):
diff --git a/modules/gui/pymod/scene/preset_list_model.py b/modules/gui/pymod/scene/preset_list_model.py
index dbe1926ad..6bdf05db7 100644
--- a/modules/gui/pymod/scene/preset_list_model.py
+++ b/modules/gui/pymod/scene/preset_list_model.py
@@ -3,8 +3,8 @@ from ost import gfx
 import os
 import ost
 from PyQt5 import QtCore, QtWidgets, QtGui
-from immutable_preset_info_handler import ImmutablePresetInfoHandler
-from preset_info_handler import PresetInfoHandler
+from .immutable_preset_info_handler import ImmutablePresetInfoHandler
+from .preset_info_handler import PresetInfoHandler
 class PresetListModel(QtCore.QAbstractListModel):
   
   IMMUTABLE_PRESET_PATH = os.path.join(ost.GetSharedDataPath(),"scene", 
@@ -64,11 +64,11 @@ class PresetListModel(QtCore.QAbstractListModel):
   def LoadPresetsFromInfo(self):
     if self.immutable_infoh_:
       presets = self.immutable_infoh_.GetPresets()
-      for k, v in presets.iteritems():
+      for k, v in presets.items():
         self.AddItem(v, self.GetLastRow(), False, False)
     
     presets = self.infoh_.GetPresets()
-    for k, v in presets.iteritems():
+    for k, v in presets.items():
       self.AddItem(v, self.GetLastRow(), True, False)
 
   def GetPreset(self, model_index):
diff --git a/modules/gui/pymod/scene/preset_widget.py b/modules/gui/pymod/scene/preset_widget.py
index aec2f2d29..878e5268c 100644
--- a/modules/gui/pymod/scene/preset_widget.py
+++ b/modules/gui/pymod/scene/preset_widget.py
@@ -23,10 +23,10 @@ import ost
 import os
 from datetime import datetime
 from PyQt5 import QtCore, QtWidgets, QtGui
-from scene_selection_helper import SelHelper
-from preset_list_model import PresetListModel
-from preset_editor_widget import PresetEditor
-from preset import Preset
+from .scene_selection_helper import SelHelper
+from .preset_list_model import PresetListModel
+from .preset_editor_widget import PresetEditor
+from .preset import Preset
 
 class PresetWidget(QtWidgets.QWidget):
   PRESET_XML_FILE = os.path.join(ost.GetSharedDataPath(), "scene", "presets.xml")
diff --git a/modules/gui/pymod/scene/remote.py b/modules/gui/pymod/scene/remote.py
index 3f4459914..dea7de08d 100644
--- a/modules/gui/pymod/scene/remote.py
+++ b/modules/gui/pymod/scene/remote.py
@@ -28,7 +28,7 @@ class RemoteLoader(QtWidgets.QWidget):
   def _RemoteMenu(self):
     menu = QtWidgets.QMenu()
     action_group = QtWidgets.QActionGroup(menu)
-    for k,v in REMOTE_REPOSITORIES.iteritems():
+    for k,v in REMOTE_REPOSITORIES.items():
       action = menu.addAction(v.name)
       action.setCheckable(True)
       if k == 'pdb':
@@ -56,13 +56,13 @@ class RemoteLoader(QtWidgets.QWidget):
     for split_id in split_ids:
       try:
         ent = RemoteLoad(split_id, from_repo=self._current_repo)
-      except Exception, e:
+      except Exception as e:
         LogError(str(e))
         continue
       g = gfx.Entity(split_id, ent)
       try:
         gfx.Scene().Add(g)
-      except Exception, e:
+      except Exception as e:
         LogError(str(e))
 
 remote_loader=RemoteLoader()
diff --git a/modules/gui/pymod/scene/render_mode_widget.py b/modules/gui/pymod/scene/render_mode_widget.py
index 31b33ddd7..520f6be65 100644
--- a/modules/gui/pymod/scene/render_mode_widget.py
+++ b/modules/gui/pymod/scene/render_mode_widget.py
@@ -31,10 +31,10 @@ class RenderModeWidget(QtWidgets.QWidget):
     self.entities_ = set()
 
   def GetText(self):
-    raise NotImplementedError, "Subclasses must define GetText()"
+    raise NotImplementedError("Subclasses must define GetText()")
   
   def GetRenderMode(self):
-    raise NotImplementedError, "Subclasses must define GetRenderMode()"
+    raise NotImplementedError("Subclasses must define GetRenderMode()")
     
   def UpdateGui(self, options):
     pass
diff --git a/modules/gui/pymod/scene/render_options_widget.py b/modules/gui/pymod/scene/render_options_widget.py
index 90cc213e8..069b87dda 100644
--- a/modules/gui/pymod/scene/render_options_widget.py
+++ b/modules/gui/pymod/scene/render_options_widget.py
@@ -23,22 +23,22 @@ from ost import gui
 from ost import gfx
 try: 
   from ost import img
-  from wireframe_widget import WireframeWidget
+  from .wireframe_widget import WireframeWidget
   _img_present=True
 except ImportError:
   _img_present=False
   pass
 from PyQt5 import QtCore, QtWidgets
-from scene_selection_helper import SelHelper
-from combo_options_widget import ComboOptionsWidget
-from custom_widget import CustomWidget
-from cpk_widget import CPKWidget
-from tube_widget import TubeWidget
-from simple_widget import SimpleWidget
-from sline_widget import SlineWidget
-from hsc_widget import HSCWidget
-from trace_widget import TraceWidget
-from line_trace_widget import LineTraceWidget
+from .scene_selection_helper import SelHelper
+from .combo_options_widget import ComboOptionsWidget
+from .custom_widget import CustomWidget
+from .cpk_widget import CPKWidget
+from .tube_widget import TubeWidget
+from .simple_widget import SimpleWidget
+from .sline_widget import SlineWidget
+from .hsc_widget import HSCWidget
+from .trace_widget import TraceWidget
+from .line_trace_widget import LineTraceWidget
 
 class RenderOptionsWidget(ComboOptionsWidget):
   def __init__(self, parent=None):
diff --git a/modules/gui/pymod/scene/simple_widget.py b/modules/gui/pymod/scene/simple_widget.py
index 53e172afd..198426dd1 100644
--- a/modules/gui/pymod/scene/simple_widget.py
+++ b/modules/gui/pymod/scene/simple_widget.py
@@ -21,7 +21,7 @@
 from ost import gui
 from ost import gfx
 from PyQt5 import QtCore, QtWidgets
-from render_mode_widget import RenderModeWidget
+from .render_mode_widget import RenderModeWidget
 
 #Simple Render Options
 class SimpleWidget(RenderModeWidget):
diff --git a/modules/gui/pymod/scene/sline_widget.py b/modules/gui/pymod/scene/sline_widget.py
index c42f2f98f..890506bed 100644
--- a/modules/gui/pymod/scene/sline_widget.py
+++ b/modules/gui/pymod/scene/sline_widget.py
@@ -21,7 +21,7 @@
 from ost import gui
 from ost import gfx
 from PyQt5 import QtCore, QtWidgets
-from render_mode_widget import RenderModeWidget
+from .render_mode_widget import RenderModeWidget
 
 #Simple Render Options
 class SlineWidget(RenderModeWidget):
diff --git a/modules/gui/pymod/scene/toolbar_options_widget.py b/modules/gui/pymod/scene/toolbar_options_widget.py
index 55e45b046..638ee2228 100644
--- a/modules/gui/pymod/scene/toolbar_options_widget.py
+++ b/modules/gui/pymod/scene/toolbar_options_widget.py
@@ -100,7 +100,7 @@ class ToolBarOptionsWidget(QtWidgets.QWidget):
      This abstract method must be implemented by all subclasses. 
      It can be used to do something ;-) whenever the combobox changes its value.
     """
-    raise NotImplementedError, "Subclasses must define OnComboChange()"
+    raise NotImplementedError("Subclasses must define OnComboChange()")
   
   def DoResize(self):
     item = self.__GetCurrentWidget()
diff --git a/modules/gui/pymod/scene/trace_widget.py b/modules/gui/pymod/scene/trace_widget.py
index 8705d18d8..b68663e70 100644
--- a/modules/gui/pymod/scene/trace_widget.py
+++ b/modules/gui/pymod/scene/trace_widget.py
@@ -21,7 +21,7 @@
 from ost import gui
 from ost import gfx
 from PyQt5 import QtCore, QtWidgets
-from render_mode_widget import RenderModeWidget
+from .render_mode_widget import RenderModeWidget
 
 #Trace Render Options
 class TraceWidget(RenderModeWidget):
diff --git a/modules/gui/pymod/scene/tube_widget.py b/modules/gui/pymod/scene/tube_widget.py
index 682fd0b05..2d7538f4d 100644
--- a/modules/gui/pymod/scene/tube_widget.py
+++ b/modules/gui/pymod/scene/tube_widget.py
@@ -21,7 +21,7 @@
 from ost import gui
 from ost import gfx
 from PyQt5 import QtCore, QtWidgets
-from render_mode_widget import RenderModeWidget
+from .render_mode_widget import RenderModeWidget
 
 #Tube Render Options
 class TubeWidget(RenderModeWidget):
diff --git a/modules/gui/pymod/scene/uniform_color_widget.py b/modules/gui/pymod/scene/uniform_color_widget.py
index 2459cbc4c..fb97cb853 100644
--- a/modules/gui/pymod/scene/uniform_color_widget.py
+++ b/modules/gui/pymod/scene/uniform_color_widget.py
@@ -28,7 +28,7 @@ except ImportError:
   _img_present=False
   pass
 from PyQt5 import QtCore, QtWidgets, QtGui
-from color_select_widget import ColorSelectWidget
+from .color_select_widget import ColorSelectWidget
 
 #Uniform Color Widget
 class UniformColorWidget(QtWidgets.QWidget):
diff --git a/modules/gui/pymod/scene/wireframe_widget.py b/modules/gui/pymod/scene/wireframe_widget.py
index 458a8f09b..76f2662f3 100644
--- a/modules/gui/pymod/scene/wireframe_widget.py
+++ b/modules/gui/pymod/scene/wireframe_widget.py
@@ -27,7 +27,7 @@ try:
 except ImportError:
   _img_present=False
   pass
-from scene_selection_helper import SelHelper
+from .scene_selection_helper import SelHelper
 
 #Wireframe Options
 class WireframeWidget(QtWidgets.QWidget):
diff --git a/modules/gui/pymod/table.py b/modules/gui/pymod/table.py
index d45b5d74e..cc6537a9b 100644
--- a/modules/gui/pymod/table.py
+++ b/modules/gui/pymod/table.py
@@ -51,7 +51,7 @@ class Table(QTableView):
      QObject.connect(self, SIGNAL('doubleClicked(QModelIndex)'), 
                      self.OnDoubleClick)
   def OnDoubleClick(self, model_index):
-    print 'DOUBLE'
+    print('DOUBLE')
     if not self.double_click:
       return
     row = table.TableRow(self._model.table.rows[model_index.row()],
diff --git a/modules/gui/pymod/trajectory_viewer.py b/modules/gui/pymod/trajectory_viewer.py
index 133091fce..32960ffa1 100644
--- a/modules/gui/pymod/trajectory_viewer.py
+++ b/modules/gui/pymod/trajectory_viewer.py
@@ -255,9 +255,9 @@ class TrajWidget(_QWidget):
     ref_eh=self.ehlist_[self.ref_index_]
     ref_v=ref_eh.Select(str(self._align_selection.text()))
     if ref_v.GetAtomCount()<=3:
-      print 'not enough atoms for alignment'
+      print('not enough atoms for alignment')
       return
-    for i,t,eh in zip(range(len(self.trajlist_)),self.trajlist_,self.ehlist_):
+    for i,t,eh in zip(list(range(len(self.trajlist_))),self.trajlist_,self.ehlist_):
       t=_ost.mol.alg.SuperposeFrames(t,eh.Select(str(self._align_selection.text())),ref_v)
       self.trajlist_[i]=t
       
@@ -342,10 +342,10 @@ class TrajWidget(_QWidget):
   def SetSpeed(self,val):
   #Value should be between 0 and 1
     if not (val<=1. and val >=0.):
-      print 'Speed should be set between 0 and 1'
+      print('Speed should be set between 0 and 1')
       return
     else:
       val=self._speed_slider_min-val*(self._speed_slider_min-self._speed_slider_max)
       self._SetSpeedSliderPos(val)
-      print val
+      print(val)
       
diff --git a/modules/gui/pymod/wrap_gui.cc b/modules/gui/pymod/wrap_gui.cc
index b24863ca8..64952c829 100644
--- a/modules/gui/pymod/wrap_gui.cc
+++ b/modules/gui/pymod/wrap_gui.cc
@@ -77,7 +77,7 @@ namespace {
 
     static void* convertible(PyObject* obj_ptr)
     {
-      if (!PyString_Check(obj_ptr)) return 0;
+      if (!PyUnicode_Check(obj_ptr)) return 0;
       return obj_ptr;
     }
 
@@ -85,8 +85,15 @@ namespace {
       PyObject* obj_ptr,
       boost::python::converter::rvalue_from_python_stage1_data* data)
     {
-      const char* value = PyString_AsString(obj_ptr);
-      if (value == 0) boost::python::throw_error_already_set();
+      PyObject* temp_bytes = PyUnicode_AsEncodedString(obj_ptr, "UTF-8", "strict");
+      char* value = NULL;
+      if (temp_bytes != NULL) {
+        value = PyBytes_AS_STRING(temp_bytes);
+        value = strdup(value);
+        Py_DECREF(temp_bytes);
+      }
+      if (value == NULL) boost::python::throw_error_already_set();
+
       void* storage = (
         (boost::python::converter::rvalue_from_python_storage<QString>*)
           data)->storage.bytes;
diff --git a/modules/gui/src/python_shell/python_interpreter_worker.hh b/modules/gui/src/python_shell/python_interpreter_worker.hh
index 557604900..66639a2cd 100644
--- a/modules/gui/src/python_shell/python_interpreter_worker.hh
+++ b/modules/gui/src/python_shell/python_interpreter_worker.hh
@@ -23,8 +23,12 @@
 
 // workaround for QTBUG-22829: https://bugreports.qt-project.org/browse/QTBUG-22829
 #ifndef Q_MOC_RUN
+  // https://stackoverflow.com/questions/23068700/embedding-python3-in-qt-5
+  #pragma push_macro("slots")
+  #undef slots
   #include <boost/python.hpp>
   #include <boost/shared_ptr.hpp>
+  #pragma pop_macro("slots")
 #endif
 #include "output_redirector.hh"
 #include <csignal>
diff --git a/modules/io/pymod/__init__.py b/modules/io/pymod/__init__.py
index 2a8232118..eee7af12d 100644
--- a/modules/io/pymod/__init__.py
+++ b/modules/io/pymod/__init__.py
@@ -16,7 +16,7 @@
 # along with this library; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 #------------------------------------------------------------------------------
-import os, tempfile, ftplib, httplib
+import os, tempfile, ftplib, http.client
 
 from _ost_io import *
 from ost import mol, geom, conop, seq
diff --git a/modules/io/pymod/remote.py b/modules/io/pymod/remote.py
index 1f593060f..c0d8990e0 100644
--- a/modules/io/pymod/remote.py
+++ b/modules/io/pymod/remote.py
@@ -17,7 +17,7 @@
 # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 #------------------------------------------------------------------------------
 
-import urllib2
+import urllib.request, urllib.error, urllib.parse
 import tempfile
 
 from ost.io import LoadPDB, LoadMMCIF
@@ -49,12 +49,12 @@ class RemoteRepository:
       tmp_file_suffix+='.gz'
 
     try:
-      connection = urllib2.urlopen(remote_url)
+      connection = urllib.request.urlopen(remote_url)
       if hasattr(connection, 'code'):
         status = connection.code
       else:
         status = connection.getcode()
-    except urllib2.HTTPError, e:
+    except urllib.error.HTTPError as e:
       status = e.code
     if status != 200:
       raise IOError('Could not load %s from %s (status code %d, url %s)' \
diff --git a/modules/io/tests/test_io_mmcif.py b/modules/io/tests/test_io_mmcif.py
index e0672fc39..8a3773b0a 100644
--- a/modules/io/tests/test_io_mmcif.py
+++ b/modules/io/tests/test_io_mmcif.py
@@ -10,51 +10,51 @@ class TestMMCifInfo(unittest.TestCase):
     c = io.MMCifInfoCitation()
     # test ID setting/ getting
     c.SetID('ID')
-    self.assertEquals(c.GetID(), 'ID')
+    self.assertEqual(c.GetID(), 'ID')
     # test CAS setting/ getting
     c.SetCAS('FOO')
-    self.assertEquals(c.GetCAS(), 'FOO')
+    self.assertEqual(c.GetCAS(), 'FOO')
     # test ISBN setting/ getting
     c.SetISBN('0-0-0-0-0-0')
-    self.assertEquals(c.GetISBN(), '0-0-0-0-0-0')
+    self.assertEqual(c.GetISBN(), '0-0-0-0-0-0')
     # test published_in setting/ getting
     c.SetPublishedIn('Best Book Ever')
-    self.assertEquals(c.GetPublishedIn(), 'Best Book Ever')
+    self.assertEqual(c.GetPublishedIn(), 'Best Book Ever')
     # test volume setting/ getting
     c.SetVolume('3')
-    self.assertEquals(c.GetVolume(), '3')
+    self.assertEqual(c.GetVolume(), '3')
     # test page setting/ getting
     c.SetPageFirst('1')
-    self.assertEquals(c.GetPageFirst(), '1')
+    self.assertEqual(c.GetPageFirst(), '1')
     c.SetPageLast('10')
-    self.assertEquals(c.GetPageLast(), '10')
+    self.assertEqual(c.GetPageLast(), '10')
     # test doi setting/ getting
     c.SetDOI('HERE')
-    self.assertEquals(c.GetDOI(), 'HERE')
+    self.assertEqual(c.GetDOI(), 'HERE')
     # test PubMed setting/ getting
     c.SetPubMed(815)
-    self.assertEquals(c.GetPubMed(), 815)
+    self.assertEqual(c.GetPubMed(), 815)
     # test year setting/ getting
     c.SetYear(815)
-    self.assertEquals(c.GetYear(), 815)
+    self.assertEqual(c.GetYear(), 815)
     # test title setting/ getting
     c.SetTitle('Foo')
-    self.assertEquals(c.GetTitle(), 'Foo')
+    self.assertEqual(c.GetTitle(), 'Foo')
     # test book_publisher set & get
     c.SetBookPublisher("Hugo")
-    self.assertEquals(c.GetBookPublisher(), "Hugo")
+    self.assertEqual(c.GetBookPublisher(), "Hugo")
     # test book_publisher_city set & get
     c.SetBookPublisherCity("Basel")
-    self.assertEquals(c.book_publisher_city, "Basel")
+    self.assertEqual(c.book_publisher_city, "Basel")
     # test citation type
     self.assertTrue(c.IsCitationTypeUnknown())
-    self.assertEquals(c.citation_type, io.MMCifInfoCType.Unknown)
+    self.assertEqual(c.citation_type, io.MMCifInfoCType.Unknown)
     # test auhtors setting/ getting
     s = ost.StringList()
     s.append('Foo')
     c.SetAuthorList(s)
     s2 = c.GetAuthorList()
-    self.assertEquals(s2[0], 'Foo')
+    self.assertEqual(s2[0], 'Foo')
 
     i = io.MMCifInfo()
     i.SetMethod('Deep-Fry')
@@ -64,31 +64,31 @@ class TestMMCifInfo(unittest.TestCase):
     i.AddAuthorsToCitation('ID', s)
 
     cl = i.GetCitations()
-    self.assertEquals(len(cl), 1)
+    self.assertEqual(len(cl), 1)
     al = cl[0].GetAuthorList()
-    self.assertEquals(len(al), 2)
-    self.assertEquals(al[0], 'Foo')
-    self.assertEquals(al[1], 'Bar')
+    self.assertEqual(len(al), 2)
+    self.assertEqual(al[0], 'Foo')
+    self.assertEqual(al[1], 'Bar')
 
-    self.assertEquals(i.GetMethod(), 'Deep-Fry')
-    self.assertEquals(i.GetResolution(), 2.0)
+    self.assertEqual(i.GetMethod(), 'Deep-Fry')
+    self.assertEqual(i.GetResolution(), 2.0)
 
 
   def test_mmcifinfo_biounit(self):
     b = io.MMCifInfoBioUnit()
     b.SetDetails('Details')
     b.SetMethodDetails('MethodDetails')
-    self.assertEquals(b.GetDetails(), 'Details')
-    self.assertEquals(b.GetMethodDetails(), 'MethodDetails')
+    self.assertEqual(b.GetDetails(), 'Details')
+    self.assertEqual(b.GetMethodDetails(), 'MethodDetails')
     b.method_details = 'AttrMethodDetails'
-    self.assertEquals(b.method_details, 'AttrMethodDetails')
+    self.assertEqual(b.method_details, 'AttrMethodDetails')
     b.AddChain('A')
     b.AddChain('B')
     cl = b.GetChainList()
     il = b.GetChainIntervalList()
-    self.assertEquals(cl[0], 'A')
-    self.assertEquals(il[0][0], 0)
-    self.assertEquals(il[0][1], 2)
+    self.assertEqual(cl[0], 'A')
+    self.assertEqual(il[0][0], 0)
+    self.assertEqual(il[0][1], 2)
     s = ost.StringList()
     s.append('B')
     s.append('C')
@@ -96,11 +96,11 @@ class TestMMCifInfo(unittest.TestCase):
     b.SetChainList(s)
     cl = b.GetChainList()
     il = b.GetChainIntervalList()
-    self.assertEquals(il[0][0], 0)
-    self.assertEquals(il[0][1], 3)
-    self.assertEquals(cl[0], 'B')
-    self.assertEquals(cl[1], 'C')
-    self.assertEquals(cl[2], 'D')
+    self.assertEqual(il[0][0], 0)
+    self.assertEqual(il[0][1], 3)
+    self.assertEqual(cl[0], 'B')
+    self.assertEqual(cl[1], 'C')
+    self.assertEqual(cl[2], 'D')
 
     i = io.MMCifInfo()
     i.AddBioUnit(b)
@@ -110,40 +110,40 @@ class TestMMCifInfo(unittest.TestCase):
 
     bl = i.GetBioUnits()
     il = bl[0].GetChainIntervalList()
-    self.assertEquals(il[0][0], 0)
-    self.assertEquals(il[0][1], 3)
-    self.assertEquals(il[1][0], 3)
-    self.assertEquals(il[1][1], 6)
-    self.assertEquals(len(bl), 2)
+    self.assertEqual(il[0][0], 0)
+    self.assertEqual(il[0][1], 3)
+    self.assertEqual(il[1][0], 3)
+    self.assertEqual(il[1][1], 6)
+    self.assertEqual(len(bl), 2)
 
 
   def test_mmcifinfo_transoperation(self):
     o = io.MMCifInfoTransOp()
     o.SetID("1")
-    self.assertEquals(o.GetID(), '1')
+    self.assertEqual(o.GetID(), '1')
     o.SetType("identity operation")
-    self.assertEquals(o.GetType(), 'identity operation')
+    self.assertEqual(o.GetType(), 'identity operation')
     o.SetVector(1.0, 2.0, 3.0)
-    self.assertEquals(o.GetVector().x, 1.0)
-    self.assertEquals(o.GetVector().y, 2.0)
-    self.assertEquals(o.GetVector().z, 3.0)
+    self.assertEqual(o.GetVector().x, 1.0)
+    self.assertEqual(o.GetVector().y, 2.0)
+    self.assertEqual(o.GetVector().z, 3.0)
     o.SetMatrix(1, 2, 3, 4, 5, 6, 7, 8, 9)
-    self.assertEquals(geom.Equal(o.GetMatrix(),
+    self.assertEqual(geom.Equal(o.GetMatrix(),
                                  geom.Mat3(1, 2, 3, 4, 5, 6, 7, 8, 9)), True)
 
     i = io.MMCifInfo()
     i.AddOperation(o)
     ol = i.GetOperations()
-    self.assertEquals(ol[0].GetID(), '1')
+    self.assertEqual(ol[0].GetID(), '1')
 
     b = io.MMCifInfoBioUnit()
     b.AddOperations(ol)
     oll = b.GetOperations()
-    self.assertEquals(oll[0][0].GetID(), '1')
+    self.assertEqual(oll[0][0].GetID(), '1')
     tr_ol = b.GetOperationsIntervalList()
-    self.assertEquals(len(tr_ol), 1)
-    self.assertEquals(tr_ol[0][0], 0)
-    self.assertEquals(tr_ol[0][1], 1)
+    self.assertEqual(len(tr_ol), 1)
+    self.assertEqual(tr_ol[0][0], 0)
+    self.assertEqual(tr_ol[0][1], 1)
 
   def test_mmcifinfo_biounit_pdbize_with_multiple_transforms(self):
     ent, seqres, info = io.LoadMMCIF("testfiles/mmcif/multiple_transforms.cif.gz",
@@ -151,11 +151,11 @@ class TestMMCifInfo(unittest.TestCase):
                                      info=True)
     pdb_ent = info.GetBioUnits()[0].PDBize(ent)
     chains = pdb_ent.chains
-    self.assertEquals(''.join([c.name for c in chains]), 
+    self.assertEqual(''.join([c.name for c in chains]), 
                       'A_-BCD')
     ligand_chain = chains[1]
     ligand_residues = ligand_chain.residues
-    self.assertEquals([r.number for r in ligand_residues],
+    self.assertEqual([r.number for r in ligand_residues],
                       [mol.ResNum(1), mol.ResNum(2), mol.ResNum(3), mol.ResNum(4)])
 
   def test_mmcifinfo_biounit_pdbize(self):
@@ -165,22 +165,22 @@ class TestMMCifInfo(unittest.TestCase):
     pdb_ent = info.GetBioUnits()[0].PDBize(ent)
     pdb_seqres_ent = info.GetBioUnits()[0].PDBize(ent, seqres)
 
-    self.assertEquals(''.join([c.name for c in pdb_ent.chains]),
+    self.assertEqual(''.join([c.name for c in pdb_ent.chains]),
                       'AB_-CDEFGH')
-    self.assertEquals([c.residue_count for c in pdb_ent.chains],
+    self.assertEqual([c.residue_count for c in pdb_ent.chains],
                       [415, 414, 64, 3816, 415, 414, 415, 414, 415, 414])
-    self.assertEquals([c.atom_count for c in pdb_ent.chains],
+    self.assertEqual([c.atom_count for c in pdb_ent.chains],
                       [3231, 3223, 268, 3816, 3231, 3223, 3231, 3223, 3231, 3223])
-    self.assertEquals([c.GetBondCount() for c in pdb_ent.chains],
+    self.assertEqual([c.GetBondCount() for c in pdb_ent.chains],
                       [3311, 3303, 204, 0, 3311, 3303, 3311, 3303, 3311, 3303])
 
-    self.assertEquals(''.join([c.name for c in pdb_seqres_ent.chains]),
+    self.assertEqual(''.join([c.name for c in pdb_seqres_ent.chains]),
                       ''.join([c.name for c in pdb_ent.chains]))
-    self.assertEquals([c.residue_count for c in pdb_seqres_ent.chains],
+    self.assertEqual([c.residue_count for c in pdb_seqres_ent.chains],
                       [c.residue_count for c in pdb_ent.chains])
-    self.assertEquals([c.atom_count for c in pdb_seqres_ent.chains],
+    self.assertEqual([c.atom_count for c in pdb_seqres_ent.chains],
                       [c.atom_count for c in pdb_ent.chains])
-    self.assertEquals([c.GetBondCount() for c in pdb_seqres_ent.chains],
+    self.assertEqual([c.GetBondCount() for c in pdb_seqres_ent.chains],
                       [c.GetBondCount() for c in pdb_ent.chains])
 
   def test_mmcifinfo_biounit_pdbize_transformation(self):
@@ -188,10 +188,10 @@ class TestMMCifInfo(unittest.TestCase):
                                      seqres=True,
                                      info=True)
     pdb_ent, t = info.GetBioUnits()[0].PDBize(ent, transformation=True)
-    self.assertAlmostEquals(pdb_ent.GetCenterOfAtoms()[0], -915.759, 1)
-    self.assertAlmostEquals(pdb_ent.GetCenterOfAtoms()[1], -952.345, 1)
-    self.assertAlmostEquals(pdb_ent.GetCenterOfAtoms()[2], 3221.75, 1)
-    self.assertEquals(geom.Equal(t,
+    self.assertAlmostEqual(pdb_ent.GetCenterOfAtoms()[0], -915.759, 1)
+    self.assertAlmostEqual(pdb_ent.GetCenterOfAtoms()[1], -952.345, 1)
+    self.assertAlmostEqual(pdb_ent.GetCenterOfAtoms()[2], 3221.75, 1)
+    self.assertEqual(geom.Equal(t,
                                  geom.Mat4(1,0,0,-920.462,
                                            0,1,0,-966.654,
                                            0,0,1,1703,
@@ -209,26 +209,26 @@ class TestMMCifInfo(unittest.TestCase):
     d.SetMassMethod('Good Guess')
     d.SetModelDetails('Created with SwissModel')
     d.SetModelTypeDetails('Average')
-    self.assertEquals(d.GetEntryID(), '1BAR')
-    self.assertEquals(d.GetTitle(), 'A Title')
-    self.assertEquals(d.GetCASPFlag(), 'N')
-    self.assertEquals(d.GetDescriptor(), 'FooBar')
-    self.assertEquals(d.GetMass(), 1.0)
-    self.assertEquals(d.GetMassMethod(), 'Good Guess')
-    self.assertEquals(d.GetModelDetails(), 'Created with SwissModel')  
-    self.assertEquals(d.GetModelTypeDetails(), 'Average') 
+    self.assertEqual(d.GetEntryID(), '1BAR')
+    self.assertEqual(d.GetTitle(), 'A Title')
+    self.assertEqual(d.GetCASPFlag(), 'N')
+    self.assertEqual(d.GetDescriptor(), 'FooBar')
+    self.assertEqual(d.GetMass(), 1.0)
+    self.assertEqual(d.GetMassMethod(), 'Good Guess')
+    self.assertEqual(d.GetModelDetails(), 'Created with SwissModel')  
+    self.assertEqual(d.GetModelTypeDetails(), 'Average') 
 
     i = io.MMCifInfo()
     i.SetStructDetails(d)
-    self.assertEquals(i.GetStructDetails().GetEntryID(), '1BAR')
-    self.assertEquals(i.GetStructDetails().GetTitle(), 'A Title')
-    self.assertEquals(i.GetStructDetails().GetCASPFlag(), 'N')
-    self.assertEquals(i.GetStructDetails().GetDescriptor(), 'FooBar')
-    self.assertEquals(i.GetStructDetails().GetMass(), 1.0)
-    self.assertEquals(i.GetStructDetails().GetMassMethod(), 'Good Guess')
-    self.assertEquals(i.GetStructDetails().GetModelDetails(),
+    self.assertEqual(i.GetStructDetails().GetEntryID(), '1BAR')
+    self.assertEqual(i.GetStructDetails().GetTitle(), 'A Title')
+    self.assertEqual(i.GetStructDetails().GetCASPFlag(), 'N')
+    self.assertEqual(i.GetStructDetails().GetDescriptor(), 'FooBar')
+    self.assertEqual(i.GetStructDetails().GetMass(), 1.0)
+    self.assertEqual(i.GetStructDetails().GetMassMethod(), 'Good Guess')
+    self.assertEqual(i.GetStructDetails().GetModelDetails(),
                       'Created with SwissModel')
-    self.assertEquals(i.GetStructDetails().GetModelTypeDetails(), 'Average')
+    self.assertEqual(i.GetStructDetails().GetModelTypeDetails(), 'Average')
 
   def test_mmcifinfo_obsolete(self):
     obs = io.MMCifInfoObsolete()
@@ -236,18 +236,18 @@ class TestMMCifInfo(unittest.TestCase):
     obs.SetID('SPRSDE')
     obs.SetPDBID('1FOO')
     obs.SetReplacedPDBID('2BAR')
-    self.assertEquals(obs.GetDate(), '2011-08-31')
-    self.assertEquals(obs.GetID(), 'Supersede')
-    self.assertEquals(obs.GetPDBID(), '1FOO')
-    self.assertEquals(obs.GetReplacedPDBID(), '2BAR')
+    self.assertEqual(obs.GetDate(), '2011-08-31')
+    self.assertEqual(obs.GetID(), 'Supersede')
+    self.assertEqual(obs.GetPDBID(), '1FOO')
+    self.assertEqual(obs.GetReplacedPDBID(), '2BAR')
 
     i = io.MMCifInfo()
     obs.id = 'OBSLTE'
     i.SetObsoleteInfo(obs)
-    self.assertEquals(i.GetObsoleteInfo().GetDate(), '2011-08-31')
-    self.assertEquals(i.GetObsoleteInfo().GetID(), 'Obsolete')
-    self.assertEquals(i.GetObsoleteInfo().GetPDBID(), '1FOO')
-    self.assertEquals(i.GetObsoleteInfo().GetReplacedPDBID(), '2BAR')
+    self.assertEqual(i.GetObsoleteInfo().GetDate(), '2011-08-31')
+    self.assertEqual(i.GetObsoleteInfo().GetID(), 'Obsolete')
+    self.assertEqual(i.GetObsoleteInfo().GetPDBID(), '1FOO')
+    self.assertEqual(i.GetObsoleteInfo().GetReplacedPDBID(), '2BAR')
 
 if __name__== '__main__':
   from ost import testutils
diff --git a/modules/io/tests/test_io_pdb.py b/modules/io/tests/test_io_pdb.py
index f3f3acbdf..3ba3539dc 100644
--- a/modules/io/tests/test_io_pdb.py
+++ b/modules/io/tests/test_io_pdb.py
@@ -7,9 +7,9 @@ class TestPDB(unittest.TestCase):
 
   def test_compnd_parser(self):
     e=io.LoadPDB('testfiles/pdb/compnd.pdb', restrict_chains="A")
-    self.assertEquals(e.GetChainCount(), 1)
+    self.assertEqual(e.GetChainCount(), 1)
     ch = e.FindChain("A");
-    self.assertEquals(ch.GetIntProp("mol_id"), 1)
+    self.assertEqual(ch.GetIntProp("mol_id"), 1)
 
   def test_properly_assigns_profile_properties(self):
     io.profiles['TEST'] = io.IOProfile()
diff --git a/modules/mol/alg/pymod/hbond.py b/modules/mol/alg/pymod/hbond.py
index 33545bd2f..0807fe833 100644
--- a/modules/mol/alg/pymod/hbond.py
+++ b/modules/mol/alg/pymod/hbond.py
@@ -139,10 +139,10 @@ class HBondDonor:
     _donor=res.FindAtom(donor_name).handle
     _hydrogen=res.FindAtom(hydrogen_name).handle
     if not _donor.IsValid():
-      if verbose:print 'Could not find '+donor_name+' in residue '+str(res)
+      if verbose:print('Could not find '+donor_name+' in residue '+str(res))
       return
     if not _hydrogen.IsValid():
-      if verbose:print 'Could not find '+hydrogen_name+' in residue '+str(res)
+      if verbose:print('Could not find '+hydrogen_name+' in residue '+str(res))
       return
     return cls(_donor,_hydrogen)
   
@@ -164,11 +164,11 @@ class HBondAcceptor:
     _acceptor=res.FindAtom(acceptor_name).handle
     _antecedent_list=_ost.mol.AtomHandleList([res.FindAtom(name).handle for name in antecedent_name_list])
     if not _acceptor.IsValid():
-      if verbose:print 'Could not find '+acceptor_name+' in residue '+str(res)
+      if verbose:print('Could not find '+acceptor_name+' in residue '+str(res))
       return
     for i,a in enumerate(_antecedent_list):
       if not a.IsValid():
-        if verbose:print 'Could not find '+antecedent_name_list[i]+' in residue '+str(res)
+        if verbose:print('Could not find '+antecedent_name_list[i]+' in residue '+str(res))
         return
     return cls(_acceptor,_antecedent_list)
   
@@ -223,13 +223,13 @@ def GetHbondDonorAcceptorList(eh,hbond_donor_acceptor_dict={},verbose=True):
   These names are given in a dictionary, which defaults to CHARMM.
   """
   if not hbond_donor_acceptor_dict:
-    print 'Using default CHARMM atom namings'
+    print('Using default CHARMM atom namings')
     hbond_donor_acceptor_dict=BuildCHARMMHBondDonorAcceptorDict()
   donor_list=[]
   acceptor_list=[]
   for r in eh.residues:
     if not r.name in hbond_donor_acceptor_dict:
-      print 'donors and acceptors for',r,'are not defined in the dictionary and will not be included'
+      print('donors and acceptors for',r,'are not defined in the dictionary and will not be included')
       continue
     res_da_dict=hbond_donor_acceptor_dict[r.name]
     for acceptor in res_da_dict.acceptors:
@@ -274,10 +274,10 @@ def GetHbondListFromTraj(t,eh,cutoff=0.7,stride=1,swap=False,donor_swap_dict={},
   """
   if swap:
     if not donor_swap_dict:
-      print 'use of standard CHARMM HBond donor swap dictionary'
+      print('use of standard CHARMM HBond donor swap dictionary')
       donor_swap_dict=BuildCHARMMHBondDonorEquivalenceDict()
     if not acceptor_swap_dict:
-      print 'use of standard CHARMM HBond acceptor swap dictionary'  
+      print('use of standard CHARMM HBond acceptor swap dictionary')  
       acceptor_swap_dict=BuildCHARMMHBondAcceptorEquivalenceDict()
   [donor_list,acceptor_list]=GetHbondDonorAcceptorList(eh,hbond_donor_acceptor_dict,verbose)
   hb_list=[]
@@ -339,10 +339,10 @@ def GetEquivalentHBonds(ref_hbond_list,eh,swap=False,donor_swap_dict={},acceptor
       hbond_list.append(set([HBond(donor,acceptor)]))
   else:
     if not donor_swap_dict:
-      print 'use of standard CHARMM HBond donor swap dictionary'
+      print('use of standard CHARMM HBond donor swap dictionary')
       donor_swap_dict=BuildCHARMMHBondDonorEquivalenceDict()
     if not acceptor_swap_dict:
-      print 'use of standard CHARMM HBond acceptor swap dictionary'  
+      print('use of standard CHARMM HBond acceptor swap dictionary')  
       acceptor_swap_dict=BuildCHARMMHBondAcceptorEquivalenceDict()
     for hbond in ref_hbond_list:
       res1=eh.FindResidue(hbond.donor.heavy_atom.chain.name,hbond.donor.heavy_atom.residue.number.num)
@@ -374,7 +374,7 @@ def CalculateHBondScore(ref_eh,eh2,ref_eh2=None,hbond_donor_acceptor_dict={},swa
   else:hbond_list1=GetHbondListFromView(ref_eh,hbond_donor_acceptor_dict,verbose)
   nbonds=float(len(hbond_list1))
   if nbonds==0:
-    print 'No HBonds in reference view'
+    print('No HBonds in reference view')
     return None
   hbond_list2=GetEquivalentHBonds(hbond_list1,eh2,swap,donor_swap_dict,acceptor_swap_dict,verbose)
   c=0
@@ -392,19 +392,19 @@ def AnalyzeHBondScore(ref_eh,t,eh2,ref_eh2=None,hbond_donor_acceptor_dict={},swa
   """
   if swap:
     if not donor_swap_dict:
-      print 'use of standard CHARMM HBond donor swap dictionary'
+      print('use of standard CHARMM HBond donor swap dictionary')
       donor_swap_dict=BuildCHARMMHBondDonorEquivalenceDict()
     if not acceptor_swap_dict:
-      print 'use of standard CHARMM HBond acceptor swap dictionary'  
+      print('use of standard CHARMM HBond acceptor swap dictionary')  
       acceptor_swap_dict=BuildCHARMMHBondAcceptorEquivalenceDict()
   if ref_eh2:hbond_list1=GetHbondListBetweenViews(ref_eh,ref_eh2,hbond_donor_acceptor_dict,verbose)
   elif type(ref_eh)==list:hbond_list1=ref_eh
   else:hbond_list1=GetHbondListFromView(ref_eh,hbond_donor_acceptor_dict,verbose)
   nbonds=float(len(hbond_list1))
   if nbonds==0:
-    print 'No HBonds in reference view'
+    print('No HBonds in reference view')
     return None
-  print 'number of hbonds in ref_eh:',nbonds
+  print('number of hbonds in ref_eh:',nbonds)
   hbond_list2=GetEquivalentHBonds(hbond_list1,eh2,swap,donor_swap_dict,acceptor_swap_dict,verbose)
   if last==-1:last=t.GetFrameCount()
   score=FloatList()
@@ -423,10 +423,10 @@ def AnalyzeHBondScore(ref_eh,t,eh2,ref_eh2=None,hbond_donor_acceptor_dict={},swa
 def GetHBondListIntersection(ref_hbond_list,ref_eh,hbond_list,swap=False,donor_swap_dict={},acceptor_swap_dict={}):
   if swap:
     if not donor_swap_dict:
-      print 'use of standard CHARMM HBond donor swap dictionary'
+      print('use of standard CHARMM HBond donor swap dictionary')
       donor_swap_dict=BuildCHARMMHBondDonorEquivalenceDict()
     if not acceptor_swap_dict:
-      print 'use of standard CHARMM HBond acceptor swap dictionary'  
+      print('use of standard CHARMM HBond acceptor swap dictionary')  
       acceptor_swap_dict=BuildCHARMMHBondAcceptorEquivalenceDict()
   hbond_list=GetEquivalentHBonds(hbond_list,ref_eh,swap,donor_swap_dict,acceptor_swap_dict)
   ref_hbond_list=GetEquivalentHBonds(ref_hbond_list,ref_eh,swap,donor_swap_dict,acceptor_swap_dict)
diff --git a/modules/mol/alg/pymod/helix_kinks.py b/modules/mol/alg/pymod/helix_kinks.py
index 3bfb86047..7d9de7da0 100644
--- a/modules/mol/alg/pymod/helix_kinks.py
+++ b/modules/mol/alg/pymod/helix_kinks.py
@@ -9,17 +9,17 @@ import ost
 
 def __FindProline(sele,proline):
   if not sele.IsValid():
-    print 'selection is not valid'
+    print('selection is not valid')
     raise RuntimeError
   if proline==False:
     proline=sele.Select('rname=PRO')
   if not proline.GetResidueCount()==1:
-    print proline.GetResidueCount(),'prolines in the selection. One proline is needed'
+    print(proline.GetResidueCount(),'prolines in the selection. One proline is needed')
     raise RuntimeError
   proline=proline.residues[0]
   proline_ca=proline.FindAtom('CA')
   if not proline_ca.IsValid():
-    print 'proline has no CA atom'
+    print('proline has no CA atom')
     raise RuntimeError
   return (proline,proline_ca)
   proline.GetNumber().num
@@ -27,14 +27,14 @@ def __FindProline(sele,proline):
 def __SelectPreAndPostProline(sele,proline_num):
   pre_proline=sele.Select('rnum<'+str(proline_num))
   post_proline=sele.Select('rnum>'+str(proline_num))
-  print 'pre-proline residues'
+  print('pre-proline residues')
   for res in pre_proline.residues:
-    print res
-  print 'post-proline residues'
+    print(res)
+  print('post-proline residues')
   for res in post_proline.residues:
-    print res
+    print(res)
   if pre_proline.GetResidueCount()<4 or post_proline.GetResidueCount()<4:
-    print 'pre and post proline helices should be at least 4 residues long, 7 for better stability'
+    print('pre and post proline helices should be at least 4 residues long, 7 for better stability')
     raise RuntimeError
   return (pre_proline,post_proline)
 
@@ -42,7 +42,7 @@ def __FindCa3AndCa4(sele,proline_ca,proline_num):
   ca_3=sele.FindAtom(proline_ca.GetHandle().GetChain().GetName(),proline_num-3,'CA')
   ca_4=sele.FindAtom(proline_ca.GetHandle().GetChain().GetName(),proline_num-4,'CA')
   if not (ca_3.IsValid() and ca_4.IsValid()):
-    print 'CA not found in (i-4) or (i-3) residue'
+    print('CA not found in (i-4) or (i-3) residue')
     raise RuntimeError
   return (ca_3,ca_4)
 
diff --git a/modules/mol/alg/pymod/qsscoring.py b/modules/mol/alg/pymod/qsscoring.py
index 3ed23ae9e..ba9c2af9d 100644
--- a/modules/mol/alg/pymod/qsscoring.py
+++ b/modules/mol/alg/pymod/qsscoring.py
@@ -1098,7 +1098,7 @@ class OligoLDDTScorer(object):
       nominator = sum([s * w for s, w in zip(scores, weights)])
       if self.penalize_extra_chains:
         ref_scorers = self._GetRefScorers()
-        denominator = sum(s.total_contacts for s in ref_scorers.values())
+        denominator = sum(s.total_contacts for s in list(ref_scorers.values()))
         denominator += self._GetModelPenalty()
       else:
         denominator = sum(weights)
@@ -1259,7 +1259,7 @@ class OligoLDDTScorer(object):
       for ch_name_mdl in sorted(unmapped_mdl_chains):
         # get penalty for chain
         cur_penalty = None
-        for cm_ref, cm_mdl in self.chem_mapping.iteritems():
+        for cm_ref, cm_mdl in self.chem_mapping.items():
           if ch_name_mdl in cm_mdl:
             # penalize by an average of the chem. mapped ref. chains
             cur_penalty = 0
@@ -1633,8 +1633,8 @@ def _GetChemGroupsMapping(qs_ent_1, qs_ent_2):
   chain_pairs = []
   ca_chains_1 = qs_ent_1.ca_chains
   ca_chains_2 = qs_ent_2.ca_chains
-  for ch_1 in repr_chains_1.keys():
-    for ch_2 in repr_chains_2.keys():
+  for ch_1 in list(repr_chains_1.keys()):
+    for ch_2 in list(repr_chains_2.keys()):
       aln = _AlignAtomSeqs(ca_chains_1[ch_1], ca_chains_2[ch_2])
       if aln:
         chains_seqid = seq.alg.SequenceIdentity(aln)
@@ -1646,24 +1646,24 @@ def _GetChemGroupsMapping(qs_ent_1, qs_ent_2):
   chem_mapping = {}
   for _, c1, c2 in chain_pairs:
     skip = False
-    for a,b in chem_mapping.iteritems():
+    for a,b in chem_mapping.items():
       if repr_chains_1[c1] == a or repr_chains_2[c2] == b:
         skip = True
         break
     if not skip:
       chem_mapping[repr_chains_1[c1]] = repr_chains_2[c2]
   if swapped:
-    chem_mapping = {y: x for x, y in chem_mapping.iteritems()}
+    chem_mapping = {y: x for x, y in chem_mapping.items()}
     qs_ent_1, qs_ent_2 = qs_ent_2, qs_ent_1
 
   # notify chains without partner
-  mapped_1 = set([i for s in chem_mapping.keys() for i in s])
+  mapped_1 = set([i for s in list(chem_mapping.keys()) for i in s])
   chains_1 = set([c.name for c in qs_ent_1.ent.chains])
   if chains_1 - mapped_1:
     LogWarning('Unmapped Chains in %s: %s'
                % (qs_ent_1.GetName(), ','.join(list(chains_1 - mapped_1))))
 
-  mapped_2 = set([i for s in chem_mapping.values() for i in s])
+  mapped_2 = set([i for s in list(chem_mapping.values()) for i in s])
   chains_2 = set([c.name for c in qs_ent_2.ent.chains])
   if chains_2 - mapped_2:
     LogWarning('Unmapped Chains in %s: %s'
@@ -1714,7 +1714,7 @@ def _GetAlignedResidues(qs_ent_1, qs_ent_2, chem_mapping, max_ca_per_chain,
   ca_chains_1 = qs_ent_1.ca_chains
   ca_chains_2 = qs_ent_2.ca_chains
   # go through all mapped chemical groups
-  for group_1, group_2 in chem_mapping.iteritems():
+  for group_1, group_2 in chem_mapping.items():
     # MSA with ClustalW
     seq_list = seq.CreateSequenceList()
     # keep sequence-name (must be unique) to view mapping
@@ -1770,9 +1770,9 @@ def _FindSymmetry(qs_ent_1, qs_ent_2, ent_to_cm_1, ent_to_cm_2, chem_mapping):
 
   # get possible symmetry groups
   symm_subg_1 = _GetSymmetrySubgroups(qs_ent_1, ent_to_cm_1,
-                                      chem_mapping.keys())
+                                      list(chem_mapping.keys()))
   symm_subg_2 = _GetSymmetrySubgroups(qs_ent_2, ent_to_cm_2,
-                                      chem_mapping.values())
+                                      list(chem_mapping.values()))
 
   # check combination of groups
   LogInfo('Selecting Symmetry Groups...')
@@ -1878,7 +1878,7 @@ def _GetChainMapping(ent_1, ent_2, symm_1, symm_2, chem_mapping,
   ref_symm_2 = symm_2[0]
   asu_chem_mapping = _LimitChemMapping(chem_mapping, ref_symm_1, ref_symm_2)
   # for each chemically identical group
-  for g1, g2 in asu_chem_mapping.iteritems():
+  for g1, g2 in asu_chem_mapping.items():
     # try to superpose all possible pairs
     for c1, c2 in itertools.product(g1, g2):
       # get superposition transformation
@@ -1906,7 +1906,7 @@ def _GetChainMapping(ent_1, ent_2, symm_1, symm_2, chem_mapping,
     index_asu_c1 = ref_symm_1.index(intra_asu_c1)
     index_asu_c2 = ref_symm_2.index(intra_asu_c2)
     index_mapp = {ref_symm_1.index(s1): ref_symm_2.index(s2) \
-                  for s1, s2 in intra_asu_mapp.iteritems()}
+                  for s1, s2 in intra_asu_mapp.items()}
     LogInfo('Intra symmetry-group mapping: %s' % str(intra_asu_mapp))
 
     # get INTER symmetry group chain mapping
@@ -1928,8 +1928,8 @@ def _GetChainMapping(ent_1, ent_2, symm_1, symm_2, chem_mapping,
         check += 1
         # need to extract full chain mapping (use indexing)
         mapping = {}
-        for a, b in asu_mapp.iteritems():
-          for id_a, id_b in index_mapp.iteritems():
+        for a, b in asu_mapp.items():
+          for id_a, id_b in index_mapp.items():
             mapping[a[id_a]] = b[id_b]
         chains_rmsd = cached_rmsd.GetMappedRMSD(mapping, res.transformation)
         full_mappings.append((chains_rmsd, mapping))
@@ -2035,16 +2035,16 @@ def _GetDihedralSubgroups(ent, chem_groups, angles, angle_thr):
 
   # get those which are non redundant and covering all chains
   symm_groups = []
-  for clst in same_angles.values():
-    group = clst.keys()
+  for clst in list(same_angles.values()):
+    group = list(clst.keys())
     if _ValidChainGroup(group, ent):
       if len(chem_groups) > 1:
         # if hetero, we want to group toghether different chains only
-        symm_groups.append(zip(*group))
+        symm_groups.append(list(zip(*group)))
       else:
         # if homo, we also want pairs
         symm_groups.append(group)
-        symm_groups.append(zip(*group))
+        symm_groups.append(list(zip(*group)))
   return symm_groups
 
 def _GetCyclicSubgroups(ent, chem_groups, axis, axis_thr):
@@ -2066,8 +2066,8 @@ def _GetCyclicSubgroups(ent, chem_groups, axis, axis_thr):
 
   # use to get grouping
   symm_groups = []
-  for clst in same_axis.values():
-    all_chain = [item for sublist in clst.keys() for item in sublist]
+  for clst in list(same_axis.values()):
+    all_chain = [item for sublist in list(clst.keys()) for item in sublist]
     if len(set(all_chain)) == ent.chain_count:
       # if we have an hetero we can exploit cyclic symmetry for grouping
       if len(chem_groups) > 1:
@@ -2104,14 +2104,14 @@ def _ClusterData(data, thr, metric):
   """
   # special case length 1
   if len(data) == 1:
-    return {0: {data.keys()[0]: data.values()[0]} }
+    return {0: {list(data.keys())[0]: list(data.values())[0]} }
   # do the clustering
-  cluster_indices = fclusterdata(np.asarray(data.values()), thr,
+  cluster_indices = fclusterdata(np.asarray(list(data.values())), thr,
                                  method='complete', criterion='distance',
                                  metric=metric)
   # fclusterdata output is cluster ids -> put into dict of clusters
   res = {}
-  for cluster_idx, data_key in zip(cluster_indices, data.keys()):
+  for cluster_idx, data_key in zip(cluster_indices, list(data.keys())):
     if not cluster_idx in res:
       res[cluster_idx] = {}
     res[cluster_idx][data_key] = data[data_key]
@@ -2215,7 +2215,7 @@ def _LimitChemMapping(chem_mapping, limit_1, limit_2):
   limit_1_set = set(limit_1)
   limit_2_set = set(limit_2)
   asu_chem_mapping = dict()
-  for key, value in chem_mapping.iteritems():
+  for key, value in chem_mapping.items():
     new_key = tuple(set(key) & limit_1_set)
     if new_key:
       asu_chem_mapping[new_key] = tuple(set(value) & limit_2_set)
@@ -2247,7 +2247,7 @@ def _CountSuperpositionsAndMappings(symm_1, symm_2, chem_mapping):
   ref_symm_1 = symm_1[0]
   ref_symm_2 = symm_2[0]
   asu_chem_mapping = _LimitChemMapping(chem_mapping, ref_symm_1, ref_symm_2)
-  for g1, g2 in asu_chem_mapping.iteritems():
+  for g1, g2 in asu_chem_mapping.items():
     chain_superpositions = len(g1) * len(g2)
     c['intra']['superpositions'] += chain_superpositions
     map_per_sup = _PermutationOrCombinations(g1[0], g2[0], asu_chem_mapping)
@@ -2266,12 +2266,12 @@ def _PermutationOrCombinations(sup1, sup2, chem_mapping):
   """Should match len(_ListPossibleMappings(sup1, sup2, chem_mapping))."""
   # remove superposed elements, put smallest complex as key
   chem_map = {}
-  for a,b in chem_mapping.iteritems():
+  for a,b in chem_mapping.items():
     new_a = tuple([x for x in a if x != sup1])
     new_b = tuple([x for x in b if x != sup2])
     chem_map[new_a] = new_b
   mapp_nr = 1.0
-  for a, b in chem_map.iteritems():
+  for a, b in chem_map.items():
     if len(a) == len(b):
       mapp_nr *= factorial(len(a))
     elif len(a) < len(b):
@@ -2306,14 +2306,14 @@ def _ListPossibleMappings(sup1, sup2, chem_mapping):
            less elements) has more elements for any given mapped group.
   """
   # find smallest complex
-  chain1 = [i for s in chem_mapping.keys() for i in s]
-  chain2 = [i for s in chem_mapping.values() for i in s]
+  chain1 = [i for s in list(chem_mapping.keys()) for i in s]
+  chain2 = [i for s in list(chem_mapping.values()) for i in s]
   swap = False
   if len(chain1) > len(chain2):
     swap = True
   # remove superposed elements, put smallest complex as key
   chem_map = {}
-  for a, b in chem_mapping.iteritems():
+  for a, b in chem_mapping.items():
     new_a = tuple([x for x in a if x != sup1])
     new_b = tuple([x for x in b if x != sup2])
     if swap:
@@ -2324,7 +2324,7 @@ def _ListPossibleMappings(sup1, sup2, chem_mapping):
   # equivalent chains
   chem_perm = []
   chem_ref = []
-  for a, b in chem_map.iteritems():
+  for a, b in chem_map.items():
     if len(a) == len(b):
       chem_perm.append(list(itertools.permutations(b)))
       chem_ref.append(a)
@@ -2341,7 +2341,7 @@ def _ListPossibleMappings(sup1, sup2, chem_mapping):
     flat_perm = [i for s in perm for i in s]
     d = {c1: c2 for c1, c2 in zip(flat_ref, flat_perm)}
     if swap:
-      d = {v: k for k, v in d.items()}
+      d = {v: k for k, v in list(d.items())}
     d.update({sup1: sup2})
     mappings.append(d)
   return mappings
@@ -2387,7 +2387,7 @@ def _CheckClosedSymmetry(ent_1, ent_2, symm_1, symm_2, chem_mapping,
   asu_chem_mapping = _LimitChemMapping(chem_mapping, ref_symm_1, ref_symm_2)
   # for each chemically identical group
   rmsd_mappings = []
-  for g1, g2 in asu_chem_mapping.iteritems():
+  for g1, g2 in asu_chem_mapping.items():
     # try to superpose all possible pairs
     # -> note that some chain-chain combinations may work better than others
     #    to superpose the full oligomer (e.g. if some chains are open/closed)
@@ -2436,10 +2436,10 @@ def _GetSuperpositionMapping(ent_1, ent_2, chem_mapping, transformation,
     swap = True
     ent_1, ent_2 = ent_2, ent_1
     transformation = geom.Invert(transformation)
-    chem_pairs = zip(chem_mapping.values(), chem_mapping.keys())
+    chem_pairs = list(zip(list(chem_mapping.values()), list(chem_mapping.keys())))
   else:
     swap = False
-    chem_pairs = chem_mapping.iteritems()
+    chem_pairs = iter(chem_mapping.items())
   # sanity check
   if ent_1.chain_count == 0:
     return None
@@ -2461,7 +2461,7 @@ def _GetSuperpositionMapping(ent_1, ent_2, chem_mapping, transformation,
         ch_atoms[a_2.chain.name].add(a_2.hash_code)
     # get one with most atoms in overlap
     max_num, max_name = max((len(atoms), name)
-                            for name, atoms in ch_atoms.iteritems())
+                            for name, atoms in ch_atoms.items())
     # early abort if overlap fraction not good enough
     ch_2 = ent_2.FindChain(max_name)
     if max_num == 0 or max_num / float(ch_2.handle.atom_count) < sup_fract:
@@ -2480,7 +2480,7 @@ def _GetSuperpositionMapping(ent_1, ent_2, chem_mapping, transformation,
     mapped_chains.add(max_name)
   # unswap if needed and return
   if swap:
-    mapping = {v: k for k, v in mapping.iteritems()}
+    mapping = {v: k for k, v in mapping.items()}
   return mapping
 
 def _GetMappedRMSD(ent_1, ent_2, chain_mapping, transformation):
@@ -2496,7 +2496,7 @@ def _GetMappedRMSD(ent_1, ent_2, chain_mapping, transformation):
   # collect RMSDs and atom counts chain by chain and combine afterwards
   rmsds = []
   atoms = []
-  for c1, c2 in chain_mapping.iteritems():
+  for c1, c2 in chain_mapping.items():
     # get views and atom counts
     chain_1 = ent_1.Select('cname="%s"' % c1)
     chain_2 = ent_2.Select('cname="%s"' % c2)
@@ -2566,7 +2566,7 @@ class _CachedRMSD:
     # collect RMSDs and atom counts chain by chain and combine afterwards
     rmsds = []
     atoms = []
-    for c1, c2 in chain_mapping.iteritems():
+    for c1, c2 in chain_mapping.items():
       # cached?
       if (c1, c2) in self._pair_rmsd:
         atom_count, rmsd = self._pair_rmsd[(c1, c2)]
diff --git a/modules/mol/alg/pymod/structure_analysis.py b/modules/mol/alg/pymod/structure_analysis.py
index 75ae223e1..2103b14a7 100644
--- a/modules/mol/alg/pymod/structure_analysis.py
+++ b/modules/mol/alg/pymod/structure_analysis.py
@@ -30,11 +30,11 @@ def GetDistanceBetwCenterOfMass(sele1,sele2):
   :return: :class:`float`
   """
   if not sele1.IsValid() and sele2.IsValid():
-    print 'invalid view'
+    print('invalid view')
     return
   eh=sele1.GetHandle()
   if not eh==sele2.GetHandle():
-    print 'The two views must be from the same entity'
+    print('The two views must be from the same entity')
     return
   f=GetFrameFromEntity(eh)
   return f.GetDistanceBetwCenterOfMass(sele1,sele2)
@@ -52,11 +52,11 @@ def GetMinDistanceBetweenViews(sele1,sele2):
   :return: :class:`float`
   """
   if not sele1.IsValid() and sele2.IsValid():
-    print 'invalid view'
+    print('invalid view')
     return
   eh=sele1.GetHandle()
   if not eh==sele2.GetHandle():
-    print 'The two views must be from the same entity'
+    print('The two views must be from the same entity')
     return
   f=GetFrameFromEntity(eh)
   return f.GetMinDistance(sele1,sele2)
@@ -74,11 +74,11 @@ def GetMinDistBetwCenterOfMassAndView(sele1,sele2):
   :return: distance (\ :class:`float`\ )
   """
   if not sele1.IsValid() and sele2.IsValid():
-    print 'invalid view'
+    print('invalid view')
     return
   eh=sele1.GetHandle()
   if not eh==sele2.GetHandle():
-    print 'The two views must be from the same entity'
+    print('The two views must be from the same entity')
     return
   f=GetFrameFromEntity(eh)
   return f.GetMinDistBetwCenterOfMassAndView(sele1,sele2)
@@ -95,7 +95,7 @@ def GetAlphaHelixContent(sele1):
   :return: :class:`float`
   """
   if not sele1.IsValid():
-    print 'invalid view'
+    print('invalid view')
     return
   eh=sele1.GetHandle()
   f=GetFrameFromEntity(eh)
@@ -112,7 +112,7 @@ def CalculateBestFitLine(sele1):
   :return: :class:`~ost.geom.Line3`
   """
   if not sele1.IsValid():
-    print 'invalid view'
+    print('invalid view')
     return
   eh=sele1.GetHandle()
   f=GetFrameFromEntity(eh)
@@ -128,7 +128,7 @@ def CalculateBestFitPlane(sele1):
   :return: :class:`~ost.geom.Plane`
   """
   if not sele1.IsValid():
-    print 'invalid view'
+    print('invalid view')
     return
   eh=sele1.GetHandle()
   f=GetFrameFromEntity(eh)
@@ -146,7 +146,7 @@ def CalculateHelixAxis(sele1):
   :return: :class:`~ost.geom.Line3`
   """
   if not sele1.IsValid():
-    print 'invalid view'
+    print('invalid view')
     return
   eh=sele1.GetHandle()
   f=GetFrameFromEntity(eh)
@@ -172,10 +172,10 @@ def CalculateDistanceDifferenceMatrix(sele1,sele2):
     LogError("Function needs numpy, but I could not import it.")
     raise
   if not sele1.IsValid() and sele2.IsValid():
-    print 'invalid view'
+    print('invalid view')
     return
   if not sele1.GetAtomCount()==sele2.GetAtomCount():
-    print 'The two views must have the same number of atoms'
+    print('The two views must have the same number of atoms')
     return
   n_atoms=sele1.GetAtomCount()
   M=npy.zeros([n_atoms,n_atoms])
diff --git a/modules/mol/alg/pymod/superpose.py b/modules/mol/alg/pymod/superpose.py
index 546d71c46..515a49703 100644
--- a/modules/mol/alg/pymod/superpose.py
+++ b/modules/mol/alg/pymod/superpose.py
@@ -110,13 +110,13 @@ def MatchResidueByNum(ent_a, ent_b, atoms='all'):
       ## check residues & copy to views
       try:
         while True:
-          r_a=residues_a.next()
-          r_b=residues_b.next()
+          r_a=next(residues_a)
+          r_b=next(residues_b)
           while r_a.number!=r_b.number:
             while r_a.number<r_b.number:
-              r_a=residues_a.next()
+              r_a=next(residues_a)
             while r_b.number<r_a.number:
-              r_b=residues_b.next()
+              r_b=next(residues_b)
           assert r_a.number==r_b.number
           result_a,result_b=_fetch_atoms(r_a, r_b, result_a, result_b, atmset)
       except StopIteration:
@@ -125,7 +125,7 @@ def MatchResidueByNum(ent_a, ent_b, atoms='all'):
       ## iterate one list of residues, search in other list
       try:
         while True:
-          r_a=residues_a.next()
+          r_a=next(residues_a)
           r_b=chain_b.FindResidue(r_a.number)
           if r_b.IsValid():
             result_a,result_b=_fetch_atoms(r_a, r_b, result_a, result_b, atmset)
diff --git a/modules/mol/alg/pymod/trajectory_analysis.py b/modules/mol/alg/pymod/trajectory_analysis.py
index 2d71b5a4d..9ff4b341e 100644
--- a/modules/mol/alg/pymod/trajectory_analysis.py
+++ b/modules/mol/alg/pymod/trajectory_analysis.py
@@ -199,7 +199,7 @@ def DistRMSDFromTraj(t,sele,ref_sele,radius=7.0,average=False,seq_sep=4,first=0,
   :return: a numpy vecor dist_rmsd(N\ :subscript:`frames`).  
   """
   if not sele.GetAtomCount()==ref_sele.GetAtomCount():
-    print 'Not same number of atoms in the two views'
+    print('Not same number of atoms in the two views')
     return
   try:
     import numpy as npy
diff --git a/modules/mol/alg/pymod/views.py b/modules/mol/alg/pymod/views.py
index f4a203077..888953824 100644
--- a/modules/mol/alg/pymod/views.py
+++ b/modules/mol/alg/pymod/views.py
@@ -38,12 +38,12 @@ def PairResiduesByNum(view_a, view_b,
   result_b=_EmptyView(view_b)
   try:
     while True:
-      r1=residues_a.next()
-      r2=residues_b.next()
+      r1=next(residues_a)
+      r2=next(residues_b)
       while r1.number<r2.number:
-        r1=residues_a.next()
+        r1=next(residues_a)
       while r2.number<r1.number:
-        r2=residues_b.next()
+        r2=next(residues_b)
       assert r1.number==r2.number
       result_a.AddResidue(r1, view_add_flags)
       result_b.AddResidue(r2, view_add_flags)
diff --git a/modules/mol/alg/tests/test_accessibility.py b/modules/mol/alg/tests/test_accessibility.py
index c3d857fd5..8a951529f 100644
--- a/modules/mol/alg/tests/test_accessibility.py
+++ b/modules/mol/alg/tests/test_accessibility.py
@@ -106,7 +106,7 @@ class TestAccessibility(unittest.TestCase):
       acc_naccess = AccessibilitiesRaw(ent_three, use_naccess=True)
       self.assertTrue(Compare(acc_classic, acc_naccess))
     except:
-      print "Could not find NACCESS, could not compare Accessiblity function..."
+      print("Could not find NACCESS, could not compare Accessiblity function...")
 
 
   def testAccDSSP(self):
@@ -117,7 +117,7 @@ class TestAccessibility(unittest.TestCase):
       dssp_path = settings.Locate(['dsspcmbi', 'dssp', 'mkdssp'],
                                   env_name='DSSP_EXECUTABLE')
     except:
-      print "Could not find DSSP, could not compare Accessibility function..."
+      print("Could not find DSSP, could not compare Accessibility function...")
       return
 
     # we assume oligo mode to be working as it is tested in 
diff --git a/modules/mol/alg/tests/test_convenient_superpose.py b/modules/mol/alg/tests/test_convenient_superpose.py
index 30f062429..8e01b4937 100644
--- a/modules/mol/alg/tests/test_convenient_superpose.py
+++ b/modules/mol/alg/tests/test_convenient_superpose.py
@@ -9,9 +9,9 @@ class TestConvenientSuperpose(unittest.TestCase):
     pass
   
   def assertEqualAtomOrder(self, view1, view2):
-    self.assertEquals(len(view1.atoms),len(view2.atoms))
+    self.assertEqual(len(view1.atoms),len(view2.atoms))
     for a1, a2 in zip(view1.atoms, view2.atoms):
-      self.assertEquals(a1.element, a2.element)
+      self.assertEqual(a1.element, a2.element)
       self.assertTrue(geom.Equal(a1.pos, a2.pos))
 
   def testAssertion(self):
diff --git a/modules/mol/alg/tests/test_nonstandard.py b/modules/mol/alg/tests/test_nonstandard.py
index c81c92479..cbe8257a7 100644
--- a/modules/mol/alg/tests/test_nonstandard.py
+++ b/modules/mol/alg/tests/test_nonstandard.py
@@ -121,4 +121,4 @@ if __name__ == "__main__":
   if testutils.SetDefaultCompoundLib():
     testutils.RunTests()
   else:
-    print 'No compound library available. Ignoring test_nonstandard.py tests.'
+    print('No compound library available. Ignoring test_nonstandard.py tests.')
diff --git a/modules/mol/alg/tests/test_pdbize.py b/modules/mol/alg/tests/test_pdbize.py
index abf2bc892..0048f9b36 100644
--- a/modules/mol/alg/tests/test_pdbize.py
+++ b/modules/mol/alg/tests/test_pdbize.py
@@ -18,13 +18,13 @@ class TestPDBize(unittest.TestCase):
     seqs = seq.CreateSequenceList()
     pdbizer.Add(m.Select(''), transformations, seqs)
     pdbized = pdbizer.Finish()
-    self.assertEquals([c.name for c in pdbized.chains], ["-"])
+    self.assertEqual([c.name for c in pdbized.chains], ["-"])
     residues = pdbized.residues
     for i in range(26):
-      self.assertEquals(residues[i].number.num, 1)
-      self.assertEquals(residues[i].number.ins_code, chr(ord('A')+i))
-    self.assertEquals(residues[26].number.num, 2)
-    self.assertEquals(residues[26].number.ins_code, 'A')
+      self.assertEqual(residues[i].number.num, 1)
+      self.assertEqual(residues[i].number.ins_code, chr(ord('A')+i))
+    self.assertEqual(residues[26].number.num, 2)
+    self.assertEqual(residues[26].number.ins_code, 'A')
   def test_starts_from_last_water_rnum(self):
     m = mol.CreateEntity()
     e = m.EditXCS(mol.BUFFERED_EDIT)
@@ -38,9 +38,9 @@ class TestPDBize(unittest.TestCase):
     pdbizer.Add(m.Select(''), transformations,seqs)
     pdbizer.Add(m.Select(''), transformations,seqs)
     pdbized = pdbizer.Finish()
-    self.assertEquals([c.name for c in pdbized.chains], ["-"])
+    self.assertEqual([c.name for c in pdbized.chains], ["-"])
     residues = pdbized.residues
-    self.assertEquals([r.number for r in residues],
+    self.assertEqual([r.number for r in residues],
                       [mol.ResNum(1, 'A'), mol.ResNum(1, 'B')])
 
 
diff --git a/modules/mol/alg/tests/test_qsscoring.py b/modules/mol/alg/tests/test_qsscoring.py
index c241eea49..770c95f6e 100644
--- a/modules/mol/alg/tests/test_qsscoring.py
+++ b/modules/mol/alg/tests/test_qsscoring.py
@@ -5,8 +5,8 @@ from ost import io, mol, settings
 try:
   from ost.mol.alg.qsscoring import *
 except ImportError:
-  print "Failed to import qsscoring. Happens when numpy or scipy missing. " \
-        "Ignoring test_qsscoring.py tests."
+  print("Failed to import qsscoring. Happens when numpy or scipy missing. " \
+        "Ignoring test_qsscoring.py tests.")
   sys.exit(0)
 
 from ost.mol.alg import lDDTSettings
@@ -605,7 +605,7 @@ class TestQSscore(unittest.TestCase):
     cn2 = set([c for cg in qs_ent_2.chem_groups for c in cg])
     cm_names_1 = list()
     cm_names_2 = list()
-    for cg1, cg2 in qs_scorer.chem_mapping.iteritems():
+    for cg1, cg2 in qs_scorer.chem_mapping.items():
       ch_ref = qs_scorer.ent_to_cm_1.FindChain(cg1[0])
       self.assertEqual(ch_ref.residue_count, ch_ref.atom_count)
       self.assertGreaterEqual(ch_ref.residue_count, 5)
@@ -646,12 +646,12 @@ class TestQSscore(unittest.TestCase):
                      cm_names_2)
     # check chain_mapping
     # (all chains of ent with less chains mapped, each only once, chem_map)
-    chm_names_1 = qs_scorer.chain_mapping.keys()
-    chm_names_2 = qs_scorer.chain_mapping.values()
+    chm_names_1 = list(qs_scorer.chain_mapping.keys())
+    chm_names_2 = list(qs_scorer.chain_mapping.values())
     self.assertEqual(len(chm_names_1), min(len(cm_names_1), len(cm_names_2)))
     self.assertEqual(len(set(chm_names_1)), len(chm_names_1))
     self.assertEqual(len(set(chm_names_2)), len(chm_names_2))
-    for cg1, cg2 in qs_scorer.chem_mapping.iteritems():
+    for cg1, cg2 in qs_scorer.chem_mapping.items():
       for ch_name in cg1:
         if ch_name in qs_scorer.chain_mapping:
           self.assertTrue(qs_scorer.chain_mapping[ch_name] in cg2)
@@ -678,10 +678,10 @@ if __name__ == "__main__":
   try:
     settings.Locate(('clustalw', 'clustalw2'))
   except:
-    print "Could not find ClustalW. Ignoring test_qsscoring.py tests."
+    print("Could not find ClustalW. Ignoring test_qsscoring.py tests.")
     sys.exit(0)
   from ost import testutils
   if testutils.SetDefaultCompoundLib():
     testutils.RunTests()
   else:
-    print 'No compound library available. Ignoring test_qsscoring.py tests.'
+    print('No compound library available. Ignoring test_qsscoring.py tests.')
diff --git a/modules/mol/alg/tests/test_sec_struct.py b/modules/mol/alg/tests/test_sec_struct.py
index 207f8600c..c6b6a928e 100644
--- a/modules/mol/alg/tests/test_sec_struct.py
+++ b/modules/mol/alg/tests/test_sec_struct.py
@@ -14,7 +14,7 @@ class TestSecStruct(unittest.TestCase):
       dssp_path = settings.Locate(['dsspcmbi', 'dssp', 'mkdssp'],
                                   env_name='DSSP_EXECUTABLE')
     except:
-      print "Could not find DSSP, could not compare sec struct assignment..."
+      print("Could not find DSSP, could not compare sec struct assignment...")
       return
 
     dssp_ent = io.LoadPDB(os.path.join("testfiles", "1a0s.pdb"))
diff --git a/modules/mol/base/tests/test_numpy.py b/modules/mol/base/tests/test_numpy.py
index 27061a234..fb69b9afe 100644
--- a/modules/mol/base/tests/test_numpy.py
+++ b/modules/mol/base/tests/test_numpy.py
@@ -11,7 +11,7 @@ if ost.WITH_NUMPY:
   has_numpy=True
   try:
     import numpy
-  except ImportError, e:
+  except ImportError as e:
     has_numpy=False
 else:
   has_numpy=False
diff --git a/modules/mol/mm/pymod/__init__.py b/modules/mol/mm/pymod/__init__.py
index a4a7c650e..f10fd7e6b 100644
--- a/modules/mol/mm/pymod/__init__.py
+++ b/modules/mol/mm/pymod/__init__.py
@@ -19,7 +19,7 @@
 
 import os.path
 from _ost_mol_mm import *
-import antechamber
+from . import antechamber
 import ost
 
 def LoadAMBERForcefield():
diff --git a/modules/mol/mm/pymod/antechamber.py b/modules/mol/mm/pymod/antechamber.py
index 6fc48ce3f..979dd4d99 100644
--- a/modules/mol/mm/pymod/antechamber.py
+++ b/modules/mol/mm/pymod/antechamber.py
@@ -226,7 +226,7 @@ def _ParseAmberForceField(filename):
       if not keyword in keywords: continue
       # loop until empty line found
       ff_dict[keyword] = []
-      line = in_file.next()
+      line = next(in_file)
       while len(line.strip()) > 0:
         # check for warnings
         if 'ATTN' in line:
@@ -286,7 +286,7 @@ def _ParseAmberForceField(filename):
           epsilon = float(s[1])
           ff_dict[keyword].append([atype, Rvdw, epsilon])
         # next...
-        line = in_file.next()
+        line = next(in_file)
   return ff_dict
 ###############################################################################
 
diff --git a/modules/seq/alg/pymod/__init__.py b/modules/seq/alg/pymod/__init__.py
index 44fa50acf..8d9b9c806 100644
--- a/modules/seq/alg/pymod/__init__.py
+++ b/modules/seq/alg/pymod/__init__.py
@@ -188,7 +188,7 @@ def PredictContacts(ali):
   import math
   from ost import seq
   if not type(ali)==type(seq.AlignmentHandle()):
-    print "Parameter should be an AlignmentHandle"
+    print("Parameter should be an AlignmentHandle")
     return
   mi=CalculateMutualInformation(ali)
   CoEvoSc=CalculateContactSubstitutionScore(ali)
diff --git a/modules/seq/alg/tests/test_aligntoseqres.py b/modules/seq/alg/tests/test_aligntoseqres.py
index 2aa1bb4fe..5664ff837 100644
--- a/modules/seq/alg/tests/test_aligntoseqres.py
+++ b/modules/seq/alg/tests/test_aligntoseqres.py
@@ -71,4 +71,4 @@ if __name__ == "__main__":
   if testutils.SetDefaultCompoundLib():
     testutils.RunTests()
   else:
-    print 'No compound library available. Ignoring test_aligntoseqres.py tests.'
+    print('No compound library available. Ignoring test_aligntoseqres.py tests.')
diff --git a/modules/seq/alg/tests/test_renumber.py b/modules/seq/alg/tests/test_renumber.py
index 0309f5a1b..f587d588f 100644
--- a/modules/seq/alg/tests/test_renumber.py
+++ b/modules/seq/alg/tests/test_renumber.py
@@ -167,6 +167,6 @@ if __name__ == "__main__":
   # test renumbering
   # test if clustalw package is available on system, otherwise ignore tests
   if not clustalw_path:
-    print "Could not find clustalw executable: ignoring some renumber unit tests"
+    print("Could not find clustalw executable: ignoring some renumber unit tests")
   from ost import testutils
   testutils.RunTests()
diff --git a/scripts/bump-version.py b/scripts/bump-version.py
index 053b08bfd..ddf414cd6 100755
--- a/scripts/bump-version.py
+++ b/scripts/bump-version.py
@@ -2,9 +2,9 @@
 import sys
 
 if len(sys.argv) < 2:
-  print "USAGE: python scripts/bump-version.py OST_VERSION"
-  print "-> OST_VERSION is MAJOR.MINOR.PATCH (e.g. 1.9.1)"
-  print "-> assumption is that a git tag OST_VERSION will exist"
+  print("USAGE: python scripts/bump-version.py OST_VERSION")
+  print("-> OST_VERSION is MAJOR.MINOR.PATCH (e.g. 1.9.1)")
+  print("-> assumption is that a git tag OST_VERSION will exist")
   sys.exit(1)
 
 # split up version number
diff --git a/scripts/code_coverage.py b/scripts/code_coverage.py
index 6df65830e..1cb87bc3a 100644
--- a/scripts/code_coverage.py
+++ b/scripts/code_coverage.py
@@ -42,7 +42,7 @@ def ParseOptions():
 
 def CheckLCOV():
   if not Which('lcov'):
-    print 'please install lcov to run this script'
+    print('please install lcov to run this script')
     sys.exit(-1)
 
 CheckLCOV()
@@ -50,9 +50,9 @@ CheckLCOV()
 opts=ParseOptions()
 
 def Print(message):
-  print '~~~~~~'
-  print '|  *  |', message
-  print '~~~~~~'  
+  print('~~~~~~')
+  print('|  *  |', message)
+  print('~~~~~~')  
   
 def RemoveFiles(directory, extension):
   glob_pattern='*.%s' % extension
@@ -113,7 +113,7 @@ def RunTests(modules):
     if os.path.exists(test_dir):
       test_binary=os.path.join(os.path.join(test_dir, 'tests'))
       if os.path.exists(test_binary) and os.access(test_binary, os.X_OK):
-        print 'running tests for module', module        
+        print('running tests for module', module)        
         Cleanup(modules)
         cmd='cd "%s"; ./tests' % test_dir
         os.system(cmd)
-- 
GitLab