messaging.txt
284 lines
| 8.2 KiB
| text/plain
|
TextLexer
Fernando Perez
|
r2599 | ===================== | ||
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. | ||||
Fernando Perez
|
r2727 | 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. | ||||
Fernando Perez
|
r2599 | 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 | ||||
} | ||||
Fernando Perez
|
r2727 | |||
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 } | ||||
Fernando Perez
|
r2599 | Side effect: (PUB/SUB) | ||
====================== | ||||
Fernando Perez
|
r2727 | Message type: 'stream':: | ||
Fernando Perez
|
r2599 | |||
content = { | ||||
name : 'stdout', | ||||
data : 'blob', | ||||
} | ||||
Fernando Perez
|
r2727 | 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':: | ||||
Fernando Perez
|
r2599 | |||
content = { | ||||
code = 'x=1', | ||||
} | ||||
Fernando Perez
|
r2727 | Message type: 'pyout':: | ||
Fernando Perez
|
r2599 | |||
content = { | ||||
data = 'repr(obj)', | ||||
prompt_number = 10 | ||||
} | ||||
Fernando Perez
|
r2727 | 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':: | ||||
Fernando Perez
|
r2599 | |||
content = { | ||||
traceback : 'full traceback', | ||||
exc_type : 'TypeError', | ||||
exc_value : 'msg' | ||||
} | ||||
Fernando Perez
|
r2727 | |||
Other potential message types, currently unimplemented, listed below as ideas. | ||||
Message type: 'file':: | ||||
Fernando Perez
|
r2599 | content = { | ||
Fernando Perez
|
r2727 | path : 'cool.jpg', | ||
mimetype : string | ||||
Fernando Perez
|
r2599 | data : 'blob' | ||
} | ||||
Fernando Perez
|
r2727 | |||
Fernando Perez
|
r2599 | Request/Reply | ||
============= | ||||
Execute | ||||
------- | ||||
Fernando Perez
|
r2727 | 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... | ||||
Fernando Perez
|
r2599 | |||
Fernando Perez
|
r2727 | Message type: 'execute_request':: | ||
Fernando Perez
|
r2599 | |||
content = { | ||||
code : 'a = 10', | ||||
} | ||||
Reply: | ||||
Fernando Perez
|
r2727 | Message type: 'execute_reply':: | ||
Fernando Perez
|
r2599 | |||
content = { | ||||
'status' : 'ok' OR 'error' OR 'abort' | ||||
Fernando Perez
|
r2727 | # 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 | ||||
Fernando Perez
|
r2599 | } | ||
Fernando Perez
|
r2727 | 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. | ||||
Fernando Perez
|
r2599 | Complete | ||
-------- | ||||
Fernando Perez
|
r2727 | Message type: 'complete_request':: | ||
Fernando Perez
|
r2599 | |||
content = { | ||||
text : 'a.f', # complete on this | ||||
line : 'print a.f' # full line | ||||
} | ||||
Fernando Perez
|
r2727 | Message type: 'complete_reply':: | ||
Fernando Perez
|
r2599 | |||
content = { | ||||
matches : ['a.foo', 'a.bar'] | ||||
} | ||||
Fernando Perez
|
r2727 | |||
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. | ||||
} | ||||
Fernando Perez
|
r2599 | Control | ||
------- | ||||
Fernando Perez
|
r2727 | Message type: 'heartbeat':: | ||
Fernando Perez
|
r2599 | |||
content = { | ||||
# XXX - unfinished | ||||
} | ||||