Skip to content

Commit

Permalink
Make json_clean a no-op for jupyter-client >= 7
Browse files Browse the repository at this point in the history
  • Loading branch information
martinRenou committed Sep 6, 2021
1 parent 3c6037f commit a3cea30
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 21 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)
35 changes: 24 additions & 11 deletions ipykernel/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
from tornado.queues import Queue
from tornado.locks import Event

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)

from IPython.core.getipython import get_ipython

from .jsonutil import json_clean

# This import is required to have the next ones working...
from debugpy.server import api
Expand All @@ -21,6 +26,7 @@
# Required for backwards compatiblity
ROUTING_ID = getattr(zmq, 'ROUTING_ID', None) or zmq.IDENTITY


class _FakeCode:
def __init__(self, co_filename, co_name):
self.co_filename = co_filename
Expand Down Expand Up @@ -60,6 +66,7 @@ def get_children_variables(self, variable_ref = None):
variables = self.suspended_frame_manager.get_variable(var_ref)
return [x.get_var_data() for x in variables.get_children_variables()]


class DebugpyMessageQueue:

HEADER = 'Content-Length: '
Expand Down Expand Up @@ -102,7 +109,7 @@ def put_tcp_frame(self, frame):
self.header_pos = self.tcp_buffer.find(DebugpyMessageQueue.HEADER)
if self.header_pos == -1:
return

self.log.debug('QUEUE - found header at pos %i', self.header_pos)

#Finds separator
Expand Down Expand Up @@ -138,7 +145,7 @@ def put_tcp_frame(self, frame):

async def get_message(self):
return await self.message_queue.get()


class DebugpyClient:

Expand Down Expand Up @@ -167,15 +174,20 @@ 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
self.log.debug("DEBUGPYCLIENT:")
self.log.debug(self.routing_id)
self.log.debug(buf)
self.debugpy_stream.send_multipart((self.routing_id, buf))

async def _wait_for_response(self):
# Since events are never pushed to the message_queue
# we can safely assume the next message in queue
Expand All @@ -185,7 +197,7 @@ async def _wait_for_response(self):
async def _handle_init_sequence(self):
# 1] Waits for initialized event
await self.init_event.wait()

# 2] Sends configurationDone request
configurationDone = {
'type': 'request',
Expand Down Expand Up @@ -237,6 +249,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 All @@ -246,7 +259,7 @@ class Debugger:
'variables', 'attach',
'configurationDone'
]

# Requests that can be handled even if the debugger is not running
static_debug_msg_types = [
'debugInfo', 'inspectVariables', 'richInspectVariables'
Expand All @@ -259,7 +272,7 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session):
self.session = session
self.is_started = False
self.event_callback = event_callback

self.started_debug_handlers = {}
for msg_type in Debugger.started_debug_msg_types:
self.started_debug_handlers[msg_type] = getattr(self, msg_type)
Expand Down Expand Up @@ -324,7 +337,7 @@ def start(self):
}
self.session.send(self.shell_socket, 'execute_request', content,
None, (self.shell_socket.getsockopt(ROUTING_ID)))

ident, msg = self.session.recv(self.shell_socket, mode=0)
self.debugpy_initialized = msg['content']['status'] == 'ok'
self.debugpy_client.connect_tcp_socket()
Expand Down Expand Up @@ -535,7 +548,7 @@ async def richInspectVariables(self, message):
'data': {},
'metadata': {}
}

for key, value in repr_data.items():
body['data']['key'] = value
if repr_metadata.has_key(key):
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 a3cea30

Please sign in to comment.