Skip to content

Commit

Permalink
Merge pull request #708 from martinRenou/clean_json_clean
Browse files Browse the repository at this point in the history
Make json_clean a no-op for jupyter-client >= 7
  • Loading branch information
Steven Silvester authored Sep 7, 2021
2 parents c810559 + a7aa5a0 commit 3cee9ce
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 13 deletions.
2 changes: 1 addition & 1 deletion ipykernel/datapub.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,6 @@ def publish_data(data):
DeprecationWarning,
stacklevel=2
)

from ipykernel.zmqshell import ZMQInteractiveShell
ZMQInteractiveShell.instance().data_pub.publish_data(data)
17 changes: 14 additions & 3 deletions ipykernel/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
from tornado.queues import Queue
from tornado.locks import Event

from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed)

from IPython.core.getipython import get_ipython

try:
from jupyter_client.jsonutil import json_default
except ImportError:
from jupyter_client.jsonutil import date_default as json_default

from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed)

# This import is required to have the next ones working...
from debugpy.server import api # noqa
from _pydevd_bundle import pydevd_frame_utils
Expand Down Expand Up @@ -170,7 +175,12 @@ def _forward_event(self, msg):
def _send_request(self, msg):
if self.routing_id is None:
self.routing_id = self.debugpy_stream.socket.getsockopt(ROUTING_ID)
content = jsonapi.dumps(msg)
content = jsonapi.dumps(
msg,
default=json_default,
ensure_ascii=False,
allow_nan=False,
)
content_length = str(len(content))
buf = (DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR).encode('ascii')
buf += content
Expand Down Expand Up @@ -240,6 +250,7 @@ async def send_dap_request(self, msg):
self.log.debug(rep)
return rep


class Debugger:

# Requests that requires that the debugger has started
Expand Down
4 changes: 2 additions & 2 deletions ipykernel/inprocess/ipkernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,12 @@ def _default_shell_class(self):

@default('stdout')
def _default_stdout(self):
return OutStream(self.session, self.iopub_thread, 'stdout',
return OutStream(self.session, self.iopub_thread, 'stdout',
watchfd=False)

@default('stderr')
def _default_stderr(self):
return OutStream(self.session, self.iopub_thread, 'stderr',
return OutStream(self.session, self.iopub_thread, 'stderr',
watchfd=False)

#-----------------------------------------------------------------------------
Expand Down
17 changes: 13 additions & 4 deletions ipykernel/jsonutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import types
from datetime import datetime
import numbers
from jupyter_client._version import version_info as jupyter_client_version

next_attr_name = '__next__'

Expand Down Expand Up @@ -42,6 +43,9 @@
# front of PDF base64-encoded
PDF64 = b'JVBER'

JUPYTER_CLIENT_MAJOR_VERSION = jupyter_client_version[0]


def encode_images(format_dict):
"""b64-encodes images in a displaypub format dict
Expand All @@ -68,7 +72,9 @@ def encode_images(format_dict):


def json_clean(obj):
"""Clean an object to ensure it's safe to encode in JSON.
"""Deprecated, this is a no-op for jupyter-client>=7.
Clean an object to ensure it's safe to encode in JSON.
Atomic, immutable objects are returned unmodified. Sets and tuples are
converted to lists, lists are copied and dicts are also copied.
Expand All @@ -89,6 +95,9 @@ def json_clean(obj):
it simply sanitizes it so that there will be no encoding errors later.
"""
if JUPYTER_CLIENT_MAJOR_VERSION >= 7:
return obj

# types that are 'atomic' and ok in json as-is.
atomic_ok = (str, type(None))

Expand All @@ -110,10 +119,10 @@ def json_clean(obj):
if math.isnan(obj) or math.isinf(obj):
return repr(obj)
return float(obj)

if isinstance(obj, atomic_ok):
return obj

if isinstance(obj, bytes):
# unanmbiguous binary data is base64-encoded
# (this probably should have happened upstream)
Expand Down Expand Up @@ -142,6 +151,6 @@ def json_clean(obj):
return out
if isinstance(obj, datetime):
return obj.strftime(ISO8601)

# we don't understand it, it's probably an unserializable object
raise ValueError("Can't clean for JSON: %r" % obj)
18 changes: 15 additions & 3 deletions ipykernel/tests/test_jsonutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,28 @@

import pytest

from jupyter_client._version import version_info as jupyter_client_version

from .. import jsonutil
from ..jsonutil import json_clean, encode_images


JUPYTER_CLIENT_MAJOR_VERSION = jupyter_client_version[0]


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)


@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op")
def test():
# list of input/expected output. Use None for the expected output if it
# can be the same as the input.
Expand All @@ -47,7 +55,7 @@ def test():
(MyFloat(), 3.14),
(MyInt(), 389)
]

for val, jval in pairs:
if jval is None:
jval = val
Expand All @@ -58,13 +66,14 @@ def test():
json.loads(json.dumps(out))


@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op")
def test_encode_images():
# invalid data, but the header and footer are from real files
pngdata = b'\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82'
jpegdata = b'\xff\xd8\xff\xe0\x00\x10JFIFblahblahjpeg(\xa0\x0f\xff\xd9'
pdfdata = b'%PDF-1.\ntrailer<</Root<</Pages<</Kids[<</MediaBox[0 0 3 3]>>]>>>>>>'
bindata = b'\xff\xff\xff\xff'

fmt = {
'image/png' : pngdata,
'image/jpeg' : jpegdata,
Expand All @@ -78,16 +87,18 @@ def test_encode_images():
assert decoded == value
encoded2 = json_clean(encode_images(encoded))
assert encoded == encoded2

for key, value in fmt.items():
decoded = a2b_base64(encoded[key])
assert decoded == value

@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op")
def test_lambda():
with pytest.raises(ValueError):
json_clean(lambda : 1)


@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op")
def test_exception():
bad_dicts = [{1:'number', '1':'string'},
{True:'bool', 'True':'string'},
Expand All @@ -97,6 +108,7 @@ def test_exception():
json_clean(d)


@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op")
def test_unicode_dict():
data = {'üniço∂e': 'üniço∂e'}
clean = jsonutil.json_clean(data)
Expand Down

0 comments on commit 3cee9ce

Please sign in to comment.