|
|
=====================
|
|
|
Message Specification
|
|
|
=====================
|
|
|
|
|
|
Note: not all of these have yet been fully fleshed out, but the key ones are,
|
|
|
see kernel and frontend files for actual implementation details.
|
|
|
|
|
|
Messages are dicts of dicts with string keys and values that are reasonably
|
|
|
representable in JSON. Our current implementation uses JSON explicitly as its
|
|
|
message format, but this shouldn't be considered a permanent feature. As we've
|
|
|
discovered that JSON has non-trivial performance issues due to excessive
|
|
|
copying, we may in the future move to a pure pickle-based raw message format.
|
|
|
However, it should be possible to easily convert from the raw objects to JSON,
|
|
|
since we may have non-python clients (e.g. a web frontend). As long as it's
|
|
|
easy to make a JSON version of the objects that is a faithful representation of
|
|
|
all the data, we can communicate with such clients.
|
|
|
|
|
|
|
|
|
Python functional API
|
|
|
=====================
|
|
|
|
|
|
As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
|
|
|
should develop, at a few key points, functional forms of all the requests that
|
|
|
take arguments in this manner and automatically construct the necessary dict
|
|
|
for sending.
|
|
|
|
|
|
|
|
|
General Message Format
|
|
|
=====================
|
|
|
|
|
|
General message format::
|
|
|
|
|
|
{
|
|
|
header : { 'msg_id' : 10, # start with 0
|
|
|
'username' : 'name',
|
|
|
'session' : uuid
|
|
|
},
|
|
|
parent_header : dict,
|
|
|
msg_type : 'string_message_type',
|
|
|
content : blackbox_dict , # Must be a dict
|
|
|
}
|
|
|
|
|
|
|
|
|
Request/Reply going from kernel for stdin
|
|
|
=========================================
|
|
|
|
|
|
This is a socket that goes in the opposite direction: from the kernel to a
|
|
|
*single* frontend, and its purpose is to allow ``raw_input`` and similar
|
|
|
operations that read from ``sys.stdin`` on the kernel to be fulfilled by the
|
|
|
client. For now we will keep these messages as simple as possible, since they
|
|
|
basically only mean to convey the ``raw_input(prompt)`` call.
|
|
|
|
|
|
Message type: 'input_request'::
|
|
|
|
|
|
content = { prompt : string }
|
|
|
|
|
|
Message type: 'input_reply'::
|
|
|
|
|
|
content = { value : string }
|
|
|
|
|
|
|
|
|
Side effect: (PUB/SUB)
|
|
|
======================
|
|
|
|
|
|
Message type: 'stream'::
|
|
|
|
|
|
content = {
|
|
|
name : 'stdout',
|
|
|
data : 'blob',
|
|
|
}
|
|
|
|
|
|
When a kernel receives a raw_input call, it should also broadcast it on the pub
|
|
|
socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
|
|
|
to monitor/display kernel interactions and possibly replay them to their user
|
|
|
or otherwise expose them.
|
|
|
|
|
|
Message type: 'pyin'::
|
|
|
|
|
|
content = {
|
|
|
code = 'x=1',
|
|
|
}
|
|
|
|
|
|
Message type: 'pyout'::
|
|
|
|
|
|
content = {
|
|
|
data = 'repr(obj)',
|
|
|
prompt_number = 10
|
|
|
}
|
|
|
|
|
|
Message type: 'pyerr'::
|
|
|
|
|
|
content = {
|
|
|
# Same as the data payload of a code execute_reply, minus the 'status'
|
|
|
# field. See below.
|
|
|
}
|
|
|
|
|
|
When the kernel has an unexpected exception, caught by the last-resort
|
|
|
sys.excepthook, we should broadcast the crash handler's output before exiting.
|
|
|
This will allow clients to notice that a kernel died, inform the user and
|
|
|
propose further actions.
|
|
|
|
|
|
Message type: 'crash'::
|
|
|
|
|
|
content = {
|
|
|
traceback : 'full traceback',
|
|
|
exc_type : 'TypeError',
|
|
|
exc_value : 'msg'
|
|
|
}
|
|
|
|
|
|
|
|
|
Other potential message types, currently unimplemented, listed below as ideas.
|
|
|
|
|
|
Message type: 'file'::
|
|
|
content = {
|
|
|
path : 'cool.jpg',
|
|
|
mimetype : string
|
|
|
data : 'blob'
|
|
|
}
|
|
|
|
|
|
|
|
|
Request/Reply
|
|
|
=============
|
|
|
|
|
|
Execute
|
|
|
-------
|
|
|
|
|
|
The execution request contains a single string, but this may be a multiline
|
|
|
string. The kernel is responsible for splitting this into possibly more than
|
|
|
one block and deciding whether to compile these in 'single' or 'exec' mode.
|
|
|
We're still sorting out this policy. The current inputsplitter is capable of
|
|
|
splitting the input for blocks that can all be run as 'single', but in the long
|
|
|
run it may prove cleaner to only use 'single' mode for truly single-line
|
|
|
inputs, and run all multiline input in 'exec' mode. This would preserve the
|
|
|
natural behavior of single-line inputs while allowing long cells to behave more
|
|
|
likea a script. Some thought is still required here...
|
|
|
|
|
|
Message type: 'execute_request'::
|
|
|
|
|
|
content = {
|
|
|
code : 'a = 10',
|
|
|
}
|
|
|
|
|
|
Reply:
|
|
|
|
|
|
Message type: 'execute_reply'::
|
|
|
|
|
|
content = {
|
|
|
'status' : 'ok' OR 'error' OR 'abort'
|
|
|
# Any additional data depends on status value
|
|
|
}
|
|
|
|
|
|
When status is 'ok', the following extra fields are present::
|
|
|
|
|
|
{
|
|
|
# This has the same structure as the output of a prompt request, but is
|
|
|
# for the client to set up the *next* prompt (with identical limitations
|
|
|
# to a prompt request)
|
|
|
'next_prompt' : {
|
|
|
prompt_string : string
|
|
|
prompt_number : int
|
|
|
}
|
|
|
|
|
|
# The prompt number of the actual execution for this code, which may be
|
|
|
# different from the one used when the code was typed, which was the
|
|
|
# 'next_prompt' field of the *previous* request. They will differ in the
|
|
|
# case where there is more than one client talking simultaneously to a
|
|
|
# kernel, since the numbers can go out of sync. GUI clients can use this
|
|
|
# to correct the previously written number in-place, terminal ones may
|
|
|
# re-print a corrected one if desired.
|
|
|
'prompt_number' : number
|
|
|
|
|
|
# The kernel will often transform the input provided to it. This
|
|
|
# contains the transformed code, which is what was actually executed.
|
|
|
'transformed_code' : new_code
|
|
|
|
|
|
# This 'payload' needs a bit more thinking. The basic idea is that
|
|
|
# certain actions will want to return additional information, such as
|
|
|
# magics producing data output for display by the clients. We may need
|
|
|
# to define a few types of payload, or specify a syntax for the, not sure
|
|
|
# yet... FIXME here.
|
|
|
'payload' : things from page(), for example.
|
|
|
}
|
|
|
|
|
|
When status is 'error', the following extra fields are present::
|
|
|
|
|
|
{
|
|
|
etype : str # Exception type, as a string
|
|
|
evalue : str # Exception value, as a string
|
|
|
|
|
|
# The traceback will contain a list of frames, represented each as a
|
|
|
# string. For now we'll stick to the existing design of ultraTB, which
|
|
|
# controls exception level of detail statefully. But eventually we'll
|
|
|
# want to grow into a model where more information is collected and
|
|
|
# packed into the traceback object, with clients deciding how little or
|
|
|
# how much of it to unpack. But for now, let's start with a simple list
|
|
|
# of strings, since that requires only minimal changes to ultratb as
|
|
|
# written.
|
|
|
traceback : list of strings
|
|
|
}
|
|
|
|
|
|
|
|
|
When status is 'abort', there are for now no additional data fields.
|
|
|
|
|
|
|
|
|
Prompt
|
|
|
------
|
|
|
|
|
|
A simple request for a current prompt string.
|
|
|
|
|
|
Message type: 'prompt_request'::
|
|
|
|
|
|
content = {}
|
|
|
|
|
|
In the reply, the prompt string comes back with the prompt number placeholder
|
|
|
*unevaluated*. The message format is:
|
|
|
|
|
|
Message type: 'prompt_reply'::
|
|
|
|
|
|
content = {
|
|
|
prompt_string : string
|
|
|
prompt_number : int
|
|
|
}
|
|
|
|
|
|
Clients can produce a prompt with ``prompt_string.format(prompt_number)``, but
|
|
|
they should be aware that the actual prompt number for that input could change
|
|
|
later, in the case where multiple clients are interacting with a single
|
|
|
kernel.
|
|
|
|
|
|
|
|
|
Complete
|
|
|
--------
|
|
|
|
|
|
Message type: 'complete_request'::
|
|
|
|
|
|
content = {
|
|
|
text : 'a.f', # complete on this
|
|
|
line : 'print a.f' # full line
|
|
|
}
|
|
|
|
|
|
Message type: 'complete_reply'::
|
|
|
|
|
|
content = {
|
|
|
matches : ['a.foo', 'a.bar']
|
|
|
}
|
|
|
|
|
|
|
|
|
History
|
|
|
-------
|
|
|
|
|
|
For clients to explicitly request history from a kernel
|
|
|
|
|
|
Message type: 'history_request'::
|
|
|
|
|
|
content = {
|
|
|
output : boolean. If true, also return output history in the resulting
|
|
|
dict.
|
|
|
|
|
|
range : optional. A number, a pair of numbers, 'all'
|
|
|
If not given, last 40 are returned.
|
|
|
- number n: return the last n entries.
|
|
|
- pair n1, n2: return entries in the range(n1, n2).
|
|
|
- 'all': return all history
|
|
|
|
|
|
filter : optional, string
|
|
|
If given, treated as a regular expression and only matching entries are
|
|
|
returned. re.search() is used to find matches.
|
|
|
}
|
|
|
|
|
|
Message type: 'history_reply'::
|
|
|
|
|
|
content = {
|
|
|
input : list of pairs (number, input)
|
|
|
output : list of pairs (number, output). Empty if not requested.
|
|
|
}
|
|
|
|
|
|
|
|
|
Control
|
|
|
-------
|
|
|
|
|
|
Message type: 'heartbeat'::
|
|
|
|
|
|
content = {
|
|
|
# XXX - unfinished
|
|
|
}
|
|
|
|