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