##// END OF EJS Templates
add ioloop.install to backported patches
MinRK -
Show More
@@ -1,569 +1,563 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import re
24 24 import select
25 25 import signal
26 26 import socket
27 27 import sys
28 28 import threading
29 29 import time
30 30 import webbrowser
31 31
32 32 # Third party
33 33 import zmq
34 34
35 35 # Install the pyzmq ioloop. This has to be done before anything else from
36 36 # tornado is imported.
37 37 from zmq.eventloop import ioloop
38 # FIXME: ioloop.install is new in pyzmq-2.1.7, so remove this conditional
39 # when pyzmq dependency is updated beyond that.
40 if hasattr(ioloop, 'install'):
41 38 ioloop.install()
42 else:
43 import tornado.ioloop
44 tornado.ioloop.IOLoop = ioloop.IOLoop
45 39
46 40 from tornado import httpserver
47 41 from tornado import web
48 42
49 43 # Our own libraries
50 44 from .kernelmanager import MappingKernelManager
51 45 from .handlers import (LoginHandler, LogoutHandler,
52 46 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
53 47 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
54 48 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
55 49 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
56 50 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler
57 51 )
58 52 from .notebookmanager import NotebookManager
59 53 from .clustermanager import ClusterManager
60 54
61 55 from IPython.config.application import catch_config_error, boolean_flag
62 56 from IPython.core.application import BaseIPythonApplication
63 57 from IPython.core.profiledir import ProfileDir
64 58 from IPython.lib.kernel import swallow_argv
65 59 from IPython.zmq.session import Session, default_secure
66 60 from IPython.zmq.zmqshell import ZMQInteractiveShell
67 61 from IPython.zmq.ipkernel import (
68 62 flags as ipkernel_flags,
69 63 aliases as ipkernel_aliases,
70 64 IPKernelApp
71 65 )
72 66 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
73 67 from IPython.utils import py3compat
74 68
75 69 #-----------------------------------------------------------------------------
76 70 # Module globals
77 71 #-----------------------------------------------------------------------------
78 72
79 73 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
80 74 _kernel_action_regex = r"(?P<action>restart|interrupt)"
81 75 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
82 76 _profile_regex = r"(?P<profile>[a-zA-Z0-9]+)"
83 77 _cluster_action_regex = r"(?P<action>start|stop)"
84 78
85 79
86 80 LOCALHOST = '127.0.0.1'
87 81
88 82 _examples = """
89 83 ipython notebook # start the notebook
90 84 ipython notebook --profile=sympy # use the sympy profile
91 85 ipython notebook --pylab=inline # pylab in inline plotting mode
92 86 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
93 87 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
94 88 """
95 89
96 90 #-----------------------------------------------------------------------------
97 91 # Helper functions
98 92 #-----------------------------------------------------------------------------
99 93
100 94 def url_path_join(a,b):
101 95 if a.endswith('/') and b.startswith('/'):
102 96 return a[:-1]+b
103 97 else:
104 98 return a+b
105 99
106 100 #-----------------------------------------------------------------------------
107 101 # The Tornado web application
108 102 #-----------------------------------------------------------------------------
109 103
110 104 class NotebookWebApplication(web.Application):
111 105
112 106 def __init__(self, ipython_app, kernel_manager, notebook_manager,
113 107 cluster_manager, log,
114 108 base_project_url, settings_overrides):
115 109 handlers = [
116 110 (r"/", ProjectDashboardHandler),
117 111 (r"/login", LoginHandler),
118 112 (r"/logout", LogoutHandler),
119 113 (r"/new", NewHandler),
120 114 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
121 115 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
122 116 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
123 117 (r"/kernels", MainKernelHandler),
124 118 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
125 119 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
126 120 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
127 121 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
128 122 (r"/notebooks", NotebookRootHandler),
129 123 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
130 124 (r"/rstservice/render", RSTHandler),
131 125 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
132 126 (r"/clusters", MainClusterHandler),
133 127 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
134 128 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
135 129 ]
136 130 settings = dict(
137 131 template_path=os.path.join(os.path.dirname(__file__), "templates"),
138 132 static_path=os.path.join(os.path.dirname(__file__), "static"),
139 133 cookie_secret=os.urandom(1024),
140 134 login_url="/login",
141 135 )
142 136
143 137 # allow custom overrides for the tornado web app.
144 138 settings.update(settings_overrides)
145 139
146 140 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
147 141 # base_project_url will always be unicode, which will in turn
148 142 # make the patterns unicode, and ultimately result in unicode
149 143 # keys in kwargs to handler._execute(**kwargs) in tornado.
150 144 # This enforces that base_project_url be ascii in that situation.
151 145 #
152 146 # Note that the URLs these patterns check against are escaped,
153 147 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
154 148 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
155 149
156 150 # prepend base_project_url onto the patterns that we match
157 151 new_handlers = []
158 152 for handler in handlers:
159 153 pattern = url_path_join(base_project_url, handler[0])
160 154 new_handler = tuple([pattern]+list(handler[1:]))
161 155 new_handlers.append( new_handler )
162 156
163 157 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
164 158
165 159 self.kernel_manager = kernel_manager
166 160 self.notebook_manager = notebook_manager
167 161 self.cluster_manager = cluster_manager
168 162 self.ipython_app = ipython_app
169 163 self.read_only = self.ipython_app.read_only
170 164 self.log = log
171 165
172 166
173 167 #-----------------------------------------------------------------------------
174 168 # Aliases and Flags
175 169 #-----------------------------------------------------------------------------
176 170
177 171 flags = dict(ipkernel_flags)
178 172 flags['no-browser']=(
179 173 {'NotebookApp' : {'open_browser' : False}},
180 174 "Don't open the notebook in a browser after startup."
181 175 )
182 176 flags['no-mathjax']=(
183 177 {'NotebookApp' : {'enable_mathjax' : False}},
184 178 """Disable MathJax
185 179
186 180 MathJax is the javascript library IPython uses to render math/LaTeX. It is
187 181 very large, so you may want to disable it if you have a slow internet
188 182 connection, or for offline use of the notebook.
189 183
190 184 When disabled, equations etc. will appear as their untransformed TeX source.
191 185 """
192 186 )
193 187 flags['read-only'] = (
194 188 {'NotebookApp' : {'read_only' : True}},
195 189 """Allow read-only access to notebooks.
196 190
197 191 When using a password to protect the notebook server, this flag
198 192 allows unauthenticated clients to view the notebook list, and
199 193 individual notebooks, but not edit them, start kernels, or run
200 194 code.
201 195
202 196 If no password is set, the server will be entirely read-only.
203 197 """
204 198 )
205 199
206 200 # Add notebook manager flags
207 201 flags.update(boolean_flag('script', 'NotebookManager.save_script',
208 202 'Auto-save a .py script everytime the .ipynb notebook is saved',
209 203 'Do not auto-save .py scripts for every notebook'))
210 204
211 205 # the flags that are specific to the frontend
212 206 # these must be scrubbed before being passed to the kernel,
213 207 # or it will raise an error on unrecognized flags
214 208 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
215 209
216 210 aliases = dict(ipkernel_aliases)
217 211
218 212 aliases.update({
219 213 'ip': 'NotebookApp.ip',
220 214 'port': 'NotebookApp.port',
221 215 'keyfile': 'NotebookApp.keyfile',
222 216 'certfile': 'NotebookApp.certfile',
223 217 'notebook-dir': 'NotebookManager.notebook_dir',
224 218 'browser': 'NotebookApp.browser',
225 219 })
226 220
227 221 # remove ipkernel flags that are singletons, and don't make sense in
228 222 # multi-kernel evironment:
229 223 aliases.pop('f', None)
230 224
231 225 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
232 226 u'notebook-dir']
233 227
234 228 #-----------------------------------------------------------------------------
235 229 # NotebookApp
236 230 #-----------------------------------------------------------------------------
237 231
238 232 class NotebookApp(BaseIPythonApplication):
239 233
240 234 name = 'ipython-notebook'
241 235 default_config_file_name='ipython_notebook_config.py'
242 236
243 237 description = """
244 238 The IPython HTML Notebook.
245 239
246 240 This launches a Tornado based HTML Notebook Server that serves up an
247 241 HTML5/Javascript Notebook client.
248 242 """
249 243 examples = _examples
250 244
251 245 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
252 246 MappingKernelManager, NotebookManager]
253 247 flags = Dict(flags)
254 248 aliases = Dict(aliases)
255 249
256 250 kernel_argv = List(Unicode)
257 251
258 252 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
259 253 default_value=logging.INFO,
260 254 config=True,
261 255 help="Set the log level by value or name.")
262 256
263 257 # create requested profiles by default, if they don't exist:
264 258 auto_create = Bool(True)
265 259
266 260 # Network related information.
267 261
268 262 ip = Unicode(LOCALHOST, config=True,
269 263 help="The IP address the notebook server will listen on."
270 264 )
271 265
272 266 def _ip_changed(self, name, old, new):
273 267 if new == u'*': self.ip = u''
274 268
275 269 port = Integer(8888, config=True,
276 270 help="The port the notebook server will listen on."
277 271 )
278 272
279 273 certfile = Unicode(u'', config=True,
280 274 help="""The full path to an SSL/TLS certificate file."""
281 275 )
282 276
283 277 keyfile = Unicode(u'', config=True,
284 278 help="""The full path to a private key file for usage with SSL/TLS."""
285 279 )
286 280
287 281 password = Unicode(u'', config=True,
288 282 help="""Hashed password to use for web authentication.
289 283
290 284 To generate, type in a python/IPython shell:
291 285
292 286 from IPython.lib import passwd; passwd()
293 287
294 288 The string should be of the form type:salt:hashed-password.
295 289 """
296 290 )
297 291
298 292 open_browser = Bool(True, config=True,
299 293 help="""Whether to open in a browser after starting.
300 294 The specific browser used is platform dependent and
301 295 determined by the python standard library `webbrowser`
302 296 module, unless it is overridden using the --browser
303 297 (NotebookApp.browser) configuration option.
304 298 """)
305 299
306 300 browser = Unicode(u'', config=True,
307 301 help="""Specify what command to use to invoke a web
308 302 browser when opening the notebook. If not specified, the
309 303 default browser will be determined by the `webbrowser`
310 304 standard library module, which allows setting of the
311 305 BROWSER environment variable to override it.
312 306 """)
313 307
314 308 read_only = Bool(False, config=True,
315 309 help="Whether to prevent editing/execution of notebooks."
316 310 )
317 311
318 312 webapp_settings = Dict(config=True,
319 313 help="Supply overrides for the tornado.web.Application that the "
320 314 "IPython notebook uses.")
321 315
322 316 enable_mathjax = Bool(True, config=True,
323 317 help="""Whether to enable MathJax for typesetting math/TeX
324 318
325 319 MathJax is the javascript library IPython uses to render math/LaTeX. It is
326 320 very large, so you may want to disable it if you have a slow internet
327 321 connection, or for offline use of the notebook.
328 322
329 323 When disabled, equations etc. will appear as their untransformed TeX source.
330 324 """
331 325 )
332 326 def _enable_mathjax_changed(self, name, old, new):
333 327 """set mathjax url to empty if mathjax is disabled"""
334 328 if not new:
335 329 self.mathjax_url = u''
336 330
337 331 base_project_url = Unicode('/', config=True,
338 332 help='''The base URL for the notebook server''')
339 333 base_kernel_url = Unicode('/', config=True,
340 334 help='''The base URL for the kernel server''')
341 335 websocket_host = Unicode("", config=True,
342 336 help="""The hostname for the websocket server."""
343 337 )
344 338
345 339 mathjax_url = Unicode("", config=True,
346 340 help="""The url for MathJax.js."""
347 341 )
348 342 def _mathjax_url_default(self):
349 343 if not self.enable_mathjax:
350 344 return u''
351 345 static_path = self.webapp_settings.get("static_path", os.path.join(os.path.dirname(__file__), "static"))
352 346 static_url_prefix = self.webapp_settings.get("static_url_prefix",
353 347 "/static/")
354 348 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
355 349 self.log.info("Using local MathJax")
356 350 return static_url_prefix+u"mathjax/MathJax.js"
357 351 else:
358 352 self.log.info("Using MathJax from CDN")
359 353 hostname = "cdn.mathjax.org"
360 354 try:
361 355 # resolve mathjax cdn alias to cloudfront, because Amazon's SSL certificate
362 356 # only works on *.cloudfront.net
363 357 true_host, aliases, IPs = socket.gethostbyname_ex(hostname)
364 358 # I've run this on a few machines, and some put the right answer in true_host,
365 359 # while others gave it in the aliases list, so we check both.
366 360 aliases.insert(0, true_host)
367 361 except Exception:
368 362 self.log.warn("Couldn't determine MathJax CDN info")
369 363 else:
370 364 for alias in aliases:
371 365 parts = alias.split('.')
372 366 # want static foo.cloudfront.net, not dynamic foo.lax3.cloudfront.net
373 367 if len(parts) == 3 and alias.endswith(".cloudfront.net"):
374 368 hostname = alias
375 369 break
376 370
377 371 if not hostname.endswith(".cloudfront.net"):
378 372 self.log.error("Couldn't resolve CloudFront host, required for HTTPS MathJax.")
379 373 self.log.error("Loading from https://cdn.mathjax.org will probably fail due to invalid certificate.")
380 374 self.log.error("For unsecured HTTP access to MathJax use config:")
381 375 self.log.error("NotebookApp.mathjax_url='http://cdn.mathjax.org/mathjax/latest/MathJax.js'")
382 376 return u"https://%s/mathjax/latest/MathJax.js" % hostname
383 377
384 378 def _mathjax_url_changed(self, name, old, new):
385 379 if new and not self.enable_mathjax:
386 380 # enable_mathjax=False overrides mathjax_url
387 381 self.mathjax_url = u''
388 382 else:
389 383 self.log.info("Using MathJax: %s", new)
390 384
391 385 def parse_command_line(self, argv=None):
392 386 super(NotebookApp, self).parse_command_line(argv)
393 387 if argv is None:
394 388 argv = sys.argv[1:]
395 389
396 390 # Scrub frontend-specific flags
397 391 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
398 392 # Kernel should inherit default config file from frontend
399 393 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
400 394
401 395 def init_configurables(self):
402 396 # force Session default to be secure
403 397 default_secure(self.config)
404 398 # Create a KernelManager and start a kernel.
405 399 self.kernel_manager = MappingKernelManager(
406 400 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
407 401 connection_dir = self.profile_dir.security_dir,
408 402 )
409 403 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
410 404 self.notebook_manager.list_notebooks()
411 405 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
412 406 self.cluster_manager.update_profiles()
413 407
414 408 def init_logging(self):
415 409 super(NotebookApp, self).init_logging()
416 410 # This prevents double log messages because tornado use a root logger that
417 411 # self.log is a child of. The logging module dipatches log messages to a log
418 412 # and all of its ancenstors until propagate is set to False.
419 413 self.log.propagate = False
420 414
421 415 def init_webapp(self):
422 416 """initialize tornado webapp and httpserver"""
423 417 self.web_app = NotebookWebApplication(
424 418 self, self.kernel_manager, self.notebook_manager,
425 419 self.cluster_manager, self.log,
426 420 self.base_project_url, self.webapp_settings
427 421 )
428 422 if self.certfile:
429 423 ssl_options = dict(certfile=self.certfile)
430 424 if self.keyfile:
431 425 ssl_options['keyfile'] = self.keyfile
432 426 else:
433 427 ssl_options = None
434 428 self.web_app.password = self.password
435 429 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
436 430 if ssl_options is None and not self.ip and not (self.read_only and not self.password):
437 431 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
438 432 'but not using any encryption or authentication. This is highly '
439 433 'insecure and not recommended.')
440 434
441 435 # Try random ports centered around the default.
442 436 from random import randint
443 437 n = 50 # Max number of attempts, keep reasonably large.
444 438 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
445 439 try:
446 440 self.http_server.listen(port, self.ip)
447 441 except socket.error, e:
448 442 if e.errno != errno.EADDRINUSE:
449 443 raise
450 444 self.log.info('The port %i is already in use, trying another random port.' % port)
451 445 else:
452 446 self.port = port
453 447 break
454 448
455 449 def init_signal(self):
456 450 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
457 451 # safely extract zmq version info:
458 452 try:
459 453 zmq_v = zmq.pyzmq_version_info()
460 454 except AttributeError:
461 455 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
462 456 if 'dev' in zmq.__version__:
463 457 zmq_v.append(999)
464 458 zmq_v = tuple(zmq_v)
465 459 if zmq_v >= (2,1,9):
466 460 # This won't work with 2.1.7 and
467 461 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
468 462 # but it will work
469 463 signal.signal(signal.SIGINT, self._handle_sigint)
470 464 signal.signal(signal.SIGTERM, self._signal_stop)
471 465
472 466 def _handle_sigint(self, sig, frame):
473 467 """SIGINT handler spawns confirmation dialog"""
474 468 # register more forceful signal handler for ^C^C case
475 469 signal.signal(signal.SIGINT, self._signal_stop)
476 470 # request confirmation dialog in bg thread, to avoid
477 471 # blocking the App
478 472 thread = threading.Thread(target=self._confirm_exit)
479 473 thread.daemon = True
480 474 thread.start()
481 475
482 476 def _restore_sigint_handler(self):
483 477 """callback for restoring original SIGINT handler"""
484 478 signal.signal(signal.SIGINT, self._handle_sigint)
485 479
486 480 def _confirm_exit(self):
487 481 """confirm shutdown on ^C
488 482
489 483 A second ^C, or answering 'y' within 5s will cause shutdown,
490 484 otherwise original SIGINT handler will be restored.
491 485 """
492 486 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
493 487 time.sleep(0.1)
494 488 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
495 489 sys.stdout.flush()
496 490 r,w,x = select.select([sys.stdin], [], [], 5)
497 491 if r:
498 492 line = sys.stdin.readline()
499 493 if line.lower().startswith('y'):
500 494 self.log.critical("Shutdown confirmed")
501 495 ioloop.IOLoop.instance().stop()
502 496 return
503 497 else:
504 498 print "No answer for 5s:",
505 499 print "resuming operation..."
506 500 # no answer, or answer is no:
507 501 # set it back to original SIGINT handler
508 502 # use IOLoop.add_callback because signal.signal must be called
509 503 # from main thread
510 504 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
511 505
512 506 def _signal_stop(self, sig, frame):
513 507 self.log.critical("received signal %s, stopping", sig)
514 508 ioloop.IOLoop.instance().stop()
515 509
516 510 @catch_config_error
517 511 def initialize(self, argv=None):
518 512 super(NotebookApp, self).initialize(argv)
519 513 self.init_configurables()
520 514 self.init_webapp()
521 515 self.init_signal()
522 516
523 517 def cleanup_kernels(self):
524 518 """shutdown all kernels
525 519
526 520 The kernels will shutdown themselves when this process no longer exists,
527 521 but explicit shutdown allows the KernelManagers to cleanup the connection files.
528 522 """
529 523 self.log.info('Shutting down kernels')
530 524 km = self.kernel_manager
531 525 # copy list, since kill_kernel deletes keys
532 526 for kid in list(km.kernel_ids):
533 527 km.kill_kernel(kid)
534 528
535 529 def start(self):
536 530 ip = self.ip if self.ip else '[all ip addresses on your system]'
537 531 proto = 'https' if self.certfile else 'http'
538 532 info = self.log.info
539 533 info("The IPython Notebook is running at: %s://%s:%i%s" %
540 534 (proto, ip, self.port,self.base_project_url) )
541 535 info("Use Control-C to stop this server and shut down all kernels.")
542 536
543 537 if self.open_browser:
544 538 ip = self.ip or '127.0.0.1'
545 539 if self.browser:
546 540 browser = webbrowser.get(self.browser)
547 541 else:
548 542 browser = webbrowser.get()
549 543 b = lambda : browser.open("%s://%s:%i%s" % (proto, ip, self.port,
550 544 self.base_project_url),
551 545 new=2)
552 546 threading.Thread(target=b).start()
553 547 try:
554 548 ioloop.IOLoop.instance().start()
555 549 except KeyboardInterrupt:
556 550 info("Interrupted...")
557 551 finally:
558 552 self.cleanup_kernels()
559 553
560 554
561 555 #-----------------------------------------------------------------------------
562 556 # Main entry point
563 557 #-----------------------------------------------------------------------------
564 558
565 559 def launch_new_instance():
566 560 app = NotebookApp.instance()
567 561 app.initialize()
568 562 app.start()
569 563
@@ -1,57 +1,68 b''
1 1 #-----------------------------------------------------------------------------
2 2 # Copyright (C) 2010-2011 The IPython Development Team
3 3 #
4 4 # Distributed under the terms of the BSD License. The full license is in
5 5 # the file COPYING.txt, distributed as part of this software.
6 6 #-----------------------------------------------------------------------------
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Verify zmq version dependency >= 2.1.4
10 10 #-----------------------------------------------------------------------------
11 11
12 12 import warnings
13 13 from distutils.version import LooseVersion as V
14 14
15 15
16 16 def patch_pyzmq():
17 17 """backport a few patches from newer pyzmq
18 18
19 19 These can be removed as we bump our minimum pyzmq version
20 20 """
21 21
22 22 import zmq
23
24 # ioloop.install, introduced in pyzmq 2.1.7
25 from zmq.eventloop import ioloop
26
27 def install():
28 import tornado.ioloop
29 tornado.ioloop.IOLoop = ioloop.IOLoop
30
31 if not hasattr(ioloop, 'install'):
32 ioloop.install = install
33
23 34 # fix missing DEALER/ROUTER aliases in pyzmq < 2.1.9
24 35 if not hasattr(zmq, 'DEALER'):
25 36 zmq.DEALER = zmq.XREQ
26 37 if not hasattr(zmq, 'ROUTER'):
27 38 zmq.ROUTER = zmq.XREP
28 39
29 40 # fallback on stdlib json if jsonlib is selected, because jsonlib breaks things.
30 41 # jsonlib support is removed from pyzmq >= 2.2.0
31 42
32 43 from zmq.utils import jsonapi
33 44 if jsonapi.jsonmod.__name__ == 'jsonlib':
34 45 import json
35 46 jsonapi.jsonmod = json
36 47
37 48
38 49 def check_for_zmq(minimum_version, module='IPython.zmq'):
39 50 try:
40 51 import zmq
41 52 except ImportError:
42 53 raise ImportError("%s requires pyzmq >= %s"%(module, minimum_version))
43 54
44 55 pyzmq_version = zmq.__version__
45 56
46 57 if 'dev' not in pyzmq_version and V(pyzmq_version) < V(minimum_version):
47 58 raise ImportError("%s requires pyzmq >= %s, but you have %s"%(
48 59 module, minimum_version, pyzmq_version))
49 60
50 61 if V(zmq.zmq_version()) >= V('4.0.0'):
51 62 warnings.warn("""libzmq 4 detected.
52 63 It is unlikely that IPython's zmq code will work properly.
53 64 Please install libzmq stable, which is 2.1.x or 2.2.x""",
54 65 RuntimeWarning)
55 66
56 67 check_for_zmq('2.1.4')
57 68 patch_pyzmq()
General Comments 0
You need to be logged in to leave comments. Login now