From facb43aad461f57ee282aaa80dac14f1e6849990 Mon Sep 17 00:00:00 2001
From: Tammo Jan Dijkema <dijkema@astron.nl>
Date: Fri, 8 Jun 2018 11:15:07 +0000
Subject: [PATCH] Task #11556: python3 support for some LOFAR python-bindings

---
 .gitattributes                                |   2 +
 .../pystationresponse/CMakeLists.txt          |   4 +-
 .../pystationresponse/src/__init__.py         |   4 +-
 .../pystationresponse/test/CMakeLists.txt     |  11 +-
 .../pystationresponse/test/tStationBeamNCP.py |   7 +-
 .../test/tpystationresponse.py                |   5 +
 .../test/tpystationresponse.sh                |   2 +
 CEP/DP3/PythonDPPP/src/PythonStep.cc          |   4 +
 CEP/DP3/PythonDPPP/test/tPythonStep.py        |   7 +-
 CEP/pyparmdb/CMakeLists.txt                   |   2 +-
 CEP/pyparmdb/src/__init__.py                  |  10 +-
 CEP/pyparmdb/test/CMakeLists.txt              |   8 +-
 CEP/pyparmdb/test/tpyparmdb.py                |  44 +++++--
 CEP/pyparmdb/test/tpyparmdb.run               |  15 ---
 CMake/FindBoost.cmake                         |  18 +++
 CMake/FindCasacore.cmake                      |  17 +++
 CMake/FindPython.cmake                        |   1 -
 CMake/FindPythonModule.cmake                  |   4 +-
 CMake/testscripts/assay                       |   3 +-
 CMake/testscripts/runctest.sh.in              |   8 ++
 LCS/pyparameterset/src/__init__.py            |   4 +-
 LCS/pyparameterset/test/tpyparameterset.py    | 120 +++++++++---------
 .../test/tpyparameterset.stdout               |   4 +-
 LCS/pytools/include/pytools/PycBasicData.h    |  15 ++-
 LCS/pytools/test/tConvert.py                  |  44 ++++---
 lofar_config.h.cmake                          |   3 +
 26 files changed, 232 insertions(+), 134 deletions(-)
 create mode 100644 CEP/Calibration/pystationresponse/test/tpystationresponse.py
 create mode 100755 CEP/Calibration/pystationresponse/test/tpystationresponse.sh
 delete mode 100755 CEP/pyparmdb/test/tpyparmdb.run

diff --git a/.gitattributes b/.gitattributes
index 84d0d644ec7..a308317f463 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -295,6 +295,8 @@ CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.info -text
 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.lock -text
 CEP/Calibration/pystationresponse/test/tStationBeamNCP.py -text
 CEP/Calibration/pystationresponse/test/tStationBeamNCP.sh -text
+CEP/Calibration/pystationresponse/test/tpystationresponse.py -text
+CEP/Calibration/pystationresponse/test/tpystationresponse.sh -text
 CEP/DP3/DPPP/etc/CMakeLists.txt -text
 CEP/DP3/DPPP/etc/DPPP.log_prop -text
 CEP/DP3/DPPP/include/DPPP/Apply.h -text
diff --git a/CEP/Calibration/pystationresponse/CMakeLists.txt b/CEP/Calibration/pystationresponse/CMakeLists.txt
index c2f387f96dd..3549dded7b5 100644
--- a/CEP/Calibration/pystationresponse/CMakeLists.txt
+++ b/CEP/Calibration/pystationresponse/CMakeLists.txt
@@ -3,9 +3,9 @@
 lofar_package(pystationresponse 1.0 DEPENDS StationResponse)
 
 include(LofarFindPackage)
-lofar_find_package(Boost REQUIRED COMPONENTS python)
 lofar_find_package(Python 2.6 REQUIRED)
-lofar_find_package(Pyrap REQUIRED)
+lofar_find_package(Boost REQUIRED COMPONENTS python)
+lofar_find_package(Casacore REQUIRED COMPONENTS python)
 
 add_subdirectory(src)
 add_subdirectory(test)
diff --git a/CEP/Calibration/pystationresponse/src/__init__.py b/CEP/Calibration/pystationresponse/src/__init__.py
index a3e5a59bbd6..87961234ba1 100755
--- a/CEP/Calibration/pystationresponse/src/__init__.py
+++ b/CEP/Calibration/pystationresponse/src/__init__.py
@@ -19,7 +19,7 @@
 #
 # $Id$
 
-import _stationresponse
+from ._stationresponse import StationResponse
 
 class stationresponse(object):
     """
@@ -64,7 +64,7 @@ class stationresponse(object):
             time = subtable.getcell("TIME", 0)
             print time, response.evaluateChannel(time, 0, 0)
         """
-        self._response = _stationresponse.StationResponse(msname, inverse,
+        self._response = StationResponse(msname, inverse,
           useElementResponse, useArrayFactor, useChanFreq)
 
     def version (self, type='other'):
diff --git a/CEP/Calibration/pystationresponse/test/CMakeLists.txt b/CEP/Calibration/pystationresponse/test/CMakeLists.txt
index d6da7e50cd0..48ba299ea0a 100644
--- a/CEP/Calibration/pystationresponse/test/CMakeLists.txt
+++ b/CEP/Calibration/pystationresponse/test/CMakeLists.txt
@@ -2,4 +2,13 @@
 
 include(LofarCTest)
 
-#lofar_add_test(tStationBeamNCP) # test not enabled because pyrap is not found
+include(FindPythonModule)
+
+find_python_module(pyrap)
+if(PYTHON_PYRAP_FOUND)
+  lofar_add_test(tStationBeamNCP)
+else(PYTHON_PYRAP_FOUND)
+  message(WARNING "Python-casacore was not found, not installing test tStationBeamNCP")
+endif(PYTHON_PYRAP_FOUND)
+
+lofar_add_test(tpystationresponse)
diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.py b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.py
index f16fbf80fa5..81e76b0c26a 100644
--- a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.py
+++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.py
@@ -1,4 +1,5 @@
 "Test the Station Beam at the NCP. Rationale: when pointing at the NCP all stations should have (almost) the same beam"
+from __future__ import print_function
 
 import sys
 
@@ -17,8 +18,8 @@ a=[mys.evaluateStation(time=times[0],station=st) for st in range(20)]
 
 for a1 in a:
     for a2 in a:
-	if np.linalg.norm(a1-a2)>1.e-3:
-            print "a1=",a1,"\na2=",a2,"\nnorm=",np.linalg.norm(a1-a2)
-	    sys.exit(1)
+        if np.linalg.norm(a1-a2)>1.e-3:
+            print("a1=",a1,"\na2=",a2,"\nnorm=",np.linalg.norm(a1-a2))
+            sys.exit(1)
 
 sys.exit(0)
diff --git a/CEP/Calibration/pystationresponse/test/tpystationresponse.py b/CEP/Calibration/pystationresponse/test/tpystationresponse.py
new file mode 100644
index 00000000000..124019cde47
--- /dev/null
+++ b/CEP/Calibration/pystationresponse/test/tpystationresponse.py
@@ -0,0 +1,5 @@
+from __future__ import print_function
+
+import lofar.stationresponse
+
+
diff --git a/CEP/Calibration/pystationresponse/test/tpystationresponse.sh b/CEP/Calibration/pystationresponse/test/tpystationresponse.sh
new file mode 100755
index 00000000000..f967776d2c3
--- /dev/null
+++ b/CEP/Calibration/pystationresponse/test/tpystationresponse.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+./runctest.sh tpystationresponse
diff --git a/CEP/DP3/PythonDPPP/src/PythonStep.cc b/CEP/DP3/PythonDPPP/src/PythonStep.cc
index ec36289830c..4cc265b2df3 100644
--- a/CEP/DP3/PythonDPPP/src/PythonStep.cc
+++ b/CEP/DP3/PythonDPPP/src/PythonStep.cc
@@ -72,7 +72,11 @@ namespace LOFAR {
       string workingDir = Path(".").absoluteName();
       char path[] = "path";    // needed to avoid warning if "path" used below
       PyObject* sysPath = PySys_GetObject(path);
+#if PYTHON_VERSION_MAJOR < 3
       PyList_Insert (sysPath, 0, PyString_FromString(workingDir.c_str()));
+#else
+      PyList_Insert (sysPath, 0, PyBytes_FromString(workingDir.c_str()));
+#endif
       // Register converters for casa types from/to python types
       casa::pyrap::register_convert_excp();
       casa::pyrap::register_convert_basicdata();
diff --git a/CEP/DP3/PythonDPPP/test/tPythonStep.py b/CEP/DP3/PythonDPPP/test/tPythonStep.py
index 3b3ad8cfb48..33aaef591c4 100644
--- a/CEP/DP3/PythonDPPP/test/tPythonStep.py
+++ b/CEP/DP3/PythonDPPP/test/tPythonStep.py
@@ -19,6 +19,7 @@
 #
 # $Id: __init__.py 23074 2012-12-03 07:51:29Z diepen $
 
+from __future__ import print_function
 
 from lofar.pythondppp import DPStep
 from lofar.parameterset import parameterset
@@ -52,7 +53,7 @@ class tPythonStep(DPStep):
         self.getWeights (self.itsWeights);
         self.getUVW (self.itsUVW);
         # Process the data.
-        print "process tPythonStep", time-4.47203e9, exposure, self.itsData.sum(), self.itsFlags.sum(), self.itsWeights.sum(), self.itsUVW.sum()
+        print("process tPythonStep", time-4.47203e9, exposure, self.itsData.sum(), self.itsFlags.sum(), self.itsWeights.sum(), self.itsUVW.sum())
         # Execute the next step in the DPPP pipeline. TIME,UVW are changed.
         return self.processNext ({'TIME': time+self.itsIncr, 'UVW': self.itsUVW+self.itsIncr})
 
@@ -60,7 +61,7 @@ class tPythonStep(DPStep):
         # Finish the step as needed.
         # This function does not need to be implemented.
         # Note: finish of the next step is called by the C++ layer.
-        print "finish tPythonStep"
+        print("finish tPythonStep")
 
     def showCounts(self):
         # Show the counts of this test.
@@ -70,4 +71,4 @@ class tPythonStep(DPStep):
     def addToMS(self, msname):
         # Add some info the the output MeasurementSet.
         # This function does not need to be implemented.
-        print "addToMS tPythonStep", msname
+        print("addToMS tPythonStep", msname)
diff --git a/CEP/pyparmdb/CMakeLists.txt b/CEP/pyparmdb/CMakeLists.txt
index 6722696a794..c051e47709f 100644
--- a/CEP/pyparmdb/CMakeLists.txt
+++ b/CEP/pyparmdb/CMakeLists.txt
@@ -5,7 +5,7 @@ lofar_package(pyparmdb 1.0 DEPENDS Common ParmDB)
 include(LofarFindPackage)
 lofar_find_package(Boost REQUIRED COMPONENTS python)
 lofar_find_package(Python 2.6 REQUIRED)
-lofar_find_package(Pyrap REQUIRED)
+lofar_find_package(Casacore REQUIRED COMPONENTS python)
 
 add_subdirectory(src)
 add_subdirectory(test)
diff --git a/CEP/pyparmdb/src/__init__.py b/CEP/pyparmdb/src/__init__.py
index cd17f86050b..221af0ae377 100755
--- a/CEP/pyparmdb/src/__init__.py
+++ b/CEP/pyparmdb/src/__init__.py
@@ -19,7 +19,7 @@
 #
 # $Id$
 
-from _parmdb import ParmDB
+from ._parmdb import ParmDB
 
 class parmdb(ParmDB):
     """
@@ -41,7 +41,7 @@ class parmdb(ParmDB):
 
         import lofar.parmdb
         pdb = parmdb(dbname)     # open existing
-	pdb = 0                  # close
+        pdb = 0                  # close
 
         Almost all functions work on a local as well as a distributed database.
         The exception is :func:`addValues`. For the time being it only works
@@ -65,7 +65,7 @@ class parmdb(ParmDB):
         return self._getRange (parmnamepattern)
 
     def getNames (self, parmnamepattern='', includeDefaults=False):
-	"""Return the list of matching parameter names with actual values.
+        """Return the list of matching parameter names with actual values.
 
         parmnamepattern
           Parameter name pattern given as a shell-like filename pattern.
@@ -78,7 +78,7 @@ class parmdb(ParmDB):
         return self._getNames (parmnamepattern)
 
     def getDefNames (self, parmnamepattern=''):
-	"""Return the list of matching parameter names with default values.
+        """Return the list of matching parameter names with default values.
 
         The pattern must be given as a shell-like filename pattern.
         An empty pattern (the default) means '*' (thus all names).
@@ -87,7 +87,7 @@ class parmdb(ParmDB):
         return self._getDefNames (parmnamepattern)
 
     def getDefValues (self, parmnamepattern=''):
-	"""Return the default values of matching parameter names as a dict.
+        """Return the default values of matching parameter names as a dict.
 
         The pattern must be given as a shell-like filename pattern.
         An empty pattern (the default) means '*' (thus all names).
diff --git a/CEP/pyparmdb/test/CMakeLists.txt b/CEP/pyparmdb/test/CMakeLists.txt
index 81a127052e6..0fd8973b287 100644
--- a/CEP/pyparmdb/test/CMakeLists.txt
+++ b/CEP/pyparmdb/test/CMakeLists.txt
@@ -1,6 +1,7 @@
 # $Id$
 
 include(LofarCTest)
+include(FindPythonModule)
 
 # We need to create a symlink to the parmdbm executable in the current
 # binary directory, so that the test program(s) can find it.
@@ -8,4 +9,9 @@ lofar_create_target_symlink(
   parmdbm
   ${CMAKE_CURRENT_BINARY_DIR}/parmdbm)
 
-lofar_add_test(tpyparmdb DEPENDS parmdbm _parmdb)
+find_python_module(numpy)
+if(PYTHON_NUMPY_FOUND)
+  lofar_add_test(tpyparmdb DEPENDS parmdbm _parmdb)
+else(PYTHON_NUMPY_FOUND)
+  message(WARNING "Numpy not found, disabling tpyparmdb test")
+endif(PYTHON_NUMPY_FOUND)
diff --git a/CEP/pyparmdb/test/tpyparmdb.py b/CEP/pyparmdb/test/tpyparmdb.py
index 3728970b4ec..52abe973e70 100644
--- a/CEP/pyparmdb/test/tpyparmdb.py
+++ b/CEP/pyparmdb/test/tpyparmdb.py
@@ -1,28 +1,48 @@
+from __future__ import print_function
+
 from lofar.parmdb import *
+import os
+import sys
+
+
+def createTestFile():
+    """Create a test parmdb using parmdbm"""
+    return os.system("""
+parmdbm <<EOF > tpyparmdb_tmp.pdbout
+ create tablename='tpyparmdb_tmp.pdb'
+ add parm1 domain=[1,5,4,10],values=2
+ add parm2 type='polc', domain=[1,5,4,10], values=[2,0.1], nx=2
+ adddef parmdef values=[3,1], nx=2
+ quit
+EOF""")
+
+### NOTE: parmdbm always returns exit code 0, so we cannot test if it worked
+if createTestFile() != 0:
+    raise RuntimeError("Could not create parmdb for tpyparmdb")
 
 def showValues (pdb, pattern='*', nf=4, nt=2):
     # Get the names.
-    print pdb.getNames()
+    print(pdb.getNames())
     # Get the range.
     rng = pdb.getRange()
-    print rng
+    print(rng)
     # Get the values.
-    print pdb.getValuesStep(pattern, rng[0], rng[2], nf, rng[1], rng[3], nt, True)
+    print(pdb.getValuesStep(pattern, rng[0], rng[2], nf, rng[1], rng[3], nt, True))
     # Get values and grid.
-    print pdb.getValuesGrid(pattern, rng[0], rng[2], rng[1], rng[3])
+    print(pdb.getValuesGrid(pattern, rng[0], rng[2], rng[1], rng[3]))
     # Print default names and values.
-    print pdb.getDefNames(pattern);
-    print pdb.getDefValues(pattern)
+    print(pdb.getDefNames(pattern))
+    print(pdb.getDefValues(pattern))
 
 # The test is the same as in tParmFacade.cc.
 # Open the parameterset (created in .run file).
 pdb=parmdb("tpyparmdb_tmp.pdb")
-print ">>>"
-print pdb.version("tree")
-print pdb.version("full")
-print pdb.version("top")
-print pdb.version()
-print "<<<"
+print(">>>")
+print(pdb.version("tree"))
+print(pdb.version("full"))
+print(pdb.version("top"))
+print(pdb.version())
+print("<<<")
 showValues (pdb);
 showValues (pdb, '', 1);
 showValues (pdb, 'parm1', 1);
diff --git a/CEP/pyparmdb/test/tpyparmdb.run b/CEP/pyparmdb/test/tpyparmdb.run
deleted file mode 100755
index c64c4ee3360..00000000000
--- a/CEP/pyparmdb/test/tpyparmdb.run
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-
-parmdbm <<EOF > tpyparmdb_tmp.pdbout
- create tablename='tpyparmdb_tmp.pdb'
- add parm1 domain=[1,5,4,10],values=2
- add parm2 type='polc', domain=[1,5,4,10], values=[2,0.1], nx=2
- adddef parmdef values=[3,1], nx=2
- quit
-EOF
-if [ $? != 0 ]; then
-  cat tpyparmdb_tmp.pdbout
-  exit 1
-fi
-
-python tpyparmdb.py
diff --git a/CMake/FindBoost.cmake b/CMake/FindBoost.cmake
index 61dad5ba235..ec13e7ce786 100644
--- a/CMake/FindBoost.cmake
+++ b/CMake/FindBoost.cmake
@@ -58,6 +58,24 @@ foreach(_comp ${Boost_FIND_COMPONENTS})
   endif(DEFINED USE_BOOST_${_COMP} AND NOT USE_BOOST_${_COMP})
 endforeach(_comp ${Boost_FIND_COMPONENTS})
 
+# For python3, append the python component with a suffix
+if("${Boost_FIND_COMPONENTS}" MATCHES "python")
+  find_package(Python)
+  if(PYTHON_FOUND)
+    if(PYTHON_VERSION_MAJOR GREATER 2)
+      if(APPLE)
+        # On apple (homebrew), boost-python for python 3 is called boost-python3
+        string(REPLACE "python" "python3" Boost_FIND_COMPONENTS "${Boost_FIND_COMPONENTS}")
+      else(APPLE)
+        # On ubuntu, boost-python for python 3 is called e.g. boost-python-py35
+        string(REPLACE "python" "python-py${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}" Boost_FIND_COMPONENTS "${Boost_FIND_COMPONENTS}")
+      endif(APPLE)
+  endif(PYTHON_VERSION_MAJOR GREATER 2)
+  else(PYTHON_FOUND)
+    message(SEND_ERROR "boost-python was requested but python was not found.")
+  endif(PYTHON_FOUND)
+endif("${Boost_FIND_COMPONENTS}" MATCHES "python")
+
 # Call the "real" FindBoost module.
 include(${CMAKE_ROOT}/Modules/FindBoost.cmake)
 
diff --git a/CMake/FindCasacore.cmake b/CMake/FindCasacore.cmake
index cf824314f79..8652068af55 100644
--- a/CMake/FindCasacore.cmake
+++ b/CMake/FindCasacore.cmake
@@ -5,6 +5,9 @@
 #   casa, coordinates, derivedmscal, fits, images, lattices, meas, 
 #   measures, mirlib, ms, msfits, python, scimath, scimath_f, tables
 #
+# The component python will be replaced by python3 if the version of the
+# python interpreter found is >= 3.
+#
 # Note that most components are dependent on other (more basic) components.
 # In that case, it suffices to specify the "top-level" components; dependent
 # components will be searched for automatically.
@@ -154,6 +157,7 @@ set(Casacore_components
   ms
   msfits
   python
+  python3
   scimath
   scimath_f
   tables
@@ -172,6 +176,7 @@ set(Casacore_mirlib_DEPENDENCIES)
 set(Casacore_ms_DEPENDENCIES            measures scimath tables casa)
 set(Casacore_msfits_DEPENDENCIES        ms fits measures tables casa)
 set(Casacore_python_DEPENDENCIES        casa)
+set(Casacore_python3_DEPENDENCIES       casa)
 set(Casacore_scimath_DEPENDENCIES       scimath_f casa)
 set(Casacore_scimath_f_DEPENDENCIES)
 set(Casacore_tables_DEPENDENCIES        casa)
@@ -220,6 +225,18 @@ else(NOT CASACORE_INCLUDE_DIR)
   # Get a list of all dependent Casacore libraries that need to be found.
   casacore_resolve_dependencies(_find_components ${Casacore_FIND_COMPONENTS})
 
+  # For python3, change the python component to python3
+  if("${_find_components}" MATCHES "python")
+    find_package(Python)
+    if(PYTHON_FOUND)
+      if(PYTHON_VERSION_MAJOR GREATER 2)
+        string(REPLACE "python" "python3" _find_components "${_find_components}")
+      endif(PYTHON_VERSION_MAJOR GREATER 2)
+    else(PYTHON_FOUND)
+      message(SEND_ERROR "casacore-python was requested but python was not found.")
+    endif(PYTHON_FOUND)
+  endif("${_find_components}" MATCHES "python")
+
   # Find the library for each component, and handle external dependencies
   foreach(_comp ${_find_components})
     casacore_find_library(casa_${_comp})
diff --git a/CMake/FindPython.cmake b/CMake/FindPython.cmake
index b6e729e2781..ae5d0eecdc3 100644
--- a/CMake/FindPython.cmake
+++ b/CMake/FindPython.cmake
@@ -59,4 +59,3 @@ set(PYTHON_FOUND FALSE)
 if(PYTHONINTERP_FOUND AND PYTHONLIBS_FOUND)
   set(PYTHON_FOUND TRUE)
 endif(PYTHONINTERP_FOUND AND PYTHONLIBS_FOUND)
-
diff --git a/CMake/FindPythonModule.cmake b/CMake/FindPythonModule.cmake
index 10185cda7af..190b85f210d 100644
--- a/CMake/FindPythonModule.cmake
+++ b/CMake/FindPythonModule.cmake
@@ -42,7 +42,7 @@ find_package(PythonInterp)
 # -----------------------------------------------------------------------------
 include(CMakeParseArguments)
 
-function(find_python_module _module)
+macro(find_python_module _module)
   # Name of module in uppercase.
   string(TOUPPER "${_module}" _MODULE)
 
@@ -103,4 +103,4 @@ function(find_python_module _module)
     endif(is_required GREATER -1)
   endif(NOT PYTHON_${_MODULE}_FOUND)
 
-endfunction()
+endmacro()
diff --git a/CMake/testscripts/assay b/CMake/testscripts/assay
index a2ceb3ee091..b9cd469c2e7 100755
--- a/CMake/testscripts/assay
+++ b/CMake/testscripts/assay
@@ -195,8 +195,9 @@
         exit $STATUS' 0 1 2 3 15
 
 # If there is a .py file then use it.
+# Force unbuffered output to give the same behavior in python3 and python2
   if [ -f "$PROG.py" ]; then
-    COMMAND="python $PROG.py"
+    COMMAND="${PYTHON_EXECUTABLE} -u $PROG.py"
     LOFAR_CHECKTOOL=
   fi
 
diff --git a/CMake/testscripts/runctest.sh.in b/CMake/testscripts/runctest.sh.in
index 052b94c0dc3..b6e935d9f20 100755
--- a/CMake/testscripts/runctest.sh.in
+++ b/CMake/testscripts/runctest.sh.in
@@ -42,6 +42,14 @@ export LD_LIBRARY_PATH
 # Add the Python build directory to PYTHONPATH.
 PYTHONPATH="@srcdir@:@PYTHON_BUILD_DIR@:${PYTHONPATH}"; export PYTHONPATH
 
+# Set the Python interpreter to the one found by CMake
+PYTHON_EXECUTABLE="@PYTHON_EXECUTABLE@"
+# Fall back to just "python"
+case $PYTHON_EXECUTABLE in
+  ("") PYTHON_EXECUTABLE="python"
+esac
+export PYTHON_EXECUTABLE
+
 # Start CMake/testscripts/runtest.sh
 "@LOFAR_ROOT@/CMake/testscripts/runtest.sh" "$@"
 
diff --git a/LCS/pyparameterset/src/__init__.py b/LCS/pyparameterset/src/__init__.py
index bd37643c8fb..eb917c41b7b 100755
--- a/LCS/pyparameterset/src/__init__.py
+++ b/LCS/pyparameterset/src/__init__.py
@@ -19,8 +19,8 @@
 #
 # $Id$
 
-from _pyparameterset import PyParameterValue
-from _pyparameterset import PyParameterSet
+from ._pyparameterset import PyParameterValue
+from ._pyparameterset import PyParameterSet
 
 class parametervalue(PyParameterValue):
     """
diff --git a/LCS/pyparameterset/test/tpyparameterset.py b/LCS/pyparameterset/test/tpyparameterset.py
index 92e61e32fcd..8b65375425c 100644
--- a/LCS/pyparameterset/test/tpyparameterset.py
+++ b/LCS/pyparameterset/test/tpyparameterset.py
@@ -1,71 +1,78 @@
+from __future__ import print_function
+
 from lofar.parameterset import *
 
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
 def checkps (ps):
-    print ps.isDefined("key1")
-    print ps.isDefined("a.b")
-    print ps.get("a.b").get()
-    print ps.getString("a.b")
-    print ps.getString("a.b", "aa")
-    print ps.getString("aa.bb", "aa")
+    print(ps.isDefined("key1"))
+    print(ps.isDefined("a.b"))
+    print(ps.get("a.b").get())
+    print(ps.getString("a.b"))
+    print(ps.getString("a.b", "aa"))
+    print(ps.getString("aa.bb", "aa"))
 
-    print ps.getString("a.b.lange_naam")
+    print(ps.getString("a.b.lange_naam"))
 
-    print ps.getBool(key="a.b.bool")
-    print ps.getBool("a.b.bool", False)
-    print ps.getBool("aa.bb", False)
+    print(ps.getBool(key="a.b.bool"))
+    print(ps.getBool("a.b.bool", False))
+    print(ps.getBool("aa.bb", False))
 
-    print ps.getInt("a.b")
-    print ps.getInt("a.b", 10)
-    print ps.getInt("aa.bb", 10)
+    print(ps.getInt("a.b"))
+    print(ps.getInt("a.b", 10))
+    print(ps.getInt("aa.bb", 10))
 
-    print ps.getFloat("a.b")
-    print ps.getFloat("a.b", 3.14)
-    print ps.getFloat("aa.bb", 3.14)
-    print ps.getDouble("a.b.double")
+    print("{:.1f}".format(ps.getFloat("a.b")))
+    print("{:.1f}".format(ps.getFloat("a.b", 3.14)))
+    print("{:.10f}".format(ps.getFloat("aa.bb", 3.14)))
+    print("{:.7f}".format(ps.getDouble("a.b.double")))
 
-    print ps.getBoolVector("vecbool")
-    print ps.getBoolVector("vecbool", (False,True))
-    print ps.getBoolVector("aa.bb", [False,True])
+    print(ps.getBoolVector("vecbool"))
+    print(ps.getBoolVector("vecbool", (False,True)))
+    print(ps.getBoolVector("aa.bb", [False,True]))
 
-    print ps.getIntVector("vec")
-    print ps.getIntVector("vec", (5,6))
-    print ps.getIntVector("aa.bb", [5,6])
+    print(ps.getIntVector("vec"))
+    print(ps.getIntVector("vec", (5,6)))
+    print(ps.getIntVector("aa.bb", [5,6]))
 
-    print ps.getFloatVector("vec")
-    print ps.getFloatVector("vec", (5,6))
-    print ps.getFloatVector("aa.bb", [5,6])
+    print(ps.getFloatVector("vec"))
+    print(ps.getFloatVector("vec", (5,6)))
+    print(ps.getFloatVector("aa.bb", [5,6]))
 
-    print ps.getDoubleVector("vec")
-    print ps.getDoubleVector("vec", (5,6))
-    print ps.getDoubleVector("aa.bb", [5,6])
+    print(ps.getDoubleVector("vec"))
+    print(ps.getDoubleVector("vec", (5,6)))
+    print(ps.getDoubleVector("aa.bb", [5,6]))
 
-    print ps.getStringVector("vec")
-    print ps.getStringVector("vec", ('5','6'))
-    print ps.getStringVector("aa.bb", ['5','6'])
+    print(ps.getStringVector("vec"))
+    print(ps.getStringVector("vec", ('5','6')))
+    print(ps.getStringVector("aa.bb", ['5','6']))
 
-    print ps.getIntVector("vecexp", True)
-    print ps.getIntVector("vecexp", [1,2], True)
-    print ps.getIntVector("aa.bb", [1,2], True)
+    print(ps.getIntVector("vecexp", True))
+    print(ps.getIntVector("vecexp", [1,2], True))
+    print(ps.getIntVector("aa.bb", [1,2], True))
 
     pvs = ps["vecnest"]
-    print pvs.isVector()
+    print(pvs.isVector())
     pvsvec = pvs.getVector()
-    print pvsvec[0].get()
-    print pvsvec[0].expand().getIntVector()
-    print pvsvec[1].expand().getIntVector()
+    print(pvsvec[0].get())
+    print(pvsvec[0].expand().getIntVector())
+    print(pvsvec[1].expand().getIntVector())
 
 # Check using given parset file.
 checkps (parameterset("tpyparameterset.in"))
-print ""
+print("")
 
 # Create and check a new parset using same keys/values as in parset file.
 ps=parameterset()
-print ">>>"
-print ps.version("tree")
-print ps.version("full")
-print ps.version("top")
-print ps.version()
-print "<<<"
+print(">>>")
+print(ps.version("tree"))
+print(ps.version("full"))
+print(ps.version("top"))
+print(ps.version())
+print("<<<")
 ps.add ("a.b", "7")
 ps.add ("a.b.lange_naam", "'dit \"is\" nu een andere naam geworden zonder extra spaties aan het einde want die gaan verloren bij wegschrijven + teruglezen'")
 ps.add ("a.b.c", "5")
@@ -77,27 +84,26 @@ ps.add ("vecbool", "[true,false,true]")
 ps.add ("vec", "[1,2,3]")
 ps.add ("vecexp", "[1..3,5..10]")
 ps.add ("vecnest", "[[1..3,5*10],[5..10]]")
-print ps.keys()
+print(ps.keys())
 checkps (ps)
 
 # Check if a subset can be made and its name can be read.
 pss = ps.makeSubset('a.')
-print pss.keys()
-print 'b.c =', pss.getString ('b.c')
-print pss.makeSubset('b.', 'aa.bb.').keys()
-print pss.makeSubset('b.').size()
-print pss.makeSubset('cc').keys()    # should be empty
-print len(pss.makeSubset('cc'))
+print(pss.keys())
+print('b.c =', pss.getString ('b.c'))
+print(pss.makeSubset('b.', 'aa.bb.').keys())
+print(pss.makeSubset('b.').size())
+print(pss.makeSubset('cc').keys())    # should be empty
+print(len(pss.makeSubset('cc')))
 
 # Check the dict functionality.
-print pss.dict()
-print pss.dict(True)     # remove quotes around strings
+print(sorted(pss.dict().items()))
+print(sorted(pss.dict(True).items()))     # remove quotes around strings
 
 # Check str()
-print str(ps)
+print(str(ps))
 
 # Check picking/unpickling
-import cPickle as pickle
 s = pickle.dumps(ps)
 ps2 = pickle.loads(s)
 assert str(ps) == str(ps2)
diff --git a/LCS/pyparameterset/test/tpyparameterset.stdout b/LCS/pyparameterset/test/tpyparameterset.stdout
index 0fc009b824f..854ff79614f 100644
--- a/LCS/pyparameterset/test/tpyparameterset.stdout
+++ b/LCS/pyparameterset/test/tpyparameterset.stdout
@@ -128,8 +128,8 @@ b.c = 5
 4
 []
 0
-{'b.c': '5', 'b': '7', 'b.lange_naam': '\'dit "is" nu een andere naam geworden zonder extra spaties aan het einde want die gaan verloren bij wegschrijven + teruglezen\'', 'b.bool': 'true', 'b.double': '3.1415926'}
-{'b.c': '5', 'b': '7', 'b.lange_naam': 'dit "is" nu een andere naam geworden zonder extra spaties aan het einde want die gaan verloren bij wegschrijven + teruglezen', 'b.bool': 'true', 'b.double': '3.1415926'}
+[('b', '7'), ('b.bool', 'true'), ('b.c', '5'), ('b.double', '3.1415926'), ('b.lange_naam', '\'dit "is" nu een andere naam geworden zonder extra spaties aan het einde want die gaan verloren bij wegschrijven + teruglezen\'')]
+[('b', '7'), ('b.bool', 'true'), ('b.c', '5'), ('b.double', '3.1415926'), ('b.lange_naam', 'dit "is" nu een andere naam geworden zonder extra spaties aan het einde want die gaan verloren bij wegschrijven + teruglezen')]
 a.b=7
 a.b.bool=true
 a.b.c=5
diff --git a/LCS/pytools/include/pytools/PycBasicData.h b/LCS/pytools/include/pytools/PycBasicData.h
index 85ecd8efc11..f2e12e55f72 100755
--- a/LCS/pytools/include/pytools/PycBasicData.h
+++ b/LCS/pytools/include/pytools/PycBasicData.h
@@ -231,11 +231,16 @@ namespace LOFAR { namespace pytools {
       incref(obj_ptr);        // incr refcount, because ~object decrements it
       // Accept single values.
       if (PyBool_Check(obj_ptr)
+#if PYTHON_VERSION_MAJOR < 3
 	  || PyInt_Check(obj_ptr)
+	  || PyString_Check(obj_ptr)
+#else
+	  || PyUnicode_Check(obj_ptr)
+#endif
 	  || PyLong_Check(obj_ptr)
 	  || PyFloat_Check(obj_ptr)
 	  || PyComplex_Check(obj_ptr)
-	  || PyString_Check(obj_ptr)) {
+      ) {
 	extract<container_element_type> elem_proxy(py_obj);
 	if (!elem_proxy.check()) return 0;
 	return obj_ptr;
@@ -277,12 +282,16 @@ namespace LOFAR { namespace pytools {
       data->convertible = storage;
       ContainerType& result = *((ContainerType*)storage);
       if (PyBool_Check(obj_ptr)
+#if PYTHON_VERSION_MAJOR < 3
 	  || PyInt_Check(obj_ptr)
+	  || PyString_Check(obj_ptr)
+#else
+	  || PyUnicode_Check(obj_ptr)
+#endif
 	  || PyLong_Check(obj_ptr)
 	  || PyFloat_Check(obj_ptr)
 	  || PyComplex_Check(obj_ptr)
-	  || PyString_Check(obj_ptr)) {
-          ///	  || PyString_Check(obj_ptr)
+      ) {
           ///	  || PycArrayScalarCheck(obj_ptr)) {
 	extract<container_element_type> elem_proxy(obj_ptr);
 	ConversionPolicy::reserve(result, 1);
diff --git a/LCS/pytools/test/tConvert.py b/LCS/pytools/test/tConvert.py
index 551ebbe1738..3b245086108 100755
--- a/LCS/pytools/test/tConvert.py
+++ b/LCS/pytools/test/tConvert.py
@@ -1,30 +1,32 @@
 #!/usr/bin/env python
+from __future__ import print_function
+
 from _tConvert import *
 
 def dotest(t):
 
-    print ''
-    print 'begin dotest'
-    print t.testbool (True);
-    print t.testbool (False);
-    print t.testint (-1);
-    print t.testint (10L);
-    print t.testint64 (-123456789013L);
-    print t.testint64 (123456789014L);
-    print t.testssize (-2);
-    print t.testssize (11);
-    print t.testfloat (3.14);
-    print t.testfloat (12);
-    print t.teststring ("this is a string");
+    print('')
+    print('begin dotest')
+    print(t.testbool (True))
+    print(t.testbool (False))
+    print(t.testint (-1))
+    print(t.testint (10))
+    print(t.testint64 (-123456789013))
+    print(t.testint64 (123456789014))
+    print(t.testssize (-2))
+    print(t.testssize (11))
+    print(t.testfloat (3.14))
+    print(t.testfloat (12))
+    print(t.teststring ("this is a string"))
 
-    print t.testvecint ([1,2,3,4]);
-    print t.testvecint ([]);
-    print t.testvecint ((-1,-2,-3,-4));
-    print t.testvecint (-10);
-    print t.testveccomplex ([1+2j, -1-3j, -1.5+2.5j]);
-    print t.testvecstr (["a1","a2","b1","b2"])
-    print t.testvecstr (())
-    print t.testvecstr ("sc1")
+    print(t.testvecint ([1,2,3,4]))
+    print(t.testvecint ([]))
+    print(t.testvecint ((-1,-2,-3,-4)))
+    print(t.testvecint (-10))
+    print(t.testveccomplex ([1+2j, -1-3j, -1.5+2.5j]))
+    print(t.testvecstr (["a1","a2","b1","b2"]))
+    print(t.testvecstr (()))
+    print(t.testvecstr ("sc1"))
 
 
 t = tConvert();
diff --git a/lofar_config.h.cmake b/lofar_config.h.cmake
index 6ff3b27b943..80ce0102abe 100644
--- a/lofar_config.h.cmake
+++ b/lofar_config.h.cmake
@@ -174,6 +174,9 @@
 /* Define if WCSLIB is installed */
 #cmakedefine HAVE_WCSLIB 1
 
+/* Define if python3 is installed */
+#cmakedefine PYTHON_VERSION_MAJOR ${PYTHON_VERSION_MAJOR}
+
 
 /*-------------------------------------------------------------------------*\
 |  Defines for the presence or absence of (system) functions                |
-- 
GitLab