diff --git a/IPython/core/release.py b/IPython/core/release.py index fa6f434..f81185c 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -38,6 +38,9 @@ __version__ = '.'.join(map(str, _ver)) version = __version__ # backwards compatibility name version_info = (_version_major, _version_minor, _version_micro, _version_extra) +# Change this when incrementing the kernel protocol version +kernel_protocol_version_info = (4, 0) + description = "IPython: Productive Interactive Computing" long_description = \ diff --git a/IPython/zmq/ipkernel.py b/IPython/zmq/ipkernel.py index d37b5b4..ee4999a 100755 --- a/IPython/zmq/ipkernel.py +++ b/IPython/zmq/ipkernel.py @@ -39,6 +39,7 @@ from IPython.config.configurable import Configurable from IPython.config.application import boolean_flag, catch_config_error from IPython.core.application import ProfileDir from IPython.core.error import StdinNotImplementedError +from IPython.core import release from IPython.core.shellapp import ( InteractiveShellApp, shell_flags, shell_aliases ) @@ -61,6 +62,11 @@ from zmqshell import ZMQInteractiveShell # Main kernel class #----------------------------------------------------------------------------- +protocol_version = list(release.kernel_protocol_version_info) +ipython_version = list(release.version_info) +language_version = list(sys.version_info[:3]) + + class Kernel(Configurable): #--------------------------------------------------------------------------- @@ -156,6 +162,7 @@ class Kernel(Configurable): # Build dict of handlers for message types msg_types = [ 'execute_request', 'complete_request', 'object_info_request', 'history_request', + 'kernel_info_request', 'connect_request', 'shutdown_request', 'apply_request', ] @@ -509,6 +516,17 @@ class Kernel(Configurable): content, parent, ident) self.log.debug("%s", msg) + def kernel_info_request(self, stream, ident, parent): + vinfo = { + 'protocol_version': protocol_version, + 'ipython_version': ipython_version, + 'language_version': language_version, + 'language': 'python', + } + msg = self.session.send(stream, 'kernel_info_reply', + vinfo, parent, ident) + self.log.debug("%s", msg) + def shutdown_request(self, stream, ident, parent): self.shell.exit_now = True content = dict(status='ok') diff --git a/IPython/zmq/kernelmanager.py b/IPython/zmq/kernelmanager.py index 8eb5efd..77aded0 100644 --- a/IPython/zmq/kernelmanager.py +++ b/IPython/zmq/kernelmanager.py @@ -363,6 +363,12 @@ class ShellSocketChannel(ZMQSocketChannel): self._queue_send(msg) return msg['header']['msg_id'] + def kernel_info(self): + """Request kernel info.""" + msg = self.session.msg('kernel_info_request') + self._queue_send(msg) + return msg['header']['msg_id'] + def shutdown(self, restart=False): """Request an immediate kernel shutdown. diff --git a/IPython/zmq/session.py b/IPython/zmq/session.py index 1edf6db..a38ee4d 100644 --- a/IPython/zmq/session.py +++ b/IPython/zmq/session.py @@ -43,7 +43,6 @@ from zmq.utils import jsonapi from zmq.eventloop.ioloop import IOLoop from zmq.eventloop.zmqstream import ZMQStream -import IPython from IPython.config.application import Application, boolean_flag from IPython.config.configurable import Configurable, LoggingConfigurable from IPython.utils.importstring import import_item @@ -75,7 +74,6 @@ def squash_unicode(obj): # globals and defaults #----------------------------------------------------------------------------- -_version_info_list = list(IPython.version_info) # ISO8601-ify datetime objects json_packer = lambda obj: jsonapi.dumps(obj, default=date_default) json_unpacker = lambda s: extract_dates(jsonapi.loads(s)) @@ -188,7 +186,6 @@ class Message(object): def msg_header(msg_id, msg_type, username, session): date = datetime.now() - version = _version_info_list return locals() def extract_header(msg_or_header): diff --git a/IPython/zmq/tests/test_message_spec.py b/IPython/zmq/tests/test_message_spec.py index 2bbb90c..6f2e72c 100644 --- a/IPython/zmq/tests/test_message_spec.py +++ b/IPython/zmq/tests/test_message_spec.py @@ -21,7 +21,7 @@ from ..blockingkernelmanager import BlockingKernelManager from IPython.testing import decorators as dec from IPython.utils import io from IPython.utils.traitlets import ( - HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, + HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any, ) #----------------------------------------------------------------------------- @@ -83,7 +83,17 @@ def execute(code='', **kwargs): class Reference(HasTraits): - + + """ + Base class for message spec specification testing. + + This class is the core of the message specification test. The + idea is that child classes implement trait attributes for each + message keys, so that message keys can be tested against these + traits using :meth:`check` method. + + """ + def check(self, d): """validate a dict against our traits""" for key in self.trait_names(): @@ -185,6 +195,25 @@ class CompleteReply(Reference): matches = List(Unicode) +def Version(num, trait=Integer): + return List(trait, default_value=[0] * num, minlen=num, maxlen=num) + + +class KernelInfoReply(Reference): + + protocol_version = Version(2) + ipython_version = Version(4, Any) + language_version = Version(3) + language = Unicode() + + def _ipython_version_changed(self, name, old, new): + for v in new: + nt.assert_true( + isinstance(v, int) or isinstance(v, basestring), + 'expected int or string as version component, got {0!r}' + .format(v)) + + # IOPub messages class PyIn(Reference): @@ -226,12 +255,16 @@ references = { 'object_info_reply' : OInfoReply(), 'status' : Status(), 'complete_reply' : CompleteReply(), + 'kernel_info_reply': KernelInfoReply(), 'pyin' : PyIn(), 'pyout' : PyOut(), 'pyerr' : PyErr(), 'stream' : Stream(), 'display_data' : DisplayData(), } +""" +Specifications of `content` part of the reply messages. +""" def validate_message(msg, msg_type=None, parent=None): @@ -421,6 +454,18 @@ def test_complete(): yield nt.assert_true(name in matches, "Missing match: %r" % name) +@dec.parametric +def test_kernel_info_request(): + flush_channels() + + shell = KM.shell_channel + + msg_id = shell.kernel_info() + reply = shell.get_msg(timeout=2) + for tst in validate_message(reply, 'kernel_info_reply', msg_id): + yield tst + + # IOPub channel diff --git a/docs/source/development/messaging.txt b/docs/source/development/messaging.txt index 55bfbf9..7505acb 100644 --- a/docs/source/development/messaging.txt +++ b/docs/source/development/messaging.txt @@ -670,6 +670,47 @@ Message type: ``connect_reply``:: } +Kernel info +----------- + +If a client needs to know what protocol the kernel supports, it can +ask version number of the messaging protocol supported by the kernel. +This message can be used to fetch other core information of the +kernel, including language (e.g., Python), language version number and +IPython version number. + +Message type: ``kernel_info_request``:: + + content = { + } + +Message type: ``kernel_info_reply``:: + + content = { + # Version of messaging protocol (mandatory). + # The first integer indicates major version. It is incremented when + # there is any backward incompatible change. + # The second integer indicates minor version. It is incremented when + # there is any backward compatible change. + 'protocol_version': [int, int], + + # IPython version number (optional). + # Non-python kernel backend may not have this version number. + # The last component is an extra field, which may be 'dev' or + # 'rc1' in development version. It is an empty string for + # released version. + 'ipython_version': [int, int, int, str], + + # Language version number (mandatory). + # It is Python version number (e.g., [2, 7, 3]) for the kernel + # included in IPython. + 'language_version': [int, ...], + + # Programming language in which kernel is implemented (mandatory). + # Kernel included in IPython returns 'python'. + 'language': str, + } + Kernel shutdown ---------------