Repository: psychopy/psychopy

Issue:
unicode in participant name = crash on save .psydat
as called in `__del__` of an ExperimentHandler.

changing from cPickle to pickle (for doing `pickle.dump(self, f)`) avoids the unicode error, but I get

```
Exception RuntimeError: RuntimeError('sys.meta_path must be a list of import hooks',) in <bound method ExperimentHandler.__del__ of <psychopy.data.ExperimentHandler object at 0x113c11cd0>> ignored
```



Discussion:

Resolving patch:
diff --git a/psychopy/tools/filetools.py b/psychopy/tools/filetools.py
--- a/psychopy/tools/filetools.py
+++ b/psychopy/tools/filetools.py
@@ -82,7 +82,9 @@ def openOutputFile(fileName, append=False, delim=None,
         is set to ``True``.
         Defaults to `rename`.
     encoding : string, optional
-        The encoding to use when writing the file.
+        The encoding to use when writing the file. This parameter will be
+        ignored if `append` is `False` and `fileName` ends with `.psydat`
+        or `.npy` (i.e. if a binary file is to be written).
         Defaults to ``'utf-8'``.
 
     :Returns:
@@ -129,6 +131,10 @@ def openOutputFile(fileName, append=False, delim=None,
                 fileCollisionMethod=fileCollisionMethod
             )
 
+    # Do not use encoding when writing a binary file.
+    if 'b' in writeFormat:
+        encoding = None
+
     if os.path.exists(fileName) and writeFormat in ['w', 'wb']:
         logging.warning('Data file, %s will be overwritten!' % fileName)
 


Test patch:
diff --git a/psychopy/tests/test_data/test_ExperimentHandler.py b/psychopy/tests/test_data/test_ExperimentHandler.py
--- a/psychopy/tests/test_data/test_ExperimentHandler.py
+++ b/psychopy/tests/test_data/test_ExperimentHandler.py
@@ -1,65 +1,108 @@
+# -*- coding: utf-8 -*-
+
 from psychopy import data, logging
 from numpy import random
 import os, glob, shutil
 logging.console.setLevel(logging.DEBUG)
-
 from tempfile import mkdtemp
-tmpFile = mkdtemp(prefix='psychopy-tests-testExp')
-
-def teardown():
-    #    remove the tmp files
-    shutil.rmtree(tmpFile)
-    #for a while (until 1.74.00) files were being left in the tests folder by mistake
-    for f in glob.glob('testExp*.psyexp'):
-        os.remove(f)
-    for f in glob.glob('testExp*.csv'):
-        os.remove(f)
-
-
-def test_ExperimentHandler():
-    exp = data.ExperimentHandler(name='testExp',
-                    version='0.1',
-                    extraInfo={'participant':'jwp','ori':45},
-                    runtimeInfo=None,
-                    originPath=None,
-                    savePickle=True,
-                    saveWideText=True,
-                    dataFileName=tmpFile+'x')
-
-    #a first loop (like training?)
-    conds = data.createFactorialTrialList({'faceExpression':['happy','sad'],'presTime':[0.2,0.3]})
-    training=data.TrialHandler(trialList=conds, nReps=3,name='train',
-                     method='random',
-                     seed=100)#this will set the global seed - so fixed for whole exp
-    exp.addLoop(training)
-    #run those trials
-    for trial in training:
-        training.addData('training.rt',random.random()*0.5+0.5)
-        if random.random()>0.5:
-            training.addData('training.key','left')
-        else:
-            training.addData('training.key','right')
-        exp.nextEntry()
-
-    #then run 3 repeats of a staircase
-    outerLoop=data.TrialHandler(trialList=[], nReps=3,name='stairBlock',
-                     method='random')
-    exp.addLoop(outerLoop)
-    for thisRep in outerLoop:#the outer loop doesn't save any data
-        staircase=data.StairHandler(startVal=10, name='staircase', nTrials=5)
-        exp.addLoop(staircase)
-        for thisTrial in staircase:
-            id=random.random()
-            if random.random()>0.5:
-                staircase.addData(1)
+
+
+class TestExperimentHandler():
+    def setup_class(self):
+        self.tmpDir = mkdtemp(prefix='psychopy-tests-testExp')
+
+    def teardown_class(self):
+        shutil.rmtree(self.tmpDir)
+        # for a while (until 1.74.00) files were being left in the tests folder by mistake
+        for f in glob.glob('testExp*.psyexp'):
+            os.remove(f)
+        for f in glob.glob('testExp*.csv'):
+            os.remove(f)
+
+    def test_default(self):
+        exp = data.ExperimentHandler(
+            name='testExp',
+            version='0.1',
+            extraInfo={'participant': 'jwp', 'ori': 45},
+            runtimeInfo=None,
+            originPath=None,
+            savePickle=True,
+            saveWideText=True,
+            dataFileName=self.tmpDir + 'default'
+        )
+
+        # First loop: Training.
+        conds = data.createFactorialTrialList(
+            {'faceExpression': ['happy', 'sad'], 'presTime': [0.2, 0.3]}
+        )
+        training = data.TrialHandler(
+            trialList=conds, nReps=3, name='train',
+            method='random',
+            seed=100  # Global seed - so fixed for whole experiment.
+        )
+        exp.addLoop(training)
+
+        for trial in training:
+            training.addData('training.rt',random.random()*0.5+0.5)
+            if random.random() > 0.5:
+                training.addData('training.key', 'left')
             else:
-                staircase.addData(0)
-            exp.addData('id',id)
+                training.addData('training.key', 'right')
             exp.nextEntry()
-    #exp should then automatically save the pickle and csv data files
-    #for e in exp.entries:
-    #    print e
-    #print 'done'
 
-if __name__=='__main__':
-    test_ExperimentHandler()
+        # Then run 3 repeats of a staircase.
+        outerLoop = data.TrialHandler(
+            trialList=[], nReps=3,name='stairBlock', method='random'
+        )
+        exp.addLoop(outerLoop)
+
+        for thisRep in outerLoop:  # The outer loop doesn't save any data.
+            staircase = data.StairHandler(
+                startVal=10, name='staircase', nTrials=5
+            )
+            exp.addLoop(staircase)
+
+            for thisTrial in staircase:
+                id = random.random()
+                if random.random() > 0.5:
+                    staircase.addData(1)
+                else:
+                    staircase.addData(0)
+                exp.addData('id', id)
+                exp.nextEntry()
+
+        #exp should then automatically save the pickle and csv data files
+        #for e in exp.entries:
+        #    print e
+        #print 'done'
+
+    def test_unicode_conditions(self):
+        fileName = self.tmpDir + 'unicode_conds'
+
+        exp = data.ExperimentHandler(
+            savePickle=False,
+            saveWideText=False,
+            dataFileName=fileName
+        )
+
+        conds = [
+            {'id': '01', u'näme': u'umlauts-öäü'},
+            {'id': '02', u'näme': u'accents-àáâă'}
+        ]
+
+        trials = data.TrialHandler(
+            trialList=conds, nReps=1, method='sequential'
+        )
+
+        exp.addLoop(trials)
+        for trial in trials:
+            pass
+
+        trials.saveAsWideText(fileName)
+        exp.saveAsWideText(fileName)
+        exp.saveAsPickle(fileName)
+
+
+if __name__ == '__main__':
+    import pytest
+    pytest.main()