Repository: jupyter/jupyter_client
Pull request #698 (2021-09-27 09:00:33)

Fix json_default so that it's closer to what ipykernel had before
ipykernel's `json_clean` used to support generators, iterables, and int/float like types, this adds support for them


Patch:
diff --git a/jupyter_client/jsonutil.py b/jupyter_client/jsonutil.py
index e12601d..2ba0bcf 100644
--- a/jupyter_client/jsonutil.py
+++ b/jupyter_client/jsonutil.py
@@ -1,9 +1,11 @@
 """Utilities to manipulate JSON objects."""
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
+import numbers
 import re
 import warnings
 from binascii import b2a_base64
+from collections.abc import Iterable
 from datetime import datetime
 from typing import Optional
 from typing import Union
@@ -106,7 +108,17 @@ def json_default(obj):
     if isinstance(obj, datetime):
         obj = _ensure_tzinfo(obj)
         return obj.isoformat().replace('+00:00', 'Z')
-    elif isinstance(obj, bytes):
+
+    if isinstance(obj, bytes):
         return b2a_base64(obj).decode('ascii')
-    else:
-        raise TypeError("%r is not JSON serializable" % obj)
+
+    if isinstance(obj, Iterable):
+        return list(obj)
+
+    if isinstance(obj, numbers.Integral):
+        return int(obj)
+
+    if isinstance(obj, numbers.Real):
+        return float(obj)
+
+    raise TypeError("%r is not JSON serializable" % obj)
diff --git a/jupyter_client/manager.py b/jupyter_client/manager.py
index fdae9a7..0c489d3 100644
--- a/jupyter_client/manager.py
+++ b/jupyter_client/manager.py
@@ -286,7 +286,6 @@ class KernelManager(ConnectionFileMixin):
              keyword arguments that are passed down to build the kernel_cmd
              and launching the kernel (e.g. Popen kwargs).
         """
-        self.shutting_down = False
         self.kernel_id = self.kernel_id or kw.pop('kernel_id', str(uuid.uuid4()))
         # save kwargs for use in restart
         self._launch_args = kw.copy()


Test patch:
diff --git a/jupyter_client/tests/test_jsonutil.py b/jupyter_client/tests/test_jsonutil.py
index 0364e12..bc33110 100644
--- a/jupyter_client/tests/test_jsonutil.py
+++ b/jupyter_client/tests/test_jsonutil.py
@@ -3,6 +3,7 @@
 # Distributed under the terms of the Modified BSD License.
 import datetime
 import json
+import numbers
 from datetime import timedelta
 from unittest import mock
 
@@ -16,6 +17,22 @@ from jupyter_client.session import utcnow
 REFERENCE_DATETIME = datetime.datetime(2013, 7, 3, 16, 34, 52, 249482, tzlocal())
 
 
+class MyInt(object):
+    def __int__(self):
+        return 389
+
+
+numbers.Integral.register(MyInt)
+
+
+class MyFloat(object):
+    def __float__(self):
+        return 3.14
+
+
+numbers.Real.register(MyFloat)
+
+
 def test_extract_date_from_naive():
     ref = REFERENCE_DATETIME
     timestamp = "2013-07-03T16:34:52.249482"
@@ -65,7 +82,7 @@ def test_parse_ms_precision():
             assert isinstance(parsed, str)
 
 
-def test_json_default():
+def test_json_default_date():
     naive = datetime.datetime.now()
     local = tzoffset("Local", -8 * 3600)
     other = tzoffset("Other", 2 * 3600)
@@ -79,3 +96,35 @@ def test_json_default():
     for dt in extracted.values():
         assert isinstance(dt, datetime.datetime)
         assert dt.tzinfo is not None
+
+
+def test_json_default():
+    # list of input/expected output.  Use None for the expected output if it
+    # can be the same as the input.
+    pairs = [
+        (1, None),  # start with scalars
+        (1.123, None),
+        (1.0, None),
+        ('a', None),
+        (True, None),
+        (False, None),
+        (None, None),
+        # Containers
+        ([1, 2], None),
+        ((1, 2), [1, 2]),
+        (set([1, 2]), [1, 2]),
+        (dict(x=1), None),
+        ({'x': 1, 'y': [1, 2, 3], '1': 'int'}, None),
+        # More exotic objects
+        ((x for x in range(3)), [0, 1, 2]),
+        (iter([1, 2]), [1, 2]),
+        (MyFloat(), 3.14),
+        (MyInt(), 389),
+    ]
+
+    for val, jval in pairs:
+        if jval is None:
+            jval = val
+        out = json.loads(json.dumps(val, default=jsonutil.json_default))
+        # validate our cleanup
+        assert out == jval