Index: src/sas/sascalc/calculator/BaseComponent.py
===================================================================
--- src/sas/sascalc/calculator/BaseComponent.py	(revision 9a5097cd1c76689d1f997b85ba43d12a95d8f243)
+++ src/sas/sascalc/calculator/BaseComponent.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -143,5 +143,5 @@
                 qdist[1].__class__.__name__ != 'ndarray':
                 msg = "evalDistribution expects a list of 2 ndarrays"
-                raise RuntimeError, msg
+                raise RuntimeError(msg)
 
             # Extract qx and qy for code clarity
@@ -167,5 +167,5 @@
             mesg = "evalDistribution is expecting an ndarray of scalar q-values"
             mesg += " or a list [qx,qy] where qx,qy are 2D ndarrays."
-            raise RuntimeError, mesg
+            raise RuntimeError(mesg)
 
 
@@ -228,5 +228,5 @@
                     return
 
-        raise ValueError, "Model does not contain parameter %s" % name
+        raise ValueError("Model does not contain parameter %s" % name)
 
     def getParam(self, name):
@@ -250,5 +250,5 @@
                     return self.params[item]
 
-        raise ValueError, "Model does not contain parameter %s" % name
+        raise ValueError("Model does not contain parameter %s" % name)
 
     def getParamList(self):
@@ -294,20 +294,20 @@
         add
         """
-        raise ValueError, "Model operation are no longer supported"
+        raise ValueError("Model operation are no longer supported")
     def __sub__(self, other):
         """
         sub
         """
-        raise ValueError, "Model operation are no longer supported"
+        raise ValueError("Model operation are no longer supported")
     def __mul__(self, other):
         """
         mul
         """
-        raise ValueError, "Model operation are no longer supported"
+        raise ValueError("Model operation are no longer supported")
     def __div__(self, other):
         """
         div
         """
-        raise ValueError, "Model operation are no longer supported"
+        raise ValueError("Model operation are no longer supported")
 
 
Index: src/sas/sascalc/calculator/c_extensions/sld2i_module.cpp
===================================================================
--- src/sas/sascalc/calculator/c_extensions/sld2i_module.cpp	(revision b523c0e4093c0c8e82cebd10672473d88adeaee9)
+++ src/sas/sascalc/calculator/c_extensions/sld2i_module.cpp	(revision 1d014cbb271bb612a92607b84c582f19f50b2b11)
@@ -5,4 +5,12 @@
 #include <stdio.h>
 #include <sld2i.hh>
+
+#if PY_MAJOR_VERSION < 3
+typedef void (*PyCapsule_Destructor)(PyObject *);
+typedef void (*PyCObject_Destructor)(void *);
+#define PyCapsule_New(pointer, name, destructor) (PyCObject_FromVoidPtr(pointer, (PyCObject_Destructor)destructor))
+#define PyCapsule_GetPointer(capsule, name) (PyCObject_AsVoidPtr(capsule))
+#endif
+
 
 // Utilities
@@ -25,6 +33,7 @@
  * Delete a GenI object
  */
-void del_sld2i(void *ptr){
-	GenI* sld2i = static_cast<GenI *>(ptr);
+void
+del_sld2i(PyObject *obj){
+	GenI* sld2i = static_cast<GenI *>(PyCapsule_GetPointer(obj, "GenI"));
 	delete sld2i;
 	return;
@@ -71,5 +80,5 @@
 	OUTVECTOR(vol_pix_obj, vol_pix, n_x);
 	GenI* sld2i = new GenI(n_pix,x_val,y_val,z_val,sldn_val,mx_val,my_val,mz_val,vol_pix,inspin,outspin,stheta);
-	return PyCObject_FromVoidPtr(sld2i, del_sld2i);
+	return PyCapsule_New(sld2i, "GenI", del_sld2i);
 }
 
@@ -97,5 +106,5 @@
 
 	// Set the array pointers
-	void *temp = PyCObject_AsVoidPtr(gen_obj);
+	void *temp = PyCapsule_GetPointer(gen_obj, "GenI");
 	GenI* s = static_cast<GenI *>(temp);
 
@@ -125,5 +134,5 @@
 
 	// Set the array pointers
-	void *temp = PyCObject_AsVoidPtr(gen_obj);
+	void *temp = PyCapsule_GetPointer(gen_obj, "GenI");
 	GenI* s = static_cast<GenI *>(temp);
 
@@ -146,11 +155,48 @@
 };
 
-
-#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
-#define PyMODINIT_FUNC void
+#define MODULE_DOC "Sld2i C Library"
+#define MODULE_NAME "sld2i"
+#define MODULE_INIT2 initsld2i
+#define MODULE_INIT3 PyInit_sld2i
+#define MODULE_METHODS module_methods
+
+/* ==== boilerplate python 2/3 interface bootstrap ==== */
+
+
+#if defined(WIN32) && !defined(__MINGW32__)
+    #define DLL_EXPORT __declspec(dllexport)
+#else
+    #define DLL_EXPORT
 #endif
-PyMODINIT_FUNC
-initsld2i(void)
-{
-    Py_InitModule3("sld2i", module_methods, "Sld2i module");
-}
+
+#if PY_MAJOR_VERSION >= 3
+
+  DLL_EXPORT PyMODINIT_FUNC MODULE_INIT3(void)
+  {
+    static struct PyModuleDef moduledef = {
+      PyModuleDef_HEAD_INIT,
+      MODULE_NAME,         /* m_name */
+      MODULE_DOC,          /* m_doc */
+      -1,                  /* m_size */
+      MODULE_METHODS,      /* m_methods */
+      NULL,                /* m_reload */
+      NULL,                /* m_traverse */
+      NULL,                /* m_clear */
+      NULL,                /* m_free */
+    };
+    return PyModule_Create(&moduledef);
+  }
+
+#else /* !PY_MAJOR_VERSION >= 3 */
+
+  DLL_EXPORT PyMODINIT_FUNC MODULE_INIT2(void)
+  {
+    Py_InitModule4(MODULE_NAME,
+		 MODULE_METHODS,
+		 MODULE_DOC,
+		 0,
+		 PYTHON_API_VERSION
+		 );
+  }
+
+#endif /* !PY_MAJOR_VERSION >= 3 */
Index: src/sas/sascalc/calculator/instrument.py
===================================================================
--- src/sas/sascalc/calculator/instrument.py	(revision 9a5097cd1c76689d1f997b85ba43d12a95d8f243)
+++ src/sas/sascalc/calculator/instrument.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -222,7 +222,6 @@
         """
         # check if the wavelength is in range
-        if min(band) < self.min or\
-                max(band) > self.max:
-            raise
+        if min(band) < self.min or max(band) > self.max:
+            raise ValueError("band out of range")
         self.band = band
 
@@ -239,7 +238,6 @@
         """
         # check if the wavelength is in range
-        if wavelength < min(self.band) or\
-                wavelength > max(self.band):
-            raise
+        if wavelength < min(self.band) or wavelength > max(self.band):
+            raise ValueError("wavelength out of range")
         self.wavelength = wavelength
         validate(wavelength)
@@ -324,5 +322,5 @@
             plt.show()
         except:
-            raise RuntimeError, "Can't import matplotlib required to plot..."
+            raise RuntimeError("Can't import matplotlib required to plot...")
 
 
Index: src/sas/sascalc/calculator/resolution_calculator.py
===================================================================
--- src/sas/sascalc/calculator/resolution_calculator.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sascalc/calculator/resolution_calculator.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -4,15 +4,15 @@
 instrumental parameters.
 """
-from instrument import Sample
-from instrument import Detector
-from instrument import TOF as Neutron
-from instrument import Aperture
-# import math stuffs
-from math import pi
-from math import sqrt
+import sys
+from math import pi, sqrt
 import math
+import logging
+
 import numpy as np
-import sys
-import logging
+
+from .instrument import Sample
+from .instrument import Detector
+from .instrument import TOF as Neutron
+from .instrument import Aperture
 
 logger = logging.getLogger(__name__)
@@ -208,5 +208,5 @@
         if wavelength == 0:
             msg = "Can't compute the resolution: the wavelength is zero..."
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
         return self.intensity
 
@@ -379,14 +379,14 @@
         if qx_min < self.qx_min:
             self.qx_min = qx_min
-            #raise ValueError, msg
+            #raise ValueError(msg)
         if qx_max > self.qx_max:
             self.qx_max = qx_max
-            #raise ValueError, msg
+            #raise ValueError(msg)
         if qy_min < self.qy_min:
             self.qy_min = qy_min
-            #raise ValueError, msg
+            #raise ValueError(msg)
         if qy_max > self.qy_max:
             self.qy_max = qy_max
-            #raise ValueError, msg
+            #raise ValueError(msg)
         if not full_cal:
             return None
@@ -503,5 +503,5 @@
         # otherwise
         else:
-            raise ValueError, " Improper input..."
+            raise ValueError(" Improper input...")
         # get them squared
         sigma = x_comp * x_comp
@@ -706,5 +706,5 @@
             #self.set_wavelength(wavelength)
         else:
-            raise
+            raise TypeError("invalid wavlength---should be list or float")
 
     def set_wave_spread(self, wavelength_spread):
@@ -717,5 +717,5 @@
             self.wave.set_wave_spread_list([wavelength_spread])
         else:
-            raise
+            raise TypeError("invalid wavelength spread---should be list or float")
 
     def set_wavelength(self, wavelength):
@@ -766,5 +766,5 @@
         """
         if len(size) < 1 or len(size) > 2:
-            raise RuntimeError, "The length of the size must be one or two."
+            raise RuntimeError("The length of the size must be one or two.")
         self.aperture.set_source_size(size)
 
@@ -783,5 +783,5 @@
         """
         if len(size) < 1 or len(size) > 2:
-            raise RuntimeError, "The length of the size must be one or two."
+            raise RuntimeError("The length of the size must be one or two.")
         self.aperture.set_sample_size(size)
 
@@ -806,5 +806,5 @@
         """
         if len(distance) < 1 or len(distance) > 2:
-            raise RuntimeError, "The length of the size must be one or two."
+            raise RuntimeError("The length of the size must be one or two.")
         self.aperture.set_sample_distance(distance)
 
@@ -816,5 +816,5 @@
         """
         if len(distance) < 1 or len(distance) > 2:
-            raise RuntimeError, "The length of the size must be one or two."
+            raise RuntimeError("The length of the size must be one or two.")
         self.sample.set_distance(distance)
 
@@ -826,5 +826,5 @@
         """
         if len(distance) < 1 or len(distance) > 2:
-            raise RuntimeError, "The length of the size must be one or two."
+            raise RuntimeError("The length of the size must be one or two.")
         self.detector.set_distance(distance)
 
@@ -998,5 +998,5 @@
             pix_y_size = detector_pix_size[1]
         else:
-            raise ValueError, " Input value format error..."
+            raise ValueError(" Input value format error...")
         # Sample to detector distance = sample slit to detector
         # minus sample offset
Index: src/sas/sascalc/calculator/sas_gen.py
===================================================================
--- src/sas/sascalc/calculator/sas_gen.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sascalc/calculator/sas_gen.py	(revision 1d014cbb271bb612a92607b84c582f19f50b2b11)
@@ -5,15 +5,24 @@
 from __future__ import print_function
 
-import sas.sascalc.calculator.core.sld2i as mod
-from sas.sascalc.calculator.BaseComponent import BaseComponent
+import os
+import sys
+import copy
+import logging
+
 from periodictable import formula
 from periodictable import nsf
 import numpy as np
-import os
-import copy
-import sys
-import logging
+
+from .core import sld2i as mod
+from .BaseComponent import BaseComponent
 
 logger = logging.getLogger(__name__)
+
+if sys.version_info[0] < 3:
+    def decode(s):
+        return s
+else:
+    def decode(s):
+        return s.decode() if isinstance(s, bytes) else s
 
 MFACTOR_AM = 2.853E-12
@@ -34,5 +43,5 @@
         factor = MFACTOR_MT
     else:
-        raise ValueError, "Invalid valueunit"
+        raise ValueError("Invalid valueunit")
     sld_m = factor * mag
     return sld_m
@@ -100,5 +109,5 @@
         """
         if self.data_vol is None:
-            raise
+            raise TypeError("data_vol is missing")
         self.data_vol = volume
 
@@ -174,5 +183,5 @@
             if len(x[1]) > 0:
                 msg = "Not a 1D."
-                raise ValueError, msg
+                raise ValueError(msg)
             i_out = np.zeros_like(x[0])
             # 1D I is found at y =0 in the 2D pattern
@@ -181,5 +190,5 @@
         else:
             msg = "Q must be given as list of qx's and qy's"
-            raise ValueError, msg
+            raise ValueError(msg)
 
     def runXY(self, x=0.0):
@@ -196,5 +205,5 @@
         else:
             msg = "Q must be given as list of qx's and qy's"
-            raise ValueError, msg
+            raise ValueError(msg)
 
     def evalDistribution(self, qdist):
@@ -214,5 +223,5 @@
             mesg = "evalDistribution is expecting an ndarray of "
             mesg += "a list [qx,qy] where qx,qy are arrays."
-            raise RuntimeError, mesg
+            raise RuntimeError(mesg)
 
 class OMF2SLD(object):
@@ -288,5 +297,5 @@
                 z_dir2 *= z_dir2
                 mask = (x_dir2 + y_dir2 + z_dir2) <= 1.0
-            except:
+            except Exception:
                 logger.error(sys.exc_value)
         self.output = MagSLD(self.pos_x[mask], self.pos_y[mask],
@@ -313,17 +322,7 @@
         :Params length: data length
         """
-        msg = "Error: Inconsistent data length."
-        if len(self.pos_x) != length:
-            raise ValueError, msg
-        if len(self.pos_y) != length:
-            raise ValueError, msg
-        if len(self.pos_z) != length:
-            raise ValueError, msg
-        if len(self.mx) != length:
-            raise ValueError, msg
-        if len(self.my) != length:
-            raise ValueError, msg
-        if len(self.mz) != length:
-            raise ValueError, msg
+        parts = (self.pos_x, self.pos_y, self.pos_z, self.mx, self.my, self.mz)
+        if any(len(v) != length for v in parts):
+            raise ValueError("Error: Inconsistent data length.")
 
     def remove_null_points(self, remove=False, recenter=False):
@@ -378,5 +377,5 @@
         try:
             input_f = open(path, 'rb')
-            buff = input_f.read()
+            buff = decode(input_f.read())
             lines = buff.split('\n')
             input_f.close()
@@ -384,19 +383,21 @@
             valueunit = None
             for line in lines:
-                toks = line.split()
+                line = line.strip()
                 # Read data
-                try:
-                    _mx = float(toks[0])
-                    _my = float(toks[1])
-                    _mz = float(toks[2])
-                    _mx = mag2sld(_mx, valueunit)
-                    _my = mag2sld(_my, valueunit)
-                    _mz = mag2sld(_mz, valueunit)
-                    mx = np.append(mx, _mx)
-                    my = np.append(my, _my)
-                    mz = np.append(mz, _mz)
-                except:
-                    # Skip non-data lines
-                    logger.error(sys.exc_value)
+                if line and not line.startswith('#'):
+                    try:
+                        toks = line.split()
+                        _mx = float(toks[0])
+                        _my = float(toks[1])
+                        _mz = float(toks[2])
+                        _mx = mag2sld(_mx, valueunit)
+                        _my = mag2sld(_my, valueunit)
+                        _mz = mag2sld(_mz, valueunit)
+                        mx = np.append(mx, _mx)
+                        my = np.append(my, _my)
+                        mz = np.append(mz, _mz)
+                    except Exception as exc:
+                        # Skip non-data lines
+                        logger.error(str(exc)+" when processing %r"%line)
                 #Reading Header; Segment count ignored
                 s_line = line.split(":", 1)
@@ -415,5 +416,5 @@
                         msg = "Error: \n"
                         msg += "We accept only m as meshunit"
-                        raise ValueError, msg
+                        raise ValueError(msg)
                 if s_line[0].lower().count("xbase") > 0:
                     xbase = s_line[1].lstrip()
@@ -482,8 +483,8 @@
             output.set_m(mx, my, mz)
             return output
-        except:
+        except Exception:
             msg = "%s is not supported: \n" % path
             msg += "We accept only Text format OMF file."
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
 class PDBReader(object):
@@ -522,5 +523,5 @@
         try:
             input_f = open(path, 'rb')
-            buff = input_f.read()
+            buff = decode(input_f.read())
             lines = buff.split('\n')
             input_f.close()
@@ -536,5 +537,5 @@
                             float(line[12])
                             atom_name = atom_name[1].upper()
-                        except:
+                        except Exception:
                             if len(atom_name) == 4:
                                 atom_name = atom_name[0].upper()
@@ -559,5 +560,5 @@
                             vol = 1.0e+24 * atom.mass / atom.density / NA
                             vol_pix = np.append(vol_pix, vol)
-                        except:
+                        except Exception:
                             print("Error: set the sld of %s to zero"% atom_name)
                             sld_n = np.append(sld_n, 0.0)
@@ -573,5 +574,5 @@
                             try:
                                 int_val = int(val)
-                            except:
+                            except Exception:
                                 break
                             if int_val == 0:
@@ -592,5 +593,5 @@
                         y_lines.append(y_line)
                         z_lines.append(z_line)
-                except:
+                except Exception:
                     logger.error(sys.exc_value)
 
@@ -604,6 +605,6 @@
             output.sld_unit = '1/A^(2)'
             return output
-        except:
-            raise RuntimeError, "%s is not a sld file" % path
+        except Exception:
+            raise RuntimeError("%s is not a sld file" % path)
 
     def write(self, path, data):
@@ -657,8 +658,8 @@
                 elif ncols == 7:
                     vol_pix = None
-            except:
+            except Exception:
                 # For older version of numpy
                 input_f = open(path, 'rb')
-                buff = input_f.read()
+                buff = decode(input_f.read())
                 lines = buff.split('\n')
                 input_f.close()
@@ -683,7 +684,7 @@
                             _vol_pix = float(toks[7])
                             vol_pix = np.append(vol_pix, _vol_pix)
-                        except:
+                        except Exception:
                             vol_pix = None
-                    except:
+                    except Exception:
                         # Skip non-data lines
                         logger.error(sys.exc_value)
@@ -696,6 +697,6 @@
                 output.set_pixel_volumes(vol_pix)
             return output
-        except:
-            raise RuntimeError, "%s is not a sld file" % path
+        except Exception:
+            raise RuntimeError("%s is not a sld file" % path)
 
     def write(self, path, data):
@@ -706,7 +707,7 @@
         """
         if path is None:
-            raise ValueError, "Missing the file path."
+            raise ValueError("Missing the file path.")
         if data is None:
-            raise ValueError, "Missing the data to save."
+            raise ValueError("Missing the data to save.")
         x_val = data.pos_x
         y_val = data.pos_y
@@ -977,5 +978,5 @@
                 self.ynodes = int(ydist) + 1
                 self.znodes = int(zdist) + 1
-            except:
+            except Exception:
                 self.xnodes = None
                 self.ynodes = None
@@ -1012,5 +1013,5 @@
                 self.set_pixel_volumes(vol)
                 self.has_stepsize = True
-            except:
+            except Exception:
                 self.xstepsize = None
                 self.ystepsize = None
@@ -1057,6 +1058,6 @@
     reader = SLDReader()
     oreader = OMFReader()
-    output = reader.read(tfpath)
-    ooutput = oreader.read(ofpath)
+    output = decode(reader.read(tfpath))
+    ooutput = decode(oreader.read(ofpath))
     foutput = OMF2SLD()
     foutput.set_data(ooutput)
@@ -1099,5 +1100,5 @@
             break
     oreader = OMFReader()
-    ooutput = oreader.read(ofpath)
+    ooutput = decode(oreader.read(ofpath))
     foutput = OMF2SLD()
     foutput.set_data(ooutput)
Index: src/sas/sascalc/corfunc/corfunc_calculator.py
===================================================================
--- src/sas/sascalc/corfunc/corfunc_calculator.py	(revision a859f9916f07adbcfeb4bebd403b46e6f32d18a7)
+++ src/sas/sascalc/corfunc/corfunc_calculator.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -88,5 +88,5 @@
         # Only process data of the class Data1D
         if not issubclass(data.__class__, Data1D):
-            raise ValueError, "Data must be of the type DataLoader.Data1D"
+            raise ValueError("Data must be of the type DataLoader.Data1D")
 
         # Prepare the data
@@ -161,5 +161,5 @@
             err = ("Incorrect transform type supplied, must be 'fourier'",
                 " or 'hilbert'")
-            raise ValueError, err
+            raise ValueError(err)
 
         self._transform_thread.queue()
Index: src/sas/sascalc/data_util/calcthread.py
===================================================================
--- src/sas/sascalc/data_util/calcthread.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sascalc/data_util/calcthread.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -6,16 +6,19 @@
 from __future__ import print_function
 
-import thread
 import traceback
 import sys
 import logging
+try:
+    import _thread as thread
+except ImportError: # CRUFT: python 2 support
+    import thread
 
 if sys.platform.count("darwin") > 0:
     import time
     stime = time.time()
-    
+
     def clock():
         return time.time() - stime
-    
+
     def sleep(t):
         return time.sleep(t)
@@ -35,5 +38,5 @@
     CalcThread.__init__, passing it the keyword arguments for
     yieldtime, worktime, update and complete.
-    
+
     When defining the compute() method you need to include code which
     allows the GUI to run.  They are as follows: ::
@@ -211,5 +214,5 @@
             self._lock.release()
             self._time_for_update += 1e6  # No more updates
-            
+
             self.updatefn(**kwargs)
             sleep(self.yieldtime)
Index: src/sas/sascalc/data_util/nxsunit.py
===================================================================
--- src/sas/sascalc/data_util/nxsunit.py	(revision 8e9536f96bb3931f62eeba158ab01e1a3a25ded7)
+++ src/sas/sascalc/data_util/nxsunit.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -13,5 +13,5 @@
 in the NeXus definition files.
 
-Unlike other units packages, this package does not carry the units along with 
+Unlike other units packages, this package does not carry the units along with
 the value but merely provides a conversion function for transforming values.
 
@@ -68,5 +68,5 @@
     Ack! Allows, e.g., Coulomb and coulomb even though Coulomb is not
     a unit because some NeXus files store it that way!
-    
+
     Returns a dictionary of names and scales.
     """
@@ -78,10 +78,10 @@
                         n=1e-9,p=1e-12,f=1e-15)
     map = {abbr:1}
-    map.update([(P+abbr,scale) for (P,scale) in short_prefix.iteritems()])
+    map.update([(P+abbr,scale) for (P,scale) in short_prefix.items()])
     for name in [unit,unit.capitalize()]:
         map.update({name:1,name+'s':1})
-        map.update([(P+name,scale) for (P,scale) in prefix.iteritems()])
-        map.update([(P+'*'+name,scale) for (P,scale) in prefix.iteritems()])
-        map.update([(P+name+'s',scale) for (P,scale) in prefix.iteritems()])
+        map.update([(P+name,scale) for (P,scale) in prefix.items()])
+        map.update([(P+'*'+name,scale) for (P,scale) in prefix.items()])
+        map.update([(P+name+'s',scale) for (P,scale) in prefix.items()])
     return map
 
@@ -91,6 +91,6 @@
     """
     map = {}
-    map.update([(name,scale) for name,scale in kw.iteritems()])
-    map.update([(name+'s',scale) for name,scale in kw.iteritems()])
+    map.update([(name,scale) for name,scale in kw.items()])
+    map.update([(name+'s',scale) for name,scale in kw.items()])
     return map
 
@@ -101,6 +101,6 @@
     * WARNING * this will incorrect transform 10^3 to 103.
     """
-    s.update((k.replace('^',''),v) 
-             for k,v in s.items()
+    s.update((k.replace('^',''),v)
+             for k, v in list(s.items())
              if '^' in k)
 
@@ -130,10 +130,10 @@
     temperature.update(_build_metric_units('Celcius', 'C'))
     temperature.update(_build_metric_units('celcius', 'C'))
-    
+
     charge = _build_metric_units('coulomb','C')
     charge.update({'microAmp*hour':0.0036})
 
     sld = { '10^-6 Angstrom^-2': 1e-6, 'Angstrom^-2': 1 }
-    Q = { 'invA': 1, 'invAng': 1, 'invAngstroms': 1, '1/A': 1, 
+    Q = { 'invA': 1, 'invAng': 1, 'invAngstroms': 1, '1/A': 1,
           '10^-3 Angstrom^-1': 1e-3, '1/cm': 1e-8, '1/m': 1e-10,
           'nm^-1': 0.1, '1/nm': 0.1, 'n_m^-1': 0.1 }
@@ -189,5 +189,6 @@
 
 def _check(expect,get):
-    if expect != get: raise ValueError, "Expected %s but got %s"%(expect,get)
+    if expect != get:
+        raise ValueError("Expected %s but got %s"%(expect, get))
      #print expect,"==",get
 
@@ -202,7 +203,10 @@
     _check(123,Converter('a.u.')(123,units='s')) # arbitrary units always returns the same value
     _check(123,Converter('a.u.')(123,units='')) # arbitrary units always returns the same value
-    try: Converter('help')
-    except KeyError: pass
-    else: raise Exception("unknown unit did not raise an error")
+    try:
+        Converter('help')
+    except KeyError:
+        pass
+    else:
+        raise Exception("unknown unit did not raise an error")
 
     # TODO: more tests
Index: src/sas/sascalc/data_util/odict.py
===================================================================
--- src/sas/sascalc/data_util/odict.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/data_util/odict.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -39,15 +39,15 @@
     """
     A class of dictionary that keeps the insertion order of keys.
-    
+
     All appropriate methods return keys, items, or values in an ordered way.
-    
+
     All normal dictionary methods are available. Update and comparison is
     restricted to other OrderedDict objects.
-    
+
     Various sequence methods are available, including the ability to explicitly
     mutate the key ordering.
-    
+
     __contains__ tests:
-    
+
     >>> d = OrderedDict(((1, 3),))
     >>> 1 in d
@@ -55,7 +55,7 @@
     >>> 4 in d
     0
-    
+
     __getitem__ tests:
-    
+
     >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[2]
     1
@@ -63,14 +63,14 @@
     Traceback (most recent call last):
     KeyError: 4
-    
+
     __len__ tests:
-    
+
     >>> len(OrderedDict())
     0
     >>> len(OrderedDict(((1, 3), (3, 2), (2, 1))))
     3
-    
+
     get tests:
-    
+
     >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
     >>> d.get(1)
@@ -82,7 +82,7 @@
     >>> d
     OrderedDict([(1, 3), (3, 2), (2, 1)])
-    
+
     has_key tests:
-    
+
     >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
     >>> d.has_key(1)
@@ -96,9 +96,9 @@
         Create a new ordered dictionary. Cannot init from a normal dict,
         nor from kwargs, since items order is undefined in those cases.
-        
+
         If the ``strict`` keyword argument is ``True`` (``False`` is the
         default) then when doing slice assignment - the ``OrderedDict`` you are
         assigning from *must not* contain any keys in the remaining dict.
-        
+
         >>> OrderedDict()
         OrderedDict([])
@@ -283,5 +283,5 @@
         """
         Used for __repr__ and __str__
-        
+
         >>> r1 = repr(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f'))))
         >>> r1
@@ -321,5 +321,5 @@
         >>> d
         OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)])
-        
+
         >>> a = OrderedDict(((0, 1), (1, 2), (2, 3)), strict=True)
         >>> a[3] = 4
@@ -345,10 +345,10 @@
         >>> a
         OrderedDict([(3, 4), (2, 3), (1, 2), (0, 1)])
-        
+
         >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
         >>> d[:1] = 3
         Traceback (most recent call last):
         TypeError: slice assignment requires an OrderedDict
-        
+
         >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
         >>> d[:1] = OrderedDict([(9, 8)])
@@ -444,5 +444,5 @@
         """
         Implemented so that access to ``sequence`` raises a warning.
-        
+
         >>> d = OrderedDict()
         >>> d.sequence
@@ -463,5 +463,5 @@
         """
         To allow deepcopy to work with OrderedDict.
-        
+
         >>> from copy import deepcopy
         >>> a = OrderedDict([(1, 1), (2, 2), (3, 3)])
@@ -490,7 +490,7 @@
     def items(self):
         """
-        ``items`` returns a list of tuples representing all the 
+        ``items`` returns a list of tuples representing all the
         ``(key, value)`` pairs in the dictionary.
-        
+
         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
         >>> d.items()
@@ -505,5 +505,5 @@
         """
         Return a list of keys in the ``OrderedDict``.
-        
+
         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
         >>> d.keys()
@@ -515,8 +515,8 @@
         """
         Return a list of all the values in the OrderedDict.
-        
+
         Optionally you can pass in a list of values, which will replace the
         current list. The value list must be the same len as the OrderedDict.
-        
+
         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
         >>> d.values()
@@ -596,5 +596,5 @@
         """
         No dict.pop in Python 2.2, gotta reimplement it
-        
+
         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
         >>> d.pop(3)
@@ -612,5 +612,5 @@
         """
         if len(args) > 1:
-            raise TypeError, ('pop expected at most 2 arguments, got %s' %
+            raise TypeError('pop expected at most 2 arguments, got %s' %
                 (len(args) + 1))
         if key in self:
@@ -628,5 +628,5 @@
         Delete and return an item specified by index, not a random one as in
         dict. The index is -1 by default (the last item).
-        
+
         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
         >>> d.popitem()
@@ -674,5 +674,5 @@
         """
         Update from another OrderedDict or sequence of (key, value) pairs
-        
+
         >>> d = OrderedDict(((1, 0), (0, 1)))
         >>> d.update(OrderedDict(((1, 3), (3, 2), (2, 1))))
@@ -706,9 +706,9 @@
         """
         Rename the key for a given value, without modifying sequence order.
-        
+
         For the case where new_key already exists this raise an exception,
         since if new_key exists, it is ambiguous as to what happens to the
         associated values, and the position of new_key in the sequence.
-        
+
         >>> od = OrderedDict()
         >>> od['a'] = 1
@@ -732,5 +732,5 @@
             raise ValueError("New key already exists: %r" % new_key)
         # rename sequence entry
-        value = self[old_key] 
+        value = self[old_key]
         old_idx = self._sequence.index(old_key)
         self._sequence[old_idx] = new_key
@@ -742,8 +742,8 @@
         """
         This method allows you to set the items in the dict.
-        
+
         It takes a list of tuples - of the same sort returned by the ``items``
         method.
-        
+
         >>> d = OrderedDict()
         >>> d.setitems(((3, 1), (2, 3), (1, 2)))
@@ -760,8 +760,8 @@
         replace the current set. This must contain the same set of keys, but
         need not be in the same order.
-        
+
         If you pass in new keys that don't match, a ``KeyError`` will be
         raised.
-        
+
         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
         >>> d.keys()
@@ -791,7 +791,7 @@
         You can pass in a list of values, which will replace the
         current list. The value list must be the same len as the OrderedDict.
-        
+
         (Or a ``ValueError`` is raised.)
-        
+
         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
         >>> d.setvalues((1, 2, 3))
@@ -813,5 +813,5 @@
         """
         Return the position of the specified key in the OrderedDict.
-        
+
         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
         >>> d.index(3)
@@ -826,8 +826,8 @@
         """
         Takes ``index``, ``key``, and ``value`` as arguments.
-        
+
         Sets ``key`` to ``value``, so that ``key`` is at position ``index`` in
         the OrderedDict.
-        
+
         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
         >>> d.insert(0, 4, 0)
@@ -850,5 +850,5 @@
         """
         Reverse the order of the OrderedDict.
-        
+
         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
         >>> d.reverse()
@@ -861,8 +861,8 @@
         """
         Sort the key order in the OrderedDict.
-        
+
         This method takes the same arguments as the ``list.sort`` method on
         your version of Python.
-        
+
         >>> d = OrderedDict(((4, 1), (2, 2), (3, 3), (1, 4)))
         >>> d.sort()
@@ -876,5 +876,5 @@
     """
     Custom object for accessing the keys of an OrderedDict.
-    
+
     Can be called like the normal ``OrderedDict.keys`` method, but also
     supports indexing and sequence methods.
@@ -897,5 +897,5 @@
         You cannot assign to keys, but you can do slice assignment to re-order
         them.
-        
+
         You can only do slice assignment if the new set of keys is a reordering
         of the original set.
@@ -967,5 +967,5 @@
     """
     Custom object for accessing the items of an OrderedDict.
-    
+
     Can be called like the normal ``OrderedDict.items`` method, but also
     supports indexing and sequence methods.
@@ -1077,5 +1077,5 @@
     """
     Custom object for accessing the values of an OrderedDict.
-    
+
     Can be called like the normal ``OrderedDict.values`` method, but also
     supports indexing and sequence methods.
@@ -1099,5 +1099,5 @@
         """
         Set the value at position i to value.
-        
+
         You can only do slice assignment to values if you supply a sequence of
         equal length to the slice you are replacing.
@@ -1168,10 +1168,10 @@
     Experimental version of OrderedDict that has a custom object for ``keys``,
     ``values``, and ``items``.
-    
+
     These are callable sequence objects that work as methods, or can be
     manipulated directly as sequences.
-    
+
     Test for ``keys``, ``items`` and ``values``.
-    
+
     >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
     >>> d
@@ -1293,5 +1293,5 @@
     >>> d
     SequenceOrderedDict([(1, 1), (2, 2), (3, 3)])
-    
+
     >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
     >>> d
Index: src/sas/sascalc/data_util/registry.py
===================================================================
--- src/sas/sascalc/data_util/registry.py	(revision 3ece5dd0ab4005c590b101addcee167772e5954b)
+++ src/sas/sascalc/data_util/registry.py	(revision dc8d1c2838582489b1d85751d7e27f7348c8685c)
@@ -101,5 +101,5 @@
         extlist = [ext for ext in self.extensions() if path.endswith(ext)]
         # Sort matching extensions by decreasing order of length
-        extlist.sort(lambda a,b: len(a)<len(b))
+        extlist.sort(key=len)
         # Combine loaders for matching extensions into one big list
         loaders = []
Index: src/sas/sascalc/data_util/uncertainty.py
===================================================================
--- src/sas/sascalc/data_util/uncertainty.py	(revision 9a5097cd1c76689d1f997b85ba43d12a95d8f243)
+++ src/sas/sascalc/data_util/uncertainty.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -2,10 +2,10 @@
 Uncertainty propagation class for arithmetic, log and exp.
 
-Based on scalars or numpy vectors, this class allows you to store and 
+Based on scalars or numpy vectors, this class allows you to store and
 manipulate values+uncertainties, with propagation of gaussian error for
 addition, subtraction, multiplication, division, power, exp and log.
 
 Storage properties are determined by the numbers used to set the value
-and uncertainty.  Be sure to use floating point uncertainty vectors 
+and uncertainty.  Be sure to use floating point uncertainty vectors
 for inplace operations since numpy does not do automatic type conversion.
 Normal operations can use mixed integer and floating point.  In place
@@ -18,6 +18,7 @@
 
 import numpy as np
-import err1d
-from formatnum import format_uncertainty
+
+from .import err1d
+from .formatnum import format_uncertainty
 
 __all__ = ['Uncertainty']
@@ -28,5 +29,5 @@
     # Make standard deviation available
     def _getdx(self): return np.sqrt(self.variance)
-    def _setdx(self,dx): 
+    def _setdx(self,dx):
         # Direct operation
         #    variance = dx**2
@@ -38,10 +39,10 @@
     # Constructor
     def __init__(self, x, variance=None):
-        self.x, self.variance = x, variance    
- 
+        self.x, self.variance = x, variance
+
     # Numpy array slicing operations
-    def __len__(self): 
+    def __len__(self):
         return len(self.x)
-    def __getitem__(self,key): 
+    def __getitem__(self,key):
         return Uncertainty(self.x[key],self.variance[key])
     def __setitem__(self,key,value):
@@ -137,5 +138,5 @@
     def __idiv__(self, other): return self.__itruediv__(other)
 
-        
+
     # Unary ops
     def __neg__(self):
@@ -151,5 +152,5 @@
             return format_uncertainty(self.x,np.sqrt(self.variance))
         else:
-            return [format_uncertainty(v,dv) 
+            return [format_uncertainty(v,dv)
                     for v,dv in zip(self.x,np.sqrt(self.variance))]
     def __repr__(self):
@@ -219,5 +220,5 @@
     z = a/4
     assert z.x == 5./4 and z.variance == 3./4**2
-    
+
     # Reverse scalar operations
     z = 4+a
@@ -229,5 +230,5 @@
     z = 4/a
     assert z.x == 4./5 and abs(z.variance - 3./5**4 * 4**2) < 1e-15
-    
+
     # Power operations
     z = a**2
@@ -250,5 +251,5 @@
     assert z.x == 5./4 and abs(z.variance - (3./5**2 + 2./4**2)*(5./4)**2) < 1e-15
 
-    # ===== Inplace operations =====    
+    # ===== Inplace operations =====
     # Scalar operations
     y = a+0; y += 4
@@ -308,5 +309,5 @@
     assert (z.x == 5./4).all()
     assert (abs(z.variance - (3./5**2 + 2./4**2)*(5./4)**2) < 1e-15).all()
-    
+
     # printing; note that sqrt(3) ~ 1.7
     assert str(Uncertainty(5,3)) == "5.0(17)"
Index: src/sas/sascalc/dataloader/__init__.py
===================================================================
--- src/sas/sascalc/dataloader/__init__.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/__init__.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -1,3 +1,3 @@
-from data_info import *
-from manipulations import *
-from readers import *
+from .data_info import *
+from .manipulations import *
+from .readers import *
Index: src/sas/sascalc/dataloader/data_info.py
===================================================================
--- src/sas/sascalc/dataloader/data_info.py	(revision 17e257b538be266d4161c1dfa5ee2bea9ef364f2)
+++ src/sas/sascalc/dataloader/data_info.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -716,5 +716,5 @@
                 self.y_unit = '1/cm'
         except: # the data is not recognized/supported, and the user is notified
-            raise(TypeError, 'data not recognized, check documentation for supported 1D data formats')
+            raise TypeError('data not recognized, check documentation for supported 1D data formats')
 
     def __str__(self):
@@ -796,5 +796,5 @@
                 len(self.y) != len(other.y):
                 msg = "Unable to perform operation: data length are not equal"
-                raise ValueError, msg
+                raise ValueError(msg)
             # Here we could also extrapolate between data points
             TOLERANCE = 0.01
@@ -802,5 +802,5 @@
                 if math.fabs((self.x[i] - other.x[i])/self.x[i]) > TOLERANCE:
                     msg = "Incompatible data sets: x-values do not match"
-                    raise ValueError, msg
+                    raise ValueError(msg)
 
             # Check that the other data set has errors, otherwise
@@ -876,5 +876,5 @@
         if not isinstance(other, Data1D):
             msg = "Unable to perform operation: different types of data set"
-            raise ValueError, msg
+            raise ValueError(msg)
         return True
 
@@ -948,5 +948,5 @@
 
         if len(self.detector) > 0:
-            raise RuntimeError, "Data2D: Detector bank already filled at init"
+            raise RuntimeError("Data2D: Detector bank already filled at init")
 
     def __str__(self):
@@ -1020,12 +1020,12 @@
                 len(self.qy_data) != len(other.qy_data):
                 msg = "Unable to perform operation: data length are not equal"
-                raise ValueError, msg
+                raise ValueError(msg)
             for ind in range(len(self.data)):
                 if math.fabs((self.qx_data[ind] - other.qx_data[ind])/self.qx_data[ind]) > TOLERANCE:
                     msg = "Incompatible data sets: qx-values do not match: %s %s" % (self.qx_data[ind], other.qx_data[ind])
-                    raise ValueError, msg
+                    raise ValueError(msg)
                 if math.fabs((self.qy_data[ind] - other.qy_data[ind])/self.qy_data[ind]) > TOLERANCE:
                     msg = "Incompatible data sets: qy-values do not match: %s %s" % (self.qy_data[ind], other.qy_data[ind])
-                    raise ValueError, msg
+                    raise ValueError(msg)
 
             # Check that the scales match
@@ -1108,5 +1108,5 @@
         if not isinstance(other, Data2D):
             msg = "Unable to perform operation: different types of data set"
-            raise ValueError, msg
+            raise ValueError(msg)
         return True
 
Index: src/sas/sascalc/dataloader/file_reader_base_class.py
===================================================================
--- src/sas/sascalc/dataloader/file_reader_base_class.py	(revision ae69c690bae8eb1cae0c6902dd71755dda9710f8)
+++ src/sas/sascalc/dataloader/file_reader_base_class.py	(revision 7b50f14eb38e44aca64b1a233e5b996b05f5dc0c)
@@ -6,15 +6,23 @@
 
 import os
+import sys
 import re
 import logging
+from abc import abstractmethod
+
 import numpy as np
-from abc import abstractmethod
-from loader_exceptions import NoKnownLoaderException, FileContentsException,\
+from .loader_exceptions import NoKnownLoaderException, FileContentsException,\
     DataReaderException, DefaultReaderException
-from data_info import Data1D, Data2D, DataInfo, plottable_1D, plottable_2D,\
+from .data_info import Data1D, Data2D, DataInfo, plottable_1D, plottable_2D,\
     combine_data_info_with_plottable
 
 logger = logging.getLogger(__name__)
 
+if sys.version_info[0] < 3:
+    def decode(s):
+        return s
+else:
+    def decode(s):
+        return s.decode() if isinstance(s, bytes) else s
 
 class FileReader(object):
@@ -78,4 +86,26 @@
         # Return a list of parsed entries that data_loader can manage
         return self.output
+
+    def nextline(self):
+        """
+        Returns the next line in the file as a string.
+        """
+        #return self.f_open.readline()
+        return decode(self.f_open.readline())
+
+    def nextlines(self):
+        """
+        Returns the next line in the file as a string.
+        """
+        for line in self.f_open:
+            #yield line
+            yield decode(line)
+
+    def readall(self):
+        """
+        Returns the entire file as a string.
+        """
+        #return self.f_open.read()
+        return decode(self.f_open.read())
 
     def handle_error_message(self, msg):
Index: src/sas/sascalc/dataloader/loader.py
===================================================================
--- src/sas/sascalc/dataloader/loader.py	(revision dcb91cf83e474bdbe1c788d87690ca5f5e7841cb)
+++ src/sas/sascalc/dataloader/loader.py	(revision dc8d1c2838582489b1d85751d7e27f7348c8685c)
@@ -26,12 +26,14 @@
 import time
 from zipfile import ZipFile
+
 from sas.sascalc.data_util.registry import ExtensionRegistry
+
 # Default readers are defined in the readers sub-module
-import readers
-from loader_exceptions import NoKnownLoaderException, FileContentsException,\
+from . import readers
+from .loader_exceptions import NoKnownLoaderException, FileContentsException,\
     DefaultReaderException
-from readers import ascii_reader
-from readers import cansas_reader
-from readers import cansas_reader_HDF5
+from .readers import ascii_reader
+from .readers import cansas_reader
+from .readers import cansas_reader_HDF5
 
 logger = logging.getLogger(__name__)
@@ -73,4 +75,5 @@
         try:
             return super(Registry, self).load(path, format=format)
+        #except Exception: raise  # for debugging, don't use fallback loader
         except NoKnownLoaderException as nkl_e:
             pass  # Try the ASCII reader
@@ -327,5 +330,5 @@
         extlist = [ext for ext in self.extensions() if path.endswith(ext)]
         # Sort matching extensions by decreasing order of length
-        extlist.sort(lambda a, b: len(a) < len(b))
+        extlist.sort(key=len)
         # Combine loaders for matching extensions into one big list
         writers = []
@@ -341,5 +344,5 @@
         # Raise an error if there are no matching extensions
         if len(writers) == 0:
-            raise ValueError, "Unknown file type for " + path
+            raise ValueError("Unknown file type for " + path)
         # All done
         return writers
@@ -360,5 +363,5 @@
             try:
                 return fn(path, data)
-            except:
+            except Exception:
                 pass  # give other loaders a chance to succeed
         # If we get here it is because all loaders failed
Index: src/sas/sascalc/dataloader/manipulations.py
===================================================================
--- src/sas/sascalc/dataloader/manipulations.py	(revision 324e0bf8b73f2240c3c9fc343111f7c06cd3bca8)
+++ src/sas/sascalc/dataloader/manipulations.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -26,5 +26,5 @@
 
 #from data_info import plottable_2D
-from data_info import Data1D
+from .data_info import Data1D
 
 
Index: src/sas/sascalc/dataloader/readers/__init__.py
===================================================================
--- src/sas/sascalc/dataloader/readers/__init__.py	(revision 488f3a5d892488fd37274dc8ae6ce49780434f37)
+++ src/sas/sascalc/dataloader/readers/__init__.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -1,2 +1,2 @@
 # Method to associate extensions to default readers
-from associations import read_associations
+from .associations import read_associations
Index: src/sas/sascalc/dataloader/readers/abs_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/abs_reader.py	(revision ad92c5a96ecb73ee7094bafca363a6a7a61a530d)
+++ src/sas/sascalc/dataloader/readers/abs_reader.py	(revision 46cf4c9afade5339c43974291e5429d30610b75b)
@@ -11,10 +11,11 @@
 
 import logging
+
 import numpy as np
-from sas.sascalc.dataloader.file_reader_base_class import FileReader
-from sas.sascalc.dataloader.data_info import DataInfo, plottable_1D, Data1D,\
-    Detector
-from sas.sascalc.dataloader.loader_exceptions import FileContentsException,\
-    DefaultReaderException
+
+from sas.sascalc.data_util.nxsunit import Converter
+from ..file_reader_base_class import FileReader
+from ..data_info import DataInfo, plottable_1D, Data1D, Detector
+from ..loader_exceptions import FileContentsException, DefaultReaderException
 
 logger = logging.getLogger(__name__)
@@ -31,20 +32,15 @@
     # List of allowed extensions
     ext = ['.abs']
-    
+
     def get_file_contents(self):
-        """ 
+        """
         Get the contents of the file
-        
+
         :raise RuntimeError: when the file can't be opened
         :raise ValueError: when the length of the data vectors are inconsistent
         """
-        buff = self.f_open.read()
+        buff = self.readall()
         filepath = self.f_open.name
         lines = buff.splitlines()
-        self.has_converter = True
-        try:
-            from sas.sascalc.data_util.nxsunit import Converter
-        except:
-            self.has_converter = False
         self.output = []
         self.current_datainfo = DataInfo()
@@ -75,6 +71,5 @@
                 try:
                     value = float(line_toks[1])
-                    if self.has_converter and \
-                            self.current_datainfo.source.wavelength_unit != 'A':
+                    if self.current_datainfo.source.wavelength_unit != 'A':
                         conv = Converter('A')
                         self.current_datainfo.source.wavelength = conv(value,
@@ -89,5 +84,5 @@
                 try:
                     value = float(line_toks[3])
-                    if self.has_converter and detector.distance_unit != 'm':
+                    if detector.distance_unit != 'm':
                         conv = Converter('m')
                         detector.distance = conv(value,
@@ -95,5 +90,5 @@
                     else:
                         detector.distance = value
-                except:
+                except Exception:
                     msg = "ABSReader cannot read SDD from %s" % filepath
                     self.current_datainfo.errors.append(msg)
@@ -110,6 +105,5 @@
                 try:
                     value = float(line_toks[5])
-                    if self.has_converter and \
-                            self.current_datainfo.sample.thickness_unit != 'cm':
+                    if self.current_datainfo.sample.thickness_unit != 'cm':
                         conv = Converter('cm')
                         self.current_datainfo.sample.thickness = conv(value,
@@ -134,5 +128,5 @@
 
                 # Bin size
-                if self.has_converter and detector.pixel_size_unit != 'mm':
+                if detector.pixel_size_unit != 'mm':
                     conv = Converter('mm')
                     detector.pixel_size.x = conv(5.08,
@@ -146,10 +140,10 @@
                 # Store beam center in distance units
                 # Det 640 x 640 mm
-                if self.has_converter and detector.beam_center_unit != 'mm':
+                if detector.beam_center_unit != 'mm':
                     conv = Converter('mm')
                     detector.beam_center.x = conv(center_x * 5.08,
                                      units=detector.beam_center_unit)
                     detector.beam_center.y = conv(center_y * 5.08,
-                                    units=detector.beam_center_unit)
+                                     units=detector.beam_center_unit)
                 else:
                     detector.beam_center.x = center_x * 5.08
Index: src/sas/sascalc/dataloader/readers/anton_paar_saxs_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/anton_paar_saxs_reader.py	(revision fafe52a82bebe8d8a20bfb820388c964aa3c4b7b)
+++ src/sas/sascalc/dataloader/readers/anton_paar_saxs_reader.py	(revision a5bd87a4eae90ea636d8e5a5ddb6cd6370e029b3)
@@ -63,5 +63,5 @@
         ## Reinitialize the class when loading a new data file to reset all class variables
         self.reset_state()
-        buff = self.f_open.read()
+        buff = self.readall()
         self.raw_data = buff.splitlines()
         self.read_data()
Index: src/sas/sascalc/dataloader/readers/ascii_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/ascii_reader.py	(revision f994e8b1aa05e01ca78e9e360337d188540cd1bc)
+++ src/sas/sascalc/dataloader/readers/ascii_reader.py	(revision f7d720f97952cbb67364a7712043a0cf53bc7a06)
@@ -45,5 +45,5 @@
         """
 
-        buff = self.f_open.read()
+        buff = self.readall()
         filepath = self.f_open.name
         lines = buff.splitlines()
@@ -130,5 +130,5 @@
                 # Reset # of lines of data candidates
                 candidate_lines = 0
-        
+
         if not is_data:
             self.set_all_to_none()
Index: src/sas/sascalc/dataloader/readers/associations.py
===================================================================
--- src/sas/sascalc/dataloader/readers/associations.py	(revision ce8c7bd4870bc1b63b94231ed8f227410e8faee1)
+++ src/sas/sascalc/dataloader/readers/associations.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -40,5 +40,5 @@
     """
     # For each FileType entry, get the associated reader and extension
-    for ext, reader in settings.iteritems():
+    for ext, reader in settings.items():
         if reader is not None and ext is not None:
             # Associate the extension with a particular reader
@@ -47,9 +47,9 @@
             # and remove the extra line below.
             try:
-                exec "import %s" % reader
-                exec "loader.associate_file_type('%s', %s)" % (ext.lower(),
-                                                                reader)
-                exec "loader.associate_file_type('%s', %s)" % (ext.upper(),
-                                                                reader)
+                exec("from . import %s" % reader)
+                exec("loader.associate_file_type('%s', %s)"
+                     % (ext.lower(), reader))
+                exec("loader.associate_file_type('%s', %s)"
+                     % (ext.upper(), reader))
             except:
                 msg = "read_associations: skipping association"
Index: src/sas/sascalc/dataloader/readers/cansas_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/cansas_reader.py	(revision ae69c690bae8eb1cae0c6902dd71755dda9710f8)
+++ src/sas/sascalc/dataloader/readers/cansas_reader.py	(revision e6e89c48e4673e2c76326dc1f6f7b8fd4927d873)
@@ -1,20 +1,9 @@
 import logging
-import numpy as np
 import os
 import sys
 import datetime
 import inspect
-# For saving individual sections of data
-from sas.sascalc.dataloader.data_info import Data1D, Data2D, DataInfo, \
-    plottable_1D, plottable_2D
-from sas.sascalc.dataloader.data_info import Collimation, TransmissionSpectrum, \
-    Detector, Process, Aperture
-from sas.sascalc.dataloader.data_info import \
-    combine_data_info_with_plottable as combine_data
-import sas.sascalc.dataloader.readers.xml_reader as xml_reader
-from sas.sascalc.dataloader.readers.xml_reader import XMLreader
-from sas.sascalc.dataloader.readers.cansas_constants import CansasConstants, CurrentLevel
-from sas.sascalc.dataloader.loader_exceptions import FileContentsException, \
-    DefaultReaderException, DataReaderException
+
+import numpy as np
 
 # The following 2 imports *ARE* used. Do not remove either.
@@ -23,4 +12,16 @@
 
 from lxml import etree
+
+from sas.sascalc.data_util.nxsunit import Converter
+
+# For saving individual sections of data
+from ..data_info import Data1D, Data2D, DataInfo, plottable_1D, plottable_2D, \
+    Collimation, TransmissionSpectrum, Detector, Process, Aperture, \
+    combine_data_info_with_plottable as combine_data
+from ..loader_exceptions import FileContentsException, DefaultReaderException, \
+    DataReaderException
+from . import xml_reader
+from .xml_reader import XMLreader
+from .cansas_constants import CansasConstants, CurrentLevel
 
 logger = logging.getLogger(__name__)
@@ -34,8 +35,4 @@
               "as much of the data as possible.\n\n"
 HAS_CONVERTER = True
-try:
-    from sas.sascalc.data_util.nxsunit import Converter
-except ImportError:
-    HAS_CONVERTER = False
 
 CONSTANTS = CansasConstants()
@@ -163,5 +160,6 @@
                 raise fc_exc
         except Exception as e: # Convert all other exceptions to FileContentsExceptions
-            raise FileContentsException(e.message)
+            raise
+            raise FileContentsException(str(e))
 
 
@@ -632,9 +630,11 @@
                 else:
                     save_in = "current_datainfo"
-                exec "default_unit = self.{0}.{1}".format(save_in, unitname)
-                if local_unit and default_unit and local_unit.lower() != default_unit.lower() \
-                        and local_unit.lower() != "none":
-                    if HAS_CONVERTER == True:
+                default_unit = getattrchain(self, '.'.join((save_in, unitname)))
+                if (local_unit and default_unit
+                        and local_unit.lower() != default_unit.lower()
+                        and local_unit.lower() != "none"):
+                    if HAS_CONVERTER:
                         # Check local units - bad units raise KeyError
+                        #print("loading", tagname, node_value, local_unit, default_unit)
                         data_conv_q = Converter(local_unit)
                         value_unit = default_unit
@@ -654,5 +654,5 @@
                     err_msg += "expecting [{0}]".format(default_unit)
                 value_unit = local_unit
-            except:
+            except Exception:
                 err_msg = "CanSAS reader: unknown error converting "
                 err_msg += "\"{0}\" unit [{1}]"
@@ -717,5 +717,5 @@
         doc, _ = self._to_xml_doc(datainfo)
         # Write the file
-        file_ref = open(filename, 'w')
+        file_ref = open(filename, 'wb')
         if self.encoding is None:
             self.encoding = "UTF-8"
@@ -908,7 +908,7 @@
         point = self.create_element("Idata")
         node.append(point)
-        qx = ','.join([str(datainfo.qx_data[i]) for i in xrange(len(datainfo.qx_data))])
-        qy = ','.join([str(datainfo.qy_data[i]) for i in xrange(len(datainfo.qy_data))])
-        intensity = ','.join([str(datainfo.data[i]) for i in xrange(len(datainfo.data))])
+        qx = ','.join(str(v) for v in datainfo.qx_data)
+        qy = ','.join(str(v) for v in datainfo.qy_data)
+        intensity = ','.join(str(v) for v in datainfo.data)
 
         self.write_node(point, "Qx", qx,
@@ -919,22 +919,17 @@
                         {'unit': datainfo._zunit})
         if datainfo.err_data is not None:
-            err = ','.join([str(datainfo.err_data[i]) for i in
-                            xrange(len(datainfo.err_data))])
+            err = ','.join(str(v) for v in datainfo.err_data)
             self.write_node(point, "Idev", err,
                             {'unit': datainfo._zunit})
         if datainfo.dqy_data is not None:
-            dqy = ','.join([str(datainfo.dqy_data[i]) for i in
-                            xrange(len(datainfo.dqy_data))])
+            dqy = ','.join(str(v) for v in datainfo.dqy_data)
             self.write_node(point, "Qydev", dqy,
                             {'unit': datainfo._yunit})
         if datainfo.dqx_data is not None:
-            dqx = ','.join([str(datainfo.dqx_data[i]) for i in
-                            xrange(len(datainfo.dqx_data))])
+            dqx = ','.join(str(v) for v in datainfo.dqx_data)
             self.write_node(point, "Qxdev", dqx,
                             {'unit': datainfo._xunit})
         if datainfo.mask is not None:
-            mask = ','.join(
-                ["1" if datainfo.mask[i] else "0"
-                 for i in xrange(len(datainfo.mask))])
+            mask = ','.join("1" if v else "0" for v in datainfo.mask)
             self.write_node(point, "Mask", mask)
 
@@ -1280,5 +1275,5 @@
         try:
             value = float(entry.text)
-        except:
+        except ValueError:
             value = None
 
@@ -1289,13 +1284,12 @@
             if units is not None:
                 toks = variable.split('.')
-                local_unit = None
-                exec "local_unit = storage.%s_unit" % toks[0]
+                # TODO: why split() when accessing unit, but not when setting value?
+                local_unit = getattr(storage, toks[0]+"_unit")
                 if local_unit is not None and units.lower() != local_unit.lower():
                     if HAS_CONVERTER == True:
                         try:
                             conv = Converter(units)
-                            exec "storage.%s = %g" % \
-                                (variable, conv(value, units=local_unit))
-                        except:
+                            setattrchain(storage, variable, conv(value, units=local_unit))
+                        except Exception:
                             _, exc_value, _ = sys.exc_info()
                             err_mess = "CanSAS reader: could not convert"
@@ -1306,5 +1300,5 @@
                                 logger.info(err_mess)
                             else:
-                                raise ValueError, err_mess
+                                raise ValueError(err_mess)
                     else:
                         err_mess = "CanSAS reader: unrecognized %s unit [%s];"\
@@ -1315,9 +1309,9 @@
                             logger.info(err_mess)
                         else:
-                            raise ValueError, err_mess
+                            raise ValueError(err_mess)
                 else:
-                    exec "storage.%s = value" % variable
+                    setattrchain(storage, variable, value)
             else:
-                exec "storage.%s = value" % variable
+                setattrchain(storage, variable, value)
 
     # DO NOT REMOVE - used in saving and loading panel states.
@@ -1339,5 +1333,5 @@
         entry = get_content(location, node)
         if entry is not None and entry.text is not None:
-            exec "storage.%s = entry.text.strip()" % variable
+            exec("storage.%s = entry.text.strip()" % variable)
 
 # DO NOT REMOVE Called by outside packages:
@@ -1382,2 +1376,20 @@
         return True
     return False
+
+def getattrchain(obj, chain, default=None):
+    """Like getattr, but the attr may contain multiple parts separated by '.'"""
+    for part in chain.split('.'):
+        if hasattr(obj, part):
+            obj = getattr(obj, part, None)
+        else:
+            return default
+    return obj
+
+def setattrchain(obj, chain, value):
+    """Like setattr, but the attr may contain multiple parts separated by '.'"""
+    parts = list(chain.split('.'))
+    for part in parts[-1]:
+        obj = getattr(obj, part, None)
+        if obj is None:
+            raise ValueError("missing parent object "+part)
+    setattr(obj, value)
Index: src/sas/sascalc/dataloader/readers/cansas_reader_HDF5.py
===================================================================
--- src/sas/sascalc/dataloader/readers/cansas_reader_HDF5.py	(revision c9ecd1b562e38798001341ea1382b347f0cc1af9)
+++ src/sas/sascalc/dataloader/readers/cansas_reader_HDF5.py	(revision 7b50f14eb38e44aca64b1a233e5b996b05f5dc0c)
@@ -9,11 +9,13 @@
 import sys
 
-from sas.sascalc.dataloader.data_info import plottable_1D, plottable_2D,\
+from ..data_info import plottable_1D, plottable_2D,\
     Data1D, Data2D, DataInfo, Process, Aperture, Collimation, \
     TransmissionSpectrum, Detector
-from sas.sascalc.dataloader.data_info import combine_data_info_with_plottable
-from sas.sascalc.dataloader.loader_exceptions import FileContentsException, DefaultReaderException
-from sas.sascalc.dataloader.file_reader_base_class import FileReader
-
+from ..data_info import combine_data_info_with_plottable
+from ..loader_exceptions import FileContentsException, DefaultReaderException
+from ..file_reader_base_class import FileReader, decode
+
+def h5attr(node, key, default=None):
+    return decode(node.attrs.get(key, default))
 
 class Reader(FileReader):
@@ -130,8 +132,7 @@
             # Get all information for the current key
             value = data.get(key)
-            if value.attrs.get(u'canSAS_class') is not None:
-                class_name = value.attrs.get(u'canSAS_class')
-            else:
-                class_name = value.attrs.get(u'NX_class')
+            class_name = h5attr(value, u'canSAS_class')
+            if class_name is None:
+                class_name = h5attr(value, u'NX_class')
             if class_name is not None:
                 class_prog = re.compile(class_name)
@@ -225,4 +226,6 @@
 
                 for data_point in data_set:
+                    if data_point.dtype.char == 'S':
+                        data_point = decode(bytes(data_point))
                     # Top Level Meta Data
                     if key == u'definition':
@@ -231,8 +234,8 @@
                         self.current_datainfo.run.append(data_point)
                         try:
-                            run_name = value.attrs['name']
+                            run_name = h5attr(value, 'name')
                             run_dict = {data_point: run_name}
                             self.current_datainfo.run_name = run_dict
-                        except:
+                        except Exception:
                             pass
                     elif key == u'title':
@@ -576,7 +579,7 @@
         :return: unit for the value passed to the method
         """
-        unit = value.attrs.get(u'units')
+        unit = h5attr(value, u'units')
         if unit is None:
-            unit = value.attrs.get(u'unit')
+            unit = h5attr(value, u'unit')
         # Convert the unit formats
         if unit == "1/A":
Index: src/sas/sascalc/dataloader/readers/danse_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/danse_reader.py	(revision a78a02fe90f1c962f523301685c143a27f11ba7d)
+++ src/sas/sascalc/dataloader/readers/danse_reader.py	(revision af3e9f590874286ad2be3a1e5be489d605e8a9a0)
@@ -14,10 +14,12 @@
 import math
 import os
+import logging
+
 import numpy as np
-import logging
-from sas.sascalc.dataloader.data_info import plottable_2D, DataInfo, Detector
-from sas.sascalc.dataloader.manipulations import reader2D_converter
-from sas.sascalc.dataloader.file_reader_base_class import FileReader
-from sas.sascalc.dataloader.loader_exceptions import FileContentsException, DataReaderException
+
+from ..data_info import plottable_2D, DataInfo, Detector
+from ..manipulations import reader2D_converter
+from ..file_reader_base_class import FileReader
+from ..loader_exceptions import FileContentsException, DataReaderException
 
 logger = logging.getLogger(__name__)
@@ -78,5 +80,5 @@
         data_start_line = 1
         while read_on:
-            line = self.f_open.readline()
+            line = self.nextline()
             data_start_line += 1
             if line.find("DATA:") >= 0:
@@ -112,5 +114,5 @@
             raise FileContentsException(msg)
 
-        for line_num, data_str in enumerate(self.f_open.readlines()):
+        for line_num, data_str in enumerate(self.nextlines()):
             toks = data_str.split()
             try:
Index: src/sas/sascalc/dataloader/readers/red2d_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/red2d_reader.py	(revision 2f85af7145254a68c54575abc23e44599ebef7ae)
+++ src/sas/sascalc/dataloader/readers/red2d_reader.py	(revision c8321cfca1fe64cbabb0c8b781cf5aecc57c211b)
@@ -10,16 +10,14 @@
 ######################################################################
 import os
+import math
+import time
+
 import numpy as np
-import math
-from sas.sascalc.dataloader.data_info import plottable_2D, DataInfo, Detector
-from sas.sascalc.dataloader.file_reader_base_class import FileReader
-from sas.sascalc.dataloader.loader_exceptions import FileContentsException
-
-# Look for unit converter
-has_converter = True
-try:
-    from sas.sascalc.data_util.nxsunit import Converter
-except:
-    has_converter = False
+
+from sas.sascalc.data_util.nxsunit import Converter
+
+from ..data_info import plottable_2D, DataInfo, Detector
+from ..file_reader_base_class import FileReader
+from ..loader_exceptions import FileContentsException
 
 
@@ -31,5 +29,5 @@
     try:
         return float(x_point)
-    except:
+    except Exception:
         return 0
 
@@ -51,5 +49,4 @@
         :param data: data2D
         """
-        import time
         # Write the file
         try:
@@ -72,5 +69,5 @@
     def get_file_contents(self):
         # Read file
-        buf = self.f_open.read()
+        buf = self.readall()
         self.f_open.close()
         # Instantiate data object
@@ -119,31 +116,25 @@
                 try:
                     wavelength = float(line_toks[1])
-                    # Units
-                    if has_converter == True and \
-                    self.current_datainfo.source.wavelength_unit != 'A':
+                    # Wavelength is stored in angstroms; convert if necessary
+                    if self.current_datainfo.source.wavelength_unit != 'A':
                         conv = Converter('A')
                         wavelength = conv(wavelength,
                                           units=self.current_datainfo.source.wavelength_unit)
-                except:
-                    #Not required
-                    pass
-                # Distance in mm
+                except Exception:
+                    pass  # Not required
                 try:
                     distance = float(line_toks[3])
-                    # Units
-                    if has_converter == True and self.current_datainfo.detector[0].distance_unit != 'm':
+                    # Distance is stored in meters; convert if necessary
+                    if self.current_datainfo.detector[0].distance_unit != 'm':
                         conv = Converter('m')
                         distance = conv(distance,
                             units=self.current_datainfo.detector[0].distance_unit)
-                except:
-                    #Not required
-                    pass
-
-                # Distance in meters
+                except Exception:
+                    pass  # Not required
+
                 try:
                     transmission = float(line_toks[4])
-                except:
-                    #Not required
-                    pass
+                except Exception:
+                    pass  # Not required
 
             if line.count("LAMBDA") > 0:
@@ -170,5 +161,5 @@
 
             ## Read and get data.
-            if data_started == True:
+            if data_started:
                 line_toks = line.split()
                 if len(line_toks) == 0:
@@ -178,4 +169,5 @@
                 col_num = len(line_toks)
                 break
+
         # Make numpy array to remove header lines using index
         lines_array = np.array(lines)
@@ -203,5 +195,5 @@
         # Change it(string) into float
         #data_list = map(float,data_list)
-        data_list1 = map(check_point, data_list)
+        data_list1 = list(map(check_point, data_list))
 
         # numpy array form
@@ -211,5 +203,5 @@
         try:
             data_point = data_array.reshape(row_num, col_num).transpose()
-        except:
+        except Exception:
             msg = "red2d_reader can't read this file: Incorrect number of data points provided."
             raise FileContentsException(msg)
@@ -325,7 +317,7 @@
 
         # Units of axes
-        self.current_dataset.xaxis("\\rm{Q_{x}}", 'A^{-1}')
-        self.current_dataset.yaxis("\\rm{Q_{y}}", 'A^{-1}')
-        self.current_dataset.zaxis("\\rm{Intensity}", "cm^{-1}")
+        self.current_dataset.xaxis(r"\rm{Q_{x}}", 'A^{-1}')
+        self.current_dataset.yaxis(r"\rm{Q_{y}}", 'A^{-1}')
+        self.current_dataset.zaxis(r"\rm{Intensity}", "cm^{-1}")
 
         # Store loading process information
Index: src/sas/sascalc/dataloader/readers/sesans_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/sesans_reader.py	(revision be434489a51d7ec8b03d82a7e2d53cb8baa37c83)
+++ src/sas/sascalc/dataloader/readers/sesans_reader.py	(revision 849094a5f7b0f78510cbd819f7325b0940f60cdb)
@@ -6,9 +6,11 @@
     Jurrian Bakker
 """
+import os
+
 import numpy as np
-import os
-from sas.sascalc.dataloader.file_reader_base_class import FileReader
-from sas.sascalc.dataloader.data_info import plottable_1D, DataInfo
-from sas.sascalc.dataloader.loader_exceptions import FileContentsException, DataReaderException
+
+from ..file_reader_base_class import FileReader
+from ..data_info import plottable_1D, DataInfo
+from ..loader_exceptions import FileContentsException, DataReaderException
 
 # Check whether we have a converter available
@@ -42,5 +44,5 @@
         self.output = []
 
-        line = self.f_open.readline()
+        line = self.nextline()
         params = {}
         while not line.startswith("BEGIN_DATA"):
@@ -48,5 +50,5 @@
             if len(terms) >= 2:
                 params[terms[0]] = " ".join(terms[1:])
-            line = self.f_open.readline()
+            line = self.nextline()
         self.params = params
 
@@ -68,5 +70,5 @@
                                "handled by other software.")
 
-        headers = self.f_open.readline().split()
+        headers = self.nextline().split()
 
         self._insist_header(headers, "SpinEchoLength")
Index: src/sas/sascalc/dataloader/readers/tiff_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/tiff_reader.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sascalc/dataloader/readers/tiff_reader.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -2,5 +2,5 @@
 #This software was developed by the University of Tennessee as part of the
 #Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
-#project funded by the US National Science Foundation. 
+#project funded by the US National Science Foundation.
 #See the license text in license.txt
 #copyright 2008, University of Tennessee
@@ -31,9 +31,9 @@
     ## Extension
     ext = ['.tif', '.tiff']
-        
+
     def read(self, filename=None):
         """
         Open and read the data in a file
-        
+
         :param file: path of the file
         """
@@ -44,15 +44,15 @@
         except:
             msg = "tiff_reader: could not load file. Missing Image module."
-            raise RuntimeError, msg
-        
+            raise RuntimeError(msg)
+
         # Instantiate data object
         output = Data2D()
         output.filename = os.path.basename(filename)
-            
+
         # Read in the image
         try:
             im = Image.open(filename)
         except:
-            raise  RuntimeError, "cannot open %s"%(filename)
+            raise  RuntimeError("cannot open %s"%(filename))
         data = im.getdata()
 
@@ -61,5 +61,5 @@
         output.err_data = np.zeros([im.size[0], im.size[1]])
         output.mask = np.ones([im.size[0], im.size[1]], dtype=bool)
-        
+
         # Initialize
         x_vals = []
@@ -69,5 +69,5 @@
         for i_x in range(im.size[0]):
             x_vals.append(i_x)
-            
+
         itot = 0
         for i_y in range(im.size[1]):
@@ -80,5 +80,5 @@
                 logger.error("tiff_reader: had to skip a non-float point")
                 continue
-            
+
             # Get bin number
             if math.fmod(itot, im.size[0]) == 0:
@@ -87,9 +87,9 @@
             else:
                 i_x += 1
-                
+
             output.data[im.size[1] - 1 - i_y][i_x] = value
-            
+
             itot += 1
-                
+
         output.xbins = im.size[0]
         output.ybins = im.size[1]
@@ -102,5 +102,5 @@
         output.ymin = 0
         output.ymax = im.size[0] - 1
-        
+
         # Store loading process information
         output.meta_data['loader'] = self.type_name
Index: src/sas/sascalc/dataloader/readers/xml_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/xml_reader.py	(revision fafe52a82bebe8d8a20bfb820388c964aa3c4b7b)
+++ src/sas/sascalc/dataloader/readers/xml_reader.py	(revision 7b50f14eb38e44aca64b1a233e5b996b05f5dc0c)
@@ -16,7 +16,9 @@
 
 import logging
+
 from lxml import etree
 from lxml.builder import E
-from sas.sascalc.dataloader.file_reader_base_class import FileReader
+
+from ..file_reader_base_class import FileReader, decode
 
 logger = logging.getLogger(__name__)
@@ -151,6 +153,6 @@
         Converts an etree element into a string
         """
-        return etree.tostring(elem, pretty_print=pretty_print, \
-                              encoding=encoding)
+        return decode(etree.tostring(elem, pretty_print=pretty_print,
+                                     encoding=encoding))
 
     def break_processing_instructions(self, string, dic):
Index: src/sas/sascalc/file_converter/c_ext/bsl_loader.c
===================================================================
--- src/sas/sascalc/file_converter/c_ext/bsl_loader.c	(revision 2ab9c432bb247d0039ef9051ec98bfdcdc8a7021)
+++ src/sas/sascalc/file_converter/c_ext/bsl_loader.c	(revision d04ac056c476a91de374975fdb618e1cf1d27ca1)
@@ -1,4 +1,4 @@
 #include <Python.h>
-//#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
+#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
 #include <numpy/arrayobject.h>
 #include <stdio.h>
@@ -44,5 +44,5 @@
 static void CLoader_dealloc(CLoader *self) {
     free(self->params.filename);
-    self->ob_type->tp_free((PyObject *)self);
+    Py_TYPE(self)->tp_free((PyObject *)self);
 }
 
@@ -237,6 +237,7 @@
 
 static PyTypeObject CLoaderType = {
-    PyObject_HEAD_INIT(NULL)
-    0,                         /*ob_size*/
+    //PyObject_HEAD_INIT(NULL)
+    //0,                         /*ob_size*/
+    PyVarObject_HEAD_INIT(NULL, 0)
     "CLoader",             /*tp_name*/
     sizeof(CLoader),             /*tp_basicsize*/
@@ -278,15 +279,68 @@
 };
 
-PyMODINIT_FUNC
-initbsl_loader(void)
-{
-    PyObject *module;
-    module = Py_InitModule("bsl_loader", NULL);
-    import_array();
-
+static PyMethodDef module_methods[] = {
+    {NULL}
+};
+
+/**
+ * Function used to add the model class to a module
+ * @param module: module to add the class to
+ */
+void addCLoader(PyObject *module) {
     if (PyType_Ready(&CLoaderType) < 0)
         return;
 
     Py_INCREF(&CLoaderType);
-    PyModule_AddObject(module, "CLoader", (PyObject *)&CLoaderType);
-}
+    PyModule_AddObject(module, "bsl_loader", (PyObject *)&CLoaderType);
+}
+
+
+#define MODULE_DOC "C module for loading bsl."
+#define MODULE_NAME "bsl_loader"
+#define MODULE_INIT2 initbsl_loader
+#define MODULE_INIT3 PyInit_bsl_loader
+#define MODULE_METHODS module_methods
+
+/* ==== boilerplate python 2/3 interface bootstrap ==== */
+
+
+#if defined(WIN32) && !defined(__MINGW32__)
+    #define DLL_EXPORT __declspec(dllexport)
+#else
+    #define DLL_EXPORT
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+
+  DLL_EXPORT PyMODINIT_FUNC MODULE_INIT3(void)
+  {
+    static struct PyModuleDef moduledef = {
+      PyModuleDef_HEAD_INIT,
+      MODULE_NAME,         /* m_name */
+      MODULE_DOC,          /* m_doc */
+      -1,                  /* m_size */
+      MODULE_METHODS,      /* m_methods */
+      NULL,                /* m_reload */
+      NULL,                /* m_traverse */
+      NULL,                /* m_clear */
+      NULL,                /* m_free */
+    };
+	PyObject* m = PyModule_Create(&moduledef);
+	addCLoader(m);
+	return m;
+  }
+
+#else /* !PY_MAJOR_VERSION >= 3 */
+
+  DLL_EXPORT PyMODINIT_FUNC MODULE_INIT2(void)
+  {
+    PyObject* m = Py_InitModule4(MODULE_NAME,
+		 MODULE_METHODS,
+		 MODULE_DOC,
+		 0,
+		 PYTHON_API_VERSION
+		 );
+	addCLoader(m);
+  }
+
+#endif /* !PY_MAJOR_VERSION >= 3 */
Index: src/sas/sascalc/file_converter/cansas_writer.py
===================================================================
--- src/sas/sascalc/file_converter/cansas_writer.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sascalc/file_converter/cansas_writer.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -32,5 +32,5 @@
         valid_class = all([issubclass(data.__class__, Data1D) for data in frame_data])
         if not valid_class:
-            raise RuntimeError, ("The cansas writer expects an array of "
+            raise RuntimeError("The cansas writer expects an array of "
                 "Data1D instances")
 
Index: src/sas/sascalc/file_converter/nxcansas_writer.py
===================================================================
--- src/sas/sascalc/file_converter/nxcansas_writer.py	(revision 5e9062071eef0080f51c5de4c3cfb3bb129ebab6)
+++ src/sas/sascalc/file_converter/nxcansas_writer.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -166,5 +166,5 @@
             'wavelength_max': 'wavelength_max',
             'wavelength_spread': 'incident_wavelength_spread' }
-        for sasname, nxname in wavelength_keys.iteritems():
+        for sasname, nxname in wavelength_keys.items():
             value = getattr(data_info.source, sasname)
             units = getattr(data_info.source, sasname + '_unit')
Index: src/sas/sascalc/fit/AbstractFitEngine.py
===================================================================
--- src/sas/sascalc/fit/AbstractFitEngine.py	(revision 50fcb09b3ee47cf23c26b90d9c291f41415c0aa5)
+++ src/sas/sascalc/fit/AbstractFitEngine.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -251,5 +251,5 @@
             msg = "FitData1D: invalid error array "
             msg += "%d <> %d" % (np.shape(self.dy), np.size(fx))
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
         return (self.y[self.idx] - fx[self.idx]) / self.dy[self.idx], fx[self.idx]
 
Index: src/sas/sascalc/fit/Loader.py
===================================================================
--- src/sas/sascalc/fit/Loader.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sascalc/fit/Loader.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -18,20 +18,20 @@
         self.dy = dy
         self.filename = None
-        
+
     def set_filename(self, path=None):
         """
-        Store path into a variable.If the user doesn't give 
+        Store path into a variable.If the user doesn't give
         a path as a parameter a pop-up
         window appears to select the file.
-        
+
         :param path: the path given by the user
-        
+
         """
         self.filename = path
-       
+
     def get_filename(self):
         """ return the file's path"""
         return self.filename
-    
+
     def set_values(self):
         """ Store the values loaded from file in local variables"""
@@ -42,5 +42,5 @@
             self.x = []
             self.y = []
-            self.dx = [] 
+            self.dx = []
             self.dy = []
             for line in lines:
@@ -50,5 +50,5 @@
                     y = float(toks[1])
                     dy = float(toks[2])
-                    
+
                     self.x.append(x)
                     self.y.append(y)
@@ -59,13 +59,13 @@
             # Sanity check
             if not len(self.x) == len(self.dx):
-                raise ValueError, "x and dx have different length"
+                raise ValueError("x and dx have different length")
             if not len(self.y) == len(self.dy):
-                raise ValueError, "y and dy have different length"
-            
-            
+                raise ValueError("y and dy have different length")
+
+
     def get_values(self):
         """ Return x, y, dx, dy"""
         return self.x, self.y, self.dx, self.dy
-    
+
     def load_data(self, data):
         """ Return plottable"""
@@ -77,12 +77,11 @@
         #Load its View class
         #plottable.reset_view()
-       
-    
-if __name__ == "__main__": 
+
+
+if __name__ == "__main__":
     load = Load()
     load.set_filename("testdata_line.txt")
-    print(load.get_filename()) 
+    print(load.get_filename())
     load.set_values()
     print(load.get_values())
-    
-            
+
Index: src/sas/sascalc/fit/MultiplicationModel.py
===================================================================
--- src/sas/sascalc/fit/MultiplicationModel.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sascalc/fit/MultiplicationModel.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -109,5 +109,5 @@
         """
         ##set dispersion only from p_model
-        for name , value in self.p_model.dispersion.iteritems():
+        for name , value in self.p_model.dispersion.items():
             self.dispersion[name] = value
 
@@ -135,9 +135,9 @@
         """
 
-        for name , value in self.p_model.params.iteritems():
+        for name , value in self.p_model.params.items():
             if not name in self.params.keys() and name not in self.excluded_params:
                 self.params[name] = value
 
-        for name , value in self.s_model.params.iteritems():
+        for name , value in self.s_model.params.items():
             #Remove the radius_effective from the (P*S) model parameters.
             if not name in self.params.keys() and name not in self.excluded_params:
@@ -155,9 +155,9 @@
         this model's details
         """
-        for name, detail in self.p_model.details.iteritems():
+        for name, detail in self.p_model.details.items():
             if name not in self.excluded_params:
                 self.details[name] = detail
 
-        for name , detail in self.s_model.details.iteritems():
+        for name , detail in self.s_model.details.items():
             if not name in self.details.keys() or name not in self.exluded_params:
                 self.details[name] = detail
@@ -245,5 +245,5 @@
                     return
 
-        raise ValueError, "Model does not contain parameter %s" % name
+        raise ValueError("Model does not contain parameter %s" % name)
 
 
Index: src/sas/sascalc/fit/expression.py
===================================================================
--- src/sas/sascalc/fit/expression.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sascalc/fit/expression.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -59,5 +59,5 @@
     occur multiple times.  The return value is a set with the elements in
     no particular order.
-    
+
     This is the first step in computing a dependency graph.
     """
@@ -81,5 +81,5 @@
         offset = end
     pieces.append(expr[offset:])
-    
+
     # Join the pieces and return them
     return "".join(pieces)
@@ -88,5 +88,5 @@
     """
     Returns a list of pair-wise dependencies from the parameter expressions.
-    
+
     For example, if p3 = p1+p2, then find_dependencies([p1,p2,p3]) will
     return [(p3,p1),(p3,p2)].  For base expressions without dependencies,
@@ -110,5 +110,5 @@
     """
     Find the parameter substitution we need so that expressions can
-    be evaluated without having to traverse a chain of 
+    be evaluated without having to traverse a chain of
     model.layer.parameter.value
     """
@@ -122,5 +122,5 @@
     return definition, substitution
 
-def no_constraints(): 
+def no_constraints():
     """
     This parameter set has no constraints between the parameters.
@@ -163,10 +163,10 @@
 
     Parameter names are assumed to contain only _.a-zA-Z0-9#[]
-    
+
     Both names are provided for inverse functions, e.g., acos and arccos.
 
     Should try running the function to identify syntax errors before
     running it in a fit.
-    
+
     Use help(fn) to see the code generated for the returned function fn.
     dis.dis(fn) will show the corresponding python vm instructions.
@@ -239,5 +239,5 @@
         if independent == emptyset:
             cycleset = ", ".join(str(s) for s in left)
-            raise ValueError,"Cyclic dependencies amongst %s"%cycleset
+            raise ValueError("Cyclic dependencies amongst %s"%cycleset)
 
         # The possibly resolvable items are those that depend on the independents
@@ -267,8 +267,8 @@
         n.sort()
         items = list(items); items.sort()
-        raise Exception,"%s expect %s to contain %s for %s"%(msg,n,items,pairs)
+        raise ValueError("%s expect %s to contain %s for %s"%(msg,n,items,pairs))
     for lo,hi in pairs:
         if lo in n and hi in n and n.index(lo) >= n.index(hi):
-            raise Exception,"%s expect %s before %s in %s for %s"%(msg,lo,hi,n,pairs)
+            raise ValueError("%s expect %s before %s in %s for %s"%(msg,lo,hi,n,pairs))
 
 def test_deps():
@@ -288,7 +288,10 @@
     # Cycle test
     pairs = [(1,4),(4,3),(4,5),(5,1)]
-    try: n = order_dependencies(pairs)
-    except ValueError: pass
-    else: raise Exception,"test3 expect ValueError exception for %s"%(pairs,)
+    try:
+        n = order_dependencies(pairs)
+    except ValueError:
+        pass
+    else:
+        raise ValueError("test3 expect ValueError exception for %s"%(pairs,))
 
     # large test for gross speed check
@@ -308,8 +311,8 @@
     import inspect, dis
     import math
-    
+
     symtab = {'a.b.x':1, 'a.c':2, 'a.b':3, 'b.x':4}
     expr = 'a.b.x + sin(4*pi*a.c) + a.b.x/a.b'
-    
+
     # Check symbol lookup
     assert _symbols(expr, symtab) == set([1,2,3])
@@ -357,5 +360,5 @@
     expected = 2*math.pi*math.sin(5/.1875) + 6
     assert p2.value == expected,"Value was %s, not %s"%(p2.value,expected)
-    
+
     # Check empty dependency set doesn't crash
     fn = compile_constraints(*world(p1,p3))
@@ -381,8 +384,8 @@
     fn()
     assert p5.value == 2.07,"Value for %s was %s"%(p5.expression,p5.value)
-    
+
 
     # Verify that we capture invalid expressions
-    for expr in ['G4.cage', 'M0.cage', 'M1.G1 + *2', 
+    for expr in ['G4.cage', 'M0.cage', 'M1.G1 + *2',
                  'piddle',
                  '5; import sys; print "p0wned"',
Index: src/sas/sascalc/fit/pagestate.py
===================================================================
--- src/sas/sascalc/fit/pagestate.py	(revision 277257f6caa3c7a18ad588f88501a89a34aa36e4)
+++ src/sas/sascalc/fit/pagestate.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -313,14 +313,14 @@
 
         if len(self.disp_obj_dict) > 0:
-            for k, v in self.disp_obj_dict.iteritems():
+            for k, v in self.disp_obj_dict.items():
                 obj.disp_obj_dict[k] = v
         if len(self.disp_cb_dict) > 0:
-            for k, v in self.disp_cb_dict.iteritems():
+            for k, v in self.disp_cb_dict.items():
                 obj.disp_cb_dict[k] = v
         if len(self.values) > 0:
-            for k, v in self.values.iteritems():
+            for k, v in self.values.items():
                 obj.values[k] = v
         if len(self.weights) > 0:
-            for k, v in self.weights.iteritems():
+            for k, v in self.weights.items():
                 obj.weights[k] = v
         obj.enable_smearer = copy.deepcopy(self.enable_smearer)
@@ -347,5 +347,5 @@
         obj.version = copy.deepcopy(self.version)
 
-        for name, state in self.saved_states.iteritems():
+        for name, state in self.saved_states.items():
             copy_name = copy.deepcopy(name)
             copy_state = state.clone()
@@ -430,5 +430,5 @@
         """
         p_map = []
-        for name, info in params.iteritems():
+        for name, info in params.items():
             if ".fittable" in name or ".std" in name or ".upper" in name or \
                             ".lower" in name or ".units" in name:
@@ -475,5 +475,5 @@
             formfactor, str_params = convert.convert_model(
                 self.formfactorcombobox, str_pars, False, self.version)
-            for key, value in str_params.iteritems():
+            for key, value in str_params.items():
                 params[key] = value
 
@@ -835,5 +835,5 @@
             element = newdoc.createElement(item[0])
             value_list = getattr(self, item[1])
-            for key, value in value_list.iteritems():
+            for key, value in value_list.items():
                 sub_element = newdoc.createElement(key)
                 sub_element.setAttribute('name', str(key))
@@ -848,5 +848,5 @@
             element = newdoc.createElement(tagname)
             value_list = getattr(self, varname)
-            for key, value in value_list.iteritems():
+            for key, value in value_list.items():
                 sub_element = newdoc.createElement(key)
                 sub_element.setAttribute('name', str(key))
@@ -950,5 +950,5 @@
             msg = "PageState no longer supports non-CanSAS"
             msg += " format for fitting files"
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
         if node.get('version'):
@@ -1241,5 +1241,5 @@
             else:
                 self.call_back(format=ext)
-                raise RuntimeError, "%s is not a file" % path
+                raise RuntimeError("%s is not a file" % path)
 
             # Return output consistent with the loader's api
Index: src/sas/sascalc/fit/pluginmodel.py
===================================================================
--- src/sas/sascalc/fit/pluginmodel.py	(revision 5213d22cc2537cc6875bff5b448ef1c8caadfcd8)
+++ src/sas/sascalc/fit/pluginmodel.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -35,5 +35,5 @@
             return self.function(x_val)*self.function(y_val)
         elif x.__class__.__name__ == 'tuple':
-            raise ValueError, "Tuples are not allowed as input to BaseComponent models"
+            raise ValueError("Tuples are not allowed as input to BaseComponent models")
         else:
             return self.function(x)
@@ -52,5 +52,5 @@
             return self.function(x[0])*self.function(x[1])
         elif x.__class__.__name__ == 'tuple':
-            raise ValueError, "Tuples are not allowed as input to BaseComponent models"
+            raise ValueError("Tuples are not allowed as input to BaseComponent models")
         else:
             return self.function(x)
Index: src/sas/sascalc/invariant/invariant.py
===================================================================
--- src/sas/sascalc/invariant/invariant.py	(revision b1f20d17802efab479556ccd4fab9ef91f08b7bb)
+++ src/sas/sascalc/invariant/invariant.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -424,5 +424,5 @@
         if not issubclass(data.__class__, LoaderData1D):
             #Process only data that inherited from DataLoader.Data_info.Data1D
-            raise ValueError, "Data must be of type DataLoader.Data1D"
+            raise ValueError("Data must be of type DataLoader.Data1D")
         #from copy import deepcopy
         new_data = (self._scale * data) - self._background
@@ -484,5 +484,5 @@
             msg = "Length x and y must be equal"
             msg += " and greater than 1; got x=%s, y=%s" % (len(data.x), len(data.y))
-            raise ValueError, msg
+            raise ValueError(msg)
         else:
             # Take care of smeared data
@@ -507,5 +507,5 @@
                 #iterate between for element different
                 #from the first and the last
-                for i in xrange(1, n - 1):
+                for i in range(1, n - 1):
                     dxi = (data.x[i + 1] - data.x[i - 1]) / 2
                     total += gx[i] * data.y[i] * dxi
@@ -533,5 +533,5 @@
             msg = "Length of data.x and data.y must be equal"
             msg += " and greater than 1; got x=%s, y=%s" % (len(data.x), len(data.y))
-            raise ValueError, msg
+            raise ValueError(msg)
         else:
             #Create error for data without dy error
@@ -560,5 +560,5 @@
                 #iterate between for element different
                 #from the first and the last
-                for i in xrange(1, n - 1):
+                for i in range(1, n - 1):
                     dxi = (data.x[i + 1] - data.x[i - 1]) / 2
                     total += (gx[i] * dy[i] * dxi) ** 2
@@ -742,14 +742,14 @@
         range = range.lower()
         if range not in ['high', 'low']:
-            raise ValueError, "Extrapolation range should be 'high' or 'low'"
+            raise ValueError("Extrapolation range should be 'high' or 'low'")
         function = function.lower()
         if function not in ['power_law', 'guinier']:
             msg = "Extrapolation function should be 'guinier' or 'power_law'"
-            raise ValueError, msg
+            raise ValueError(msg)
 
         if range == 'high':
             if function != 'power_law':
                 msg = "Extrapolation only allows a power law at high Q"
-                raise ValueError, msg
+                raise ValueError(msg)
             self._high_extrapolation_npts = npts
             self._high_extrapolation_power = power
@@ -852,5 +852,5 @@
         """
         if contrast <= 0:
-            raise ValueError, "The contrast parameter must be greater than zero"
+            raise ValueError("The contrast parameter must be greater than zero")
 
         # Make sure Q star is up to date
@@ -859,5 +859,5 @@
         if self._qstar <= 0:
             msg = "Invalid invariant: Invariant Q* must be greater than zero"
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
         # Compute intermediate constant
@@ -869,5 +869,5 @@
         if discrim < 0:
             msg = "Could not compute the volume fraction: negative discriminant"
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
         elif discrim == 0:
             return 1 / 2
@@ -881,5 +881,5 @@
                 return volume2
             msg = "Could not compute the volume fraction: inconsistent results"
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
     def get_qstar_with_error(self, extrapolation=None):
Index: src/sas/sascalc/pr/c_extensions/Cinvertor.c
===================================================================
--- src/sas/sascalc/pr/c_extensions/Cinvertor.c	(revision cb62bd55ae79395ac6e01d8b2077f4d496625c41)
+++ src/sas/sascalc/pr/c_extensions/Cinvertor.c	(revision d04ac056c476a91de374975fdb618e1cf1d27ca1)
@@ -52,5 +52,5 @@
     invertor_dealloc(&(self->params));
 
-    self->ob_type->tp_free((PyObject*)self);
+    Py_TYPE(self)->tp_free((PyObject*)self);
 
 }
@@ -1054,6 +1054,7 @@
 
 static PyTypeObject CinvertorType = {
-    PyObject_HEAD_INIT(NULL)
-    0,                         /*ob_size*/
+    //PyObject_HEAD_INIT(NULL)
+    //0,                         /*ob_size*/
+    PyVarObject_HEAD_INIT(NULL, 0)
     "Cinvertor",             /*tp_name*/
     sizeof(Cinvertor),             /*tp_basicsize*/
@@ -1119,15 +1120,51 @@
 
 
-#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
-#define PyMODINIT_FUNC void
+#define MODULE_DOC "C extension module for inversion to P(r)."
+#define MODULE_NAME "pr_inversion"
+#define MODULE_INIT2 initpr_inversion
+#define MODULE_INIT3 PyInit_pr_inversion
+#define MODULE_METHODS module_methods
+
+/* ==== boilerplate python 2/3 interface bootstrap ==== */
+
+
+#if defined(WIN32) && !defined(__MINGW32__)
+    #define DLL_EXPORT __declspec(dllexport)
+#else
+    #define DLL_EXPORT
 #endif
-PyMODINIT_FUNC
-initpr_inversion(void)
-{
-    PyObject* m;
-
-    m = Py_InitModule3("pr_inversion", module_methods,
-                       "C extension module for inversion to P(r).");
-
-    addCinvertor(m);
-}
+
+#if PY_MAJOR_VERSION >= 3
+
+  DLL_EXPORT PyMODINIT_FUNC MODULE_INIT3(void)
+  {
+    static struct PyModuleDef moduledef = {
+      PyModuleDef_HEAD_INIT,
+      MODULE_NAME,         /* m_name */
+      MODULE_DOC,          /* m_doc */
+      -1,                  /* m_size */
+      MODULE_METHODS,      /* m_methods */
+      NULL,                /* m_reload */
+      NULL,                /* m_traverse */
+      NULL,                /* m_clear */
+      NULL,                /* m_free */
+    };
+	PyObject* m = PyModule_Create(&moduledef);
+	addCinvertor(m);
+	return m;
+  }
+
+#else /* !PY_MAJOR_VERSION >= 3 */
+
+  DLL_EXPORT PyMODINIT_FUNC MODULE_INIT2(void)
+  {
+    PyObject* m = Py_InitModule4(MODULE_NAME,
+		 MODULE_METHODS,
+		 MODULE_DOC,
+		 0,
+		 PYTHON_API_VERSION
+		 );
+	addCinvertor(m);
+  }
+
+#endif /* !PY_MAJOR_VERSION >= 3 */
Index: src/sas/sascalc/pr/fit/AbstractFitEngine.py
===================================================================
--- src/sas/sascalc/pr/fit/AbstractFitEngine.py	(revision 50fcb09b3ee47cf23c26b90d9c291f41415c0aa5)
+++ src/sas/sascalc/pr/fit/AbstractFitEngine.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -251,5 +251,5 @@
             msg = "FitData1D: invalid error array "
             msg += "%d <> %d" % (np.shape(self.dy), np.size(fx))
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
         return (self.y[self.idx] - fx[self.idx]) / self.dy[self.idx], fx[self.idx]
 
Index: src/sas/sascalc/pr/fit/Loader.py
===================================================================
--- src/sas/sascalc/pr/fit/Loader.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sascalc/pr/fit/Loader.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -18,20 +18,20 @@
         self.dy = dy
         self.filename = None
-        
+
     def set_filename(self, path=None):
         """
-        Store path into a variable.If the user doesn't give 
+        Store path into a variable.If the user doesn't give
         a path as a parameter a pop-up
         window appears to select the file.
-        
+
         :param path: the path given by the user
-        
+
         """
         self.filename = path
-       
+
     def get_filename(self):
         """ return the file's path"""
         return self.filename
-    
+
     def set_values(self):
         """ Store the values loaded from file in local variables"""
@@ -42,5 +42,5 @@
             self.x = []
             self.y = []
-            self.dx = [] 
+            self.dx = []
             self.dy = []
             for line in lines:
@@ -50,5 +50,5 @@
                     y = float(toks[1])
                     dy = float(toks[2])
-                    
+
                     self.x.append(x)
                     self.y.append(y)
@@ -59,13 +59,13 @@
             # Sanity check
             if not len(self.x) == len(self.dx):
-                raise ValueError, "x and dx have different length"
+                raise ValueError("x and dx have different length")
             if not len(self.y) == len(self.dy):
-                raise ValueError, "y and dy have different length"
-            
-            
+                raise ValueError("y and dy have different length")
+
+
     def get_values(self):
         """ Return x, y, dx, dy"""
         return self.x, self.y, self.dx, self.dy
-    
+
     def load_data(self, data):
         """ Return plottable"""
@@ -77,12 +77,11 @@
         #Load its View class
         #plottable.reset_view()
-       
-    
-if __name__ == "__main__": 
+
+
+if __name__ == "__main__":
     load = Load()
     load.set_filename("testdata_line.txt")
-    print(load.get_filename()) 
+    print(load.get_filename())
     load.set_values()
     print(load.get_values())
-    
-            
+
Index: src/sas/sascalc/pr/fit/expression.py
===================================================================
--- src/sas/sascalc/pr/fit/expression.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sascalc/pr/fit/expression.py	(revision 574adc767a9e86744546f8edd6a23dc4f81914ad)
@@ -59,5 +59,5 @@
     occur multiple times.  The return value is a set with the elements in
     no particular order.
-    
+
     This is the first step in computing a dependency graph.
     """
@@ -81,5 +81,5 @@
         offset = end
     pieces.append(expr[offset:])
-    
+
     # Join the pieces and return them
     return "".join(pieces)
@@ -88,5 +88,5 @@
     """
     Returns a list of pair-wise dependencies from the parameter expressions.
-    
+
     For example, if p3 = p1+p2, then find_dependencies([p1,p2,p3]) will
     return [(p3,p1),(p3,p2)].  For base expressions without dependencies,
@@ -110,5 +110,5 @@
     """
     Find the parameter substitution we need so that expressions can
-    be evaluated without having to traverse a chain of 
+    be evaluated without having to traverse a chain of
     model.layer.parameter.value
     """
@@ -122,5 +122,5 @@
     return definition, substitution
 
-def no_constraints(): 
+def no_constraints():
     """
     This parameter set has no constraints between the parameters.
@@ -163,10 +163,10 @@
 
     Parameter names are assumed to contain only _.a-zA-Z0-9#[]
-    
+
     Both names are provided for inverse functions, e.g., acos and arccos.
 
     Should try running the function to identify syntax errors before
     running it in a fit.
-    
+
     Use help(fn) to see the code generated for the returned function fn.
     dis.dis(fn) will show the corresponding python vm instructions.
@@ -239,5 +239,5 @@
         if independent == emptyset:
             cycleset = ", ".join(str(s) for s in left)
-            raise ValueError,"Cyclic dependencies amongst %s"%cycleset
+            raise ValueError("Cyclic dependencies amongst %s"%cycleset)
 
         # The possibly resolvable items are those that depend on the independents
@@ -267,8 +267,8 @@
         n.sort()
         items = list(items); items.sort()
-        raise Exception,"%s expect %s to contain %s for %s"%(msg,n,items,pairs)
+        raise ValueError("%s expect %s to contain %s for %s"%(msg,n,items,pairs))
     for lo,hi in pairs:
         if lo in n and hi in n and n.index(lo) >= n.index(hi):
-            raise Exception,"%s expect %s before %s in %s for %s"%(msg,lo,hi,n,pairs)
+            raise ValueError("%s expect %s before %s in %s for %s"%(msg,lo,hi,n,pairs))
 
 def test_deps():
@@ -288,7 +288,10 @@
     # Cycle test
     pairs = [(1,4),(4,3),(4,5),(5,1)]
-    try: n = order_dependencies(pairs)
-    except ValueError: pass
-    else: raise Exception,"test3 expect ValueError exception for %s"%(pairs,)
+    try:
+        n = order_dependencies(pairs)
+    except ValueError:
+        pass
+    else:
+        raise Exception("test3 expect ValueError exception for %s"%(pairs,))
 
     # large test for gross speed check
@@ -308,8 +311,8 @@
     import inspect, dis
     import math
-    
+
     symtab = {'a.b.x':1, 'a.c':2, 'a.b':3, 'b.x':4}
     expr = 'a.b.x + sin(4*pi*a.c) + a.b.x/a.b'
-    
+
     # Check symbol lookup
     assert _symbols(expr, symtab) == set([1,2,3])
@@ -357,5 +360,5 @@
     expected = 2*math.pi*math.sin(5/.1875) + 6
     assert p2.value == expected,"Value was %s, not %s"%(p2.value,expected)
-    
+
     # Check empty dependency set doesn't crash
     fn = compile_constraints(*world(p1,p3))
@@ -381,8 +384,8 @@
     fn()
     assert p5.value == 2.07,"Value for %s was %s"%(p5.expression,p5.value)
-    
+
 
     # Verify that we capture invalid expressions
-    for expr in ['G4.cage', 'M0.cage', 'M1.G1 + *2', 
+    for expr in ['G4.cage', 'M0.cage', 'M1.G1 + *2',
                  'piddle',
                  '5; import sys; print "p0wned"',
Index: src/sas/sascalc/pr/invertor.py
===================================================================
--- src/sas/sascalc/pr/invertor.py	(revision cb62bd55ae79395ac6e01d8b2077f4d496625c41)
+++ src/sas/sascalc/pr/invertor.py	(revision d04ac056c476a91de374975fdb618e1cf1d27ca1)
@@ -148,5 +148,5 @@
                 msg = "Invertor: one of your q-values is zero. "
                 msg += "Delete that entry before proceeding"
-                raise ValueError, msg
+                raise ValueError(msg)
             return self.set_x(value)
         elif name == 'y':
@@ -159,5 +159,5 @@
                 msg = "Invertor: d_max must be greater than zero."
                 msg += "Correct that entry before proceeding"
-                raise ValueError, msg
+                raise ValueError(msg)
             return self.set_dmax(value)
         elif name == 'q_min':
@@ -181,5 +181,5 @@
                 return self.set_est_bck(0)
             else:
-                raise ValueError, "Invertor: est_bck can only be True or False"
+                raise ValueError("Invertor: est_bck can only be True or False")
 
         return Cinvertor.__setattr__(self, name, value)
@@ -331,5 +331,5 @@
         if self.is_valid() <= 0:
             msg = "Invertor.invert: Data array are of different length"
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
         p = np.ones(nfunc)
@@ -364,5 +364,5 @@
         if self.is_valid() <= 0:
             msg = "Invertor.invert: Data arrays are of different length"
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
         p = np.ones(nfunc)
@@ -448,5 +448,5 @@
         if self.is_valid() < 0:
             msg = "Invertor: invalid data; incompatible data lengths."
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
         self.nfunc = nfunc
@@ -472,6 +472,6 @@
         try:
             self._get_matrix(nfunc, nq, a, b)
-        except:
-            raise RuntimeError, "Invertor: could not invert I(Q)\n  %s" % sys.exc_value
+        except Exception as exc:
+            raise RuntimeError("Invertor: could not invert I(Q)\n  %s" % str(exc))
 
         # Perform the inversion (least square fit)
@@ -756,6 +756,6 @@
             except:
                 msg = "Invertor.from_file: corrupted file\n%s" % sys.exc_value
-                raise RuntimeError, msg
+                raise RuntimeError(msg)
         else:
             msg = "Invertor.from_file: '%s' is not a file" % str(path)
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
