Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make json_clean a no-op for jupyter-client >= 7 #708

Merged
merged 1 commit into from Sep 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion ipykernel/datapub.py
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
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
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
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
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