diff options
author | robot-contrib <robot-contrib@yandex-team.com> | 2023-09-30 10:27:28 +0300 |
---|---|---|
committer | robot-contrib <robot-contrib@yandex-team.com> | 2023-09-30 10:47:10 +0300 |
commit | 5a6373c9d09bbfb7094f9992a4531477bb97829e (patch) | |
tree | ebea8fd55fee858876743312cdf789a1f01487b5 /contrib/python/ipython/py3 | |
parent | 15f3c7493474de25a6b23296878bb8f49470d2e6 (diff) | |
download | ydb-5a6373c9d09bbfb7094f9992a4531477bb97829e.tar.gz |
Update contrib/python/ipython/py3 to 8.15.0
Diffstat (limited to 'contrib/python/ipython/py3')
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 694e2535e3..504e0953c2 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 c8082e34e7..30be9fc0d1 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 aba4f904d8..b411f11613 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 fd5a8680bf..fb67d158ef 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 10707d3d6b..a4401184bd 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 77f69f388f..81cd1fa08c 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 37f0e7699c..949cf383e2 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 7392de7c02..894403f98b 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 99e7ce2918..ab12d10e22 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 0000000000..e28b9f1231 --- /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 814dec72e2..54b0c2a4de 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 228cbd9da7..4147dac963 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 deadf038ea..e5715a9497 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 50080642ee..a1df4d1058 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 61b5939398..cb0d8f951f 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 c428e7917f..10257a6d69 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 7a0623c847..19ed3c7f60 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 75cf25ea66..37e0b86981 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 2754820efc..9b8a0cd98e 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 49941f7881..b3341865eb 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 6f8cb1004a..401e6a90a5 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 697d2b504a..5fd8a1fbe1 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 e9d28face4..b4eeb0a830 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 |