aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/ipython/py3/IPython/core
diff options
context:
space:
mode:
authorrobot-contrib <robot-contrib@yandex-team.com>2023-09-30 10:27:28 +0300
committerrobot-contrib <robot-contrib@yandex-team.com>2023-09-30 10:47:10 +0300
commit5a6373c9d09bbfb7094f9992a4531477bb97829e (patch)
treeebea8fd55fee858876743312cdf789a1f01487b5 /contrib/python/ipython/py3/IPython/core
parent15f3c7493474de25a6b23296878bb8f49470d2e6 (diff)
downloadydb-5a6373c9d09bbfb7094f9992a4531477bb97829e.tar.gz
Update contrib/python/ipython/py3 to 8.15.0
Diffstat (limited to 'contrib/python/ipython/py3/IPython/core')
-rw-r--r--contrib/python/ipython/py3/IPython/core/debugger.py118
-rw-r--r--contrib/python/ipython/py3/IPython/core/displayhook.py7
-rw-r--r--contrib/python/ipython/py3/IPython/core/history.py18
-rw-r--r--contrib/python/ipython/py3/IPython/core/inputsplitter.py18
-rw-r--r--contrib/python/ipython/py3/IPython/core/inputtransformer.py7
-rw-r--r--contrib/python/ipython/py3/IPython/core/inputtransformer2.py46
-rw-r--r--contrib/python/ipython/py3/IPython/core/interactiveshell.py58
-rw-r--r--contrib/python/ipython/py3/IPython/core/logger.py4
-rw-r--r--contrib/python/ipython/py3/IPython/core/magics/ast_mod.py320
-rw-r--r--contrib/python/ipython/py3/IPython/core/magics/basic.py4
-rw-r--r--contrib/python/ipython/py3/IPython/core/magics/execution.py106
-rw-r--r--contrib/python/ipython/py3/IPython/core/pylabtools.py26
-rw-r--r--contrib/python/ipython/py3/IPython/core/release.py2
-rw-r--r--contrib/python/ipython/py3/IPython/core/ultratb.py11
14 files changed, 682 insertions, 63 deletions
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