Repository: psychopy/psychopy

Issue:
encoding parameter causes crash when using Python 3.9
In Python 3.9, the `encoding` kwarg of `json.loads` is removed, see https://docs.python.org/dev/whatsnew/3.9.html#removed:

> The encoding parameter of json.loads() has been removed. As of Python 3.1, it was deprecated and ignored; using it has emitted a DeprecationWarning since Python 3.8. (Contributed by Inada Naoki in bpo-39377)

This causes a crash because the encoding parameter is passed to json_tricks in https://github.com/psychopy/psychopy/blob/master/psychopy/monitors/calibTools.py#L446
which forwards it to `json.loads` 

```python
  ...
  File "/Users/daniel/miniconda3/envs/os3.3-py39/lib/python3.9/site-packages/json_tricks/nonp.py", line 266, in load
    return loads(string, preserve_order=preserve_order, ignore_comments=ignore_comments, decompression=decompression,
  File "/Users/daniel/miniconda3/envs/os3.3-py39/lib/python3.9/site-packages/json_tricks/nonp.py", line 236, in loads
    return json_loads(string, object_pairs_hook=hook, **jsonkwargs)
  File "/Users/daniel/miniconda3/envs/os3.3-py39/lib/python3.9/json/__init__.py", line 359, in loads
    return cls(**kw).decode(s)
TypeError: __init__() got an unexpected keyword argument 'encoding'
```
I think this parameter can be safely removed for Python 3, since it is a dud anyway, but you may want to retain it for Python 2.


Discussion:
I have created https://github.com/psychopy/psychopy/pull/3343 as a potential fix

Resolving patch:
diff --git a/psychopy/monitors/calibTools.py b/psychopy/monitors/calibTools.py
--- a/psychopy/monitors/calibTools.py
+++ b/psychopy/monitors/calibTools.py
@@ -37,6 +37,8 @@
 from scipy import interpolate
 import json_tricks  # allows json to dump/load np.arrays and dates
 
+from psychopy import constants
+
 DEBUG = False
 
 # set and create (if necess) the data folder
@@ -435,15 +437,24 @@ def _loadAll(self):
             ext = ".json"
         else:
             ext = ".calib"
+
         # the name of the actual file:
         thisFileName = os.path.join(monitorFolder, self.name + ext)
         if not os.path.exists(thisFileName):
             self.calibNames = []
         else:
-            if ext==".json":
+            if ext == ".json":
                 with open(thisFileName, 'r') as thisFile:
-                    self.calibs = json_tricks.load(thisFile, ignore_comments=False,
-                                                   encoding='utf-8', preserve_order=False)
+                    if constants.PY3:
+                        # Passing encoding parameter to json.loads has been
+                        # deprecated and removed in Python 3.9
+                        self.calibs = json_tricks.load(
+                            thisFile, ignore_comments=False,
+                            preserve_order=False)
+                    else:
+                        self.calibs = json_tricks.load(
+                            thisFile, ignore_comments=False, encoding='utf-8',
+                            preserve_order=False)
             else:
                 with open(thisFileName, 'rb') as thisFile:
                     self.calibs = pickle.load(thisFile)
@@ -564,7 +575,6 @@ def _saveJSON(self):
             json_tricks.dump(self.calibs, outfile, indent=2,
                              allow_nan=True)
 
-
     def copyCalib(self, calibName=None):
         """Stores the settings for the current calibration settings as
         new monitor.


Test patch: