##// END OF EJS Templates
Readd warning at import time.
Matthias Bussonnier -
Show More
@@ -1,659 +1,664 b''
1 1 # coding: utf-8
2 2 """
3 3 Deprecated since IPython 5.0
4 4
5 5 Inputhook management for GUI event loop integration.
6 6 """
7 7
8 8 # Copyright (c) IPython Development Team.
9 9 # Distributed under the terms of the Modified BSD License.
10 10
11 11 try:
12 12 import ctypes
13 13 except ImportError:
14 14 ctypes = None
15 15 except SystemError: # IronPython issue, 2/8/2014
16 16 ctypes = None
17 17 import os
18 18 import platform
19 19 import sys
20 20 from distutils.version import LooseVersion as V
21 21
22 22 from warnings import warn
23 23
24
25 warn("`IPython.lib.inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
26 DeprecationWarning, stacklevel=2)
27
28
24 29 #-----------------------------------------------------------------------------
25 30 # Constants
26 31 #-----------------------------------------------------------------------------
27 32
28 33 # Constants for identifying the GUI toolkits.
29 34 GUI_WX = 'wx'
30 35 GUI_QT = 'qt'
31 36 GUI_QT4 = 'qt4'
32 37 GUI_GTK = 'gtk'
33 38 GUI_TK = 'tk'
34 39 GUI_OSX = 'osx'
35 40 GUI_GLUT = 'glut'
36 41 GUI_PYGLET = 'pyglet'
37 42 GUI_GTK3 = 'gtk3'
38 43 GUI_NONE = 'none' # i.e. disable
39 44
40 45 #-----------------------------------------------------------------------------
41 46 # Utilities
42 47 #-----------------------------------------------------------------------------
43 48
44 49 def _stdin_ready_posix():
45 50 """Return True if there's something to read on stdin (posix version)."""
46 51 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
47 52 return bool(infds)
48 53
49 54 def _stdin_ready_nt():
50 55 """Return True if there's something to read on stdin (nt version)."""
51 56 return msvcrt.kbhit()
52 57
53 58 def _stdin_ready_other():
54 59 """Return True, assuming there's something to read on stdin."""
55 60 return True
56 61
57 62 def _use_appnope():
58 63 """Should we use appnope for dealing with OS X app nap?
59 64
60 65 Checks if we are on OS X 10.9 or greater.
61 66 """
62 67 return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9')
63 68
64 69 def _ignore_CTRL_C_posix():
65 70 """Ignore CTRL+C (SIGINT)."""
66 71 signal.signal(signal.SIGINT, signal.SIG_IGN)
67 72
68 73 def _allow_CTRL_C_posix():
69 74 """Take CTRL+C into account (SIGINT)."""
70 75 signal.signal(signal.SIGINT, signal.default_int_handler)
71 76
72 77 def _ignore_CTRL_C_other():
73 78 """Ignore CTRL+C (not implemented)."""
74 79 pass
75 80
76 81 def _allow_CTRL_C_other():
77 82 """Take CTRL+C into account (not implemented)."""
78 83 pass
79 84
80 85 if os.name == 'posix':
81 86 import select
82 87 import signal
83 88 stdin_ready = _stdin_ready_posix
84 89 ignore_CTRL_C = _ignore_CTRL_C_posix
85 90 allow_CTRL_C = _allow_CTRL_C_posix
86 91 elif os.name == 'nt':
87 92 import msvcrt
88 93 stdin_ready = _stdin_ready_nt
89 94 ignore_CTRL_C = _ignore_CTRL_C_other
90 95 allow_CTRL_C = _allow_CTRL_C_other
91 96 else:
92 97 stdin_ready = _stdin_ready_other
93 98 ignore_CTRL_C = _ignore_CTRL_C_other
94 99 allow_CTRL_C = _allow_CTRL_C_other
95 100
96 101
97 102 #-----------------------------------------------------------------------------
98 103 # Main InputHookManager class
99 104 #-----------------------------------------------------------------------------
100 105
101 106
102 107 class InputHookManager(object):
103 108 """DEPRECATED since IPython 5.0
104 109
105 110 Manage PyOS_InputHook for different GUI toolkits.
106 111
107 112 This class installs various hooks under ``PyOSInputHook`` to handle
108 113 GUI event loop integration.
109 114 """
110 115
111 116 def __init__(self):
112 117 if ctypes is None:
113 118 warn("IPython GUI event loop requires ctypes, %gui will not be available")
114 119 else:
115 120 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
116 121 self.guihooks = {}
117 122 self.aliases = {}
118 123 self.apps = {}
119 124 self._reset()
120 125
121 126 def _reset(self):
122 127 self._callback_pyfunctype = None
123 128 self._callback = None
124 129 self._installed = False
125 130 self._current_gui = None
126 131
127 132 def get_pyos_inputhook(self):
128 133 """DEPRECATED since IPython 5.0
129 134
130 135 Return the current PyOS_InputHook as a ctypes.c_void_p."""
131 136 warn("`get_pyos_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
132 137 DeprecationWarning, stacklevel=2)
133 138 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
134 139
135 140 def get_pyos_inputhook_as_func(self):
136 141 """DEPRECATED since IPython 5.0
137 142
138 143 Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
139 144 warn("`get_pyos_inputhook_as_func` is deprecated since IPython 5.0 and will be removed in future versions.",
140 145 DeprecationWarning, stacklevel=2)
141 146 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
142 147
143 148 def set_inputhook(self, callback):
144 149 """DEPRECATED since IPython 5.0
145 150
146 151 Set PyOS_InputHook to callback and return the previous one."""
147 152 # On platforms with 'readline' support, it's all too likely to
148 153 # have a KeyboardInterrupt signal delivered *even before* an
149 154 # initial ``try:`` clause in the callback can be executed, so
150 155 # we need to disable CTRL+C in this situation.
151 156 ignore_CTRL_C()
152 157 self._callback = callback
153 158 self._callback_pyfunctype = self.PYFUNC(callback)
154 159 pyos_inputhook_ptr = self.get_pyos_inputhook()
155 160 original = self.get_pyos_inputhook_as_func()
156 161 pyos_inputhook_ptr.value = \
157 162 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
158 163 self._installed = True
159 164 return original
160 165
161 166 def clear_inputhook(self, app=None):
162 167 """DEPRECATED since IPython 5.0
163 168
164 169 Set PyOS_InputHook to NULL and return the previous one.
165 170
166 171 Parameters
167 172 ----------
168 173 app : optional, ignored
169 174 This parameter is allowed only so that clear_inputhook() can be
170 175 called with a similar interface as all the ``enable_*`` methods. But
171 176 the actual value of the parameter is ignored. This uniform interface
172 177 makes it easier to have user-level entry points in the main IPython
173 178 app like :meth:`enable_gui`."""
174 179 warn("`clear_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
175 180 DeprecationWarning, stacklevel=2)
176 181 pyos_inputhook_ptr = self.get_pyos_inputhook()
177 182 original = self.get_pyos_inputhook_as_func()
178 183 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
179 184 allow_CTRL_C()
180 185 self._reset()
181 186 return original
182 187
183 188 def clear_app_refs(self, gui=None):
184 189 """DEPRECATED since IPython 5.0
185 190
186 191 Clear IPython's internal reference to an application instance.
187 192
188 193 Whenever we create an app for a user on qt4 or wx, we hold a
189 194 reference to the app. This is needed because in some cases bad things
190 195 can happen if a user doesn't hold a reference themselves. This
191 196 method is provided to clear the references we are holding.
192 197
193 198 Parameters
194 199 ----------
195 200 gui : None or str
196 201 If None, clear all app references. If ('wx', 'qt4') clear
197 202 the app for that toolkit. References are not held for gtk or tk
198 203 as those toolkits don't have the notion of an app.
199 204 """
200 205 warn("`clear_app_refs` is deprecated since IPython 5.0 and will be removed in future versions.",
201 206 DeprecationWarning, stacklevel=2)
202 207 if gui is None:
203 208 self.apps = {}
204 209 elif gui in self.apps:
205 210 del self.apps[gui]
206 211
207 212 def register(self, toolkitname, *aliases):
208 213 """DEPRECATED since IPython 5.0
209 214
210 215 Register a class to provide the event loop for a given GUI.
211 216
212 217 This is intended to be used as a class decorator. It should be passed
213 218 the names with which to register this GUI integration. The classes
214 219 themselves should subclass :class:`InputHookBase`.
215 220
216 221 ::
217 222
218 223 @inputhook_manager.register('qt')
219 224 class QtInputHook(InputHookBase):
220 225 def enable(self, app=None):
221 226 ...
222 227 """
223 228 warn("`register` is deprecated since IPython 5.0 and will be removed in future versions.",
224 229 DeprecationWarning, stacklevel=2)
225 230 def decorator(cls):
226 231 if ctypes is not None:
227 232 inst = cls(self)
228 233 self.guihooks[toolkitname] = inst
229 234 for a in aliases:
230 235 self.aliases[a] = toolkitname
231 236 return cls
232 237 return decorator
233 238
234 239 def current_gui(self):
235 240 """DEPRECATED since IPython 5.0
236 241
237 242 Return a string indicating the currently active GUI or None."""
238 243 warn("`current_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
239 244 DeprecationWarning, stacklevel=2)
240 245 return self._current_gui
241 246
242 247 def enable_gui(self, gui=None, app=None):
243 248 """DEPRECATED since IPython 5.0
244 249
245 250 Switch amongst GUI input hooks by name.
246 251
247 252 This is a higher level method than :meth:`set_inputhook` - it uses the
248 253 GUI name to look up a registered object which enables the input hook
249 254 for that GUI.
250 255
251 256 Parameters
252 257 ----------
253 258 gui : optional, string or None
254 259 If None (or 'none'), clears input hook, otherwise it must be one
255 260 of the recognized GUI names (see ``GUI_*`` constants in module).
256 261
257 262 app : optional, existing application object.
258 263 For toolkits that have the concept of a global app, you can supply an
259 264 existing one. If not given, the toolkit will be probed for one, and if
260 265 none is found, a new one will be created. Note that GTK does not have
261 266 this concept, and passing an app if ``gui=="GTK"`` will raise an error.
262 267
263 268 Returns
264 269 -------
265 270 The output of the underlying gui switch routine, typically the actual
266 271 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
267 272 one.
268 273 """
269 274 warn("`enable_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
270 275 DeprecationWarning, stacklevel=2)
271 276 if gui in (None, GUI_NONE):
272 277 return self.disable_gui()
273 278
274 279 if gui in self.aliases:
275 280 return self.enable_gui(self.aliases[gui], app)
276 281
277 282 try:
278 283 gui_hook = self.guihooks[gui]
279 284 except KeyError:
280 285 e = "Invalid GUI request {!r}, valid ones are: {}"
281 286 raise ValueError(e.format(gui, ', '.join(self.guihooks)))
282 287 self._current_gui = gui
283 288
284 289 app = gui_hook.enable(app)
285 290 if app is not None:
286 291 app._in_event_loop = True
287 292 self.apps[gui] = app
288 293 return app
289 294
290 295 def disable_gui(self):
291 296 """DEPRECATED since IPython 5.0
292 297
293 298 Disable GUI event loop integration.
294 299
295 300 If an application was registered, this sets its ``_in_event_loop``
296 301 attribute to False. It then calls :meth:`clear_inputhook`.
297 302 """
298 303 warn("`disable_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
299 304 DeprecationWarning, stacklevel=2)
300 305 gui = self._current_gui
301 306 if gui in self.apps:
302 307 self.apps[gui]._in_event_loop = False
303 308 return self.clear_inputhook()
304 309
305 310 class InputHookBase(object):
306 311 """DEPRECATED since IPython 5.0
307 312
308 313 Base class for input hooks for specific toolkits.
309 314
310 315 Subclasses should define an :meth:`enable` method with one argument, ``app``,
311 316 which will either be an instance of the toolkit's application class, or None.
312 317 They may also define a :meth:`disable` method with no arguments.
313 318 """
314 319 def __init__(self, manager):
315 320 self.manager = manager
316 321
317 322 def disable(self):
318 323 pass
319 324
320 325 inputhook_manager = InputHookManager()
321 326
322 327 @inputhook_manager.register('osx')
323 328 class NullInputHook(InputHookBase):
324 329 """DEPRECATED since IPython 5.0
325 330
326 331 A null inputhook that doesn't need to do anything"""
327 332 def enable(self, app=None):
328 333 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
329 334 DeprecationWarning, stacklevel=2)
330 335
331 336 @inputhook_manager.register('wx')
332 337 class WxInputHook(InputHookBase):
333 338 def enable(self, app=None):
334 339 """DEPRECATED since IPython 5.0
335 340
336 341 Enable event loop integration with wxPython.
337 342
338 343 Parameters
339 344 ----------
340 345 app : WX Application, optional.
341 346 Running application to use. If not given, we probe WX for an
342 347 existing application object, and create a new one if none is found.
343 348
344 349 Notes
345 350 -----
346 351 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
347 352 the wxPython to integrate with terminal based applications like
348 353 IPython.
349 354
350 355 If ``app`` is not given we probe for an existing one, and return it if
351 356 found. If no existing app is found, we create an :class:`wx.App` as
352 357 follows::
353 358
354 359 import wx
355 360 app = wx.App(redirect=False, clearSigInt=False)
356 361 """
357 362 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
358 363 DeprecationWarning, stacklevel=2)
359 364 import wx
360 365
361 366 wx_version = V(wx.__version__).version
362 367
363 368 if wx_version < [2, 8]:
364 369 raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__)
365 370
366 371 from IPython.lib.inputhookwx import inputhook_wx
367 372 self.manager.set_inputhook(inputhook_wx)
368 373 if _use_appnope():
369 374 from appnope import nope
370 375 nope()
371 376
372 377 import wx
373 378 if app is None:
374 379 app = wx.GetApp()
375 380 if app is None:
376 381 app = wx.App(redirect=False, clearSigInt=False)
377 382
378 383 return app
379 384
380 385 def disable(self):
381 386 """DEPRECATED since IPython 5.0
382 387
383 388 Disable event loop integration with wxPython.
384 389
385 390 This restores appnapp on OS X
386 391 """
387 392 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
388 393 DeprecationWarning, stacklevel=2)
389 394 if _use_appnope():
390 395 from appnope import nap
391 396 nap()
392 397
393 398 @inputhook_manager.register('qt', 'qt4')
394 399 class Qt4InputHook(InputHookBase):
395 400 def enable(self, app=None):
396 401 """DEPRECATED since IPython 5.0
397 402
398 403 Enable event loop integration with PyQt4.
399 404
400 405 Parameters
401 406 ----------
402 407 app : Qt Application, optional.
403 408 Running application to use. If not given, we probe Qt for an
404 409 existing application object, and create a new one if none is found.
405 410
406 411 Notes
407 412 -----
408 413 This methods sets the PyOS_InputHook for PyQt4, which allows
409 414 the PyQt4 to integrate with terminal based applications like
410 415 IPython.
411 416
412 417 If ``app`` is not given we probe for an existing one, and return it if
413 418 found. If no existing app is found, we create an :class:`QApplication`
414 419 as follows::
415 420
416 421 from PyQt4 import QtCore
417 422 app = QtGui.QApplication(sys.argv)
418 423 """
419 424 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
420 425 DeprecationWarning, stacklevel=2)
421 426 from IPython.lib.inputhookqt4 import create_inputhook_qt4
422 427 app, inputhook_qt4 = create_inputhook_qt4(self.manager, app)
423 428 self.manager.set_inputhook(inputhook_qt4)
424 429 if _use_appnope():
425 430 from appnope import nope
426 431 nope()
427 432
428 433 return app
429 434
430 435 def disable_qt4(self):
431 436 """DEPRECATED since IPython 5.0
432 437
433 438 Disable event loop integration with PyQt4.
434 439
435 440 This restores appnapp on OS X
436 441 """
437 442 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
438 443 DeprecationWarning, stacklevel=2)
439 444 if _use_appnope():
440 445 from appnope import nap
441 446 nap()
442 447
443 448
444 449 @inputhook_manager.register('qt5')
445 450 class Qt5InputHook(Qt4InputHook):
446 451 def enable(self, app=None):
447 452 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
448 453 DeprecationWarning, stacklevel=2)
449 454 os.environ['QT_API'] = 'pyqt5'
450 455 return Qt4InputHook.enable(self, app)
451 456
452 457
453 458 @inputhook_manager.register('gtk')
454 459 class GtkInputHook(InputHookBase):
455 460 def enable(self, app=None):
456 461 """DEPRECATED since IPython 5.0
457 462
458 463 Enable event loop integration with PyGTK.
459 464
460 465 Parameters
461 466 ----------
462 467 app : ignored
463 468 Ignored, it's only a placeholder to keep the call signature of all
464 469 gui activation methods consistent, which simplifies the logic of
465 470 supporting magics.
466 471
467 472 Notes
468 473 -----
469 474 This methods sets the PyOS_InputHook for PyGTK, which allows
470 475 the PyGTK to integrate with terminal based applications like
471 476 IPython.
472 477 """
473 478 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
474 479 DeprecationWarning, stacklevel=2)
475 480 import gtk
476 481 try:
477 482 gtk.set_interactive(True)
478 483 except AttributeError:
479 484 # For older versions of gtk, use our own ctypes version
480 485 from IPython.lib.inputhookgtk import inputhook_gtk
481 486 self.manager.set_inputhook(inputhook_gtk)
482 487
483 488
484 489 @inputhook_manager.register('tk')
485 490 class TkInputHook(InputHookBase):
486 491 def enable(self, app=None):
487 492 """DEPRECATED since IPython 5.0
488 493
489 494 Enable event loop integration with Tk.
490 495
491 496 Parameters
492 497 ----------
493 498 app : toplevel :class:`Tkinter.Tk` widget, optional.
494 499 Running toplevel widget to use. If not given, we probe Tk for an
495 500 existing one, and create a new one if none is found.
496 501
497 502 Notes
498 503 -----
499 504 If you have already created a :class:`Tkinter.Tk` object, the only
500 505 thing done by this method is to register with the
501 506 :class:`InputHookManager`, since creating that object automatically
502 507 sets ``PyOS_InputHook``.
503 508 """
504 509 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
505 510 DeprecationWarning, stacklevel=2)
506 511 if app is None:
507 512 try:
508 513 from tkinter import Tk # Py 3
509 514 except ImportError:
510 515 from Tkinter import Tk # Py 2
511 516 app = Tk()
512 517 app.withdraw()
513 518 self.manager.apps[GUI_TK] = app
514 519 return app
515 520
516 521
517 522 @inputhook_manager.register('glut')
518 523 class GlutInputHook(InputHookBase):
519 524 def enable(self, app=None):
520 525 """DEPRECATED since IPython 5.0
521 526
522 527 Enable event loop integration with GLUT.
523 528
524 529 Parameters
525 530 ----------
526 531
527 532 app : ignored
528 533 Ignored, it's only a placeholder to keep the call signature of all
529 534 gui activation methods consistent, which simplifies the logic of
530 535 supporting magics.
531 536
532 537 Notes
533 538 -----
534 539
535 540 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
536 541 integrate with terminal based applications like IPython. Due to GLUT
537 542 limitations, it is currently not possible to start the event loop
538 543 without first creating a window. You should thus not create another
539 544 window but use instead the created one. See 'gui-glut.py' in the
540 545 docs/examples/lib directory.
541 546
542 547 The default screen mode is set to:
543 548 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
544 549 """
545 550 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
546 551 DeprecationWarning, stacklevel=2)
547 552
548 553 import OpenGL.GLUT as glut
549 554 from IPython.lib.inputhookglut import glut_display_mode, \
550 555 glut_close, glut_display, \
551 556 glut_idle, inputhook_glut
552 557
553 558 if GUI_GLUT not in self.manager.apps:
554 559 glut.glutInit( sys.argv )
555 560 glut.glutInitDisplayMode( glut_display_mode )
556 561 # This is specific to freeglut
557 562 if bool(glut.glutSetOption):
558 563 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
559 564 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
560 565 glut.glutCreateWindow( sys.argv[0] )
561 566 glut.glutReshapeWindow( 1, 1 )
562 567 glut.glutHideWindow( )
563 568 glut.glutWMCloseFunc( glut_close )
564 569 glut.glutDisplayFunc( glut_display )
565 570 glut.glutIdleFunc( glut_idle )
566 571 else:
567 572 glut.glutWMCloseFunc( glut_close )
568 573 glut.glutDisplayFunc( glut_display )
569 574 glut.glutIdleFunc( glut_idle)
570 575 self.manager.set_inputhook( inputhook_glut )
571 576
572 577
573 578 def disable(self):
574 579 """DEPRECATED since IPython 5.0
575 580
576 581 Disable event loop integration with glut.
577 582
578 583 This sets PyOS_InputHook to NULL and set the display function to a
579 584 dummy one and set the timer to a dummy timer that will be triggered
580 585 very far in the future.
581 586 """
582 587 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
583 588 DeprecationWarning, stacklevel=2)
584 589 import OpenGL.GLUT as glut
585 590 from glut_support import glutMainLoopEvent
586 591
587 592 glut.glutHideWindow() # This is an event to be processed below
588 593 glutMainLoopEvent()
589 594 super(GlutInputHook, self).disable()
590 595
591 596 @inputhook_manager.register('pyglet')
592 597 class PygletInputHook(InputHookBase):
593 598 def enable(self, app=None):
594 599 """DEPRECATED since IPython 5.0
595 600
596 601 Enable event loop integration with pyglet.
597 602
598 603 Parameters
599 604 ----------
600 605 app : ignored
601 606 Ignored, it's only a placeholder to keep the call signature of all
602 607 gui activation methods consistent, which simplifies the logic of
603 608 supporting magics.
604 609
605 610 Notes
606 611 -----
607 612 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
608 613 pyglet to integrate with terminal based applications like
609 614 IPython.
610 615
611 616 """
612 617 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
613 618 DeprecationWarning, stacklevel=2)
614 619 from IPython.lib.inputhookpyglet import inputhook_pyglet
615 620 self.manager.set_inputhook(inputhook_pyglet)
616 621 return app
617 622
618 623
619 624 @inputhook_manager.register('gtk3')
620 625 class Gtk3InputHook(InputHookBase):
621 626 def enable(self, app=None):
622 627 """DEPRECATED since IPython 5.0
623 628
624 629 Enable event loop integration with Gtk3 (gir bindings).
625 630
626 631 Parameters
627 632 ----------
628 633 app : ignored
629 634 Ignored, it's only a placeholder to keep the call signature of all
630 635 gui activation methods consistent, which simplifies the logic of
631 636 supporting magics.
632 637
633 638 Notes
634 639 -----
635 640 This methods sets the PyOS_InputHook for Gtk3, which allows
636 641 the Gtk3 to integrate with terminal based applications like
637 642 IPython.
638 643 """
639 644 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
640 645 DeprecationWarning, stacklevel=2)
641 646 from IPython.lib.inputhookgtk3 import inputhook_gtk3
642 647 self.manager.set_inputhook(inputhook_gtk3)
643 648
644 649
645 650 clear_inputhook = inputhook_manager.clear_inputhook
646 651 set_inputhook = inputhook_manager.set_inputhook
647 652 current_gui = inputhook_manager.current_gui
648 653 clear_app_refs = inputhook_manager.clear_app_refs
649 654 enable_gui = inputhook_manager.enable_gui
650 655 disable_gui = inputhook_manager.disable_gui
651 656 register = inputhook_manager.register
652 657 guis = inputhook_manager.guihooks
653 658
654 659
655 660 def _deprecated_disable():
656 661 warn("This function is deprecated since IPython 4.0 use disable_gui() instead", DeprecationWarning)
657 662 inputhook_manager.disable_gui()
658 663 disable_wx = disable_qt4 = disable_gtk = disable_gtk3 = disable_glut = \
659 664 disable_pyglet = disable_osx = _deprecated_disable
@@ -1,436 +1,433 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Suite Runner.
3 3
4 4 This module provides a main entry point to a user script to test IPython
5 5 itself from the command line. There are two ways of running this script:
6 6
7 7 1. With the syntax `iptest all`. This runs our entire test suite by
8 8 calling this script (with different arguments) recursively. This
9 9 causes modules and package to be tested in different processes, using nose
10 10 or trial where appropriate.
11 11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 12 the script simply calls nose, but with special command line flags and
13 13 plugins loaded.
14 14
15 15 """
16 16
17 17 # Copyright (c) IPython Development Team.
18 18 # Distributed under the terms of the Modified BSD License.
19 19
20 20 from __future__ import print_function
21 21
22 22 import glob
23 23 from io import BytesIO
24 24 import os
25 25 import os.path as path
26 26 import sys
27 27 from threading import Thread, Lock, Event
28 28 import warnings
29 29
30 30 import nose.plugins.builtin
31 31 from nose.plugins.xunit import Xunit
32 32 from nose import SkipTest
33 33 from nose.core import TestProgram
34 34 from nose.plugins import Plugin
35 35 from nose.util import safe_str
36 36
37 37 from IPython import version_info
38 38 from IPython.utils.py3compat import bytes_to_str
39 39 from IPython.utils.importstring import import_item
40 40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
41 41 from IPython.external.decorators import KnownFailure, knownfailureif
42 42
43 43 pjoin = path.join
44 44
45 45
46 46 # Enable printing all warnings raise by IPython's modules
47 47 warnings.filterwarnings('ignore', message='.*Matplotlib is building the font cache.*', category=UserWarning, module='.*')
48 48 if sys.version_info > (3,0):
49 49 warnings.filterwarnings('error', message='.*', category=ResourceWarning, module='.*')
50 50 warnings.filterwarnings('error', message=".*{'config': True}.*", category=DeprecationWarning, module='IPy.*')
51 51 warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*')
52 52
53 53 if version_info < (6,):
54 54 # nose.tools renames all things from `camelCase` to `snake_case` which raise an
55 55 # warning with the runner they also import from standard import library. (as of Dec 2015)
56 56 # Ignore, let's revisit that in a couple of years for IPython 6.
57 57 warnings.filterwarnings('ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*')
58 58
59 59
60 60 # ------------------------------------------------------------------------------
61 61 # Monkeypatch Xunit to count known failures as skipped.
62 62 # ------------------------------------------------------------------------------
63 63 def monkeypatch_xunit():
64 64 try:
65 65 knownfailureif(True)(lambda: None)()
66 66 except Exception as e:
67 67 KnownFailureTest = type(e)
68 68
69 69 def addError(self, test, err, capt=None):
70 70 if issubclass(err[0], KnownFailureTest):
71 71 err = (SkipTest,) + err[1:]
72 72 return self.orig_addError(test, err, capt)
73 73
74 74 Xunit.orig_addError = Xunit.addError
75 75 Xunit.addError = addError
76 76
77 77 #-----------------------------------------------------------------------------
78 78 # Check which dependencies are installed and greater than minimum version.
79 79 #-----------------------------------------------------------------------------
80 80 def extract_version(mod):
81 81 return mod.__version__
82 82
83 83 def test_for(item, min_version=None, callback=extract_version):
84 84 """Test to see if item is importable, and optionally check against a minimum
85 85 version.
86 86
87 87 If min_version is given, the default behavior is to check against the
88 88 `__version__` attribute of the item, but specifying `callback` allows you to
89 89 extract the value you are interested in. e.g::
90 90
91 91 In [1]: import sys
92 92
93 93 In [2]: from IPython.testing.iptest import test_for
94 94
95 95 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
96 96 Out[3]: True
97 97
98 98 """
99 99 try:
100 100 check = import_item(item)
101 101 except (ImportError, RuntimeError):
102 102 # GTK reports Runtime error if it can't be initialized even if it's
103 103 # importable.
104 104 return False
105 105 else:
106 106 if min_version:
107 107 if callback:
108 108 # extra processing step to get version to compare
109 109 check = callback(check)
110 110
111 111 return check >= min_version
112 112 else:
113 113 return True
114 114
115 115 # Global dict where we can store information on what we have and what we don't
116 116 # have available at test run time
117 117 have = {'matplotlib': test_for('matplotlib'),
118 118 'pygments': test_for('pygments'),
119 119 'sqlite3': test_for('sqlite3')}
120 120
121 121 #-----------------------------------------------------------------------------
122 122 # Test suite definitions
123 123 #-----------------------------------------------------------------------------
124 124
125 125 test_group_names = ['core',
126 126 'extensions', 'lib', 'terminal', 'testing', 'utils',
127 127 ]
128 128
129 129 class TestSection(object):
130 130 def __init__(self, name, includes):
131 131 self.name = name
132 132 self.includes = includes
133 133 self.excludes = []
134 134 self.dependencies = []
135 135 self.enabled = True
136 136
137 137 def exclude(self, module):
138 138 if not module.startswith('IPython'):
139 139 module = self.includes[0] + "." + module
140 140 self.excludes.append(module.replace('.', os.sep))
141 141
142 142 def requires(self, *packages):
143 143 self.dependencies.extend(packages)
144 144
145 145 @property
146 146 def will_run(self):
147 147 return self.enabled and all(have[p] for p in self.dependencies)
148 148
149 149 # Name -> (include, exclude, dependencies_met)
150 150 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
151 151
152 152
153 153 # Exclusions and dependencies
154 154 # ---------------------------
155 155
156 156 # core:
157 157 sec = test_sections['core']
158 158 if not have['sqlite3']:
159 159 sec.exclude('tests.test_history')
160 160 sec.exclude('history')
161 161 if not have['matplotlib']:
162 162 sec.exclude('pylabtools'),
163 163 sec.exclude('tests.test_pylabtools')
164 164
165 165 # lib:
166 166 sec = test_sections['lib']
167 167 sec.exclude('kernel')
168 168 if not have['pygments']:
169 169 sec.exclude('tests.test_lexers')
170 170 # We do this unconditionally, so that the test suite doesn't import
171 171 # gtk, changing the default encoding and masking some unicode bugs.
172 172 sec.exclude('inputhookgtk')
173 173 # We also do this unconditionally, because wx can interfere with Unix signals.
174 174 # There are currently no tests for it anyway.
175 175 sec.exclude('inputhookwx')
176 176 # Testing inputhook will need a lot of thought, to figure out
177 177 # how to have tests that don't lock up with the gui event
178 178 # loops in the picture
179 179 sec.exclude('inputhook')
180 180
181 181 # testing:
182 182 sec = test_sections['testing']
183 183 # These have to be skipped on win32 because they use echo, rm, cd, etc.
184 184 # See ticket https://github.com/ipython/ipython/issues/87
185 185 if sys.platform == 'win32':
186 186 sec.exclude('plugin.test_exampleip')
187 187 sec.exclude('plugin.dtexample')
188 188
189 189 # don't run jupyter_console tests found via shim
190 190 test_sections['terminal'].exclude('console')
191 191
192 192 # extensions:
193 193 sec = test_sections['extensions']
194 194 # This is deprecated in favour of rpy2
195 195 sec.exclude('rmagic')
196 196 # autoreload does some strange stuff, so move it to its own test section
197 197 sec.exclude('autoreload')
198 198 sec.exclude('tests.test_autoreload')
199 199 test_sections['autoreload'] = TestSection('autoreload',
200 200 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
201 201 test_group_names.append('autoreload')
202 202
203 203
204 204 #-----------------------------------------------------------------------------
205 205 # Functions and classes
206 206 #-----------------------------------------------------------------------------
207 207
208 208 def check_exclusions_exist():
209 209 from IPython.paths import get_ipython_package_dir
210 210 from warnings import warn
211 211 parent = os.path.dirname(get_ipython_package_dir())
212 212 for sec in test_sections:
213 213 for pattern in sec.exclusions:
214 214 fullpath = pjoin(parent, pattern)
215 215 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
216 216 warn("Excluding nonexistent file: %r" % pattern)
217 217
218 218
219 219 class ExclusionPlugin(Plugin):
220 220 """A nose plugin to effect our exclusions of files and directories.
221 221 """
222 222 name = 'exclusions'
223 223 score = 3000 # Should come before any other plugins
224 224
225 225 def __init__(self, exclude_patterns=None):
226 226 """
227 227 Parameters
228 228 ----------
229 229
230 230 exclude_patterns : sequence of strings, optional
231 231 Filenames containing these patterns (as raw strings, not as regular
232 232 expressions) are excluded from the tests.
233 233 """
234 234 self.exclude_patterns = exclude_patterns or []
235 235 super(ExclusionPlugin, self).__init__()
236 236
237 237 def options(self, parser, env=os.environ):
238 238 Plugin.options(self, parser, env)
239 239
240 240 def configure(self, options, config):
241 241 Plugin.configure(self, options, config)
242 242 # Override nose trying to disable plugin.
243 243 self.enabled = True
244 244
245 245 def wantFile(self, filename):
246 246 """Return whether the given filename should be scanned for tests.
247 247 """
248 248 if any(pat in filename for pat in self.exclude_patterns):
249 249 return False
250 250 return None
251 251
252 252 def wantDirectory(self, directory):
253 253 """Return whether the given directory should be scanned for tests.
254 254 """
255 255 if any(pat in directory for pat in self.exclude_patterns):
256 256 return False
257 257 return None
258 258
259 259
260 260 class StreamCapturer(Thread):
261 261 daemon = True # Don't hang if main thread crashes
262 262 started = False
263 263 def __init__(self, echo=False):
264 264 super(StreamCapturer, self).__init__()
265 265 self.echo = echo
266 266 self.streams = []
267 267 self.buffer = BytesIO()
268 268 self.readfd, self.writefd = os.pipe()
269 269 self.buffer_lock = Lock()
270 270 self.stop = Event()
271 271
272 272 def run(self):
273 273 self.started = True
274 274
275 275 while not self.stop.is_set():
276 276 chunk = os.read(self.readfd, 1024)
277 277
278 278 with self.buffer_lock:
279 279 self.buffer.write(chunk)
280 280 if self.echo:
281 281 sys.stdout.write(bytes_to_str(chunk))
282 282
283 283 os.close(self.readfd)
284 284 os.close(self.writefd)
285 285
286 286 def reset_buffer(self):
287 287 with self.buffer_lock:
288 288 self.buffer.truncate(0)
289 289 self.buffer.seek(0)
290 290
291 291 def get_buffer(self):
292 292 with self.buffer_lock:
293 293 return self.buffer.getvalue()
294 294
295 295 def ensure_started(self):
296 296 if not self.started:
297 297 self.start()
298 298
299 299 def halt(self):
300 300 """Safely stop the thread."""
301 301 if not self.started:
302 302 return
303 303
304 304 self.stop.set()
305 305 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
306 306 self.join()
307 307
308 308 class SubprocessStreamCapturePlugin(Plugin):
309 309 name='subprocstreams'
310 310 def __init__(self):
311 311 Plugin.__init__(self)
312 312 self.stream_capturer = StreamCapturer()
313 313 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
314 314 # This is ugly, but distant parts of the test machinery need to be able
315 315 # to redirect streams, so we make the object globally accessible.
316 316 nose.iptest_stdstreams_fileno = self.get_write_fileno
317 317
318 318 def get_write_fileno(self):
319 319 if self.destination == 'capture':
320 320 self.stream_capturer.ensure_started()
321 321 return self.stream_capturer.writefd
322 322 elif self.destination == 'discard':
323 323 return os.open(os.devnull, os.O_WRONLY)
324 324 else:
325 325 return sys.__stdout__.fileno()
326 326
327 327 def configure(self, options, config):
328 328 Plugin.configure(self, options, config)
329 329 # Override nose trying to disable plugin.
330 330 if self.destination == 'capture':
331 331 self.enabled = True
332 332
333 333 def startTest(self, test):
334 334 # Reset log capture
335 335 self.stream_capturer.reset_buffer()
336 336
337 337 def formatFailure(self, test, err):
338 338 # Show output
339 339 ec, ev, tb = err
340 340 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
341 341 if captured.strip():
342 342 ev = safe_str(ev)
343 343 out = [ev, '>> begin captured subprocess output <<',
344 344 captured,
345 345 '>> end captured subprocess output <<']
346 346 return ec, '\n'.join(out), tb
347 347
348 348 return err
349 349
350 350 formatError = formatFailure
351 351
352 352 def finalize(self, result):
353 353 self.stream_capturer.halt()
354 354
355 355
356 356 def run_iptest():
357 357 """Run the IPython test suite using nose.
358 358
359 359 This function is called when this script is **not** called with the form
360 360 `iptest all`. It simply calls nose with appropriate command line flags
361 361 and accepts all of the standard nose arguments.
362 362 """
363 363 # Apply our monkeypatch to Xunit
364 364 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
365 365 monkeypatch_xunit()
366 366
367 warnings.filterwarnings('ignore',
368 'This will be removed soon. Use IPython.testing.util instead')
369
370 367 arg1 = sys.argv[1]
371 368 if arg1 in test_sections:
372 369 section = test_sections[arg1]
373 370 sys.argv[1:2] = section.includes
374 371 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
375 372 section = test_sections[arg1[8:]]
376 373 sys.argv[1:2] = section.includes
377 374 else:
378 375 section = TestSection(arg1, includes=[arg1])
379 376
380 377
381 378 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
382 379 # We add --exe because of setuptools' imbecility (it
383 380 # blindly does chmod +x on ALL files). Nose does the
384 381 # right thing and it tries to avoid executables,
385 382 # setuptools unfortunately forces our hand here. This
386 383 # has been discussed on the distutils list and the
387 384 # setuptools devs refuse to fix this problem!
388 385 '--exe',
389 386 ]
390 387 if '-a' not in argv and '-A' not in argv:
391 388 argv = argv + ['-a', '!crash']
392 389
393 390 if nose.__version__ >= '0.11':
394 391 # I don't fully understand why we need this one, but depending on what
395 392 # directory the test suite is run from, if we don't give it, 0 tests
396 393 # get run. Specifically, if the test suite is run from the source dir
397 394 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
398 395 # even if the same call done in this directory works fine). It appears
399 396 # that if the requested package is in the current dir, nose bails early
400 397 # by default. Since it's otherwise harmless, leave it in by default
401 398 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
402 399 argv.append('--traverse-namespace')
403 400
404 401 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
405 402 SubprocessStreamCapturePlugin() ]
406 403
407 404 # we still have some vestigial doctests in core
408 405 if (section.name.startswith(('core', 'IPython.core'))):
409 406 plugins.append(IPythonDoctest())
410 407 argv.extend([
411 408 '--with-ipdoctest',
412 409 '--ipdoctest-tests',
413 410 '--ipdoctest-extension=txt',
414 411 ])
415 412
416 413
417 414 # Use working directory set by parent process (see iptestcontroller)
418 415 if 'IPTEST_WORKING_DIR' in os.environ:
419 416 os.chdir(os.environ['IPTEST_WORKING_DIR'])
420 417
421 418 # We need a global ipython running in this process, but the special
422 419 # in-process group spawns its own IPython kernels, so for *that* group we
423 420 # must avoid also opening the global one (otherwise there's a conflict of
424 421 # singletons). Ultimately the solution to this problem is to refactor our
425 422 # assumptions about what needs to be a singleton and what doesn't (app
426 423 # objects should, individual shells shouldn't). But for now, this
427 424 # workaround allows the test suite for the inprocess module to complete.
428 425 if 'kernel.inprocess' not in section.name:
429 426 from IPython.testing import globalipapp
430 427 globalipapp.start_ipython()
431 428
432 429 # Now nose can run
433 430 TestProgram(argv=argv, addplugins=plugins)
434 431
435 432 if __name__ == '__main__':
436 433 run_iptest()
General Comments 0
You need to be logged in to leave comments. Login now