Repository: jupyter/jupyter_client
Pull request #664 (2021-07-01 14:14:11)

Use a default serializer that is not only for date types
This would allow making more general changes here. I am also adding serialization of bytes type into base64. 

For more context, this is going in the direction of https://github.com/ipython/ipykernel/issues/706. This allows getting rid of the `json_clean` function, moving the bytes treatment from ipykernel to jupyter_client when actually doing the serializion of the object.

Carreau: +1
martinRenou: pip timed out in one of the builds. Could someone restart it?
davidbrochart: Is it ready to be merged?
martinRenou: I guess I still need to process https://github.com/jupyter/jupyter_client/pull/664#discussion_r662345972

But the question is what should be the comment? Should it be `since version 7.0`?
davidbrochart: > But the question is what should be the comment? Should it be `since version 7.0`?

I think `since version 7.0` is fine.
martinRenou: Done :)
davidbrochart: Not quite yet :-)

Patch:
diff --git a/jupyter_client/jsonutil.py b/jupyter_client/jsonutil.py
index 3bd5f7a..e12601d 100644
--- a/jupyter_client/jsonutil.py
+++ b/jupyter_client/jsonutil.py
@@ -3,6 +3,7 @@
 # Distributed under the terms of the Modified BSD License.
 import re
 import warnings
+from binascii import b2a_base64
 from datetime import datetime
 from typing import Optional
 from typing import Union
@@ -91,9 +92,21 @@ def squash_dates(obj):
 
 
 def date_default(obj):
-    """default function for packing datetime objects in JSON."""
+    """DEPRECATED: Use jupyter_client.jsonutil.json_default"""
+    warnings.warn(
+        "date_default is deprecated since jupyter_client 7.0.0."
+        " Use jupyter_client.jsonutil.json_default.",
+        stacklevel=2,
+    )
+    return json_default(obj)
+
+
+def json_default(obj):
+    """default function for packing objects in JSON."""
     if isinstance(obj, datetime):
         obj = _ensure_tzinfo(obj)
-        return obj.isoformat().replace("+00:00", "Z")
+        return obj.isoformat().replace('+00:00', 'Z')
+    elif isinstance(obj, bytes):
+        return b2a_base64(obj).decode('ascii')
     else:
         raise TypeError("%r is not JSON serializable" % obj)
diff --git a/jupyter_client/session.py b/jupyter_client/session.py
index 7aac30d..dfa331a 100644
--- a/jupyter_client/session.py
+++ b/jupyter_client/session.py
@@ -49,8 +49,8 @@ from zmq.utils import jsonapi
 
 from jupyter_client import protocol_version
 from jupyter_client.adapter import adapt
-from jupyter_client.jsonutil import date_default
 from jupyter_client.jsonutil import extract_dates
+from jupyter_client.jsonutil import json_default
 from jupyter_client.jsonutil import squash_dates
 
 
@@ -94,7 +94,7 @@ MAX_BYTES = 1024
 def json_packer(obj):
     return jsonapi.dumps(
         obj,
-        default=date_default,
+        default=json_default,
         ensure_ascii=False,
         allow_nan=False,
     )


Test patch:
diff --git a/jupyter_client/tests/test_jsonutil.py b/jupyter_client/tests/test_jsonutil.py
index 2f17e4f..0364e12 100644
--- a/jupyter_client/tests/test_jsonutil.py
+++ b/jupyter_client/tests/test_jsonutil.py
@@ -65,14 +65,14 @@ def test_parse_ms_precision():
             assert isinstance(parsed, str)
 
 
-def test_date_default():
+def test_json_default():
     naive = datetime.datetime.now()
     local = tzoffset("Local", -8 * 3600)
     other = tzoffset("Other", 2 * 3600)
     data = dict(naive=naive, utc=utcnow(), withtz=naive.replace(tzinfo=other))
     with mock.patch.object(jsonutil, "tzlocal", lambda: local):
         with pytest.deprecated_call(match="Please add timezone info"):
-            jsondata = json.dumps(data, default=jsonutil.date_default)
+            jsondata = json.dumps(data, default=jsonutil.json_default)
     assert "Z" in jsondata
     assert jsondata.count("Z") == 1
     extracted = jsonutil.extract_dates(json.loads(jsondata))