##// END OF EJS Templates
Merge branch 'main' into enhancement/cover-cases-where-frame-is-built-object
HoonCheol Shin -
r28169:49925522 merge
parent child Browse files
Show More
@@ -62,7 +62,7 b' jobs:'
62 run: |
62 run: |
63 python -m pip install --upgrade pip setuptools wheel build
63 python -m pip install --upgrade pip setuptools wheel build
64 python -m pip install --upgrade -e .[${{ matrix.deps }}]
64 python -m pip install --upgrade -e .[${{ matrix.deps }}]
65 python -m pip install --upgrade check-manifest pytest-cov
65 python -m pip install --upgrade check-manifest pytest-cov pytest-json-report
66 - name: Try building with Python build
66 - name: Try building with Python build
67 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
67 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
68 run: |
68 run: |
@@ -75,7 +75,13 b' jobs:'
75 env:
75 env:
76 COLUMNS: 120
76 COLUMNS: 120
77 run: |
77 run: |
78 pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }}
78 pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }} --json-report --json-report-file=./report-${{ matrix.python-version }}-${{runner.os}}.json
79 - uses: actions/upload-artifact@v3
80 with:
81 name: upload pytest timing reports as json
82 path: |
83 ./report-*.json
84
79 - name: Upload coverage to Codecov
85 - name: Upload coverage to Codecov
80 uses: codecov/codecov-action@v3
86 uses: codecov/codecov-action@v3
81 with:
87 with:
@@ -567,8 +567,11 b' class HistoryManager(HistoryAccessor):'
567 conn = self.db
567 conn = self.db
568
568
569 with conn:
569 with conn:
570 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
570 cur = conn.execute(
571 NULL, "") """, (datetime.datetime.now(),))
571 """INSERT INTO sessions VALUES (NULL, ?, NULL,
572 NULL, '') """,
573 (datetime.datetime.now(),),
574 )
572 self.session_number = cur.lastrowid
575 self.session_number = cur.lastrowid
573
576
574 def end_session(self):
577 def end_session(self):
@@ -32,7 +32,7 b' from io import open as io_open'
32 from logging import error
32 from logging import error
33 from pathlib import Path
33 from pathlib import Path
34 from typing import Callable
34 from typing import Callable
35 from typing import List as ListType
35 from typing import List as ListType, Dict as DictType, Any as AnyType
36 from typing import Optional, Tuple
36 from typing import Optional, Tuple
37 from warnings import warn
37 from warnings import warn
38
38
@@ -90,6 +90,8 b' from IPython.utils.process import getoutput, system'
90 from IPython.utils.strdispatch import StrDispatch
90 from IPython.utils.strdispatch import StrDispatch
91 from IPython.utils.syspathcontext import prepended_to_syspath
91 from IPython.utils.syspathcontext import prepended_to_syspath
92 from IPython.utils.text import DollarFormatter, LSString, SList, format_screen
92 from IPython.utils.text import DollarFormatter, LSString, SList, format_screen
93 from IPython.core.oinspect import OInfo
94
93
95
94 sphinxify: Optional[Callable]
96 sphinxify: Optional[Callable]
95
97
@@ -1560,15 +1562,28 b' class InteractiveShell(SingletonConfigurable):'
1560 #-------------------------------------------------------------------------
1562 #-------------------------------------------------------------------------
1561 # Things related to object introspection
1563 # Things related to object introspection
1562 #-------------------------------------------------------------------------
1564 #-------------------------------------------------------------------------
1565 @staticmethod
1566 def _find_parts(oname: str) -> ListType[str]:
1567 """
1568 Given an object name, return a list of parts of this object name.
1569
1570 Basically split on docs when using attribute access,
1571 and extract the value when using square bracket.
1572
1573
1574 For example foo.bar[3].baz[x] -> foo, bar, 3, baz, x
1575
1576
1577 Returns
1578 -------
1579 parts_ok: bool
1580 wether we were properly able to parse parts.
1581 parts: list of str
1582 extracted parts
1563
1583
1564 def _ofind(self, oname, namespaces=None):
1565 """Find an object in the available namespaces.
1566
1584
1567 self._ofind(oname) -> dict with keys: found,obj,ospace,ismagic
1568
1585
1569 Has special code to detect magic functions.
1570 """
1586 """
1571 oname = oname.strip()
1572 raw_parts = oname.split(".")
1587 raw_parts = oname.split(".")
1573 parts = []
1588 parts = []
1574 parts_ok = True
1589 parts_ok = True
@@ -1590,12 +1605,31 b' class InteractiveShell(SingletonConfigurable):'
1590 parts_ok = False
1605 parts_ok = False
1591 parts.append(p)
1606 parts.append(p)
1592
1607
1608 return parts_ok, parts
1609
1610 def _ofind(self, oname: str, namespaces: DictType[str, AnyType] = None):
1611 """Find an object in the available namespaces.
1612
1613 self._ofind(oname) -> dict with keys: found,obj,ospace,ismagic
1614
1615 Has special code to detect magic functions.
1616 """
1617 oname = oname.strip()
1618 parts_ok, parts = self._find_parts(oname)
1619
1593 if (
1620 if (
1594 not oname.startswith(ESC_MAGIC)
1621 not oname.startswith(ESC_MAGIC)
1595 and not oname.startswith(ESC_MAGIC2)
1622 and not oname.startswith(ESC_MAGIC2)
1596 and not parts_ok
1623 and not parts_ok
1597 ):
1624 ):
1598 return {"found": False}
1625 return OInfo(
1626 ismagic=False,
1627 isalias=False,
1628 found=False,
1629 obj=None,
1630 namespace="",
1631 parent=None,
1632 )
1599
1633
1600 if namespaces is None:
1634 if namespaces is None:
1601 # Namespaces to search in:
1635 # Namespaces to search in:
@@ -1675,14 +1709,16 b' class InteractiveShell(SingletonConfigurable):'
1675 found = True
1709 found = True
1676 ospace = 'Interactive'
1710 ospace = 'Interactive'
1677
1711
1678 return {
1712 return OInfo(
1679 'obj':obj,
1713 **{
1680 'found':found,
1714 "obj": obj,
1681 'parent':parent,
1715 "found": found,
1682 'ismagic':ismagic,
1716 "parent": parent,
1683 'isalias':isalias,
1717 "ismagic": ismagic,
1684 'namespace':ospace
1718 "isalias": isalias,
1719 "namespace": ospace,
1685 }
1720 }
1721 )
1686
1722
1687 @staticmethod
1723 @staticmethod
1688 def _getattr_property(obj, attrname):
1724 def _getattr_property(obj, attrname):
@@ -1726,9 +1762,9 b' class InteractiveShell(SingletonConfigurable):'
1726 # Nothing helped, fall back.
1762 # Nothing helped, fall back.
1727 return getattr(obj, attrname)
1763 return getattr(obj, attrname)
1728
1764
1729 def _object_find(self, oname, namespaces=None):
1765 def _object_find(self, oname, namespaces=None) -> OInfo:
1730 """Find an object and return a struct with info about it."""
1766 """Find an object and return a struct with info about it."""
1731 return Struct(self._ofind(oname, namespaces))
1767 return self._ofind(oname, namespaces)
1732
1768
1733 def _inspect(self, meth, oname, namespaces=None, **kw):
1769 def _inspect(self, meth, oname, namespaces=None, **kw):
1734 """Generic interface to the inspector system.
1770 """Generic interface to the inspector system.
@@ -422,7 +422,8 b' class ExecutionMagics(Magics):'
422 )
422 )
423 @no_var_expand
423 @no_var_expand
424 @line_cell_magic
424 @line_cell_magic
425 def debug(self, line='', cell=None):
425 @needs_local_scope
426 def debug(self, line="", cell=None, local_ns=None):
426 """Activate the interactive debugger.
427 """Activate the interactive debugger.
427
428
428 This magic command support two ways of activating debugger.
429 This magic command support two ways of activating debugger.
@@ -453,7 +454,7 b' class ExecutionMagics(Magics):'
453 self._debug_post_mortem()
454 self._debug_post_mortem()
454 elif not (args.breakpoint or cell):
455 elif not (args.breakpoint or cell):
455 # If there is no breakpoints, the line is just code to execute
456 # If there is no breakpoints, the line is just code to execute
456 self._debug_exec(line, None)
457 self._debug_exec(line, None, local_ns)
457 else:
458 else:
458 # Here we try to reconstruct the code from the output of
459 # Here we try to reconstruct the code from the output of
459 # parse_argstring. This might not work if the code has spaces
460 # parse_argstring. This might not work if the code has spaces
@@ -461,18 +462,20 b' class ExecutionMagics(Magics):'
461 code = "\n".join(args.statement)
462 code = "\n".join(args.statement)
462 if cell:
463 if cell:
463 code += "\n" + cell
464 code += "\n" + cell
464 self._debug_exec(code, args.breakpoint)
465 self._debug_exec(code, args.breakpoint, local_ns)
465
466
466 def _debug_post_mortem(self):
467 def _debug_post_mortem(self):
467 self.shell.debugger(force=True)
468 self.shell.debugger(force=True)
468
469
469 def _debug_exec(self, code, breakpoint):
470 def _debug_exec(self, code, breakpoint, local_ns=None):
470 if breakpoint:
471 if breakpoint:
471 (filename, bp_line) = breakpoint.rsplit(':', 1)
472 (filename, bp_line) = breakpoint.rsplit(':', 1)
472 bp_line = int(bp_line)
473 bp_line = int(bp_line)
473 else:
474 else:
474 (filename, bp_line) = (None, None)
475 (filename, bp_line) = (None, None)
475 self._run_with_debugger(code, self.shell.user_ns, filename, bp_line)
476 self._run_with_debugger(
477 code, self.shell.user_ns, filename, bp_line, local_ns=local_ns
478 )
476
479
477 @line_magic
480 @line_magic
478 def tb(self, s):
481 def tb(self, s):
@@ -867,8 +870,9 b' class ExecutionMagics(Magics):'
867
870
868 return stats
871 return stats
869
872
870 def _run_with_debugger(self, code, code_ns, filename=None,
873 def _run_with_debugger(
871 bp_line=None, bp_file=None):
874 self, code, code_ns, filename=None, bp_line=None, bp_file=None, local_ns=None
875 ):
872 """
876 """
873 Run `code` in debugger with a break point.
877 Run `code` in debugger with a break point.
874
878
@@ -885,6 +889,8 b' class ExecutionMagics(Magics):'
885 bp_file : str, optional
889 bp_file : str, optional
886 Path to the file in which break point is specified.
890 Path to the file in which break point is specified.
887 `filename` is used if not given.
891 `filename` is used if not given.
892 local_ns : dict, optional
893 A local namespace in which `code` is executed.
888
894
889 Raises
895 Raises
890 ------
896 ------
@@ -941,7 +947,7 b' class ExecutionMagics(Magics):'
941 while True:
947 while True:
942 try:
948 try:
943 trace = sys.gettrace()
949 trace = sys.gettrace()
944 deb.run(code, code_ns)
950 deb.run(code, code_ns, local_ns)
945 except Restart:
951 except Restart:
946 print("Restarting")
952 print("Restarting")
947 if filename:
953 if filename:
@@ -46,6 +46,19 b' from pygments import highlight'
46 from pygments.lexers import PythonLexer
46 from pygments.lexers import PythonLexer
47 from pygments.formatters import HtmlFormatter
47 from pygments.formatters import HtmlFormatter
48
48
49 from typing import Any
50 from dataclasses import dataclass
51
52
53 @dataclass
54 class OInfo:
55 ismagic: bool
56 isalias: bool
57 found: bool
58 namespace: str
59 parent: Any
60 obj: Any
61
49 def pylight(code):
62 def pylight(code):
50 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
63 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
51
64
@@ -499,7 +499,7 b' class AutocallChecker(PrefilterChecker):'
499 return None
499 return None
500
500
501 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
501 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
502 if not oinfo['found']:
502 if not oinfo.found:
503 return None
503 return None
504
504
505 ignored_funs = ['b', 'f', 'r', 'u', 'br', 'rb', 'fr', 'rf']
505 ignored_funs = ['b', 'f', 'r', 'u', 'br', 'rb', 'fr', 'rf']
@@ -508,10 +508,12 b' class AutocallChecker(PrefilterChecker):'
508 if ifun.lower() in ignored_funs and (line.startswith(ifun + "'") or line.startswith(ifun + '"')):
508 if ifun.lower() in ignored_funs and (line.startswith(ifun + "'") or line.startswith(ifun + '"')):
509 return None
509 return None
510
510
511 if callable(oinfo['obj']) \
511 if (
512 and (not self.exclude_regexp.match(line_info.the_rest)) \
512 callable(oinfo.obj)
513 and self.function_name_regexp.match(line_info.ifun):
513 and (not self.exclude_regexp.match(line_info.the_rest))
514 return self.prefilter_manager.get_handler_by_name('auto')
514 and self.function_name_regexp.match(line_info.ifun)
515 ):
516 return self.prefilter_manager.get_handler_by_name("auto")
515 else:
517 else:
516 return None
518 return None
517
519
@@ -601,7 +603,7 b' class AutoHandler(PrefilterHandler):'
601 the_rest = line_info.the_rest
603 the_rest = line_info.the_rest
602 esc = line_info.esc
604 esc = line_info.esc
603 continue_prompt = line_info.continue_prompt
605 continue_prompt = line_info.continue_prompt
604 obj = line_info.ofind(self.shell)['obj']
606 obj = line_info.ofind(self.shell).obj
605
607
606 # This should only be active for single-line input!
608 # This should only be active for single-line input!
607 if continue_prompt:
609 if continue_prompt:
@@ -25,6 +25,7 b' import sys'
25
25
26 from IPython.utils import py3compat
26 from IPython.utils import py3compat
27 from IPython.utils.encoding import get_stream_enc
27 from IPython.utils.encoding import get_stream_enc
28 from IPython.core.oinspect import OInfo
28
29
29 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
30 # Main function
31 # Main function
@@ -118,7 +119,7 b' class LineInfo(object):'
118 else:
119 else:
119 self.pre_whitespace = self.pre
120 self.pre_whitespace = self.pre
120
121
121 def ofind(self, ip):
122 def ofind(self, ip) -> OInfo:
122 """Do a full, attribute-walking lookup of the ifun in the various
123 """Do a full, attribute-walking lookup of the ifun in the various
123 namespaces for the given IPython InteractiveShell instance.
124 namespaces for the given IPython InteractiveShell instance.
124
125
@@ -177,7 +177,7 b' def test_module_without_init():'
177 try:
177 try:
178 os.makedirs(os.path.join(tmpdir, fake_module_name))
178 os.makedirs(os.path.join(tmpdir, fake_module_name))
179 s = try_import(mod=fake_module_name)
179 s = try_import(mod=fake_module_name)
180 assert s == []
180 assert s == [], f"for module {fake_module_name}"
181 finally:
181 finally:
182 sys.path.remove(tmpdir)
182 sys.path.remove(tmpdir)
183
183
@@ -25,6 +25,7 b' from os.path import join'
25 from IPython.core.error import InputRejected
25 from IPython.core.error import InputRejected
26 from IPython.core.inputtransformer import InputTransformer
26 from IPython.core.inputtransformer import InputTransformer
27 from IPython.core import interactiveshell
27 from IPython.core import interactiveshell
28 from IPython.core.oinspect import OInfo
28 from IPython.testing.decorators import (
29 from IPython.testing.decorators import (
29 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 )
31 )
@@ -360,7 +361,7 b' class InteractiveShellTestCase(unittest.TestCase):'
360
361
361 # Get info on line magic
362 # Get info on line magic
362 lfind = ip._ofind("lmagic")
363 lfind = ip._ofind("lmagic")
363 info = dict(
364 info = OInfo(
364 found=True,
365 found=True,
365 isalias=False,
366 isalias=False,
366 ismagic=True,
367 ismagic=True,
@@ -379,7 +380,7 b' class InteractiveShellTestCase(unittest.TestCase):'
379
380
380 # Get info on cell magic
381 # Get info on cell magic
381 find = ip._ofind("cmagic")
382 find = ip._ofind("cmagic")
382 info = dict(
383 info = OInfo(
383 found=True,
384 found=True,
384 isalias=False,
385 isalias=False,
385 ismagic=True,
386 ismagic=True,
@@ -397,9 +398,15 b' class InteractiveShellTestCase(unittest.TestCase):'
397
398
398 a = A()
399 a = A()
399
400
400 found = ip._ofind('a.foo', [('locals', locals())])
401 found = ip._ofind("a.foo", [("locals", locals())])
401 info = dict(found=True, isalias=False, ismagic=False,
402 info = OInfo(
402 namespace='locals', obj=A.foo, parent=a)
403 found=True,
404 isalias=False,
405 ismagic=False,
406 namespace="locals",
407 obj=A.foo,
408 parent=a,
409 )
403 self.assertEqual(found, info)
410 self.assertEqual(found, info)
404
411
405 def test_ofind_multiple_attribute_lookups(self):
412 def test_ofind_multiple_attribute_lookups(self):
@@ -412,9 +419,15 b' class InteractiveShellTestCase(unittest.TestCase):'
412 a.a = A()
419 a.a = A()
413 a.a.a = A()
420 a.a.a = A()
414
421
415 found = ip._ofind('a.a.a.foo', [('locals', locals())])
422 found = ip._ofind("a.a.a.foo", [("locals", locals())])
416 info = dict(found=True, isalias=False, ismagic=False,
423 info = OInfo(
417 namespace='locals', obj=A.foo, parent=a.a.a)
424 found=True,
425 isalias=False,
426 ismagic=False,
427 namespace="locals",
428 obj=A.foo,
429 parent=a.a.a,
430 )
418 self.assertEqual(found, info)
431 self.assertEqual(found, info)
419
432
420 def test_ofind_slotted_attributes(self):
433 def test_ofind_slotted_attributes(self):
@@ -424,14 +437,26 b' class InteractiveShellTestCase(unittest.TestCase):'
424 self.foo = 'bar'
437 self.foo = 'bar'
425
438
426 a = A()
439 a = A()
427 found = ip._ofind('a.foo', [('locals', locals())])
440 found = ip._ofind("a.foo", [("locals", locals())])
428 info = dict(found=True, isalias=False, ismagic=False,
441 info = OInfo(
429 namespace='locals', obj=a.foo, parent=a)
442 found=True,
443 isalias=False,
444 ismagic=False,
445 namespace="locals",
446 obj=a.foo,
447 parent=a,
448 )
430 self.assertEqual(found, info)
449 self.assertEqual(found, info)
431
450
432 found = ip._ofind('a.bar', [('locals', locals())])
451 found = ip._ofind("a.bar", [("locals", locals())])
433 info = dict(found=False, isalias=False, ismagic=False,
452 info = OInfo(
434 namespace=None, obj=None, parent=a)
453 found=False,
454 isalias=False,
455 ismagic=False,
456 namespace=None,
457 obj=None,
458 parent=a,
459 )
435 self.assertEqual(found, info)
460 self.assertEqual(found, info)
436
461
437 def test_ofind_prefers_property_to_instance_level_attribute(self):
462 def test_ofind_prefers_property_to_instance_level_attribute(self):
@@ -443,7 +468,7 b' class InteractiveShellTestCase(unittest.TestCase):'
443 a.__dict__["foo"] = "baz"
468 a.__dict__["foo"] = "baz"
444 self.assertEqual(a.foo, "bar")
469 self.assertEqual(a.foo, "bar")
445 found = ip._ofind("a.foo", [("locals", locals())])
470 found = ip._ofind("a.foo", [("locals", locals())])
446 self.assertIs(found["obj"], A.foo)
471 self.assertIs(found.obj, A.foo)
447
472
448 def test_custom_syntaxerror_exception(self):
473 def test_custom_syntaxerror_exception(self):
449 called = []
474 called = []
@@ -715,6 +715,7 b' def doctest_precision():'
715 Out[5]: '3.141593e+00'
715 Out[5]: '3.141593e+00'
716 """
716 """
717
717
718
718 def test_debug_magic():
719 def test_debug_magic():
719 """Test debugging a small code with %debug
720 """Test debugging a small code with %debug
720
721
@@ -727,6 +728,22 b' def test_debug_magic():'
727 In [2]:
728 In [2]:
728 """
729 """
729
730
731
732 def test_debug_magic_locals():
733 """Test debugging a small code with %debug with locals
734
735 In [1]: with PdbTestInput(['c']):
736 ...: def fun():
737 ...: res = 1
738 ...: %debug print(res)
739 ...: fun()
740 ...:
741 ...
742 ipdb> c
743 1
744 In [2]:
745 """
746
730 def test_psearch():
747 def test_psearch():
731 with tt.AssertPrints("dict.fromkeys"):
748 with tt.AssertPrints("dict.fromkeys"):
732 _ip.run_cell("dict.fr*?")
749 _ip.run_cell("dict.fr*?")
@@ -994,7 +994,7 b' class VerboseTB(TBTools):'
994 pygments_formatter=formatter,
994 pygments_formatter=formatter,
995 )
995 )
996
996
997 # let's estimate the amount of code we eill have to parse/highlight.
997 # Let's estimate the amount of code we will have to parse/highlight.
998 cf = etb
998 cf = etb
999 max_len = 0
999 max_len = 0
1000 tbs = []
1000 tbs = []
@@ -10,6 +10,7 b' be accessed directly from the outside'
10 """
10 """
11 import importlib.abc
11 import importlib.abc
12 import sys
12 import sys
13 import os
13 import types
14 import types
14 from functools import partial, lru_cache
15 from functools import partial, lru_cache
15 import operator
16 import operator
@@ -368,6 +369,10 b' def load_qt(api_options):'
368 commit_api(api)
369 commit_api(api)
369 return result
370 return result
370 else:
371 else:
372 # Clear the environment variable since it doesn't work.
373 if "QT_API" in os.environ:
374 del os.environ["QT_API"]
375
371 raise ImportError(
376 raise ImportError(
372 """
377 """
373 Could not load requested Qt binding. Please ensure that
378 Could not load requested Qt binding. Please ensure that
@@ -913,10 +913,19 b' class TerminalInteractiveShell(InteractiveShell):'
913
913
914 active_eventloop = None
914 active_eventloop = None
915 def enable_gui(self, gui=None):
915 def enable_gui(self, gui=None):
916 if self._inputhook is None and gui is None:
917 print("No event loop hook running.")
918 return
919
916 if self._inputhook is not None and gui is not None:
920 if self._inputhook is not None and gui is not None:
917 warn(
921 print(
918 f"Shell was already running a gui event loop for {self.active_eventloop}; switching to {gui}."
922 f"Shell is already running a gui event loop for {self.active_eventloop}. "
923 "Call with no arguments to disable the current loop."
919 )
924 )
925 return
926 if self._inputhook is not None and gui is None:
927 self.active_eventloop = self._inputhook = None
928
920 if gui and (gui not in {"inline", "webagg"}):
929 if gui and (gui not in {"inline", "webagg"}):
921 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
930 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
922 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
931 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
@@ -934,15 +943,18 b' class TerminalInteractiveShell(InteractiveShell):'
934 # same event loop as the rest of the code. don't use an actual
943 # same event loop as the rest of the code. don't use an actual
935 # input hook. (Asyncio is not made for nesting event loops.)
944 # input hook. (Asyncio is not made for nesting event loops.)
936 self.pt_loop = get_asyncio_loop()
945 self.pt_loop = get_asyncio_loop()
946 print("Installed asyncio event loop hook.")
937
947
938 elif self._inputhook:
948 elif self._inputhook:
939 # If an inputhook was set, create a new asyncio event loop with
949 # If an inputhook was set, create a new asyncio event loop with
940 # this inputhook for the prompt.
950 # this inputhook for the prompt.
941 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
951 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
952 print(f"Installed {self.active_eventloop} event loop hook.")
942 else:
953 else:
943 # When there's no inputhook, run the prompt in a separate
954 # When there's no inputhook, run the prompt in a separate
944 # asyncio event loop.
955 # asyncio event loop.
945 self.pt_loop = asyncio.new_event_loop()
956 self.pt_loop = asyncio.new_event_loop()
957 print("GUI event loop hook disabled.")
946
958
947 # Run !system commands directly, not through pipes, so terminal programs
959 # Run !system commands directly, not through pipes, so terminal programs
948 # work correctly.
960 # work correctly.
@@ -69,9 +69,9 b' def set_qt_api(gui):'
69 if loaded is not None and gui != "qt":
69 if loaded is not None and gui != "qt":
70 if qt_env2gui[loaded] != gui:
70 if qt_env2gui[loaded] != gui:
71 print(
71 print(
72 f"Cannot switch Qt versions for this session; must use {qt_env2gui[loaded]}."
72 f"Cannot switch Qt versions for this session; will use {qt_env2gui[loaded]}."
73 )
73 )
74 return
74 return qt_env2gui[loaded]
75
75
76 if qt_api is not None and gui != "qt":
76 if qt_api is not None and gui != "qt":
77 if qt_env2gui[qt_api] != gui:
77 if qt_env2gui[qt_api] != gui:
@@ -79,6 +79,7 b' def set_qt_api(gui):'
79 f'Request for "{gui}" will be ignored because `QT_API` '
79 f'Request for "{gui}" will be ignored because `QT_API` '
80 f'environment variable is set to "{qt_api}"'
80 f'environment variable is set to "{qt_api}"'
81 )
81 )
82 return qt_env2gui[qt_api]
82 else:
83 else:
83 if gui == "qt5":
84 if gui == "qt5":
84 try:
85 try:
@@ -112,6 +113,11 b' def set_qt_api(gui):'
112 print(f'Unrecognized Qt version: {gui}. Should be "qt5", "qt6", or "qt".')
113 print(f'Unrecognized Qt version: {gui}. Should be "qt5", "qt6", or "qt".')
113 return
114 return
114
115
116 # Import it now so we can figure out which version it is.
117 from IPython.external.qt_for_kernel import QT_API
118
119 return qt_env2gui[QT_API]
120
115
121
116 def get_inputhook_name_and_func(gui):
122 def get_inputhook_name_and_func(gui):
117 if gui in registered:
123 if gui in registered:
@@ -125,7 +131,7 b' def get_inputhook_name_and_func(gui):'
125
131
126 gui_mod = gui
132 gui_mod = gui
127 if gui.startswith("qt"):
133 if gui.startswith("qt"):
128 set_qt_api(gui)
134 gui = set_qt_api(gui)
129 gui_mod = "qt"
135 gui_mod = "qt"
130
136
131 mod = importlib.import_module("IPython.terminal.pt_inputhooks." + gui_mod)
137 mod = importlib.import_module("IPython.terminal.pt_inputhooks." + gui_mod)
@@ -29,6 +29,8 b' from IPython.terminal.shortcuts import auto_suggest'
29 from IPython.terminal.shortcuts.filters import filter_from_string
29 from IPython.terminal.shortcuts.filters import filter_from_string
30 from IPython.utils.decorators import undoc
30 from IPython.utils.decorators import undoc
31
31
32 from prompt_toolkit.enums import DEFAULT_BUFFER
33
32 __all__ = ["create_ipython_shortcuts"]
34 __all__ = ["create_ipython_shortcuts"]
33
35
34
36
@@ -33,18 +33,18 b' _get_qt_vers()'
33 len(guis_avail) == 0, reason="No viable version of PyQt or PySide installed."
33 len(guis_avail) == 0, reason="No viable version of PyQt or PySide installed."
34 )
34 )
35 def test_inputhook_qt():
35 def test_inputhook_qt():
36 gui = guis_avail[0]
36 # Choose the "best" Qt version.
37 gui_ret, _ = get_inputhook_name_and_func("qt")
37
38
38 # Choose a qt version and get the input hook function. This will import Qt...
39 assert gui_ret != "qt" # you get back the specific version that was loaded.
39 get_inputhook_name_and_func(gui)
40 assert gui_ret in guis_avail
40
41
42 if len(guis_avail) > 2:
41 # ...and now we're stuck with this version of Qt for good; can't switch.
43 # ...and now we're stuck with this version of Qt for good; can't switch.
42 for not_gui in ["qt6", "qt5"]:
44 for not_gui in ["qt6", "qt5"]:
43 if not_gui not in guis_avail:
45 if not_gui != gui_ret:
44 break
46 break
45
47 # Try to import the other gui; it won't work.
46 with pytest.raises(ImportError):
48 gui_ret2, _ = get_inputhook_name_and_func(not_gui)
47 get_inputhook_name_and_func(not_gui)
49 assert gui_ret2 == gui_ret
48
50 assert gui_ret2 != not_gui
49 # A gui of 'qt' means "best available", or in this case, the last one that was used.
50 get_inputhook_name_and_func("qt")
General Comments 0
You need to be logged in to leave comments. Login now