diff options
| author | robot-contrib <[email protected]> | 2023-09-30 10:27:28 +0300 | 
|---|---|---|
| committer | robot-contrib <[email protected]> | 2023-09-30 10:47:10 +0300 | 
| commit | 5a6373c9d09bbfb7094f9992a4531477bb97829e (patch) | |
| tree | ebea8fd55fee858876743312cdf789a1f01487b5 | |
| parent | 15f3c7493474de25a6b23296878bb8f49470d2e6 (diff) | |
Update contrib/python/ipython/py3 to 8.15.0
23 files changed, 773 insertions, 107 deletions
diff --git a/contrib/python/ipython/py3/.dist-info/METADATA b/contrib/python/ipython/py3/.dist-info/METADATA index 694e2535e33..504e0953c23 100644 --- a/contrib/python/ipython/py3/.dist-info/METADATA +++ b/contrib/python/ipython/py3/.dist-info/METADATA @@ -1,6 +1,6 @@  Metadata-Version: 2.1  Name: ipython -Version: 8.14.0 +Version: 8.15.0  Summary: IPython: Productive Interactive Computing  Home-page: https://ipython.org  Author: The IPython Development Team @@ -28,29 +28,31 @@ Description-Content-Type: text/x-rst  License-File: LICENSE  Requires-Dist: backcall  Requires-Dist: decorator -Requires-Dist: jedi (>=0.13) +Requires-Dist: jedi >=0.13  Requires-Dist: matplotlib-inline  Requires-Dist: pickleshare -Requires-Dist: prompt-toolkit (!=3.0.37,<3.1.0,>=3.0.30) -Requires-Dist: pygments (>=2.4.0) +Requires-Dist: prompt-toolkit !=3.0.37,<3.1.0,>=3.0.30 +Requires-Dist: pygments >=2.4.0  Requires-Dist: stack-data -Requires-Dist: traitlets (>=5) +Requires-Dist: traitlets >=5  Requires-Dist: typing-extensions ; python_version < "3.10" -Requires-Dist: pexpect (>4.3) ; sys_platform != "win32" +Requires-Dist: exceptiongroup ; python_version < "3.11" +Requires-Dist: pexpect >4.3 ; sys_platform != "win32"  Requires-Dist: appnope ; sys_platform == "darwin"  Requires-Dist: colorama ; sys_platform == "win32"  Provides-Extra: all  Requires-Dist: black ; extra == 'all'  Requires-Dist: ipykernel ; extra == 'all' -Requires-Dist: setuptools (>=18.5) ; extra == 'all' -Requires-Dist: sphinx (>=1.3) ; extra == 'all' +Requires-Dist: setuptools >=18.5 ; extra == 'all' +Requires-Dist: sphinx >=1.3 ; extra == 'all'  Requires-Dist: sphinx-rtd-theme ; extra == 'all'  Requires-Dist: docrepr ; extra == 'all'  Requires-Dist: matplotlib ; extra == 'all'  Requires-Dist: stack-data ; extra == 'all' -Requires-Dist: pytest (<7) ; extra == 'all' +Requires-Dist: pytest <7 ; extra == 'all'  Requires-Dist: typing-extensions ; extra == 'all' -Requires-Dist: pytest (<7.1) ; extra == 'all' +Requires-Dist: exceptiongroup ; extra == 'all' +Requires-Dist: pytest <7.1 ; extra == 'all'  Requires-Dist: pytest-asyncio ; extra == 'all'  Requires-Dist: testpath ; extra == 'all'  Requires-Dist: nbconvert ; extra == 'all' @@ -60,23 +62,24 @@ Requires-Dist: notebook ; extra == 'all'  Requires-Dist: ipyparallel ; extra == 'all'  Requires-Dist: qtconsole ; extra == 'all'  Requires-Dist: curio ; extra == 'all' -Requires-Dist: matplotlib (!=3.2.0) ; extra == 'all' -Requires-Dist: numpy (>=1.21) ; extra == 'all' +Requires-Dist: matplotlib !=3.2.0 ; extra == 'all' +Requires-Dist: numpy >=1.21 ; extra == 'all'  Requires-Dist: pandas ; extra == 'all'  Requires-Dist: trio ; extra == 'all'  Provides-Extra: black  Requires-Dist: black ; extra == 'black'  Provides-Extra: doc  Requires-Dist: ipykernel ; extra == 'doc' -Requires-Dist: setuptools (>=18.5) ; extra == 'doc' -Requires-Dist: sphinx (>=1.3) ; extra == 'doc' +Requires-Dist: setuptools >=18.5 ; extra == 'doc' +Requires-Dist: sphinx >=1.3 ; extra == 'doc'  Requires-Dist: sphinx-rtd-theme ; extra == 'doc'  Requires-Dist: docrepr ; extra == 'doc'  Requires-Dist: matplotlib ; extra == 'doc'  Requires-Dist: stack-data ; extra == 'doc' -Requires-Dist: pytest (<7) ; extra == 'doc' +Requires-Dist: pytest <7 ; extra == 'doc'  Requires-Dist: typing-extensions ; extra == 'doc' -Requires-Dist: pytest (<7.1) ; extra == 'doc' +Requires-Dist: exceptiongroup ; extra == 'doc' +Requires-Dist: pytest <7.1 ; extra == 'doc'  Requires-Dist: pytest-asyncio ; extra == 'doc'  Requires-Dist: testpath ; extra == 'doc'  Provides-Extra: kernel @@ -94,17 +97,17 @@ Provides-Extra: qtconsole  Requires-Dist: qtconsole ; extra == 'qtconsole'  Provides-Extra: terminal  Provides-Extra: test -Requires-Dist: pytest (<7.1) ; extra == 'test' +Requires-Dist: pytest <7.1 ; extra == 'test'  Requires-Dist: pytest-asyncio ; extra == 'test'  Requires-Dist: testpath ; extra == 'test'  Provides-Extra: test_extra -Requires-Dist: pytest (<7.1) ; extra == 'test_extra' +Requires-Dist: pytest <7.1 ; extra == 'test_extra'  Requires-Dist: pytest-asyncio ; extra == 'test_extra'  Requires-Dist: testpath ; extra == 'test_extra'  Requires-Dist: curio ; extra == 'test_extra' -Requires-Dist: matplotlib (!=3.2.0) ; extra == 'test_extra' +Requires-Dist: matplotlib !=3.2.0 ; extra == 'test_extra'  Requires-Dist: nbformat ; extra == 'test_extra' -Requires-Dist: numpy (>=1.21) ; extra == 'test_extra' +Requires-Dist: numpy >=1.21 ; extra == 'test_extra'  Requires-Dist: pandas ; extra == 'test_extra'  Requires-Dist: trio ; extra == 'test_extra' diff --git a/contrib/python/ipython/py3/IPython/core/debugger.py b/contrib/python/ipython/py3/IPython/core/debugger.py index c8082e34e7c..30be9fc0d19 100644 --- a/contrib/python/ipython/py3/IPython/core/debugger.py +++ b/contrib/python/ipython/py3/IPython/core/debugger.py @@ -108,6 +108,7 @@ import re  import os  from IPython import get_ipython +from contextlib import contextmanager  from IPython.utils import PyColorize  from IPython.utils import coloransi, py3compat  from IPython.core.excolors import exception_colors @@ -127,6 +128,11 @@ from pdb import Pdb as OldPdb  DEBUGGERSKIP = "__debuggerskip__" +# this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676 +# on lower python versions, we backported the feature. +CHAIN_EXCEPTIONS = sys.version_info < (3, 13) + +  def make_arrow(pad):      """generate the leading arrow in front of traceback or debugger"""      if pad >= 2: @@ -185,6 +191,9 @@ class Pdb(OldPdb):      """ +    if CHAIN_EXCEPTIONS: +        MAX_CHAINED_EXCEPTION_DEPTH = 999 +      default_predicates = {          "tbhide": True,          "readonly": False, @@ -281,6 +290,10 @@ class Pdb(OldPdb):          # list of predicates we use to skip frames          self._predicates = self.default_predicates +        if CHAIN_EXCEPTIONS: +            self._chained_exceptions = tuple() +            self._chained_exception_index = 0 +      #      def set_colors(self, scheme):          """Shorthand access to the color table scheme selector method.""" @@ -330,9 +343,106 @@ class Pdb(OldPdb):              ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]          return ip_hide -    def interaction(self, frame, traceback): +    if CHAIN_EXCEPTIONS: + +        def _get_tb_and_exceptions(self, tb_or_exc): +            """ +            Given a tracecack or an exception, return a tuple of chained exceptions +            and current traceback to inspect. +            This will deal with selecting the right ``__cause__`` or ``__context__`` +            as well as handling cycles, and return a flattened list of exceptions we +            can jump to with do_exceptions. +            """ +            _exceptions = [] +            if isinstance(tb_or_exc, BaseException): +                traceback, current = tb_or_exc.__traceback__, tb_or_exc + +                while current is not None: +                    if current in _exceptions: +                        break +                    _exceptions.append(current) +                    if current.__cause__ is not None: +                        current = current.__cause__ +                    elif ( +                        current.__context__ is not None +                        and not current.__suppress_context__ +                    ): +                        current = current.__context__ + +                    if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH: +                        self.message( +                            f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}" +                            " chained exceptions found, not all exceptions" +                            "will be browsable with `exceptions`." +                        ) +                        break +            else: +                traceback = tb_or_exc +            return tuple(reversed(_exceptions)), traceback + +        @contextmanager +        def _hold_exceptions(self, exceptions): +            """ +            Context manager to ensure proper cleaning of exceptions references +            When given a chained exception instead of a traceback, +            pdb may hold references to many objects which may leak memory. +            We use this context manager to make sure everything is properly cleaned +            """ +            try: +                self._chained_exceptions = exceptions +                self._chained_exception_index = len(exceptions) - 1 +                yield +            finally: +                # we can't put those in forget as otherwise they would +                # be cleared on exception change +                self._chained_exceptions = tuple() +                self._chained_exception_index = 0 + +        def do_exceptions(self, arg): +            """exceptions [number] +            List or change current exception in an exception chain. +            Without arguments, list all the current exception in the exception +            chain. Exceptions will be numbered, with the current exception indicated +            with an arrow. +            If given an integer as argument, switch to the exception at that index. +            """ +            if not self._chained_exceptions: +                self.message( +                    "Did not find chained exceptions. To move between" +                    " exceptions, pdb/post_mortem must be given an exception" +                    " object rather than a traceback." +                ) +                return +            if not arg: +                for ix, exc in enumerate(self._chained_exceptions): +                    prompt = ">" if ix == self._chained_exception_index else " " +                    rep = repr(exc) +                    if len(rep) > 80: +                        rep = rep[:77] + "..." +                    self.message(f"{prompt} {ix:>3} {rep}") +            else: +                try: +                    number = int(arg) +                except ValueError: +                    self.error("Argument must be an integer") +                    return +                if 0 <= number < len(self._chained_exceptions): +                    self._chained_exception_index = number +                    self.setup(None, self._chained_exceptions[number].__traceback__) +                    self.print_stack_entry(self.stack[self.curindex]) +                else: +                    self.error("No exception with that number") + +    def interaction(self, frame, tb_or_exc):          try: -            OldPdb.interaction(self, frame, traceback) +            if CHAIN_EXCEPTIONS: +                # this context manager is part of interaction in 3.13 +                _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc) +                with self._hold_exceptions(_chained_exceptions): +                    OldPdb.interaction(self, frame, tb) +            else: +                OldPdb.interaction(self, frame, traceback) +          except KeyboardInterrupt:              self.stdout.write("\n" + self.shell.get_exception_only()) @@ -640,7 +750,7 @@ class Pdb(OldPdb):          """          self.lastcmd = 'list'          last = None -        if arg: +        if arg and arg != ".":              try:                  x = eval(arg, {}, {})                  if type(x) == type(()): @@ -655,7 +765,7 @@ class Pdb(OldPdb):              except:                  print('*** Error in argument:', repr(arg), file=self.stdout)                  return -        elif self.lineno is None: +        elif self.lineno is None or arg == ".":              first = max(1, self.curframe.f_lineno - 5)          else:              first = self.lineno + 1 diff --git a/contrib/python/ipython/py3/IPython/core/displayhook.py b/contrib/python/ipython/py3/IPython/core/displayhook.py index aba4f904d8d..b411f116139 100644 --- a/contrib/python/ipython/py3/IPython/core/displayhook.py +++ b/contrib/python/ipython/py3/IPython/core/displayhook.py @@ -297,8 +297,13 @@ class DisplayHook(Configurable):          for n in range(1,self.prompt_count + 1):              key = '_'+repr(n)              try: +                del self.shell.user_ns_hidden[key] +            except KeyError: +                pass +            try:                  del self.shell.user_ns[key] -            except: pass +            except KeyError: +                pass          # In some embedded circumstances, the user_ns doesn't have the          # '_oh' key set up.          oh = self.shell.user_ns.get('_oh', None) diff --git a/contrib/python/ipython/py3/IPython/core/history.py b/contrib/python/ipython/py3/IPython/core/history.py index fd5a8680bf6..fb67d158ef9 100644 --- a/contrib/python/ipython/py3/IPython/core/history.py +++ b/contrib/python/ipython/py3/IPython/core/history.py @@ -177,6 +177,10 @@ class HistoryAccessor(HistoryAccessorBase):          """      ).tag(config=True) +    @default("connection_options") +    def _default_connection_options(self): +        return dict(check_same_thread=False) +      # The SQLite database      db = Any()      @observe('db') @@ -570,7 +574,7 @@ class HistoryManager(HistoryAccessor):              cur = conn.execute(                  """INSERT INTO sessions VALUES (NULL, ?, NULL,                              NULL, '') """, -                (datetime.datetime.now(),), +                (datetime.datetime.now().isoformat(" "),),              )              self.session_number = cur.lastrowid @@ -578,9 +582,15 @@ class HistoryManager(HistoryAccessor):          """Close the database session, filling in the end time and line count."""          self.writeout_cache()          with self.db: -            self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE -                            session==?""", (datetime.datetime.now(), -                            len(self.input_hist_parsed)-1, self.session_number)) +            self.db.execute( +                """UPDATE sessions SET end=?, num_cmds=? WHERE +                            session==?""", +                ( +                    datetime.datetime.now().isoformat(" "), +                    len(self.input_hist_parsed) - 1, +                    self.session_number, +                ), +            )          self.session_number = 0      def name_session(self, name): diff --git a/contrib/python/ipython/py3/IPython/core/inputsplitter.py b/contrib/python/ipython/py3/IPython/core/inputsplitter.py index 10707d3d6b6..a4401184bdd 100644 --- a/contrib/python/ipython/py3/IPython/core/inputsplitter.py +++ b/contrib/python/ipython/py3/IPython/core/inputsplitter.py @@ -44,6 +44,7 @@ from IPython.core.inputtransformer import (leading_indent,                                             assign_from_system,                                             assemble_python_lines,                                             ) +from IPython.utils import tokenutil  # These are available in this module for backwards compatibility.  from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP, @@ -128,7 +129,7 @@ def partial_tokens(s):      readline = io.StringIO(s).readline      token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')      try: -        for token in tokenize.generate_tokens(readline): +        for token in tokenutil.generate_tokens_catch_errors(readline):              yield token      except tokenize.TokenError as e:          # catch EOF error @@ -150,9 +151,22 @@ def find_next_indent(code):          tokens.pop()      if not tokens:          return 0 -    while (tokens[-1].type in {tokenize.DEDENT, tokenize.NEWLINE, tokenize.COMMENT}): + +    while tokens[-1].type in { +        tokenize.DEDENT, +        tokenize.NEWLINE, +        tokenize.COMMENT, +        tokenize.ERRORTOKEN, +    }:          tokens.pop() +    # Starting in Python 3.12, the tokenize module adds implicit newlines at the end +    # of input. We need to remove those if we're in a multiline statement +    if tokens[-1].type == IN_MULTILINE_STATEMENT: +        while tokens[-2].type in {tokenize.NL}: +            tokens.pop(-2) + +      if tokens[-1].type == INCOMPLETE_STRING:          # Inside a multiline string          return 0 diff --git a/contrib/python/ipython/py3/IPython/core/inputtransformer.py b/contrib/python/ipython/py3/IPython/core/inputtransformer.py index 77f69f388f8..81cd1fa08c3 100644 --- a/contrib/python/ipython/py3/IPython/core/inputtransformer.py +++ b/contrib/python/ipython/py3/IPython/core/inputtransformer.py @@ -9,10 +9,11 @@ import abc  import functools  import re  import tokenize -from tokenize import generate_tokens, untokenize, TokenError +from tokenize import untokenize, TokenError  from io import StringIO  from IPython.core.splitinput import LineInfo +from IPython.utils import tokenutil  #-----------------------------------------------------------------------------  # Globals @@ -127,7 +128,7 @@ class TokenInputTransformer(InputTransformer):      def reset_tokenizer(self):          it = iter(self.buf) -        self.tokenizer = generate_tokens(it.__next__) +        self.tokenizer = tokenutil.generate_tokens_catch_errors(it.__next__)      def push(self, line):          self.buf.append(line + '\n') @@ -295,7 +296,7 @@ def _line_tokens(line):      readline = StringIO(line).readline      toktypes = set()      try: -        for t in generate_tokens(readline): +        for t in tokenutil.generate_tokens_catch_errors(readline):              toktypes.add(t[0])      except TokenError as e:          # There are only two cases where a TokenError is raised. diff --git a/contrib/python/ipython/py3/IPython/core/inputtransformer2.py b/contrib/python/ipython/py3/IPython/core/inputtransformer2.py index 37f0e7699c4..949cf383e27 100644 --- a/contrib/python/ipython/py3/IPython/core/inputtransformer2.py +++ b/contrib/python/ipython/py3/IPython/core/inputtransformer2.py @@ -13,10 +13,13 @@ deprecated in 7.0.  import ast  from codeop import CommandCompiler, Compile  import re +import sys  import tokenize  from typing import List, Tuple, Optional, Any  import warnings +from IPython.utils import tokenutil +  _indent_re = re.compile(r'^[ \t]+')  def leading_empty_lines(lines): @@ -269,9 +272,7 @@ class MagicAssign(TokenTransformBase):  class SystemAssign(TokenTransformBase):      """Transformer for assignments from system commands (a = !foo)"""      @classmethod -    def find(cls, tokens_by_line): -        """Find the first system assignment (a = !foo) in the cell. -        """ +    def find_pre_312(cls, tokens_by_line):          for line in tokens_by_line:              assign_ix = _find_assign_op(line)              if (assign_ix is not None) \ @@ -287,6 +288,26 @@ class SystemAssign(TokenTransformBase):                          break                      ix += 1 +    @classmethod +    def find_post_312(cls, tokens_by_line): +        for line in tokens_by_line: +            assign_ix = _find_assign_op(line) +            if ( +                (assign_ix is not None) +                and not line[assign_ix].line.strip().startswith("=") +                and (len(line) >= assign_ix + 2) +                and (line[assign_ix + 1].type == tokenize.OP) +                and (line[assign_ix + 1].string == "!") +            ): +                return cls(line[assign_ix + 1].start) + +    @classmethod +    def find(cls, tokens_by_line): +        """Find the first system assignment (a = !foo) in the cell.""" +        if sys.version_info < (3, 12): +            return cls.find_pre_312(tokens_by_line) +        return cls.find_post_312(tokens_by_line) +      def transform(self, lines: List[str]):          """Transform a system assignment found by the ``find()`` classmethod.          """ @@ -511,7 +532,9 @@ def make_tokens_by_line(lines:List[str]):          )      parenlev = 0      try: -        for token in tokenize.generate_tokens(iter(lines).__next__): +        for token in tokenutil.generate_tokens_catch_errors( +            iter(lines).__next__, extra_errors_to_catch=["expected EOF"] +        ):              tokens_by_line[-1].append(token)              if (token.type == NEWLINE) \                      or ((token.type == NL) and (parenlev <= 0)): @@ -677,9 +700,13 @@ class TransformerManager:          if not lines:              return 'complete', None -        if lines[-1].endswith('\\'): -            # Explicit backslash continuation -            return 'incomplete', find_last_indent(lines) +        for line in reversed(lines): +            if not line.strip(): +                continue +            elif line.strip("\n").endswith("\\"): +                return "incomplete", find_last_indent(lines) +            else: +                break          try:              for transform in self.cleanup_transforms: @@ -717,7 +744,10 @@ class TransformerManager:          if not tokens_by_line:              return 'incomplete', find_last_indent(lines) -        if tokens_by_line[-1][-1].type != tokenize.ENDMARKER: +        if ( +            tokens_by_line[-1][-1].type != tokenize.ENDMARKER +            and tokens_by_line[-1][-1].type != tokenize.ERRORTOKEN +        ):              # We're in a multiline string or expression              return 'incomplete', find_last_indent(lines) diff --git a/contrib/python/ipython/py3/IPython/core/interactiveshell.py b/contrib/python/ipython/py3/IPython/core/interactiveshell.py index 7392de7c022..894403f98b4 100644 --- a/contrib/python/ipython/py3/IPython/core/interactiveshell.py +++ b/contrib/python/ipython/py3/IPython/core/interactiveshell.py @@ -112,6 +112,8 @@ try:  except ImportError:      sphinxify = None +if sys.version_info[:2] < (3, 11): +    from exceptiongroup import BaseExceptionGroup  class ProvisionalWarning(DeprecationWarning):      """ @@ -2095,25 +2097,38 @@ class InteractiveShell(SingletonConfigurable):                      stb.extend(self.InteractiveTB.get_exception_only(etype,                                                                       value))                  else: -                    try: -                        # Exception classes can customise their traceback - we -                        # use this in IPython.parallel for exceptions occurring -                        # in the engines. This should return a list of strings. -                        if hasattr(value, "_render_traceback_"): -                            stb = value._render_traceback_() -                        else: -                            stb = self.InteractiveTB.structured_traceback( -                                etype, value, tb, tb_offset=tb_offset -                            ) -                    except Exception: -                        print( -                            "Unexpected exception formatting exception. Falling back to standard exception" -                        ) +                    def contains_exceptiongroup(val): +                        if val is None: +                            return False +                        return isinstance( +                            val, BaseExceptionGroup +                        ) or contains_exceptiongroup(val.__context__) + +                    if contains_exceptiongroup(value): +                        # fall back to native exception formatting until ultratb +                        # supports exception groups                          traceback.print_exc() -                        return None +                    else: +                        try: +                            # Exception classes can customise their traceback - we +                            # use this in IPython.parallel for exceptions occurring +                            # in the engines. This should return a list of strings. +                            if hasattr(value, "_render_traceback_"): +                                stb = value._render_traceback_() +                            else: +                                stb = self.InteractiveTB.structured_traceback( +                                    etype, value, tb, tb_offset=tb_offset +                                ) -                    self._showtraceback(etype, value, stb) +                        except Exception: +                            print( +                                "Unexpected exception formatting exception. Falling back to standard exception" +                            ) +                            traceback.print_exc() +                            return None + +                        self._showtraceback(etype, value, stb)                      if self.call_pdb:                          # drop into debugger                          self.debugger(force=True) @@ -2417,7 +2432,7 @@ class InteractiveShell(SingletonConfigurable):                  result = fn(*args, **kwargs)              # The code below prevents the output from being displayed -            # when using magics with decodator @output_can_be_silenced +            # when using magics with decorator @output_can_be_silenced              # when the last Python token in the expression is a ';'.              if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):                  if DisplayHook.semicolon_at_end_of_expression(magic_arg_s): @@ -2478,7 +2493,7 @@ class InteractiveShell(SingletonConfigurable):                  result = fn(*args, **kwargs)              # The code below prevents the output from being displayed -            # when using magics with decodator @output_can_be_silenced +            # when using magics with decorator @output_can_be_silenced              # when the last Python token in the expression is a ';'.              if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):                  if DisplayHook.semicolon_at_end_of_expression(cell): @@ -3338,8 +3353,11 @@ class InteractiveShell(SingletonConfigurable):                  # an InputRejected.  Short-circuit in this case so that we                  # don't unregister the transform.                  raise -            except Exception: -                warn("AST transformer %r threw an error. It will be unregistered." % transformer) +            except Exception as e: +                warn( +                    "AST transformer %r threw an error. It will be unregistered. %s" +                    % (transformer, e) +                )                  self.ast_transformers.remove(transformer)          if self.ast_transformers: diff --git a/contrib/python/ipython/py3/IPython/core/logger.py b/contrib/python/ipython/py3/IPython/core/logger.py index 99e7ce29185..ab12d10e229 100644 --- a/contrib/python/ipython/py3/IPython/core/logger.py +++ b/contrib/python/ipython/py3/IPython/core/logger.py @@ -15,10 +15,14 @@  # Python standard modules  import glob  import io +import logging  import os  import time +# prevent jedi/parso's debug messages pipe into interactiveshell +logging.getLogger("parso").setLevel(logging.WARNING) +  #****************************************************************************  # FIXME: This class isn't a mixin anymore, but it still needs attributes from  # ipython and does input cache management.  Finish cleanup later... diff --git a/contrib/python/ipython/py3/IPython/core/magics/ast_mod.py b/contrib/python/ipython/py3/IPython/core/magics/ast_mod.py new file mode 100644 index 00000000000..e28b9f1231f --- /dev/null +++ b/contrib/python/ipython/py3/IPython/core/magics/ast_mod.py @@ -0,0 +1,320 @@ +""" +This module contains utility function and classes to inject simple ast +transformations based on code strings into IPython. While it is already possible +with ast-transformers it is not easy to directly manipulate ast. + + +IPython has pre-code and post-code hooks, but are ran from within the IPython +machinery so may be inappropriate, for example for performance mesurement. + +This module give you tools to simplify this, and expose 2 classes: + +- `ReplaceCodeTransformer` which is a simple ast transformer based on code +  template, + +and for advance case: + +- `Mangler` which is a simple ast transformer that mangle names in the ast. + + +Example, let's try to make a simple version of the ``timeit`` magic, that run a +code snippet 10 times and print the average time taken. + +Basically we want to run : + +.. code-block:: python + +    from time import perf_counter +    now = perf_counter() +    for i in range(10): +        __code__ # our code +    print(f"Time taken: {(perf_counter() - now)/10}") +    __ret__ # the result of the last statement + +Where ``__code__`` is the code snippet we want to run, and ``__ret__`` is the +result, so that if we for example run `dataframe.head()` IPython still display +the head of dataframe instead of nothing. + +Here is a complete example of a file `timit2.py` that define such a magic: + +.. code-block:: python + +    from IPython.core.magic import ( +        Magics, +        magics_class, +        line_cell_magic, +    ) +    from IPython.core.magics.ast_mod import ReplaceCodeTransformer +    from textwrap import dedent +    import ast + +    template = template = dedent(''' +        from time import perf_counter +        now = perf_counter() +        for i in range(10): +            __code__ +        print(f"Time taken: {(perf_counter() - now)/10}") +        __ret__ +    ''' +    ) + + +    @magics_class +    class AstM(Magics): +        @line_cell_magic +        def t2(self, line, cell): +            transformer = ReplaceCodeTransformer.from_string(template) +            transformer.debug = True +            transformer.mangler.debug = True +            new_code = transformer.visit(ast.parse(cell)) +            return exec(compile(new_code, "<ast>", "exec")) + + +    def load_ipython_extension(ip): +        ip.register_magics(AstM) + + + +.. code-block:: python + +    In [1]: %load_ext timit2 + +    In [2]: %%t2 +       ...: import time +       ...: time.sleep(0.05) +       ...: +       ...: +    Time taken: 0.05435649999999441 + + +If you wish to ran all the code enter in IPython in an ast transformer, you can +do so as well: + +.. code-block:: python + +    In [1]: from IPython.core.magics.ast_mod import ReplaceCodeTransformer +       ...: +       ...: template = ''' +       ...: from time import perf_counter +       ...: now = perf_counter() +       ...: __code__ +       ...: print(f"Code ran in {perf_counter()-now}") +       ...: __ret__''' +       ...: +       ...: get_ipython().ast_transformers.append(ReplaceCodeTransformer.from_string(template)) + +    In [2]: 1+1 +    Code ran in 3.40410006174352e-05 +    Out[2]: 2 + + + +Hygiene and Mangling +-------------------- + +The ast transformer above is not hygienic, it may not work if the user code use +the same variable names as the ones used in the template. For example. + +To help with this by default the `ReplaceCodeTransformer` will mangle all names +staring with 3 underscores. This is a simple heuristic that should work in most +case, but can be cumbersome in some case. We provide a `Mangler` class that can +be overridden to change the mangling heuristic, or simply use the `mangle_all` +utility function. It will _try_ to mangle all names (except `__ret__` and +`__code__`), but this include builtins (``print``, ``range``, ``type``) and +replace those by invalid identifiers py prepending ``mangle-``: +``mangle-print``, ``mangle-range``, ``mangle-type`` etc. This is not a problem +as currently Python AST support invalid identifiers, but it may not be the case +in the future. + +You can set `ReplaceCodeTransformer.debug=True` and +`ReplaceCodeTransformer.mangler.debug=True` to see the code after mangling and +transforming: + +.. code-block:: python + + +    In [1]: from IPython.core.magics.ast_mod import ReplaceCodeTransformer, mangle_all +       ...: +       ...: template = ''' +       ...: from builtins import type, print +       ...: from time import perf_counter +       ...: now = perf_counter() +       ...: __code__ +       ...: print(f"Code ran in {perf_counter()-now}") +       ...: __ret__''' +       ...: +       ...: transformer = ReplaceCodeTransformer.from_string(template, mangling_predicate=mangle_all) + + +    In [2]: transformer.debug = True +       ...: transformer.mangler.debug = True +       ...: get_ipython().ast_transformers.append(transformer) + +    In [3]: 1+1 +    Mangling Alias mangle-type +    Mangling Alias mangle-print +    Mangling Alias mangle-perf_counter +    Mangling now +    Mangling perf_counter +    Not mangling __code__ +    Mangling print +    Mangling perf_counter +    Mangling now +    Not mangling __ret__ +    ---- Transformed code ---- +    from builtins import type as mangle-type, print as mangle-print +    from time import perf_counter as mangle-perf_counter +    mangle-now = mangle-perf_counter() +    ret-tmp = 1 + 1 +    mangle-print(f'Code ran in {mangle-perf_counter() - mangle-now}') +    ret-tmp +    ---- ---------------- ---- +    Code ran in 0.00013654199938173406 +    Out[3]: 2 + + +""" + +__skip_doctest__ = True + + +from ast import NodeTransformer, Store, Load, Name, Expr, Assign, Module +import ast +import copy + +from typing import Dict, Optional + + +mangle_all = lambda name: False if name in ("__ret__", "__code__") else True + + +class Mangler(NodeTransformer): +    """ +    Mangle given names in and ast tree to make sure they do not conflict with +    user code. +    """ + +    enabled: bool = True +    debug: bool = False + +    def log(self, *args, **kwargs): +        if self.debug: +            print(*args, **kwargs) + +    def __init__(self, predicate=None): +        if predicate is None: +            predicate = lambda name: name.startswith("___") +        self.predicate = predicate + +    def visit_Name(self, node): +        if self.predicate(node.id): +            self.log("Mangling", node.id) +            # Once in the ast we do not need +            # names to be valid identifiers. +            node.id = "mangle-" + node.id +        else: +            self.log("Not mangling", node.id) +        return node + +    def visit_FunctionDef(self, node): +        if self.predicate(node.name): +            self.log("Mangling", node.name) +            node.name = "mangle-" + node.name +        else: +            self.log("Not mangling", node.name) + +        for arg in node.args.args: +            if self.predicate(arg.arg): +                self.log("Mangling function arg", arg.arg) +                arg.arg = "mangle-" + arg.arg +            else: +                self.log("Not mangling function arg", arg.arg) +        return self.generic_visit(node) + +    def visit_ImportFrom(self, node): +        return self._visit_Import_and_ImportFrom(node) + +    def visit_Import(self, node): +        return self._visit_Import_and_ImportFrom(node) + +    def _visit_Import_and_ImportFrom(self, node): +        for alias in node.names: +            asname = alias.name if alias.asname is None else alias.asname +            if self.predicate(asname): +                new_name: str = "mangle-" + asname +                self.log("Mangling Alias", new_name) +                alias.asname = new_name +            else: +                self.log("Not mangling Alias", alias.asname) +        return node + + +class ReplaceCodeTransformer(NodeTransformer): +    enabled: bool = True +    debug: bool = False +    mangler: Mangler + +    def __init__( +        self, template: Module, mapping: Optional[Dict] = None, mangling_predicate=None +    ): +        assert isinstance(mapping, (dict, type(None))) +        assert isinstance(mangling_predicate, (type(None), type(lambda: None))) +        assert isinstance(template, ast.Module) +        self.template = template +        self.mangler = Mangler(predicate=mangling_predicate) +        if mapping is None: +            mapping = {} +        self.mapping = mapping + +    @classmethod +    def from_string( +        cls, template: str, mapping: Optional[Dict] = None, mangling_predicate=None +    ): +        return cls( +            ast.parse(template), mapping=mapping, mangling_predicate=mangling_predicate +        ) + +    def visit_Module(self, code): +        if not self.enabled: +            return code +        # if not isinstance(code, ast.Module): +        # recursively called... +        #    return generic_visit(self, code) +        last = code.body[-1] +        if isinstance(last, Expr): +            code.body.pop() +            code.body.append(Assign([Name("ret-tmp", ctx=Store())], value=last.value)) +            ast.fix_missing_locations(code) +            ret = Expr(value=Name("ret-tmp", ctx=Load())) +            ret = ast.fix_missing_locations(ret) +            self.mapping["__ret__"] = ret +        else: +            self.mapping["__ret__"] = ast.parse("None").body[0] +        self.mapping["__code__"] = code.body +        tpl = ast.fix_missing_locations(self.template) + +        tx = copy.deepcopy(tpl) +        tx = self.mangler.visit(tx) +        node = self.generic_visit(tx) +        node_2 = ast.fix_missing_locations(node) +        if self.debug: +            print("---- Transformed code ----") +            print(ast.unparse(node_2)) +            print("---- ---------------- ----") +        return node_2 + +    # this does not work as the name might be in a list and one might want to extend the list. +    # def visit_Name(self, name): +    #     if name.id in self.mapping and name.id == "__ret__": +    #         print(name, "in mapping") +    #         if isinstance(name.ctx, ast.Store): +    #             return Name("tmp", ctx=Store()) +    #         else: +    #             return copy.deepcopy(self.mapping[name.id]) +    #     return name + +    def visit_Expr(self, expr): +        if isinstance(expr.value, Name) and expr.value.id in self.mapping: +            if self.mapping[expr.value.id] is not None: +                return copy.deepcopy(self.mapping[expr.value.id]) +        return self.generic_visit(expr) diff --git a/contrib/python/ipython/py3/IPython/core/magics/basic.py b/contrib/python/ipython/py3/IPython/core/magics/basic.py index 814dec72e29..54b0c2a4de7 100644 --- a/contrib/python/ipython/py3/IPython/core/magics/basic.py +++ b/contrib/python/ipython/py3/IPython/core/magics/basic.py @@ -109,12 +109,12 @@ class BasicMagics(Magics):            Created `%%t` as an alias for `%%timeit`.            In [2]: %t -n1 pass -          1 loops, best of 3: 954 ns per loop +          107 ns ± 43.6 ns per loop (mean ± std. dev. of 7 runs, 1 loop each)            In [3]: %%t -n1               ...: pass               ...: -          1 loops, best of 3: 954 ns per loop +          107 ns ± 58.3 ns per loop (mean ± std. dev. of 7 runs, 1 loop each)            In [4]: %alias_magic --cell whereami pwd            UsageError: Cell magic function `%%pwd` not found. diff --git a/contrib/python/ipython/py3/IPython/core/magics/execution.py b/contrib/python/ipython/py3/IPython/core/magics/execution.py index 228cbd9da77..4147dac9637 100644 --- a/contrib/python/ipython/py3/IPython/core/magics/execution.py +++ b/contrib/python/ipython/py3/IPython/core/magics/execution.py @@ -8,6 +8,7 @@  import ast  import bdb  import builtins as builtin_mod +import copy  import cProfile as profile  import gc  import itertools @@ -19,14 +20,28 @@ import shlex  import sys  import time  import timeit -from ast import Module +from typing import Dict, Any +from ast import ( +    Assign, +    Call, +    Expr, +    Load, +    Module, +    Name, +    NodeTransformer, +    Store, +    parse, +    unparse, +)  from io import StringIO  from logging import error  from pathlib import Path  from pdb import Restart +from textwrap import dedent, indent  from warnings import warn  from IPython.core import magic_arguments, oinspect, page +from IPython.core.displayhook import DisplayHook  from IPython.core.error import UsageError  from IPython.core.macro import Macro  from IPython.core.magic import ( @@ -37,8 +52,8 @@ from IPython.core.magic import (      magics_class,      needs_local_scope,      no_var_expand, -    output_can_be_silenced,      on_off, +    output_can_be_silenced,  )  from IPython.testing.skipdoctest import skip_doctest  from IPython.utils.capture import capture_output @@ -47,7 +62,7 @@ from IPython.utils.ipstruct import Struct  from IPython.utils.module_paths import find_mod  from IPython.utils.path import get_py_filename, shellglob  from IPython.utils.timing import clock, clock2 -from IPython.core.displayhook import DisplayHook +from IPython.core.magics.ast_mod import ReplaceCodeTransformer  #-----------------------------------------------------------------------------  # Magic implementation classes @@ -164,9 +179,9 @@ class Timer(timeit.Timer):  @magics_class  class ExecutionMagics(Magics): -    """Magics related to code execution, debugging, profiling, etc. +    """Magics related to code execution, debugging, profiling, etc.""" -    """ +    _transformers: Dict[str, Any] = {}      def __init__(self, shell):          super(ExecutionMagics, self).__init__(shell) @@ -1035,7 +1050,7 @@ class ExecutionMagics(Magics):          provided, <N> is determined so as to get sufficient accuracy.          -r<R>: number of repeats <R>, each consisting of <N> loops, and take the -        best result. +        average result.          Default: 7          -t: use time.time to measure the time, which is the default on Unix. @@ -1474,6 +1489,83 @@ class ExecutionMagics(Magics):          elif args.output:              self.shell.user_ns[args.output] = io +    @skip_doctest +    @magic_arguments.magic_arguments() +    @magic_arguments.argument("name", type=str, default="default", nargs="?") +    @magic_arguments.argument( +        "--remove", action="store_true", help="remove the current transformer" +    ) +    @magic_arguments.argument( +        "--list", action="store_true", help="list existing transformers name" +    ) +    @magic_arguments.argument( +        "--list-all", +        action="store_true", +        help="list existing transformers name and code template", +    ) +    @line_cell_magic +    def code_wrap(self, line, cell=None): +        """ +        Simple magic to quickly define a code transformer for all IPython's future imput. + +        ``__code__`` and ``__ret__`` are special variable that represent the code to run +        and the value of the last expression of ``__code__`` respectively. + +        Examples +        -------- + +        .. ipython:: + +            In [1]: %%code_wrap before_after +               ...: print('before') +               ...: __code__ +               ...: print('after') +               ...: __ret__ + + +            In [2]: 1 +            before +            after +            Out[2]: 1 + +            In [3]: %code_wrap --list +            before_after + +            In [4]: %code_wrap --list-all +            before_after : +                print('before') +                __code__ +                print('after') +                __ret__ + +            In [5]: %code_wrap --remove before_after + +        """ +        args = magic_arguments.parse_argstring(self.code_wrap, line) + +        if args.list: +            for name in self._transformers.keys(): +                print(name) +            return +        if args.list_all: +            for name, _t in self._transformers.items(): +                print(name, ":") +                print(indent(ast.unparse(_t.template), "    ")) +            print() +            return + +        to_remove = self._transformers.pop(args.name, None) +        if to_remove in self.shell.ast_transformers: +            self.shell.ast_transformers.remove(to_remove) +        if cell is None or args.remove: +            return + +        _trs = ReplaceCodeTransformer(ast.parse(cell)) + +        self._transformers[args.name] = _trs +        self.shell.ast_transformers.append(_trs) + +  def parse_breakpoint(text, current_file):      '''Returns (file, line) for file:line and (current_file, line) for line'''      colon = text.find(':') @@ -1519,4 +1611,4 @@ def _format_time(timespan, precision=3):          order = min(-int(math.floor(math.log10(timespan)) // 3), 3)      else:          order = 3 -    return u"%.*g %s" % (precision, timespan * scaling[order], units[order]) +    return "%.*g %s" % (precision, timespan * scaling[order], units[order]) diff --git a/contrib/python/ipython/py3/IPython/core/pylabtools.py b/contrib/python/ipython/py3/IPython/core/pylabtools.py index deadf038ea3..e5715a94976 100644 --- a/contrib/python/ipython/py3/IPython/core/pylabtools.py +++ b/contrib/python/ipython/py3/IPython/core/pylabtools.py @@ -23,7 +23,7 @@ backends = {      "qt4": "Qt4Agg",      "qt5": "Qt5Agg",      "qt6": "QtAgg", -    "qt": "Qt5Agg", +    "qt": "QtAgg",      "osx": "MacOSX",      "nbagg": "nbAgg",      "webagg": "WebAgg", @@ -53,8 +53,8 @@ backend2gui["CocoaAgg"] = "osx"  # supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5,  # and Qt6.  backend2gui["QtAgg"] = "qt" -backend2gui["Qt4Agg"] = "qt" -backend2gui["Qt5Agg"] = "qt" +backend2gui["Qt4Agg"] = "qt4" +backend2gui["Qt5Agg"] = "qt5"  # And some backends that don't need GUI integration  del backend2gui["nbAgg"] @@ -208,10 +208,12 @@ def mpl_runner(safe_execfile):          #print '*** Matplotlib runner ***' # dbg          # turn off rendering until end of script -        is_interactive = matplotlib.rcParams['interactive'] -        matplotlib.interactive(False) -        safe_execfile(fname,*where,**kw) -        matplotlib.interactive(is_interactive) +        with matplotlib.rc_context({"interactive": False}): +            safe_execfile(fname, *where, **kw) + +        if matplotlib.is_interactive(): +            plt.show() +          # make rendering call now, if the user tried to do it          if plt.draw_if_interactive.called:              plt.draw() @@ -317,9 +319,15 @@ def find_gui_and_backend(gui=None, gui_select=None):      import matplotlib +    has_unified_qt_backend = getattr(matplotlib, "__version_info__", (0, 0)) >= (3, 5) + +    backends_ = dict(backends) +    if not has_unified_qt_backend: +        backends_["qt"] = "qt5agg" +      if gui and gui != 'auto':          # select backend based on requested gui -        backend = backends[gui] +        backend = backends_[gui]          if gui == 'agg':              gui = None      else: @@ -336,7 +344,7 @@ def find_gui_and_backend(gui=None, gui_select=None):          # ones allowed.          if gui_select and gui != gui_select:              gui = gui_select -            backend = backends[gui] +            backend = backends_[gui]      return gui, backend diff --git a/contrib/python/ipython/py3/IPython/core/release.py b/contrib/python/ipython/py3/IPython/core/release.py index 50080642eee..a1df4d10586 100644 --- a/contrib/python/ipython/py3/IPython/core/release.py +++ b/contrib/python/ipython/py3/IPython/core/release.py @@ -16,7 +16,7 @@  # release.  'dev' as a _version_extra string means this is a development  # version  _version_major = 8 -_version_minor = 14 +_version_minor = 15  _version_patch = 0  _version_extra = ".dev"  # _version_extra = "rc1" diff --git a/contrib/python/ipython/py3/IPython/core/ultratb.py b/contrib/python/ipython/py3/IPython/core/ultratb.py index 61b59393981..cb0d8f951f2 100644 --- a/contrib/python/ipython/py3/IPython/core/ultratb.py +++ b/contrib/python/ipython/py3/IPython/core/ultratb.py @@ -809,6 +809,7 @@ class VerboseTB(TBTools):      would appear in the traceback)."""      _tb_highlight = "" +    _tb_highlight_style = "default"      def __init__(          self, @@ -1110,7 +1111,7 @@ class VerboseTB(TBTools):          after = context // 2          before = context - after          if self.has_colors: -            style = get_style_by_name("default") +            style = get_style_by_name(self._tb_highlight_style)              style = stack_data.style_with_executing_node(style, self._tb_highlight)              formatter = Terminal256Formatter(style=style)          else: @@ -1245,7 +1246,13 @@ class VerboseTB(TBTools):                  if etb and etb.tb_next:                      etb = etb.tb_next                  self.pdb.botframe = etb.tb_frame -                self.pdb.interaction(None, etb) +                # last_value should be deprecated, but last-exc sometimme not set +                # please check why later and remove the getattr. +                exc = sys.last_value if sys.version_info < (3, 12) else getattr(sys, "last_exc", sys.last_value)  # type: ignore[attr-defined] +                if exc: +                    self.pdb.interaction(None, exc) +                else: +                    self.pdb.interaction(None, etb)          if hasattr(self, 'tb'):              del self.tb diff --git a/contrib/python/ipython/py3/IPython/sphinxext/ipython_directive.py b/contrib/python/ipython/py3/IPython/sphinxext/ipython_directive.py index c428e7917fd..10257a6d69c 100644 --- a/contrib/python/ipython/py3/IPython/sphinxext/ipython_directive.py +++ b/contrib/python/ipython/py3/IPython/sphinxext/ipython_directive.py @@ -581,7 +581,9 @@ class EmbeddedSphinxShell(object):              s += "<<<" + ("-" * 73)              logger.warning(s)              if self.warning_is_error: -                raise RuntimeError('Non Expected exception in `{}` line {}'.format(filename, lineno)) +                raise RuntimeError( +                    "Unexpected exception in `{}` line {}".format(filename, lineno) +                )          # output any warning raised during execution to stdout          # unless :okwarning: has been specified. @@ -597,7 +599,9 @@ class EmbeddedSphinxShell(object):                  s += "<<<" + ("-" * 73)                  logger.warning(s)                  if self.warning_is_error: -                    raise RuntimeError('Non Expected warning in `{}` line {}'.format(filename, lineno)) +                    raise RuntimeError( +                        "Unexpected warning in `{}` line {}".format(filename, lineno) +                    )          self.clear_cout()          return (ret, input_lines, processed_output, diff --git a/contrib/python/ipython/py3/IPython/terminal/debugger.py b/contrib/python/ipython/py3/IPython/terminal/debugger.py index 7a0623c8479..19ed3c7f604 100644 --- a/contrib/python/ipython/py3/IPython/terminal/debugger.py +++ b/contrib/python/ipython/py3/IPython/terminal/debugger.py @@ -10,6 +10,7 @@ from . import embed  from pathlib import Path  from pygments.token import Token +from prompt_toolkit.application import create_app_session  from prompt_toolkit.shortcuts.prompt import PromptSession  from prompt_toolkit.enums import EditingMode  from prompt_toolkit.formatted_text import PygmentsTokens @@ -95,6 +96,17 @@ class TerminalPdb(Pdb):              self.pt_loop = asyncio.new_event_loop()              self.pt_app = PromptSession(**options) +    def _prompt(self): +        """ +        In case other prompt_toolkit apps have to run in parallel to this one (e.g. in madbg), +        create_app_session must be used to prevent mixing up between them. According to the prompt_toolkit docs: + +        > If you need multiple applications running at the same time, you have to create a separate +        > `AppSession` using a `with create_app_session():` block. +        """ +        with create_app_session(): +            return self.pt_app.prompt() +      def cmdloop(self, intro=None):          """Repeatedly issue a prompt, accept input, parse an initial prefix          off the received input, and dispatch to action methods, passing them @@ -128,9 +140,7 @@ class TerminalPdb(Pdb):                      # Run the prompt in a different thread.                      if not _use_simple_prompt:                          try: -                            line = self.thread_executor.submit( -                                self.pt_app.prompt -                            ).result() +                            line = self.thread_executor.submit(self._prompt).result()                          except EOFError:                              line = "EOF"                      else: diff --git a/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py b/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py index 75cf25ea66e..37e0b86981e 100644 --- a/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py +++ b/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py @@ -914,6 +914,15 @@ class TerminalInteractiveShell(InteractiveShell):      active_eventloop = None      def enable_gui(self, gui=None): +        if self.simple_prompt is True and gui is not None: +            print( +                f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.' +            ) +            print( +                "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`." +            ) +            return +          if self._inputhook is None and gui is None:              print("No event loop hook running.")              return diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/osx.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/osx.py index 2754820efc8..9b8a0cd98e2 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/osx.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/osx.py @@ -116,11 +116,8 @@ def _wake(NSApp):      msg(NSApp, n('postEvent:atStart:'), void_p(event), True) -_triggered = Event() -  def _input_callback(fdref, flags, info):      """Callback to fire when there's input to be read""" -    _triggered.set()      CFFileDescriptorInvalidate(fdref)      CFRelease(fdref)      NSApp = _NSApp() @@ -134,7 +131,6 @@ _c_input_callback = _c_callback_func_type(_input_callback)  def _stop_on_read(fd):      """Register callback to stop eventloop when there's data on fd""" -    _triggered.clear()      fdref = CFFileDescriptorCreate(None, fd, False, _c_input_callback, None)      CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack)      source = CFFileDescriptorCreateRunLoopSource(None, fdref, 0) @@ -149,9 +145,3 @@ def inputhook(context):      _stop_on_read(context.fileno())      objc.objc_msgSend.argtypes = [void_p, void_p]      msg(NSApp, n('run')) -    if not _triggered.is_set(): -        # app closed without firing callback, -        # probably due to last window being closed. -        # Run the loop manually in this case, -        # since there may be events still to process (#9734) -        CoreFoundation.CFRunLoopRun() diff --git a/contrib/python/ipython/py3/IPython/utils/_sysinfo.py b/contrib/python/ipython/py3/IPython/utils/_sysinfo.py index 49941f7881e..b3341865eba 100644 --- a/contrib/python/ipython/py3/IPython/utils/_sysinfo.py +++ b/contrib/python/ipython/py3/IPython/utils/_sysinfo.py @@ -1,2 +1,2 @@  # GENERATED BY setup.py -commit = "f11276427" +commit = "bc8b2d22f" diff --git a/contrib/python/ipython/py3/IPython/utils/module_paths.py b/contrib/python/ipython/py3/IPython/utils/module_paths.py index 6f8cb1004a6..401e6a90a5b 100644 --- a/contrib/python/ipython/py3/IPython/utils/module_paths.py +++ b/contrib/python/ipython/py3/IPython/utils/module_paths.py @@ -40,11 +40,13 @@ def find_mod(module_name):      """      Find module `module_name` on sys.path, and return the path to module `module_name`. -      - If `module_name` refers to a module directory, then return path to __init__ file. -        - If `module_name` is a directory without an __init__file, return None. -      - If module is missing or does not have a `.py` or `.pyw` extension, return None. -        - Note that we are not interested in running bytecode. -      - Otherwise, return the fill path of the module. +    * If `module_name` refers to a module directory, then return path to `__init__` file. +        * If `module_name` is a directory without an __init__file, return None. + +    * If module is missing or does not have a `.py` or `.pyw` extension, return None. +        * Note that we are not interested in running bytecode. + +    * Otherwise, return the fill path of the module.      Parameters      ---------- diff --git a/contrib/python/ipython/py3/IPython/utils/tokenutil.py b/contrib/python/ipython/py3/IPython/utils/tokenutil.py index 697d2b504a1..5fd8a1fbe1b 100644 --- a/contrib/python/ipython/py3/IPython/utils/tokenutil.py +++ b/contrib/python/ipython/py3/IPython/utils/tokenutil.py @@ -21,6 +21,36 @@ def generate_tokens(readline):          # catch EOF error          return + +def generate_tokens_catch_errors(readline, extra_errors_to_catch=None): +    default_errors_to_catch = [ +        "unterminated string literal", +        "invalid non-printable character", +        "after line continuation character", +    ] +    assert extra_errors_to_catch is None or isinstance(extra_errors_to_catch, list) +    errors_to_catch = default_errors_to_catch + (extra_errors_to_catch or []) + +    tokens = [] +    try: +        for token in tokenize.generate_tokens(readline): +            tokens.append(token) +            yield token +    except tokenize.TokenError as exc: +        if any(error in exc.args[0] for error in errors_to_catch): +            if tokens: +                start = tokens[-1].start[0], tokens[-1].end[0] +                end = start +                line = tokens[-1].line +            else: +                start = end = (1, 0) +                line = "" +            yield tokenize.TokenInfo(tokenize.ERRORTOKEN, "", start, end, line) +        else: +            # Catch EOF +            raise + +  def line_at_cursor(cell, cursor_pos=0):      """Return the line in a cell at a given cursor position @@ -123,5 +153,3 @@ def token_at_cursor(cell, cursor_pos=0):          return names[-1]      else:          return '' -     - diff --git a/contrib/python/ipython/py3/ya.make b/contrib/python/ipython/py3/ya.make index e9d28face4b..b4eeb0a8300 100644 --- a/contrib/python/ipython/py3/ya.make +++ b/contrib/python/ipython/py3/ya.make @@ -2,7 +2,7 @@  PY3_LIBRARY() -VERSION(8.14.0) +VERSION(8.15.0)  LICENSE(BSD-3-Clause) @@ -81,6 +81,7 @@ PY_SRCS(      IPython/core/magic.py      IPython/core/magic_arguments.py      IPython/core/magics/__init__.py +    IPython/core/magics/ast_mod.py      IPython/core/magics/auto.py      IPython/core/magics/basic.py      IPython/core/magics/code.py  | 
