##// 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 62 run: |
63 63 python -m pip install --upgrade pip setuptools wheel build
64 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 66 - name: Try building with Python build
67 67 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
68 68 run: |
@@ -75,7 +75,13 b' jobs:'
75 75 env:
76 76 COLUMNS: 120
77 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 85 - name: Upload coverage to Codecov
80 86 uses: codecov/codecov-action@v3
81 87 with:
@@ -567,8 +567,11 b' class HistoryManager(HistoryAccessor):'
567 567 conn = self.db
568 568
569 569 with conn:
570 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
571 NULL, "") """, (datetime.datetime.now(),))
570 cur = conn.execute(
571 """INSERT INTO sessions VALUES (NULL, ?, NULL,
572 NULL, '') """,
573 (datetime.datetime.now(),),
574 )
572 575 self.session_number = cur.lastrowid
573 576
574 577 def end_session(self):
@@ -32,7 +32,7 b' from io import open as io_open'
32 32 from logging import error
33 33 from pathlib import Path
34 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 36 from typing import Optional, Tuple
37 37 from warnings import warn
38 38
@@ -90,6 +90,8 b' from IPython.utils.process import getoutput, system'
90 90 from IPython.utils.strdispatch import StrDispatch
91 91 from IPython.utils.syspathcontext import prepended_to_syspath
92 92 from IPython.utils.text import DollarFormatter, LSString, SList, format_screen
93 from IPython.core.oinspect import OInfo
94
93 95
94 96 sphinxify: Optional[Callable]
95 97
@@ -1560,15 +1562,28 b' class InteractiveShell(SingletonConfigurable):'
1560 1562 #-------------------------------------------------------------------------
1561 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 1587 raw_parts = oname.split(".")
1573 1588 parts = []
1574 1589 parts_ok = True
@@ -1590,12 +1605,31 b' class InteractiveShell(SingletonConfigurable):'
1590 1605 parts_ok = False
1591 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 1620 if (
1594 1621 not oname.startswith(ESC_MAGIC)
1595 1622 and not oname.startswith(ESC_MAGIC2)
1596 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 1634 if namespaces is None:
1601 1635 # Namespaces to search in:
@@ -1675,14 +1709,16 b' class InteractiveShell(SingletonConfigurable):'
1675 1709 found = True
1676 1710 ospace = 'Interactive'
1677 1711
1678 return {
1679 'obj':obj,
1680 'found':found,
1681 'parent':parent,
1682 'ismagic':ismagic,
1683 'isalias':isalias,
1684 'namespace':ospace
1685 }
1712 return OInfo(
1713 **{
1714 "obj": obj,
1715 "found": found,
1716 "parent": parent,
1717 "ismagic": ismagic,
1718 "isalias": isalias,
1719 "namespace": ospace,
1720 }
1721 )
1686 1722
1687 1723 @staticmethod
1688 1724 def _getattr_property(obj, attrname):
@@ -1726,9 +1762,9 b' class InteractiveShell(SingletonConfigurable):'
1726 1762 # Nothing helped, fall back.
1727 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 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 1769 def _inspect(self, meth, oname, namespaces=None, **kw):
1734 1770 """Generic interface to the inspector system.
@@ -422,7 +422,8 b' class ExecutionMagics(Magics):'
422 422 )
423 423 @no_var_expand
424 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 427 """Activate the interactive debugger.
427 428
428 429 This magic command support two ways of activating debugger.
@@ -453,7 +454,7 b' class ExecutionMagics(Magics):'
453 454 self._debug_post_mortem()
454 455 elif not (args.breakpoint or cell):
455 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 458 else:
458 459 # Here we try to reconstruct the code from the output of
459 460 # parse_argstring. This might not work if the code has spaces
@@ -461,18 +462,20 b' class ExecutionMagics(Magics):'
461 462 code = "\n".join(args.statement)
462 463 if cell:
463 464 code += "\n" + cell
464 self._debug_exec(code, args.breakpoint)
465 self._debug_exec(code, args.breakpoint, local_ns)
465 466
466 467 def _debug_post_mortem(self):
467 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 471 if breakpoint:
471 472 (filename, bp_line) = breakpoint.rsplit(':', 1)
472 473 bp_line = int(bp_line)
473 474 else:
474 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 480 @line_magic
478 481 def tb(self, s):
@@ -867,8 +870,9 b' class ExecutionMagics(Magics):'
867 870
868 871 return stats
869 872
870 def _run_with_debugger(self, code, code_ns, filename=None,
871 bp_line=None, bp_file=None):
873 def _run_with_debugger(
874 self, code, code_ns, filename=None, bp_line=None, bp_file=None, local_ns=None
875 ):
872 876 """
873 877 Run `code` in debugger with a break point.
874 878
@@ -885,6 +889,8 b' class ExecutionMagics(Magics):'
885 889 bp_file : str, optional
886 890 Path to the file in which break point is specified.
887 891 `filename` is used if not given.
892 local_ns : dict, optional
893 A local namespace in which `code` is executed.
888 894
889 895 Raises
890 896 ------
@@ -941,7 +947,7 b' class ExecutionMagics(Magics):'
941 947 while True:
942 948 try:
943 949 trace = sys.gettrace()
944 deb.run(code, code_ns)
950 deb.run(code, code_ns, local_ns)
945 951 except Restart:
946 952 print("Restarting")
947 953 if filename:
@@ -46,6 +46,19 b' from pygments import highlight'
46 46 from pygments.lexers import PythonLexer
47 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 62 def pylight(code):
50 63 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
51 64
@@ -499,7 +499,7 b' class AutocallChecker(PrefilterChecker):'
499 499 return None
500 500
501 501 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
502 if not oinfo['found']:
502 if not oinfo.found:
503 503 return None
504 504
505 505 ignored_funs = ['b', 'f', 'r', 'u', 'br', 'rb', 'fr', 'rf']
@@ -508,10 +508,12 b' class AutocallChecker(PrefilterChecker):'
508 508 if ifun.lower() in ignored_funs and (line.startswith(ifun + "'") or line.startswith(ifun + '"')):
509 509 return None
510 510
511 if callable(oinfo['obj']) \
512 and (not self.exclude_regexp.match(line_info.the_rest)) \
513 and self.function_name_regexp.match(line_info.ifun):
514 return self.prefilter_manager.get_handler_by_name('auto')
511 if (
512 callable(oinfo.obj)
513 and (not self.exclude_regexp.match(line_info.the_rest))
514 and self.function_name_regexp.match(line_info.ifun)
515 ):
516 return self.prefilter_manager.get_handler_by_name("auto")
515 517 else:
516 518 return None
517 519
@@ -601,7 +603,7 b' class AutoHandler(PrefilterHandler):'
601 603 the_rest = line_info.the_rest
602 604 esc = line_info.esc
603 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 608 # This should only be active for single-line input!
607 609 if continue_prompt:
@@ -25,6 +25,7 b' import sys'
25 25
26 26 from IPython.utils import py3compat
27 27 from IPython.utils.encoding import get_stream_enc
28 from IPython.core.oinspect import OInfo
28 29
29 30 #-----------------------------------------------------------------------------
30 31 # Main function
@@ -118,7 +119,7 b' class LineInfo(object):'
118 119 else:
119 120 self.pre_whitespace = self.pre
120 121
121 def ofind(self, ip):
122 def ofind(self, ip) -> OInfo:
122 123 """Do a full, attribute-walking lookup of the ifun in the various
123 124 namespaces for the given IPython InteractiveShell instance.
124 125
@@ -177,7 +177,7 b' def test_module_without_init():'
177 177 try:
178 178 os.makedirs(os.path.join(tmpdir, fake_module_name))
179 179 s = try_import(mod=fake_module_name)
180 assert s == []
180 assert s == [], f"for module {fake_module_name}"
181 181 finally:
182 182 sys.path.remove(tmpdir)
183 183
@@ -25,6 +25,7 b' from os.path import join'
25 25 from IPython.core.error import InputRejected
26 26 from IPython.core.inputtransformer import InputTransformer
27 27 from IPython.core import interactiveshell
28 from IPython.core.oinspect import OInfo
28 29 from IPython.testing.decorators import (
29 30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 31 )
@@ -360,7 +361,7 b' class InteractiveShellTestCase(unittest.TestCase):'
360 361
361 362 # Get info on line magic
362 363 lfind = ip._ofind("lmagic")
363 info = dict(
364 info = OInfo(
364 365 found=True,
365 366 isalias=False,
366 367 ismagic=True,
@@ -379,7 +380,7 b' class InteractiveShellTestCase(unittest.TestCase):'
379 380
380 381 # Get info on cell magic
381 382 find = ip._ofind("cmagic")
382 info = dict(
383 info = OInfo(
383 384 found=True,
384 385 isalias=False,
385 386 ismagic=True,
@@ -397,9 +398,15 b' class InteractiveShellTestCase(unittest.TestCase):'
397 398
398 399 a = A()
399 400
400 found = ip._ofind('a.foo', [('locals', locals())])
401 info = dict(found=True, isalias=False, ismagic=False,
402 namespace='locals', obj=A.foo, parent=a)
401 found = ip._ofind("a.foo", [("locals", locals())])
402 info = OInfo(
403 found=True,
404 isalias=False,
405 ismagic=False,
406 namespace="locals",
407 obj=A.foo,
408 parent=a,
409 )
403 410 self.assertEqual(found, info)
404 411
405 412 def test_ofind_multiple_attribute_lookups(self):
@@ -412,9 +419,15 b' class InteractiveShellTestCase(unittest.TestCase):'
412 419 a.a = A()
413 420 a.a.a = A()
414 421
415 found = ip._ofind('a.a.a.foo', [('locals', locals())])
416 info = dict(found=True, isalias=False, ismagic=False,
417 namespace='locals', obj=A.foo, parent=a.a.a)
422 found = ip._ofind("a.a.a.foo", [("locals", locals())])
423 info = OInfo(
424 found=True,
425 isalias=False,
426 ismagic=False,
427 namespace="locals",
428 obj=A.foo,
429 parent=a.a.a,
430 )
418 431 self.assertEqual(found, info)
419 432
420 433 def test_ofind_slotted_attributes(self):
@@ -424,14 +437,26 b' class InteractiveShellTestCase(unittest.TestCase):'
424 437 self.foo = 'bar'
425 438
426 439 a = A()
427 found = ip._ofind('a.foo', [('locals', locals())])
428 info = dict(found=True, isalias=False, ismagic=False,
429 namespace='locals', obj=a.foo, parent=a)
440 found = ip._ofind("a.foo", [("locals", locals())])
441 info = OInfo(
442 found=True,
443 isalias=False,
444 ismagic=False,
445 namespace="locals",
446 obj=a.foo,
447 parent=a,
448 )
430 449 self.assertEqual(found, info)
431 450
432 found = ip._ofind('a.bar', [('locals', locals())])
433 info = dict(found=False, isalias=False, ismagic=False,
434 namespace=None, obj=None, parent=a)
451 found = ip._ofind("a.bar", [("locals", locals())])
452 info = OInfo(
453 found=False,
454 isalias=False,
455 ismagic=False,
456 namespace=None,
457 obj=None,
458 parent=a,
459 )
435 460 self.assertEqual(found, info)
436 461
437 462 def test_ofind_prefers_property_to_instance_level_attribute(self):
@@ -443,7 +468,7 b' class InteractiveShellTestCase(unittest.TestCase):'
443 468 a.__dict__["foo"] = "baz"
444 469 self.assertEqual(a.foo, "bar")
445 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 473 def test_custom_syntaxerror_exception(self):
449 474 called = []
@@ -715,6 +715,7 b' def doctest_precision():'
715 715 Out[5]: '3.141593e+00'
716 716 """
717 717
718
718 719 def test_debug_magic():
719 720 """Test debugging a small code with %debug
720 721
@@ -727,6 +728,22 b' def test_debug_magic():'
727 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 747 def test_psearch():
731 748 with tt.AssertPrints("dict.fromkeys"):
732 749 _ip.run_cell("dict.fr*?")
@@ -994,7 +994,7 b' class VerboseTB(TBTools):'
994 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 998 cf = etb
999 999 max_len = 0
1000 1000 tbs = []
@@ -10,6 +10,7 b' be accessed directly from the outside'
10 10 """
11 11 import importlib.abc
12 12 import sys
13 import os
13 14 import types
14 15 from functools import partial, lru_cache
15 16 import operator
@@ -368,6 +369,10 b' def load_qt(api_options):'
368 369 commit_api(api)
369 370 return result
370 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 376 raise ImportError(
372 377 """
373 378 Could not load requested Qt binding. Please ensure that
@@ -913,10 +913,19 b' class TerminalInteractiveShell(InteractiveShell):'
913 913
914 914 active_eventloop = None
915 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 920 if self._inputhook is not None and gui is not None:
917 warn(
918 f"Shell was already running a gui event loop for {self.active_eventloop}; switching to {gui}."
921 print(
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 929 if gui and (gui not in {"inline", "webagg"}):
921 930 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
922 931 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
@@ -934,15 +943,18 b' class TerminalInteractiveShell(InteractiveShell):'
934 943 # same event loop as the rest of the code. don't use an actual
935 944 # input hook. (Asyncio is not made for nesting event loops.)
936 945 self.pt_loop = get_asyncio_loop()
946 print("Installed asyncio event loop hook.")
937 947
938 948 elif self._inputhook:
939 949 # If an inputhook was set, create a new asyncio event loop with
940 950 # this inputhook for the prompt.
941 951 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
952 print(f"Installed {self.active_eventloop} event loop hook.")
942 953 else:
943 954 # When there's no inputhook, run the prompt in a separate
944 955 # asyncio event loop.
945 956 self.pt_loop = asyncio.new_event_loop()
957 print("GUI event loop hook disabled.")
946 958
947 959 # Run !system commands directly, not through pipes, so terminal programs
948 960 # work correctly.
@@ -69,9 +69,9 b' def set_qt_api(gui):'
69 69 if loaded is not None and gui != "qt":
70 70 if qt_env2gui[loaded] != gui:
71 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 76 if qt_api is not None and gui != "qt":
77 77 if qt_env2gui[qt_api] != gui:
@@ -79,6 +79,7 b' def set_qt_api(gui):'
79 79 f'Request for "{gui}" will be ignored because `QT_API` '
80 80 f'environment variable is set to "{qt_api}"'
81 81 )
82 return qt_env2gui[qt_api]
82 83 else:
83 84 if gui == "qt5":
84 85 try:
@@ -112,6 +113,11 b' def set_qt_api(gui):'
112 113 print(f'Unrecognized Qt version: {gui}. Should be "qt5", "qt6", or "qt".')
113 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 122 def get_inputhook_name_and_func(gui):
117 123 if gui in registered:
@@ -125,7 +131,7 b' def get_inputhook_name_and_func(gui):'
125 131
126 132 gui_mod = gui
127 133 if gui.startswith("qt"):
128 set_qt_api(gui)
134 gui = set_qt_api(gui)
129 135 gui_mod = "qt"
130 136
131 137 mod = importlib.import_module("IPython.terminal.pt_inputhooks." + gui_mod)
@@ -29,6 +29,8 b' from IPython.terminal.shortcuts import auto_suggest'
29 29 from IPython.terminal.shortcuts.filters import filter_from_string
30 30 from IPython.utils.decorators import undoc
31 31
32 from prompt_toolkit.enums import DEFAULT_BUFFER
33
32 34 __all__ = ["create_ipython_shortcuts"]
33 35
34 36
@@ -33,18 +33,18 b' _get_qt_vers()'
33 33 len(guis_avail) == 0, reason="No viable version of PyQt or PySide installed."
34 34 )
35 35 def test_inputhook_qt():
36 gui = guis_avail[0]
37
38 # Choose a qt version and get the input hook function. This will import Qt...
39 get_inputhook_name_and_func(gui)
40
41 # ...and now we're stuck with this version of Qt for good; can't switch.
42 for not_gui in ["qt6", "qt5"]:
43 if not_gui not in guis_avail:
44 break
45
46 with pytest.raises(ImportError):
47 get_inputhook_name_and_func(not_gui)
48
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")
36 # Choose the "best" Qt version.
37 gui_ret, _ = get_inputhook_name_and_func("qt")
38
39 assert gui_ret != "qt" # you get back the specific version that was loaded.
40 assert gui_ret in guis_avail
41
42 if len(guis_avail) > 2:
43 # ...and now we're stuck with this version of Qt for good; can't switch.
44 for not_gui in ["qt6", "qt5"]:
45 if not_gui != gui_ret:
46 break
47 # Try to import the other gui; it won't work.
48 gui_ret2, _ = get_inputhook_name_and_func(not_gui)
49 assert gui_ret2 == gui_ret
50 assert gui_ret2 != not_gui
General Comments 0
You need to be logged in to leave comments. Login now