From 221eb78a8e09cea6c1634ae71fdc8621a2fe05df Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Sat, 14 Sep 2019 20:32:53 +0200 Subject: [PATCH 0001/1407] Initial integration of stack_data --- IPython/core/ultratb.py | 516 +++------------------------------------- 1 file changed, 35 insertions(+), 481 deletions(-) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 9e7e8fb390d..f7fb9e3a226 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -89,23 +89,20 @@ #***************************************************************************** -import dis import inspect -import keyword import linecache -import os import pydoc -import re import sys import time import tokenize import traceback -from tokenize import generate_tokens +import stack_data -# For purposes of monkeypatching inspect to fix a bug in it. -from inspect import getsourcefile, getfile, getmodule, \ - ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode +try: # Python 2 + generate_tokens = tokenize.generate_tokens +except AttributeError: # Python 3 + generate_tokens = tokenize.tokenize # IPython's own modules from IPython import get_ipython @@ -115,13 +112,8 @@ from IPython.utils import PyColorize from IPython.utils import path as util_path from IPython.utils import py3compat -from IPython.utils.data import uniq_stable from IPython.utils.terminal import get_terminal_size -from logging import info, error, debug - -from importlib.util import source_from_cache - import IPython.utils.colorable as colorable # Globals @@ -134,264 +126,26 @@ # to users of ultratb who are NOT running inside ipython. DEFAULT_SCHEME = 'NoColor' - -# Number of frame above which we are likely to have a recursion and will -# **attempt** to detect it. Made modifiable mostly to speedup test suite -# as detecting recursion is one of our slowest test -_FRAME_RECURSION_LIMIT = 500 - # --------------------------------------------------------------------------- # Code begins -# Utility functions -def inspect_error(): - """Print a message about internal inspect errors. - - These are unfortunately quite common.""" - - error('Internal Python error in the inspect module.\n' - 'Below is the traceback from this internal error.\n') - - -# This function is a monkeypatch we apply to the Python inspect module. We have -# now found when it's needed (see discussion on issue gh-1456), and we have a -# test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if -# the monkeypatch is not applied. TK, Aug 2012. -def findsource(object): - """Return the entire source file and starting line number for an object. - - The argument may be a module, class, method, function, traceback, frame, - or code object. The source code is returned as a list of all the lines - in the file and the line number indexes a line in that list. An IOError - is raised if the source code cannot be retrieved. - - FIXED version with which we monkeypatch the stdlib to work around a bug.""" - - file = getsourcefile(object) or getfile(object) - # If the object is a frame, then trying to get the globals dict from its - # module won't work. Instead, the frame object itself has the globals - # dictionary. - globals_dict = None - if inspect.isframe(object): - # XXX: can this ever be false? - globals_dict = object.f_globals - else: - module = getmodule(object, file) - if module: - globals_dict = module.__dict__ - lines = linecache.getlines(file, globals_dict) - if not lines: - raise IOError('could not get source code') - - if ismodule(object): - return lines, 0 - - if isclass(object): - name = object.__name__ - pat = re.compile(r'^(\s*)class\s*' + name + r'\b') - # make some effort to find the best matching class definition: - # use the one with the least indentation, which is the one - # that's most probably not inside a function definition. - candidates = [] - for i, line in enumerate(lines): - match = pat.match(line) - if match: - # if it's at toplevel, it's already the best one - if line[0] == 'c': - return lines, i - # else add whitespace to candidate list - candidates.append((match.group(1), i)) - if candidates: - # this will sort by whitespace, and by line number, - # less whitespace first - candidates.sort() - return lines, candidates[0][1] - else: - raise IOError('could not find class definition') - - if ismethod(object): - object = object.__func__ - if isfunction(object): - object = object.__code__ - if istraceback(object): - object = object.tb_frame - if isframe(object): - object = object.f_code - if iscode(object): - if not hasattr(object, 'co_firstlineno'): - raise IOError('could not find function definition') - pat = re.compile(r'^(\s*def\s)|(.*(? 0: - if pmatch(lines[lnum]): - break - lnum -= 1 - - return lines, lnum - raise IOError('could not find code object') - - -# This is a patched version of inspect.getargs that applies the (unmerged) -# patch for http://bugs.python.org/issue14611 by Stefano Taschini. This fixes -# https://github.com/ipython/ipython/issues/8205 and -# https://github.com/ipython/ipython/issues/8293 -def getargs(co): - """Get information about the arguments accepted by a code object. - - Three things are returned: (args, varargs, varkw), where 'args' is - a list of argument names (possibly containing nested lists), and - 'varargs' and 'varkw' are the names of the * and ** arguments or None.""" - if not iscode(co): - raise TypeError('{!r} is not a code object'.format(co)) - - nargs = co.co_argcount - names = co.co_varnames - args = list(names[:nargs]) - step = 0 - - # The following acrobatics are for anonymous (tuple) arguments. - for i in range(nargs): - if args[i][:1] in ('', '.'): - stack, remain, count = [], [], [] - while step < len(co.co_code): - op = ord(co.co_code[step]) - step = step + 1 - if op >= dis.HAVE_ARGUMENT: - opname = dis.opname[op] - value = ord(co.co_code[step]) + ord(co.co_code[step+1])*256 - step = step + 2 - if opname in ('UNPACK_TUPLE', 'UNPACK_SEQUENCE'): - remain.append(value) - count.append(value) - elif opname in ('STORE_FAST', 'STORE_DEREF'): - if op in dis.haslocal: - stack.append(co.co_varnames[value]) - elif op in dis.hasfree: - stack.append((co.co_cellvars + co.co_freevars)[value]) - # Special case for sublists of length 1: def foo((bar)) - # doesn't generate the UNPACK_TUPLE bytecode, so if - # `remain` is empty here, we have such a sublist. - if not remain: - stack[0] = [stack[0]] - break - else: - remain[-1] = remain[-1] - 1 - while remain[-1] == 0: - remain.pop() - size = count.pop() - stack[-size:] = [stack[-size:]] - if not remain: - break - remain[-1] = remain[-1] - 1 - if not remain: - break - args[i] = stack[0] - - varargs = None - if co.co_flags & inspect.CO_VARARGS: - varargs = co.co_varnames[nargs] - nargs = nargs + 1 - varkw = None - if co.co_flags & inspect.CO_VARKEYWORDS: - varkw = co.co_varnames[nargs] - return inspect.Arguments(args, varargs, varkw) - - -# Monkeypatch inspect to apply our bugfix. -def with_patch_inspect(f): - """ - Deprecated since IPython 6.0 - decorator for monkeypatching inspect.findsource - """ - - def wrapped(*args, **kwargs): - save_findsource = inspect.findsource - save_getargs = inspect.getargs - inspect.findsource = findsource - inspect.getargs = getargs - try: - return f(*args, **kwargs) - finally: - inspect.findsource = save_findsource - inspect.getargs = save_getargs - - return wrapped - - -def fix_frame_records_filenames(records): - """Try to fix the filenames in each record from inspect.getinnerframes(). - - Particularly, modules loaded from within zip files have useless filenames - attached to their code object, and inspect.getinnerframes() just uses it. - """ - fixed_records = [] - for frame, filename, line_no, func_name, lines, index in records: - # Look inside the frame's globals dictionary for __file__, - # which should be better. However, keep Cython filenames since - # we prefer the source filenames over the compiled .so file. - if not filename.endswith(('.pyx', '.pxd', '.pxi')): - better_fn = frame.f_globals.get('__file__', None) - if isinstance(better_fn, str): - # Check the type just in case someone did something weird with - # __file__. It might also be None if the error occurred during - # import. - filename = better_fn - fixed_records.append((frame, filename, line_no, func_name, lines, index)) - return fixed_records - - -@with_patch_inspect -def _fixed_getinnerframes(etb, context=1, tb_offset=0): - LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5 - - records = fix_frame_records_filenames(inspect.getinnerframes(etb, context)) - # If the error is at the console, don't build any context, since it would - # otherwise produce 5 blank lines printed out (there is no file at the - # console) - rec_check = records[tb_offset:] - try: - rname = rec_check[0][1] - if rname == '' or rname.endswith(''): - return rec_check - except IndexError: - pass - - aux = traceback.extract_tb(etb) - assert len(records) == len(aux) - for i, (file, lnum, _, _) in enumerate(aux): - maybeStart = lnum - 1 - context // 2 - start = max(maybeStart, 0) - end = start + context - lines = linecache.getlines(file)[start:end] - buf = list(records[i]) - buf[LNUM_POS] = lnum - buf[INDEX_POS] = lnum - 1 - start - buf[LINES_POS] = lines - records[i] = tuple(buf) - return records[tb_offset:] - # Helper function -- largely belongs to VerboseTB, but we need the same # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they # can be recognized properly by ipython.el's py-traceback-line-re # (SyntaxErrors have to be treated specially because they have no traceback) -def _format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format): +def _format_traceback_lines(lines, Colors, lvals, _line_format): """ Format tracebacks lines with pointing arrow, leading numbers... Parameters ========== - lnum: int - index: int - lines: list[string] + lines: list[Line] Colors: ColorScheme used. - lvals: bytes + lvals: str Values of local variables, already colored, to inject just after the error line. _line_format: f (str) -> (str, bool) return (colorized version of str, failure to do so) @@ -399,80 +153,30 @@ def _format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format): numbers_width = INDENT_SIZE - 1 res = [] - for i,line in enumerate(lines, lnum-index): - line = py3compat.cast_unicode(line) + for stack_line in lines: + line = stack_line.text.rstrip('\n') + '\n' new_line, err = _line_format(line, 'str') if not err: line = new_line - if i == lnum: + lineno = stack_line.lineno + if stack_line.is_current: # This is the line with the error - pad = numbers_width - len(str(i)) - num = '%s%s' % (debugger.make_arrow(pad), str(lnum)) + pad = numbers_width - len(str(lineno)) + num = '%s%s' % (debugger.make_arrow(pad), str(lineno)) line = '%s%s%s %s%s' % (Colors.linenoEm, num, Colors.line, line, Colors.Normal) else: - num = '%*s' % (numbers_width, i) + num = '%*s' % (numbers_width, lineno) line = '%s%s%s %s' % (Colors.lineno, num, Colors.Normal, line) res.append(line) - if lvals and i == lnum: + if lvals and stack_line.is_current: res.append(lvals + '\n') return res -def is_recursion_error(etype, value, records): - try: - # RecursionError is new in Python 3.5 - recursion_error_type = RecursionError - except NameError: - recursion_error_type = RuntimeError - - # The default recursion limit is 1000, but some of that will be taken up - # by stack frames in IPython itself. >500 frames probably indicates - # a recursion error. - return (etype is recursion_error_type) \ - and "recursion" in str(value).lower() \ - and len(records) > _FRAME_RECURSION_LIMIT - -def find_recursion(etype, value, records): - """Identify the repeating stack frames from a RecursionError traceback - - 'records' is a list as returned by VerboseTB.get_records() - - Returns (last_unique, repeat_length) - """ - # This involves a bit of guesswork - we want to show enough of the traceback - # to indicate where the recursion is occurring. We guess that the innermost - # quarter of the traceback (250 frames by default) is repeats, and find the - # first frame (from in to out) that looks different. - if not is_recursion_error(etype, value, records): - return len(records), 0 - - # Select filename, lineno, func_name to track frames with - records = [r[1:4] for r in records] - inner_frames = records[-(len(records)//4):] - frames_repeated = set(inner_frames) - - last_seen_at = {} - longest_repeat = 0 - i = len(records) - for frame in reversed(records): - i -= 1 - if frame not in frames_repeated: - last_unique = i - break - - if frame in last_seen_at: - distance = last_seen_at[frame] - i - longest_repeat = max(longest_repeat, distance) - - last_seen_at[frame] = i - else: - last_unique = 0 # The whole traceback was recursion - - return last_unique, longest_repeat #--------------------------------------------------------------------------- # Module classes @@ -880,63 +584,28 @@ def __init__(self, color_scheme='Linux', call_pdb=False, ostream=None, self.debugger_cls = debugger_cls or debugger.Pdb - def format_records(self, records, last_unique, recursion_repeat): - """Format the stack frames of the traceback""" - frames = [] - for r in records[:last_unique+recursion_repeat+1]: - #print '*** record:',file,lnum,func,lines,index # dbg - frames.append(self.format_record(*r)) - - if recursion_repeat: - frames.append('... last %d frames repeated, from the frame below ...\n' % recursion_repeat) - frames.append(self.format_record(*records[last_unique+recursion_repeat+1])) - - return frames - - def format_record(self, frame, file, lnum, func, lines, index): + def format_record(self, frame_info): """Format a single stack frame""" Colors = self.Colors # just a shorthand + quicker name lookup ColorsNormal = Colors.Normal # used a lot col_scheme = self.color_scheme_table.active_scheme_name indent = ' ' * INDENT_SIZE em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal) - undefined = '%sundefined%s' % (Colors.em, ColorsNormal) tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal) tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal) tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \ (Colors.vName, Colors.valEm, ColorsNormal) tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal) - tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal, - Colors.vName, ColorsNormal) tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal) - if not file: - file = '?' - elif file.startswith(str("<")) and file.endswith(str(">")): - # Not a real filename, no problem... - pass - elif not os.path.isabs(file): - # Try to make the filename absolute by trying all - # sys.path entries (which is also what linecache does) - for dirname in sys.path: - try: - fullname = os.path.join(dirname, file) - if os.path.isfile(fullname): - file = os.path.abspath(fullname) - break - except Exception: - # Just in case that sys.path contains very - # strange entries... - pass - + file = frame_info.filename file = py3compat.cast_unicode(file, util_path.fs_encoding) link = tpl_link % util_path.compress_user(file) - args, varargs, varkw, locals_ = inspect.getargvalues(frame) + args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame) - if func == '?': - call = '' - elif func == '': + func = frame_info.executing.code_qualname() + if func == '': call = tpl_call % (func, '') else: # Decide whether to include variable details or not @@ -964,111 +633,19 @@ def format_record(self, frame, file, lnum, func, lines, index): # disabled. call = tpl_call_fail % func - # Don't attempt to tokenize binary files. - if file.endswith(('.so', '.pyd', '.dll')): - return '%s %s\n' % (link, call) - - elif file.endswith(('.pyc', '.pyo')): - # Look up the corresponding source file. - try: - file = source_from_cache(file) - except ValueError: - # Failed to get the source file for some reason - # E.g. https://github.com/ipython/ipython/issues/9486 - return '%s %s\n' % (link, call) - - def linereader(file=file, lnum=[lnum], getline=linecache.getline): - line = getline(file, lnum[0]) - lnum[0] += 1 - return line - - # Build the list of names on this line of code where the exception - # occurred. - try: - names = [] - name_cont = False - - for token_type, token, start, end, line in generate_tokens(linereader): - # build composite names - if token_type == tokenize.NAME and token not in keyword.kwlist: - if name_cont: - # Continuation of a dotted name - try: - names[-1].append(token) - except IndexError: - names.append([token]) - name_cont = False - else: - # Regular new names. We append everything, the caller - # will be responsible for pruning the list later. It's - # very tricky to try to prune as we go, b/c composite - # names can fool us. The pruning at the end is easy - # to do (or the caller can print a list with repeated - # names if so desired. - names.append([token]) - elif token == '.': - name_cont = True - elif token_type == tokenize.NEWLINE: - break - - except (IndexError, UnicodeDecodeError, SyntaxError): - # signals exit of tokenizer - # SyntaxError can occur if the file is not actually Python - # - see gh-6300 - pass - except tokenize.TokenError as msg: - # Tokenizing may fail for various reasons, many of which are - # harmless. (A good example is when the line in question is the - # close of a triple-quoted string, cf gh-6864). We don't want to - # show this to users, but want make it available for debugging - # purposes. - _m = ("An unexpected error occurred while tokenizing input\n" - "The following traceback may be corrupted or invalid\n" - "The error message is: %s\n" % msg) - debug(_m) - - # Join composite names (e.g. "dict.fromkeys") - names = ['.'.join(n) for n in names] - # prune names list of duplicates, but keep the right order - unique_names = uniq_stable(names) - - # Start loop over vars lvals = '' lvals_list = [] if self.include_vars: - for name_full in unique_names: - name_base = name_full.split('.', 1)[0] - if name_base in frame.f_code.co_varnames: - if name_base in locals_: - try: - value = repr(eval(name_full, locals_)) - except: - value = undefined - else: - value = undefined - name = tpl_local_var % name_full - else: - if name_base in frame.f_globals: - try: - value = repr(eval(name_full, frame.f_globals)) - except: - value = undefined - else: - value = undefined - name = tpl_global_var % name_full - lvals_list.append(tpl_name_val % (name, value)) + for var in frame_info.variables_in_executing_piece: + lvals_list.append(tpl_name_val % (var.name, var.value)) if lvals_list: lvals = '%s%s' % (indent, em_normal.join(lvals_list)) - level = '%s %s\n' % (link, call) + result = '%s %s\n' % (link, call) - if index is None: - return level - else: - _line_format = PyColorize.Parser(style=col_scheme, parent=self).format2 - return '%s%s' % (level, ''.join( - _format_traceback_lines(lnum, index, lines, Colors, lvals, - _line_format))) + _line_format = PyColorize.Parser(style=col_scheme, parent=self).format2 + result += ''.join(_format_traceback_lines(frame_info.lines, Colors, lvals, _line_format)) + return result def prepare_header(self, etype, long_version=False): colors = self.Colors # just a shorthand + quicker name lookup @@ -1123,46 +700,23 @@ def format_exception_as_a_whole(self, etype, evalue, etb, number_of_lines_of_con head = self.prepare_header(etype, self.long_header) records = self.get_records(etb, number_of_lines_of_context, tb_offset) - if records is None: - return "" - - last_unique, recursion_repeat = find_recursion(orig_etype, evalue, records) - - frames = self.format_records(records, last_unique, recursion_repeat) + frames = list(map(self.format_record, records)) formatted_exception = self.format_exception(etype, evalue) if records: - filepath, lnum = records[-1][1:3] - filepath = os.path.abspath(filepath) + frame_info = records[-1] ipinst = get_ipython() if ipinst is not None: - ipinst.hooks.synchronize_with_editor(filepath, lnum, 0) + ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0) return [[head] + frames + [''.join(formatted_exception[0])]] def get_records(self, etb, number_of_lines_of_context, tb_offset): - try: - # Try the default getinnerframes and Alex's: Alex's fixes some - # problems, but it generates empty tracebacks for console errors - # (5 blanks lines) where none should be returned. - return _fixed_getinnerframes(etb, number_of_lines_of_context, tb_offset) - except UnicodeDecodeError: - # This can occur if a file's encoding magic comment is wrong. - # I can't see a way to recover without duplicating a bunch of code - # from the stdlib traceback module. --TK - error('\nUnicodeDecodeError while processing traceback.\n') - return None - except: - # FIXME: I've been getting many crash reports from python 2.3 - # users, traceable to inspect.py. If I can find a small test-case - # to reproduce this, I should either write a better workaround or - # file a bug report against inspect (if that's the real problem). - # So far, I haven't been able to find an isolated example to - # reproduce the problem. - inspect_error() - traceback.print_exc(file=self.ostream) - info('\nUnfortunately, your original traceback can not be constructed.\n') - return None + context = number_of_lines_of_context - 1 + after = context // 2 + before = context - after + options = stack_data.Options(before=before, after=after) + return list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:] def structured_traceback(self, etype, evalue, etb, tb_offset=None, number_of_lines_of_context=5): From 5471d4a427ab6b7486612d89ffbee1d8927f669d Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Thu, 12 Dec 2019 22:56:47 +0200 Subject: [PATCH 0002/1407] Handle stackdata.RepeatedFrames --- IPython/core/ultratb.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index f7fb9e3a226..96a4a48f2db 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -588,6 +588,11 @@ def format_record(self, frame_info): """Format a single stack frame""" Colors = self.Colors # just a shorthand + quicker name lookup ColorsNormal = Colors.Normal # used a lot + + if isinstance(frame_info, stack_data.RepeatedFrames): + return ' %s[... skipping similar frames: %s]%s\n' % ( + Colors.excName, frame_info.description, ColorsNormal) + col_scheme = self.color_scheme_table.active_scheme_name indent = ' ' * INDENT_SIZE em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal) From 245012e25756a7a4ed317e332e2ad47e89c39660 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Thu, 12 Dec 2019 23:10:22 +0200 Subject: [PATCH 0003/1407] Handle line gaps for truncated pieces --- IPython/core/ultratb.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 96a4a48f2db..f7d9fca19b0 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -154,6 +154,10 @@ def _format_traceback_lines(lines, Colors, lvals, _line_format): res = [] for stack_line in lines: + if stack_line is stack_data.LINE_GAP: + res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal)) + continue + line = stack_line.text.rstrip('\n') + '\n' new_line, err = _line_format(line, 'str') From a0dcb06b3bc084e3fe33e797c90ebcbf82bf5b19 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Tue, 17 Dec 2019 20:43:15 +0200 Subject: [PATCH 0004/1407] Fix test_ultratb (particularly recursion-related tests) --- IPython/core/tests/test_ultratb.py | 45 ++++++------------------------ 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py index 3ab0ce3cf00..71af25231d8 100644 --- a/IPython/core/tests/test_ultratb.py +++ b/IPython/core/tests/test_ultratb.py @@ -8,10 +8,8 @@ from textwrap import dedent import traceback import unittest -from unittest import mock -import IPython.core.ultratb as ultratb -from IPython.core.ultratb import ColorTB, VerboseTB, find_recursion +from IPython.core.ultratb import ColorTB, VerboseTB from IPython.testing import tools as tt @@ -38,16 +36,12 @@ def recursionlimit(frames): def inner(test_function): def wrapper(*args, **kwargs): - _orig_rec_limit = ultratb._FRAME_RECURSION_LIMIT - ultratb._FRAME_RECURSION_LIMIT = 50 - rl = sys.getrecursionlimit() sys.setrecursionlimit(frames) try: return test_function(*args, **kwargs) finally: sys.setrecursionlimit(rl) - ultratb._FRAME_RECURSION_LIMIT = _orig_rec_limit return wrapper @@ -350,45 +344,24 @@ def setUp(self): ip.run_cell(self.DEFINITIONS) def test_no_recursion(self): - with tt.AssertNotPrints("frames repeated"): + with tt.AssertNotPrints("skipping similar frames"): ip.run_cell("non_recurs()") @recursionlimit(150) def test_recursion_one_frame(self): - with tt.AssertPrints("1 frames repeated"): + with tt.AssertPrints("[... skipping similar frames: r1 at line 5 (95 times)]"): ip.run_cell("r1()") @recursionlimit(150) def test_recursion_three_frames(self): - with tt.AssertPrints("3 frames repeated"): + with tt.AssertPrints( + "[... skipping similar frames: " + "r3a at line 8 (29 times), " + "r3b at line 11 (29 times), " + "r3c at line 14 (29 times)]" + ): ip.run_cell("r3o2()") - @recursionlimit(150) - def test_find_recursion(self): - captured = [] - def capture_exc(*args, **kwargs): - captured.append(sys.exc_info()) - with mock.patch.object(ip, 'showtraceback', capture_exc): - ip.run_cell("r3o2()") - - self.assertEqual(len(captured), 1) - etype, evalue, tb = captured[0] - self.assertIn("recursion", str(evalue)) - - records = ip.InteractiveTB.get_records(tb, 3, ip.InteractiveTB.tb_offset) - for r in records[:10]: - print(r[1:4]) - - # The outermost frames should be: - # 0: the 'cell' that was running when the exception came up - # 1: r3o2() - # 2: r3o1() - # 3: r3a() - # Then repeating r3b, r3c, r3a - last_unique, repeat_length = find_recursion(etype, evalue, records) - self.assertEqual(last_unique, 2) - self.assertEqual(repeat_length, 3) - #---------------------------------------------------------------------------- From 85d8abf0335ceb909c657895c65fb7cf4ffe1eae Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Tue, 17 Dec 2019 22:08:37 +0200 Subject: [PATCH 0005/1407] Require stack_data --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 593e3a6a0af..9ce9f37c992 100755 --- a/setup.py +++ b/setup.py @@ -193,6 +193,7 @@ 'prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1', 'pygments', 'backcall', + 'stack_data', ] # Platform-specific dependencies: From 4013529c4deeb5a601726905c6da4d2d5cf9e2b0 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Wed, 18 Dec 2019 23:26:08 +0200 Subject: [PATCH 0006/1407] Update doctests. Use repr when showing variables --- IPython/core/tests/test_iplib.py | 17 ++++------------- IPython/core/ultratb.py | 2 +- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/IPython/core/tests/test_iplib.py b/IPython/core/tests/test_iplib.py index adadae56ab9..8f380f93722 100644 --- a/IPython/core/tests/test_iplib.py +++ b/IPython/core/tests/test_iplib.py @@ -65,8 +65,8 @@ def doctest_tb_context(): ZeroDivisionError Traceback (most recent call last) ... in + 29 except IndexError: 30 mode = 'div' - 31 ---> 32 bar(mode) ... in bar(mode) @@ -80,8 +80,6 @@ def doctest_tb_context(): 6 x = 1 7 y = 0 ----> 8 x/y - 9 - 10 def sysexit(stat, mode): ZeroDivisionError: ... """ @@ -97,17 +95,15 @@ def doctest_tb_verbose(): ZeroDivisionError Traceback (most recent call last) ... in + 29 except IndexError: 30 mode = 'div' - 31 ---> 32 bar(mode) - global bar = - global mode = 'div' + mode = 'div' ... in bar(mode='div') 14 "bar" 15 if mode=='div': ---> 16 div0() - global div0 = 17 elif mode=='exit': 18 try: @@ -117,8 +113,6 @@ def doctest_tb_verbose(): ----> 8 x/y x = 1 y = 0 - 9 - 10 def sysexit(stat, mode): ZeroDivisionError: ... """ @@ -154,8 +148,8 @@ def doctest_tb_sysexit(): SystemExit Traceback (most recent call last) ... + 29 except IndexError: 30 mode = 'div' - 31 ---> 32 bar(mode) ...bar(mode) @@ -166,11 +160,8 @@ def doctest_tb_sysexit(): 24 raise ValueError('Unknown mode') ...sysexit(stat, mode) - 9 10 def sysexit(stat, mode): ---> 11 raise SystemExit(stat, 'Mode = %s' % mode) - 12 - 13 def bar(mode): SystemExit: (2, 'Mode = exit') diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index f7d9fca19b0..e8d4fbb6dae 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -646,7 +646,7 @@ def format_record(self, frame_info): lvals_list = [] if self.include_vars: for var in frame_info.variables_in_executing_piece: - lvals_list.append(tpl_name_val % (var.name, var.value)) + lvals_list.append(tpl_name_val % (var.name, repr(var.value))) if lvals_list: lvals = '%s%s' % (indent, em_normal.join(lvals_list)) From f302408307b1fea6e5d2059618d405967ee998e3 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Wed, 18 Dec 2019 23:56:25 +0200 Subject: [PATCH 0007/1407] Remove all tokenizing stuff --- IPython/core/tests/test_ultratb.py | 32 ------------------------------ IPython/core/ultratb.py | 6 ------ 2 files changed, 38 deletions(-) diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py index 71af25231d8..83939ef7ff1 100644 --- a/IPython/core/tests/test_ultratb.py +++ b/IPython/core/tests/test_ultratb.py @@ -405,35 +405,3 @@ def eggs(f, g, z=globals()): except: handler(*sys.exc_info()) buff.write('') - -from IPython.testing.decorators import skipif - -class TokenizeFailureTest(unittest.TestCase): - """Tests related to https://github.com/ipython/ipython/issues/6864.""" - - # that appear to test that we are handling an exception that can be thrown - # by the tokenizer due to a bug that seem to have been fixed in 3.8, though - # I'm unsure if other sequences can make it raise this error. Let's just - # skip in 3.8 for now - @skipif(sys.version_info > (3,8)) - def testLogging(self): - message = "An unexpected error occurred while tokenizing input" - cell = 'raise ValueError("""a\nb""")' - - stream = io.StringIO() - handler = logging.StreamHandler(stream) - logger = logging.getLogger() - loglevel = logger.level - logger.addHandler(handler) - self.addCleanup(lambda: logger.removeHandler(handler)) - self.addCleanup(lambda: logger.setLevel(loglevel)) - - logger.setLevel(logging.INFO) - with tt.AssertNotPrints(message): - ip.run_cell(cell) - self.assertNotIn(message, stream.getvalue()) - - logger.setLevel(logging.DEBUG) - with tt.AssertNotPrints(message): - ip.run_cell(cell) - self.assertIn(message, stream.getvalue()) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index e8d4fbb6dae..fe08e68a8f2 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -94,16 +94,10 @@ import pydoc import sys import time -import tokenize import traceback import stack_data -try: # Python 2 - generate_tokens = tokenize.generate_tokens -except AttributeError: # Python 3 - generate_tokens = tokenize.tokenize - # IPython's own modules from IPython import get_ipython from IPython.core import debugger From 5743d5278ac226b1827d89994ece4b0a5949172a Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Fri, 20 Dec 2019 21:37:50 +0200 Subject: [PATCH 0008/1407] Fix doctest_tb_sysexit --- IPython/core/tests/test_iplib.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/IPython/core/tests/test_iplib.py b/IPython/core/tests/test_iplib.py index 8f380f93722..52fafcae422 100644 --- a/IPython/core/tests/test_iplib.py +++ b/IPython/core/tests/test_iplib.py @@ -173,31 +173,25 @@ def doctest_tb_sysexit(): SystemExit Traceback (most recent call last) ... in + 29 except IndexError: 30 mode = 'div' - 31 ---> 32 bar(mode) - global bar = - global mode = 'exit' + mode = 'exit' ... in bar(mode='exit') 20 except: 21 stat = 1 ---> 22 sysexit(stat, mode) - global sysexit = - stat = 2 mode = 'exit' + stat = 2 23 else: 24 raise ValueError('Unknown mode') ... in sysexit(stat=2, mode='exit') - 9 10 def sysexit(stat, mode): ---> 11 raise SystemExit(stat, 'Mode = %s' % mode) - global SystemExit = undefined stat = 2 mode = 'exit' - 12 - 13 def bar(mode): SystemExit: (2, 'Mode = exit') """ From 6335e7281a9df5966bd62bff0c09494964d14485 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Sat, 21 Dec 2019 00:17:13 +0200 Subject: [PATCH 0009/1407] Allow any number of frame repeats in recursion tests --- IPython/core/tests/test_ultratb.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py index 83939ef7ff1..f17972562ec 100644 --- a/IPython/core/tests/test_ultratb.py +++ b/IPython/core/tests/test_ultratb.py @@ -3,6 +3,7 @@ """ import io import logging +import re import sys import os.path from textwrap import dedent @@ -349,17 +350,19 @@ def test_no_recursion(self): @recursionlimit(150) def test_recursion_one_frame(self): - with tt.AssertPrints("[... skipping similar frames: r1 at line 5 (95 times)]"): + with tt.AssertPrints(re.compile( + r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2} times\)\]") + ): ip.run_cell("r1()") @recursionlimit(150) def test_recursion_three_frames(self): - with tt.AssertPrints( - "[... skipping similar frames: " - "r3a at line 8 (29 times), " - "r3b at line 11 (29 times), " - "r3c at line 14 (29 times)]" - ): + with tt.AssertPrints(re.compile( + r"\[\.\.\. skipping similar frames: " + r"r3a at line 8 \(\d{2} times\), " + r"r3b at line 11 \(\d{2} times\), " + r"r3c at line 14 \(\d{2} times\)\]" + )): ip.run_cell("r3o2()") From 01b1cdfb5ae129a4e5a7fe5e6151fdede0f50aa6 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Sat, 21 Dec 2019 00:31:14 +0200 Subject: [PATCH 0010/1407] Allow repeated frames in any order in test --- IPython/core/tests/test_ultratb.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py index f17972562ec..7e20a4b60b5 100644 --- a/IPython/core/tests/test_ultratb.py +++ b/IPython/core/tests/test_ultratb.py @@ -357,12 +357,10 @@ def test_recursion_one_frame(self): @recursionlimit(150) def test_recursion_three_frames(self): - with tt.AssertPrints(re.compile( - r"\[\.\.\. skipping similar frames: " - r"r3a at line 8 \(\d{2} times\), " - r"r3b at line 11 \(\d{2} times\), " - r"r3c at line 14 \(\d{2} times\)\]" - )): + with tt.AssertPrints("[... skipping similar frames: "), \ + tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \ + tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \ + tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False): ip.run_cell("r3o2()") From a9d2a6869b6a75f7a77061c8691357a8920f9022 Mon Sep 17 00:00:00 2001 From: Inception95 Date: Tue, 11 Feb 2020 15:33:11 -0800 Subject: [PATCH 0011/1407] Pop the last if path ends with slash --- IPython/terminal/ptutils.py | 1 + IPython/terminal/tests/test_interactivshell.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/IPython/terminal/ptutils.py b/IPython/terminal/ptutils.py index 4f21cb04e35..cda26973299 100644 --- a/IPython/terminal/ptutils.py +++ b/IPython/terminal/ptutils.py @@ -42,6 +42,7 @@ def _elide(string, *, min_elide=30): object_parts = string.split('.') file_parts = string.split(os.sep) + file_parts.pop() if file_parts[-1] == '' else None if len(object_parts) > 3: return '{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}'.format(object_parts[0], object_parts[1][0], object_parts[-2][-1], object_parts[-1]) diff --git a/IPython/terminal/tests/test_interactivshell.py b/IPython/terminal/tests/test_interactivshell.py index 9651a224dad..6bacc8ea48a 100644 --- a/IPython/terminal/tests/test_interactivshell.py +++ b/IPython/terminal/tests/test_interactivshell.py @@ -5,6 +5,7 @@ import sys import unittest +import os from IPython.core.inputtransformer import InputTransformer from IPython.testing import tools as tt @@ -19,6 +20,10 @@ def test_elide(self): _elide('concatenate((a1, a2, ...), axis') # do not raise _elide('concatenate((a1, a2, ..), . axis') # do not raise nt.assert_equal(_elide('aaaa.bbbb.ccccc.dddddd.eeeee.fffff.gggggg.hhhhhh'), 'aaaa.b…g.hhhhhh') + + test_string = os.sep.join(['', 10*'a', 10*'b', 10*'c', '']) + expect_stirng = os.sep + 'a' + '\N{HORIZONTAL ELLIPSIS}' + 'b' + os.sep + 10*'c' + nt.assert_equal(_elide(test_string), expect_stirng) class TestContextAwareCompletion(unittest.TestCase): From 44a51949fbaecb61ac9090b80178bfb6bf0dfa75 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 11 Feb 2020 16:11:01 -0800 Subject: [PATCH 0012/1407] Change version number to reflect that we're working toward a 8.x --- IPython/core/release.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index feb09c0e335..b1c61d9bf48 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -19,8 +19,8 @@ # IPython version information. An empty _version_extra corresponds to a full # release. 'dev' as a _version_extra string means this is a development # version -_version_major = 7 -_version_minor = 13 +_version_major = 8 +_version_minor = 0 _version_patch = 0 _version_extra = '.dev' # _version_extra = 'b1' From f7318cfff58b20cae41dcc5b0c05fe8174eb2735 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 12 Feb 2020 08:51:50 -0800 Subject: [PATCH 0013/1407] Stop testing on -dev, they are broken on travis-ci pip is missing on recent builds; and remove duplicate 3.7 testing if we drop -dev suffix --- .travis.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index cccdfb3db14..f65f4fda796 100644 --- a/.travis.yml +++ b/.travis.yml @@ -75,11 +75,7 @@ matrix: env: ARM64=True sudo: true - arch: amd64 - python: "3.8-dev" - dist: xenial - sudo: true - - arch: amd64 - python: "3.7-dev" + python: "3.8" dist: xenial sudo: true - arch: amd64 From 080452149a59d7215575d5ae8493beca375bb9fc Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 12 Feb 2020 09:17:08 -0800 Subject: [PATCH 0014/1407] Revert "Use update brew tap to fix test failures." --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f65f4fda796..def0d930b97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ before_install: if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then env | sort if ! which python$TRAVIS_PYTHON_VERSION; then - HOMEBREW_NO_AUTO_UPDATE=1 brew tap carreau/homebrew-python-frameworks + HOMEBREW_NO_AUTO_UPDATE=1 brew tap minrk/homebrew-python-frameworks HOMEBREW_NO_AUTO_UPDATE=1 brew cask install python-framework-${TRAVIS_PYTHON_VERSION/./} fi python3 -m pip install virtualenv From 1400f5e58a5e5c69fbb5c0e56aaa939ef409a760 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 12 Feb 2020 16:26:43 -0800 Subject: [PATCH 0015/1407] Be more explicit and don't use side effect of expression. --- IPython/terminal/ptutils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/terminal/ptutils.py b/IPython/terminal/ptutils.py index cda26973299..47fd6f40612 100644 --- a/IPython/terminal/ptutils.py +++ b/IPython/terminal/ptutils.py @@ -42,7 +42,8 @@ def _elide(string, *, min_elide=30): object_parts = string.split('.') file_parts = string.split(os.sep) - file_parts.pop() if file_parts[-1] == '' else None + if file_parts[-1] == '': + file_parts.pop() if len(object_parts) > 3: return '{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}'.format(object_parts[0], object_parts[1][0], object_parts[-2][-1], object_parts[-1]) From fb0abc20aa02d30e9b22b9ec4f560ec6151a9235 Mon Sep 17 00:00:00 2001 From: "Coon, Ethan T" Date: Thu, 13 Feb 2020 10:39:40 -0500 Subject: [PATCH 0016/1407] Improves detection of whether tab-completion is in a string and supresses Jedi. Refs #10926 and #11530 Jedi results swamp file_matches and dict_key_matches in tab-completion, which is a real nuisance. The logic in the jedi completor tried to catch cases where it was "in a string", but that logic only looked at the previous character, and was a little fragile, breaking in lots of cases such as: './ "mypath/ etc. This seems a bit more robust in that it searchs for the first token in the current parser tree and checks if its value starts with ' or ". This detection of "in a string" then turns of jedi and returns some sanity to the set of matches. --- IPython/core/completer.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 0942798f3b3..5fd92f377ff 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -1371,18 +1371,18 @@ def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str): try_jedi = True try: - # should we check the type of the node is Error ? + # find the first token in the current tree -- if it is a ' or " then we are in a string + completing_string = False try: - # jedi < 0.11 - from jedi.parser.tree import ErrorLeaf - except ImportError: - # jedi >= 0.11 - from parso.tree import ErrorLeaf + first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value')) + except StopIteration: + pass + else: + # note the value may be ', ", or it may also be ''' or """, or + # in some cases, """what/you/typed..., but all of these are + # strings. + completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'} - next_to_last_tree = interpreter._get_module().tree_node.children[-2] - completing_string = False - if isinstance(next_to_last_tree, ErrorLeaf): - completing_string = next_to_last_tree.value.lstrip()[0] in {'"', "'"} # if we are in a string jedi is likely not the right candidate for # now. Skip it. try_jedi = not completing_string From b5826b358582ef1c7b02acb5de453b41eeab4515 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Fri, 14 Feb 2020 15:29:14 -0700 Subject: [PATCH 0017/1407] Ensure set_custom_completer is working. Fixes #11272 --- IPython/core/completer.py | 4 ++++ IPython/core/interactiveshell.py | 2 +- IPython/core/tests/test_interactiveshell.py | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 0942798f3b3..9a81d637f15 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -626,6 +626,8 @@ def __init__(self, namespace=None, global_namespace=None, **kwargs): else: self.global_namespace = global_namespace + self.custom_matchers = [] + super(Completer, self).__init__(**kwargs) def complete(self, text, state): @@ -1122,12 +1124,14 @@ def matchers(self): if self.use_jedi: return [ + *self.custom_matchers, self.file_matches, self.magic_matches, self.dict_key_matches, ] else: return [ + *self.custom_matchers, self.python_matches, self.file_matches, self.magic_matches, diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 4cbc885cb29..848a14a481c 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2218,7 +2218,7 @@ def set_custom_completer(self, completer, pos=0): list where you want the completer to be inserted.""" newcomp = types.MethodType(completer,self.Completer) - self.Completer.matchers.insert(pos,newcomp) + self.Completer.custom_matchers.insert(pos,newcomp) def set_completer_frame(self, frame=None): """Set the frame of the completer.""" diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index 578ef27d36f..496e3bd02bc 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -998,3 +998,21 @@ def test_should_run_async(): assert not ip.should_run_async("a = 5") assert ip.should_run_async("await x") assert ip.should_run_async("import asyncio; await asyncio.sleep(1)") + + +def test_set_custom_completer(): + num_completers = len(ip.Completer.matchers) + + def foo(*args, **kwargs): + return "I'm a completer!" + + ip.set_custom_completer(foo, 0) + + # check that we've really added a new completer + assert len(ip.Completer.matchers) == num_completers + 1 + + # check that the first completer is the function we defined + assert ip.Completer.matchers[0]() == "I'm a completer!" + + # clean up + ip.Completer.custom_matchers.pop() From e73dcec46701ec3faa074e599dcc998a8e286499 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Fri, 14 Feb 2020 15:51:50 -0700 Subject: [PATCH 0018/1407] increase timeout for ARM tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index def0d930b97..4139cc2b6f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,7 +72,7 @@ matrix: - arch: arm64 python: "3.7" dist: xenial - env: ARM64=True + env: ARM64=True IPYTHON_TESTING_TIMEOUT_SCALE=2 sudo: true - arch: amd64 python: "3.8" From edc1a29fb6095ea0afa95a573ab9888c229d1e18 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Sun, 16 Feb 2020 10:08:30 +0200 Subject: [PATCH 0019/1407] Use Pygments instead of PyColorize to syntax highlight tracebacks --- IPython/core/ultratb.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index fe08e68a8f2..aabb76c02c1 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -97,13 +97,13 @@ import traceback import stack_data +from pygments.formatters.terminal import TerminalFormatter # IPython's own modules from IPython import get_ipython from IPython.core import debugger from IPython.core.display_trap import DisplayTrap from IPython.core.excolors import exception_colors -from IPython.utils import PyColorize from IPython.utils import path as util_path from IPython.utils import py3compat from IPython.utils.terminal import get_terminal_size @@ -129,7 +129,7 @@ # (SyntaxErrors have to be treated specially because they have no traceback) -def _format_traceback_lines(lines, Colors, lvals, _line_format): +def _format_traceback_lines(lines, Colors, lvals): """ Format tracebacks lines with pointing arrow, leading numbers... @@ -141,8 +141,6 @@ def _format_traceback_lines(lines, Colors, lvals, _line_format): ColorScheme used. lvals: str Values of local variables, already colored, to inject just after the error line. - _line_format: f (str) -> (str, bool) - return (colorized version of str, failure to do so) """ numbers_width = INDENT_SIZE - 1 res = [] @@ -152,23 +150,18 @@ def _format_traceback_lines(lines, Colors, lvals, _line_format): res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal)) continue - line = stack_line.text.rstrip('\n') + '\n' - - new_line, err = _line_format(line, 'str') - if not err: - line = new_line - + line = stack_line.render(pygmented=True).rstrip('\n') + '\n' lineno = stack_line.lineno if stack_line.is_current: # This is the line with the error pad = numbers_width - len(str(lineno)) num = '%s%s' % (debugger.make_arrow(pad), str(lineno)) - line = '%s%s%s %s%s' % (Colors.linenoEm, num, - Colors.line, line, Colors.Normal) + start_color = Colors.linenoEm else: num = '%*s' % (numbers_width, lineno) - line = '%s%s%s %s' % (Colors.lineno, num, - Colors.Normal, line) + start_color = Colors.lineno + + line = '%s%s%s %s' % (start_color, num, Colors.Normal, line) res.append(line) if lvals and stack_line.is_current: @@ -591,7 +584,6 @@ def format_record(self, frame_info): return ' %s[... skipping similar frames: %s]%s\n' % ( Colors.excName, frame_info.description, ColorsNormal) - col_scheme = self.color_scheme_table.active_scheme_name indent = ' ' * INDENT_SIZE em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal) tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal) @@ -646,8 +638,7 @@ def format_record(self, frame_info): result = '%s %s\n' % (link, call) - _line_format = PyColorize.Parser(style=col_scheme, parent=self).format2 - result += ''.join(_format_traceback_lines(frame_info.lines, Colors, lvals, _line_format)) + result += ''.join(_format_traceback_lines(frame_info.lines, Colors, lvals)) return result def prepare_header(self, etype, long_version=False): @@ -718,7 +709,7 @@ def get_records(self, etb, number_of_lines_of_context, tb_offset): context = number_of_lines_of_context - 1 after = context // 2 before = context - after - options = stack_data.Options(before=before, after=after) + options = stack_data.Options(before=before, after=after, pygments_formatter=TerminalFormatter()) return list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:] def structured_traceback(self, etype, evalue, etb, tb_offset=None, From bebaa9e29e60adb69658fb983b427fce2e973369 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Sun, 16 Feb 2020 12:53:57 +0200 Subject: [PATCH 0020/1407] Increase recursion limit in test_ultratb since pygments calls get very deep. --- IPython/core/tests/test_ultratb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py index 7e20a4b60b5..209c9c4a423 100644 --- a/IPython/core/tests/test_ultratb.py +++ b/IPython/core/tests/test_ultratb.py @@ -348,14 +348,14 @@ def test_no_recursion(self): with tt.AssertNotPrints("skipping similar frames"): ip.run_cell("non_recurs()") - @recursionlimit(150) + @recursionlimit(200) def test_recursion_one_frame(self): with tt.AssertPrints(re.compile( - r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2} times\)\]") + r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]") ): ip.run_cell("r1()") - @recursionlimit(150) + @recursionlimit(200) def test_recursion_three_frames(self): with tt.AssertPrints("[... skipping similar frames: "), \ tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \ From 71199efeb8fed92f29aee69360a0a7bc4cf97abb Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 16 Feb 2020 19:56:39 +0100 Subject: [PATCH 0021/1407] Use default shortcuts with terminal debugger This uses `create_ipython_shortcuts` also for `TerminalPdb`, which then allows for using e.g. `F2` there also etc. `create_ipython_shortcuts` is used in `TerminalInteractiveShell.init_prompt_toolkit_cli` otherwise only, also passing them to `PromptSession` then only. Therefore this could be done there (in `PromptSession`) by default maybe even. --- IPython/terminal/debugger.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/IPython/terminal/debugger.py b/IPython/terminal/debugger.py index 475c6b8ca94..030e4ae8689 100644 --- a/IPython/terminal/debugger.py +++ b/IPython/terminal/debugger.py @@ -6,7 +6,7 @@ from IPython.core.completer import IPCompleter from .ptutils import IPythonPTCompleter -from .shortcuts import suspend_to_bg, cursor_in_leading_ws +from .shortcuts import create_ipython_shortcuts, suspend_to_bg, cursor_in_leading_ws from prompt_toolkit.enums import DEFAULT_BUFFER from prompt_toolkit.filters import (Condition, has_focus, has_selection, @@ -42,21 +42,10 @@ def get_prompt_tokens(): ) self._ptcomp = IPythonPTCompleter(compl) - kb = KeyBindings() - supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP')) - kb.add('c-z', filter=supports_suspend)(suspend_to_bg) - - if self.shell.display_completions == 'readlinelike': - kb.add('tab', filter=(has_focus(DEFAULT_BUFFER) - & ~has_selection - & vi_insert_mode | emacs_insert_mode - & ~cursor_in_leading_ws - ))(display_completions_like_readline) - options = dict( message=(lambda: PygmentsTokens(get_prompt_tokens())), editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()), - key_bindings=kb, + key_bindings=create_ipython_shortcuts(self.shell), history=self.shell.debugger_history, completer=self._ptcomp, enable_history_search=True, From 534e3c995b1e2744705428706d35f20dabbb1c45 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 20 Feb 2020 15:57:30 -0700 Subject: [PATCH 0022/1407] add configuration controlling whether the CWD gets added to sys.path --- IPython/core/shellapp.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/IPython/core/shellapp.py b/IPython/core/shellapp.py index 3147c59f9f6..33052c44d15 100644 --- a/IPython/core/shellapp.py +++ b/IPython/core/shellapp.py @@ -60,6 +60,10 @@ colours.""", "Disable using colors for info related things." ) +addflag('ignore-cwd', 'InteractiveShellApp.ignore_cwd', + "Exclude the current working directory from sys.path", + "Include the current working directory in sys.path", +) nosep_config = Config() nosep_config.InteractiveShell.separate_in = '' nosep_config.InteractiveShell.separate_out = '' @@ -168,6 +172,12 @@ class InteractiveShellApp(Configurable): When False, pylab mode should not import any names into the user namespace. """ ).tag(config=True) + ignore_cwd = Bool( + False, + help="""If True, IPython will not add the current working directory to sys.path. + When False, the current working directory is added to sys.path, allowing imports + of modules defined in the current directory.""" + ).tag(config=True) shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) # whether interact-loop should start @@ -189,8 +199,10 @@ def init_path(self): .. versionchanged:: 7.2 Try to insert after the standard library, instead of first. + .. versionchanged:: 8.0 + Allow optionally not including the current directory in sys.path """ - if '' in sys.path: + if '' in sys.path or self.ignore_cwd: return for idx, path in enumerate(sys.path): parent, last_part = os.path.split(path) From 261bc98cb1213be2c22eebe35f4fdf77158e365d Mon Sep 17 00:00:00 2001 From: Augusto Date: Thu, 20 Feb 2020 22:26:13 -0300 Subject: [PATCH 0023/1407] Set up automatic release notes --- .github/workflows/release-notes.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/release-notes.yml diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml new file mode 100644 index 00000000000..d895f2d924c --- /dev/null +++ b/.github/workflows/release-notes.yml @@ -0,0 +1,16 @@ +# Trigger the workflow on milestone events +on: + milestone: + types: [closed] +name: New Release +jobs: + create-release-notes: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Create Release Notes + uses: docker://decathlon/release-notes-generator-action:2.0.1 + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_NOTES_TOKEN }} + OUTPUT_FOLDER: temp_release_notes + USE_MILESTONE_TITLE: "true" From 15172451fd652ca7d81b3b990e4eaa2dd53ea16d Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 21 Feb 2020 14:00:49 -0500 Subject: [PATCH 0024/1407] Test that the system() subprocess can be interrupted. --- IPython/utils/tests/test_process.py | 30 ++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index 0c81138cd19..1e03e75e91c 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -16,6 +16,9 @@ import sys import os +import time +from _thread import interrupt_main # Py 3 +import threading import nose.tools as nt @@ -107,6 +110,29 @@ def test_system_quotes(self): status = system('%s -c "import sys"' % python) self.assertEqual(status, 0) + def test_system_interrupt(self): + """ + When interrupted in the way ipykernel interrupts IPython, the + subprocess is interrupted. + """ + if threading.main_thread() != threading.current_thread(): + raise nt.SkipTest("Can't run this test if not in main thread.") + + def interrupt(): + # Wait for subprocess to start: + time.sleep(0.5) + interrupt_main() + + threading.Thread(target=interrupt).start() + try: + status = system('%s -c "import time; time.sleep(5)"' % python) + except KeyboardInterrupt: + # Success! + return + self.assertNotEqual( + status, 0, "The process wasn't interrupted. Status: %s" % (status,) + ) + def test_getoutput(self): out = getoutput('%s "%s"' % (python, self.fname)) # we can't rely on the order the line buffered streams are flushed @@ -131,7 +157,7 @@ def test_getoutput_error(self): out, err = getoutputerror('%s "%s"' % (python, self.fname)) self.assertEqual(out, 'on stdout') self.assertEqual(err, 'on stderr') - + def test_get_output_error_code(self): quiet_exit = '%s -c "import sys; sys.exit(1)"' % python out, err, code = get_output_error_code(quiet_exit) @@ -142,3 +168,5 @@ def test_get_output_error_code(self): self.assertEqual(out, 'on stdout') self.assertEqual(err, 'on stderr') self.assertEqual(code, 0) + + From bfd4fd2f8fdca88d7758a2f7af03b247ec0d9a44 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 21 Feb 2020 16:14:15 -0500 Subject: [PATCH 0025/1407] Also run unit tests, some of them are system specific. --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index b79c7f26ff4..a6f897daed3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -36,3 +36,4 @@ install: - "%CMD_IN_ENV% cd results" test_script: - "%CMD_IN_ENV% iptest --coverage xml" + - "%CMD_IN_ENV% pytest IPython" From c7cb9de69737dc7a3d4a88e4852d1c738d760e5f Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 21 Feb 2020 16:18:17 -0500 Subject: [PATCH 0026/1407] See if test is running on Windows. --- IPython/utils/tests/test_process.py | 1 + appveyor.yml | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index 1e03e75e91c..78f27fb4a5d 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -115,6 +115,7 @@ def test_system_interrupt(self): When interrupted in the way ipykernel interrupts IPython, the subprocess is interrupted. """ + raise RuntimeError("Is this even being run on Windows?") if threading.main_thread() != threading.current_thread(): raise nt.SkipTest("Can't run this test if not in main thread.") diff --git a/appveyor.yml b/appveyor.yml index a6f897daed3..b79c7f26ff4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -36,4 +36,3 @@ install: - "%CMD_IN_ENV% cd results" test_script: - "%CMD_IN_ENV% iptest --coverage xml" - - "%CMD_IN_ENV% pytest IPython" From f34471502949d4d526e1eb1214b8eb1c72ee9c87 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 21 Feb 2020 16:23:05 -0500 Subject: [PATCH 0027/1407] Is it not in main thread? --- IPython/utils/tests/test_process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index 78f27fb4a5d..023e2e75538 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -115,8 +115,8 @@ def test_system_interrupt(self): When interrupted in the way ipykernel interrupts IPython, the subprocess is interrupted. """ - raise RuntimeError("Is this even being run on Windows?") if threading.main_thread() != threading.current_thread(): + raise RuntimeEror("Not in main thread") raise nt.SkipTest("Can't run this test if not in main thread.") def interrupt(): From b156d6954058af82c7b1adff273e6fd48b4dab26 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 21 Feb 2020 16:27:31 -0500 Subject: [PATCH 0028/1407] Make sure it exited quickly. --- IPython/utils/tests/test_process.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index 023e2e75538..e1ae51c9f77 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -116,7 +116,6 @@ def test_system_interrupt(self): subprocess is interrupted. """ if threading.main_thread() != threading.current_thread(): - raise RuntimeEror("Not in main thread") raise nt.SkipTest("Can't run this test if not in main thread.") def interrupt(): @@ -125,14 +124,19 @@ def interrupt(): interrupt_main() threading.Thread(target=interrupt).start() + start = time.time() try: status = system('%s -c "import time; time.sleep(5)"' % python) except KeyboardInterrupt: # Success! return + end = time.time() self.assertNotEqual( status, 0, "The process wasn't interrupted. Status: %s" % (status,) ) + self.assertTrue( + end - start < 2, "Process didn't die quickly: %s" % (end - start) + ) def test_getoutput(self): out = getoutput('%s "%s"' % (python, self.fname)) From 356ea80d838dc945a229d2f830718761d73933e0 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 21 Feb 2020 16:40:05 -0500 Subject: [PATCH 0029/1407] Add interrupt test for getoutput(); disabled for now. --- IPython/utils/tests/test_process.py | 36 +++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index e1ae51c9f77..dc12a7c826a 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -19,6 +19,7 @@ import time from _thread import interrupt_main # Py 3 import threading +from unittest import SkipTest import nose.tools as nt @@ -110,10 +111,9 @@ def test_system_quotes(self): status = system('%s -c "import sys"' % python) self.assertEqual(status, 0) - def test_system_interrupt(self): + def assert_interrupts(self, command): """ - When interrupted in the way ipykernel interrupts IPython, the - subprocess is interrupted. + Interrupt a subprocess after a second. """ if threading.main_thread() != threading.current_thread(): raise nt.SkipTest("Can't run this test if not in main thread.") @@ -126,17 +126,28 @@ def interrupt(): threading.Thread(target=interrupt).start() start = time.time() try: - status = system('%s -c "import time; time.sleep(5)"' % python) + result = command() except KeyboardInterrupt: # Success! return end = time.time() - self.assertNotEqual( - status, 0, "The process wasn't interrupted. Status: %s" % (status,) - ) self.assertTrue( end - start < 2, "Process didn't die quickly: %s" % (end - start) ) + return result + + def test_system_interrupt(self): + """ + When interrupted in the way ipykernel interrupts IPython, the + subprocess is interrupted. + """ + def command(): + return system('%s -c "import time; time.sleep(5)"' % python) + + status = self.assert_interrupts(command) + self.assertNotEqual( + status, 0, "The process wasn't interrupted. Status: %s" % (status,) + ) def test_getoutput(self): out = getoutput('%s "%s"' % (python, self.fname)) @@ -146,6 +157,17 @@ def test_getoutput(self): except AssertionError: self.assertEqual(out, 'on stdouton stderr') + def test_getoutput_interrupt(self): + """ + When interrupted in the way ipykernel interrupts IPython, the + subprocess is interrupted. + """ + raise SkipTest("This fails on POSIX too, revisit in future.") + def command(): + return getoutput('%s -c "import time; time.sleep(5)"' % (python, )) + + self.assert_interrupts(command) + def test_getoutput_quoted(self): out = getoutput('%s -c "print (1)"' % python) self.assertEqual(out.strip(), '1') From 0cc916d47a423c40ddb15208cfd8582573bc08a0 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 21 Feb 2020 16:58:22 -0500 Subject: [PATCH 0030/1407] Attempt to make Popen.wait() interruptible. --- IPython/utils/_process_win32.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/IPython/utils/_process_win32.py b/IPython/utils/_process_win32.py index fb17e4c5d37..b5edd2b53b4 100644 --- a/IPython/utils/_process_win32.py +++ b/IPython/utils/_process_win32.py @@ -21,7 +21,7 @@ from ctypes import c_int, POINTER from ctypes.wintypes import LPCWSTR, HLOCAL -from subprocess import STDOUT +from subprocess import STDOUT, TimeoutExpired # our own imports from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split @@ -100,8 +100,14 @@ def _system_body(p): line = line.decode(enc, 'replace') print(line, file=sys.stderr) - # Wait to finish for returncode - return p.wait() + # Wait to finish for returncode. Unfortunately, Python has a bug where + # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in + # a loop instead of just doing `return p.wait()`. + while True: + try: + return p.wait(0.01) + except TimeoutExpired: + pass def system(cmd): From 080263d2ed5217ce925753acbb6b02702b5d6219 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 21 Feb 2020 17:07:36 -0500 Subject: [PATCH 0031/1407] A different approach, avoiding wait() altogether. --- IPython/utils/_process_win32.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/IPython/utils/_process_win32.py b/IPython/utils/_process_win32.py index b5edd2b53b4..149b5dc095b 100644 --- a/IPython/utils/_process_win32.py +++ b/IPython/utils/_process_win32.py @@ -18,6 +18,7 @@ import os import sys import ctypes +import time from ctypes import c_int, POINTER from ctypes.wintypes import LPCWSTR, HLOCAL @@ -104,10 +105,11 @@ def _system_body(p): # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in # a loop instead of just doing `return p.wait()`. while True: - try: - return p.wait(0.01) - except TimeoutExpired: - pass + result = p.poll() + if result is None: + time.sleep(0.01) + else: + return result def system(cmd): From 660d44f7c4f24ceb2ed7a679e20b929141c0c137 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 21 Feb 2020 17:20:06 -0500 Subject: [PATCH 0032/1407] Debug prints. --- IPython/utils/_process_win32.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/IPython/utils/_process_win32.py b/IPython/utils/_process_win32.py index 149b5dc095b..4bf1543de30 100644 --- a/IPython/utils/_process_win32.py +++ b/IPython/utils/_process_win32.py @@ -94,6 +94,7 @@ def _find_cmd(cmd): def _system_body(p): """Callback for _system.""" enc = DEFAULT_ENCODING + print("READING...") for line in read_no_interrupt(p.stdout).splitlines(): line = line.decode(enc, 'replace') print(line, file=sys.stdout) @@ -106,6 +107,7 @@ def _system_body(p): # a loop instead of just doing `return p.wait()`. while True: result = p.poll() + print("POLLED") if result is None: time.sleep(0.01) else: From 9f40a0695d7348150a7c0144fc474ebd4a24995d Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Sat, 22 Feb 2020 15:13:50 +0200 Subject: [PATCH 0033/1407] Check user config to decide whether to use pygments --- IPython/core/ultratb.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index aabb76c02c1..512594736e7 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -97,7 +97,7 @@ import traceback import stack_data -from pygments.formatters.terminal import TerminalFormatter +from pygments.formatters.terminal256 import Terminal256Formatter # IPython's own modules from IPython import get_ipython @@ -129,7 +129,7 @@ # (SyntaxErrors have to be treated specially because they have no traceback) -def _format_traceback_lines(lines, Colors, lvals): +def _format_traceback_lines(lines, Colors, has_colors, lvals): """ Format tracebacks lines with pointing arrow, leading numbers... @@ -150,7 +150,7 @@ def _format_traceback_lines(lines, Colors, lvals): res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal)) continue - line = stack_line.render(pygmented=True).rstrip('\n') + '\n' + line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n' lineno = stack_line.lineno if stack_line.is_current: # This is the line with the error @@ -246,6 +246,10 @@ def prepare_chained_exception_message(self, cause): message = [[exception_during_handling]] return message + @property + def has_colors(self): + return self.color_scheme_table.active_scheme_name.lower() != "nocolor" + def set_colors(self, *args, **kw): """Shorthand access to the color table scheme selector method.""" @@ -638,7 +642,7 @@ def format_record(self, frame_info): result = '%s %s\n' % (link, call) - result += ''.join(_format_traceback_lines(frame_info.lines, Colors, lvals)) + result += ''.join(_format_traceback_lines(frame_info.lines, Colors, self.has_colors, lvals)) return result def prepare_header(self, etype, long_version=False): @@ -709,7 +713,15 @@ def get_records(self, etb, number_of_lines_of_context, tb_offset): context = number_of_lines_of_context - 1 after = context // 2 before = context - after - options = stack_data.Options(before=before, after=after, pygments_formatter=TerminalFormatter()) + if self.has_colors: + formatter = Terminal256Formatter() + else: + formatter = None + options = stack_data.Options( + before=before, + after=after, + pygments_formatter=formatter, + ) return list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:] def structured_traceback(self, etype, evalue, etb, tb_offset=None, From 4686edc9c88c53dbfcc5433c123bcef5b7544a1e Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 24 Feb 2020 09:29:01 -0500 Subject: [PATCH 0034/1407] Test for filling stderr buffer. --- IPython/utils/tests/test_process.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index dc12a7c826a..a4369e3339e 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -26,6 +26,7 @@ from IPython.utils.process import (find_cmd, FindCmdError, arg_split, system, getoutput, getoutputerror, get_output_error_code) +from IPython.utils.capture import capture_output from IPython.testing import decorators as dec from IPython.testing import tools as tt @@ -149,6 +150,16 @@ def command(): status, 0, "The process wasn't interrupted. Status: %s" % (status,) ) + def test_stderr_while_stdout_open(self): + """ + If lots of data is written to stderr while stdout is still open, enough + data to fill the pipe buffer in fact, the process still exits (i.e. + there is no deadlock). + """ + with capture_output(display=False): + system(("%s -c 'import sys\nfor i in range(2000): " + + "sys.stderr.write(\" \" * 100 + \"\\n\")'") % (python,)) + def test_getoutput(self): out = getoutput('%s "%s"' % (python, self.fname)) # we can't rely on the order the line buffered streams are flushed From 4fc3645982e72ded809d6c484790298363021fc7 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 24 Feb 2020 09:46:39 -0500 Subject: [PATCH 0035/1407] Increase write size. --- IPython/utils/tests/test_process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index a4369e3339e..958888c55d7 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -157,7 +157,7 @@ def test_stderr_while_stdout_open(self): there is no deadlock). """ with capture_output(display=False): - system(("%s -c 'import sys\nfor i in range(2000): " + + system(("%s -c 'import sys\nfor i in range(20000): " + "sys.stderr.write(\" \" * 100 + \"\\n\")'") % (python,)) def test_getoutput(self): From c37a68e233fb4c126c70c99bf75942dedeb14237 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 24 Feb 2020 10:45:36 -0500 Subject: [PATCH 0036/1407] Assert status is OK. --- IPython/utils/tests/test_process.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index 958888c55d7..bae941db855 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -157,8 +157,9 @@ def test_stderr_while_stdout_open(self): there is no deadlock). """ with capture_output(display=False): - system(("%s -c 'import sys\nfor i in range(20000): " + - "sys.stderr.write(\" \" * 100 + \"\\n\")'") % (python,)) + status = system(("%s -c \"import sys\nfor i in range(20000): " + + "sys.stderr.write('b' * 100)\"") % (python,)) + self.assertEqual(status, 0) def test_getoutput(self): out = getoutput('%s "%s"' % (python, self.fname)) From 14a4a46b013f8c865c334b27c6d95b529a9d79b0 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 24 Feb 2020 10:57:56 -0500 Subject: [PATCH 0037/1407] Match pre-existing reality. --- IPython/utils/_process_win32.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/IPython/utils/_process_win32.py b/IPython/utils/_process_win32.py index 4bf1543de30..13a1aedb0b1 100644 --- a/IPython/utils/_process_win32.py +++ b/IPython/utils/_process_win32.py @@ -126,9 +126,7 @@ def system(cmd): Returns ------- - None : we explicitly do NOT return the subprocess status code, as this - utility is meant to be used extensively in IPython, where any return value - would trigger :func:`sys.displayhook` calls. + int : child process' exit code. """ # The controller provides interactivity with both # stdin and stdout From 4b4dbb81ece6875dcae1fa2c31e6458f22f2d979 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Tue, 25 Feb 2020 19:56:37 +0100 Subject: [PATCH 0038/1407] Run ipdb in separate thread (required when using within asyncio applications). --- IPython/terminal/debugger.py | 45 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/IPython/terminal/debugger.py b/IPython/terminal/debugger.py index 475c6b8ca94..96fb33b1440 100644 --- a/IPython/terminal/debugger.py +++ b/IPython/terminal/debugger.py @@ -1,6 +1,7 @@ import asyncio import signal import sys +import threading from IPython.core.debugger import Pdb @@ -81,18 +82,11 @@ def cmdloop(self, intro=None): if not self.use_rawinput: raise ValueError('Sorry ipdb does not support use_rawinput=False') - # In order to make sure that asyncio code written in the - # interactive shell doesn't interfere with the prompt, we run the - # prompt in a different event loop. - # If we don't do this, people could spawn coroutine with a - # while/true inside which will freeze the prompt. - - try: - old_loop = asyncio.get_event_loop() - except RuntimeError: - # This happens when the user used `asyncio.run()`. - old_loop = None - + # In order to make sure that prompt, which uses asyncio doesn't + # interfere with applications in which it's used, we always run the + # prompt itself in a different thread (we can't start an event loop + # within an event loop). This new thread won't have any event loop + # running, and here we run our prompt-loop. self.preloop() @@ -109,14 +103,25 @@ def cmdloop(self, intro=None): self._ptcomp.ipy_completer.namespace = self.curframe_locals self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals - asyncio.set_event_loop(self.pt_loop) - try: - line = self.pt_app.prompt() - except EOFError: - line = 'EOF' - finally: - # Restore the original event loop. - asyncio.set_event_loop(old_loop) + # Run the prompt in a different thread. + line = '' + keyboard_interrupt = False + + def in_thread(): + nonlocal line, keyboard_interrupt + try: + line = self.pt_app.prompt() + except EOFError: + line = 'EOF' + except KeyboardInterrupt: + keyboard_interrupt = True + + th = threading.Thread(target=in_thread) + th.start() + th.join() + + if keyboard_interrupt: + raise KeyboardInterrupt line = self.precmd(line) stop = self.onecmd(line) From f862a7cc1863426c0763734a9ecfbe005ff22aff Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 26 Feb 2020 13:33:30 -0500 Subject: [PATCH 0039/1407] Delete irrelevant tests. --- IPython/utils/tests/test_process.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index bae941db855..f8d4cc34b8d 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -150,17 +150,6 @@ def command(): status, 0, "The process wasn't interrupted. Status: %s" % (status,) ) - def test_stderr_while_stdout_open(self): - """ - If lots of data is written to stderr while stdout is still open, enough - data to fill the pipe buffer in fact, the process still exits (i.e. - there is no deadlock). - """ - with capture_output(display=False): - status = system(("%s -c \"import sys\nfor i in range(20000): " + - "sys.stderr.write('b' * 100)\"") % (python,)) - self.assertEqual(status, 0) - def test_getoutput(self): out = getoutput('%s "%s"' % (python, self.fname)) # we can't rely on the order the line buffered streams are flushed @@ -169,17 +158,6 @@ def test_getoutput(self): except AssertionError: self.assertEqual(out, 'on stdouton stderr') - def test_getoutput_interrupt(self): - """ - When interrupted in the way ipykernel interrupts IPython, the - subprocess is interrupted. - """ - raise SkipTest("This fails on POSIX too, revisit in future.") - def command(): - return getoutput('%s -c "import time; time.sleep(5)"' % (python, )) - - self.assert_interrupts(command) - def test_getoutput_quoted(self): out = getoutput('%s -c "print (1)"' % python) self.assertEqual(out.strip(), '1') From 844c6f764579bb406155db015428dca45798f84b Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 26 Feb 2020 13:35:24 -0500 Subject: [PATCH 0040/1407] Try to avoid blocking in read(). --- IPython/utils/_process_win32.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/IPython/utils/_process_win32.py b/IPython/utils/_process_win32.py index 13a1aedb0b1..6d05bdaa12e 100644 --- a/IPython/utils/_process_win32.py +++ b/IPython/utils/_process_win32.py @@ -23,6 +23,7 @@ from ctypes import c_int, POINTER from ctypes.wintypes import LPCWSTR, HLOCAL from subprocess import STDOUT, TimeoutExpired +from threading import Thread # our own imports from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split @@ -94,20 +95,25 @@ def _find_cmd(cmd): def _system_body(p): """Callback for _system.""" enc = DEFAULT_ENCODING - print("READING...") - for line in read_no_interrupt(p.stdout).splitlines(): - line = line.decode(enc, 'replace') - print(line, file=sys.stdout) - for line in read_no_interrupt(p.stderr).splitlines(): - line = line.decode(enc, 'replace') - print(line, file=sys.stderr) + + def stdout_read(): + for line in read_no_interrupt(p.stdout).splitlines(): + line = line.decode(enc, 'replace') + print(line, file=sys.stdout) + + def stderr_read(): + for line in read_no_interrupt(p.stderr).splitlines(): + line = line.decode(enc, 'replace') + print(line, file=sys.stderr) + + Thread(target=stdout_read).start() + Thread(target=stderr_read).start() # Wait to finish for returncode. Unfortunately, Python has a bug where # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in # a loop instead of just doing `return p.wait()`. while True: result = p.poll() - print("POLLED") if result is None: time.sleep(0.01) else: From 561d08809d08a4473e5a085392678544888fdb61 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 26 Feb 2020 15:06:04 -0500 Subject: [PATCH 0041/1407] Fix the signal handler that was breaking the test --- IPython/utils/tests/test_process.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index f8d4cc34b8d..fbc000c3eeb 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -15,6 +15,7 @@ #----------------------------------------------------------------------------- import sys +import signal import os import time from _thread import interrupt_main # Py 3 @@ -119,6 +120,11 @@ def assert_interrupts(self, command): if threading.main_thread() != threading.current_thread(): raise nt.SkipTest("Can't run this test if not in main thread.") + # Some tests can overwrite SIGINT handler (by using pdb for example), + # which then breaks this test, so just make sure it's operating + # normally. + signal.signal(signal.SIGINT, signal.default_int_handler) + def interrupt(): # Wait for subprocess to start: time.sleep(0.5) @@ -130,7 +136,7 @@ def interrupt(): result = command() except KeyboardInterrupt: # Success! - return + pass end = time.time() self.assertTrue( end - start < 2, "Process didn't die quickly: %s" % (end - start) From 47609e63b2d7e3b7e18b9404b0322584bb3d4a3e Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Mon, 20 Jan 2020 21:41:26 -0800 Subject: [PATCH 0042/1407] Remove clearly marked python 2 imports. --- IPython/core/debugger.py | 6 ++---- IPython/core/tests/test_debugger.py | 5 +---- IPython/lib/display.py | 5 +---- IPython/lib/inputhook.py | 5 +---- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index ebb8dcac0d8..0d0b71e88d8 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -361,10 +361,8 @@ def format_stack_entry(self, frame_lineno, lprefix=': ', context=None): print("Context must be a positive integer", file=self.stdout) except (TypeError, ValueError): print("Context must be a positive integer", file=self.stdout) - try: - import reprlib # Py 3 - except ImportError: - import repr as reprlib # Py 2 + + import reprlib ret = [] diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index dcfd9a42438..665d8f84fa0 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -48,10 +48,7 @@ def __exit__(self, *exc): #----------------------------------------------------------------------------- def test_longer_repr(): - try: - from reprlib import repr as trepr # Py 3 - except ImportError: - from repr import repr as trepr # Py 2 + from reprlib import repr as trepr a = '1234567890'* 7 ar = "'1234567890123456789012345678901234567890123456789012345678901234567890'" diff --git a/IPython/lib/display.py b/IPython/lib/display.py index de31788ab97..d24f455d782 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -266,10 +266,7 @@ def __init__(self, src, width, height, **kwargs): def _repr_html_(self): """return the embed iframe""" if self.params: - try: - from urllib.parse import urlencode # Py 3 - except ImportError: - from urllib import urlencode + from urllib.parse import urlencode params = "?" + urlencode(self.params) else: params = "" diff --git a/IPython/lib/inputhook.py b/IPython/lib/inputhook.py index e6e8f2dbbc7..62f840d23f2 100644 --- a/IPython/lib/inputhook.py +++ b/IPython/lib/inputhook.py @@ -509,10 +509,7 @@ def enable(self, app=None): warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", DeprecationWarning, stacklevel=2) if app is None: - try: - from tkinter import Tk # Py 3 - except ImportError: - from Tkinter import Tk # Py 2 + from tkinter import Tk app = Tk() app.withdraw() self.manager.apps[GUI_TK] = app From 97ebf97b95e2987b44b491ab3891680d6b9b7a2c Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Mon, 20 Jan 2020 21:45:45 -0800 Subject: [PATCH 0043/1407] Remove cProfile fallback import, since it was added in Python2.5 --- IPython/core/magics/execution.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 438d0b5f0ad..e11ce3e3283 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -19,16 +19,8 @@ import re from pdb import Restart -# cProfile was added in Python2.5 -try: - import cProfile as profile - import pstats -except ImportError: - # profile isn't bundled by default in Debian for license reasons - try: - import profile, pstats - except ImportError: - profile = pstats = None +import cProfile as profile +import pstats from IPython.core import oinspect from IPython.core import magic_arguments From c1ce3a2634079a173bfac85d5ea9afaa0d11db36 Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Mon, 20 Jan 2020 21:54:28 -0800 Subject: [PATCH 0044/1407] Remove None check for fallback cProfile import and ExecutionMagics.profile_missing_notice. This is the fallback for the fallback case of python < 2.5 --- IPython/core/magics/execution.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index e11ce3e3283..2b26ebe9688 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -173,17 +173,9 @@ class ExecutionMagics(Magics): def __init__(self, shell): super(ExecutionMagics, self).__init__(shell) - if profile is None: - self.prun = self.profile_missing_notice # Default execution function used to actually run user code. self.default_runner = None - def profile_missing_notice(self, *args, **kwargs): - error("""\ -The profile module could not be found. It has been removed from the standard -python packages because of its non-free license. To use profiling, install the -python-profiler package from non-free.""") - @skip_doctest @no_var_expand @line_cell_magic From 7a0bdabecfa745b60c3a711e1da2df378687e746 Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Mon, 20 Jan 2020 22:00:23 -0800 Subject: [PATCH 0045/1407] Remove sqlite3 fallback imports and associated checks. sqlite3 was add to the stdlib in python2.5 --- IPython/core/history.py | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/IPython/core/history.py b/IPython/core/history.py index 98373f279c9..66b4aeca9bb 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -8,13 +8,7 @@ import datetime import os import re -try: - import sqlite3 -except ImportError: - try: - from pysqlite2 import dbapi2 as sqlite3 - except ImportError: - sqlite3 = None +import sqlite3 import threading from traitlets.config.configurable import LoggingConfigurable @@ -49,26 +43,8 @@ def __exit__(self, *args, **kwargs): pass -@decorator -def needs_sqlite(f, self, *a, **kw): - """Decorator: return an empty list in the absence of sqlite.""" - if sqlite3 is None or not self.enabled: - return [] - else: - return f(self, *a, **kw) - - -if sqlite3 is not None: - DatabaseError = sqlite3.DatabaseError - OperationalError = sqlite3.OperationalError -else: - @undoc - class DatabaseError(Exception): - "Dummy exception when sqlite could not be imported. Should never occur." - - @undoc - class OperationalError(Exception): - "Dummy exception when sqlite could not be imported. Should never occur." +DatabaseError = sqlite3.DatabaseError +OperationalError = sqlite3.OperationalError # use 16kB as threshold for whether a corrupt history db should be saved # that should be at least 100 entries or so @@ -302,7 +278,6 @@ def _run_sql(self, sql, params, raw=True, output=False): return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) return cur - @needs_sqlite @catch_corrupt_db def get_session_info(self, session): """Get info about a session. @@ -558,7 +533,6 @@ def _get_hist_file_name(self, profile=None): profile_dir = self.shell.profile_dir.location return os.path.join(profile_dir, 'history.sqlite') - @needs_sqlite def new_session(self, conn=None): """Get a new session number.""" if conn is None: @@ -769,7 +743,6 @@ def _writeout_output_cache(self, conn): conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", (self.session_number,)+line) - @needs_sqlite def writeout_cache(self, conn=None): """Write any entries in the cache to the database.""" if conn is None: @@ -818,7 +791,6 @@ def __init__(self, history_manager): self.enabled = history_manager.enabled atexit.register(self.stop) - @needs_sqlite def run(self): # We need a separate db connection per thread: try: From cf265cd8bdaef49709baa36f14934d34bbc53e47 Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Mon, 20 Jan 2020 22:06:26 -0800 Subject: [PATCH 0046/1407] Remove unused import of sys. From fe7570442bf8d1021835225cb7f99b8e90c5e448 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 12 Feb 2020 09:11:23 -0800 Subject: [PATCH 0047/1407] revert back ultratb to version on master Stack_data has been introduced in a previous PRs so woudl have a lot of merge conflicts. From 49f973a0f8ea0eabbc4507cfa1d6194f15fd37b7 Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Tue, 25 Feb 2020 21:24:21 -0800 Subject: [PATCH 0048/1407] Remove sqlite3 None checks and simplify. --- IPython/core/history.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/IPython/core/history.py b/IPython/core/history.py index 66b4aeca9bb..7f79b18f9e9 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -19,7 +19,6 @@ Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError, default, observe, ) -from warnings import warn #----------------------------------------------------------------------------- # Classes and functions @@ -43,8 +42,14 @@ def __exit__(self, *args, **kwargs): pass -DatabaseError = sqlite3.DatabaseError -OperationalError = sqlite3.OperationalError +@decorator +def needs_sqlite(f, self, *a, **kw): + """Decorator: return an empty list in the absence of sqlite.""" + if not self.enabled: + return [] + else: + return f(self, *a, **kw) + # use 16kB as threshold for whether a corrupt history db should be saved # that should be at least 100 entries or so @@ -61,7 +66,7 @@ def catch_corrupt_db(f, self, *a, **kw): """ try: return f(self, *a, **kw) - except (DatabaseError, OperationalError) as e: + except (sqlite3.DatabaseError, sqlite3.OperationalError) as e: self._corrupt_db_counter += 1 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e) if self.hist_file != ':memory:': @@ -165,9 +170,7 @@ class HistoryAccessor(HistoryAccessorBase): def _db_changed(self, change): """validate the db, since it can be an Instance of two different types""" new = change['new'] - connection_types = (DummyDB,) - if sqlite3 is not None: - connection_types = (DummyDB, sqlite3.Connection) + connection_types = (DummyDB, sqlite3.Connection) if not isinstance(new, connection_types): msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \ (self.__class__.__name__, new) @@ -197,10 +200,6 @@ def __init__(self, profile='default', hist_file=u'', **traits): if self.hist_file == u'': # No one has set the hist_file, yet. self.hist_file = self._get_hist_file_name(profile) - - if sqlite3 is None and self.enabled: - warn("IPython History requires SQLite, your history will not be saved") - self.enabled = False self.init_db() @@ -278,6 +277,7 @@ def _run_sql(self, sql, params, raw=True, output=False): return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) return cur + @needs_sqlite @catch_corrupt_db def get_session_info(self, session): """Get info about a session. @@ -516,7 +516,7 @@ def __init__(self, shell=None, config=None, **traits): try: self.new_session() - except OperationalError: + except sqlite3.OperationalError: self.log.error("Failed to create history session in %s. History will not be saved.", self.hist_file, exc_info=True) self.hist_file = ':memory:' @@ -533,6 +533,7 @@ def _get_hist_file_name(self, profile=None): profile_dir = self.shell.profile_dir.location return os.path.join(profile_dir, 'history.sqlite') + @needs_sqlite def new_session(self, conn=None): """Get a new session number.""" if conn is None: @@ -743,6 +744,7 @@ def _writeout_output_cache(self, conn): conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", (self.session_number,)+line) + @needs_sqlite def writeout_cache(self, conn=None): """Write any entries in the cache to the database.""" if conn is None: @@ -791,6 +793,7 @@ def __init__(self, history_manager): self.enabled = history_manager.enabled atexit.register(self.stop) + @needs_sqlite def run(self): # We need a separate db connection per thread: try: From 02a3317e701b73326996a8989b95560ced97737d Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Tue, 25 Feb 2020 22:20:35 -0800 Subject: [PATCH 0049/1407] Rename decorator needs_sqlite to only_when_enabled. --- IPython/core/history.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/IPython/core/history.py b/IPython/core/history.py index 7f79b18f9e9..82d4d6e334b 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -43,7 +43,7 @@ def __exit__(self, *args, **kwargs): @decorator -def needs_sqlite(f, self, *a, **kw): +def only_when_enabled(f, self, *a, **kw): """Decorator: return an empty list in the absence of sqlite.""" if not self.enabled: return [] @@ -277,7 +277,7 @@ def _run_sql(self, sql, params, raw=True, output=False): return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) return cur - @needs_sqlite + @only_when_enabled @catch_corrupt_db def get_session_info(self, session): """Get info about a session. @@ -533,7 +533,7 @@ def _get_hist_file_name(self, profile=None): profile_dir = self.shell.profile_dir.location return os.path.join(profile_dir, 'history.sqlite') - @needs_sqlite + @only_when_enabled def new_session(self, conn=None): """Get a new session number.""" if conn is None: @@ -744,7 +744,7 @@ def _writeout_output_cache(self, conn): conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", (self.session_number,)+line) - @needs_sqlite + @only_when_enabled def writeout_cache(self, conn=None): """Write any entries in the cache to the database.""" if conn is None: @@ -793,7 +793,7 @@ def __init__(self, history_manager): self.enabled = history_manager.enabled atexit.register(self.stop) - @needs_sqlite + @only_when_enabled def run(self): # We need a separate db connection per thread: try: From 7b277c4fb582a2bb16e34ab47619a2052654d33e Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Tue, 25 Feb 2020 22:25:31 -0800 Subject: [PATCH 0050/1407] Assume sqlite3 always available in testing. --- IPython/core/tests/test_run.py | 10 ++-------- IPython/core/tests/test_shellapp.py | 7 ++----- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index 38d71b31740..e4f37d55822 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -248,10 +248,7 @@ def test_obj_del(self): " print('object A deleted')\n" "a = A()\n") self.mktmp(src) - if dec.module_not_available('sqlite3'): - err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' - else: - err = None + err = None tt.ipexec_validate(self.fname, 'object A deleted', err) def test_aggressive_namespace_cleanup(self): @@ -306,10 +303,7 @@ def test_tclass(self): tclass.py: deleting object: C-second tclass.py: deleting object: C-third """ - if dec.module_not_available('sqlite3'): - err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' - else: - err = None + err = None tt.ipexec_validate(self.fname, out, err) def test_run_i_after_reset(self): diff --git a/IPython/core/tests/test_shellapp.py b/IPython/core/tests/test_shellapp.py index 6808114b82e..f9ea57825be 100644 --- a/IPython/core/tests/test_shellapp.py +++ b/IPython/core/tests/test_shellapp.py @@ -20,9 +20,6 @@ from IPython.testing import decorators as dec from IPython.testing import tools as tt -sqlite_err_maybe = dec.module_not_available('sqlite3') -SQLITE_NOT_AVAILABLE_ERROR = ('WARNING: IPython History requires SQLite,' - ' your history will not be saved\n') class TestFileToRun(tt.TempFileMixin, unittest.TestCase): """Test the behavior of the file_to_run parameter.""" @@ -32,7 +29,7 @@ def test_py_script_file_attribute(self): src = "print(__file__)\n" self.mktmp(src) - err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None + err = None tt.ipexec_validate(self.fname, self.fname, err) def test_ipy_script_file_attribute(self): @@ -40,7 +37,7 @@ def test_ipy_script_file_attribute(self): src = "print(__file__)\n" self.mktmp(src, ext='.ipy') - err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None + err = None tt.ipexec_validate(self.fname, self.fname, err) # The commands option to ipexec_validate doesn't work on Windows, and it From 54396af2640f615c9a7695b9dd8e8b5fc01f0b70 Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Tue, 25 Feb 2020 22:21:49 -0800 Subject: [PATCH 0051/1407] test_magic: Remove skip_without('sqlite3'). --- IPython/core/tests/test_magic.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 9882d052db1..3d7012c62cd 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -174,7 +174,6 @@ def test_magic_parse_long_options(): nt.assert_equal(opts['bar'], "bubble") -@dec.skip_without('sqlite3') def doctest_hist_f(): """Test %hist -f with temporary filename. @@ -188,7 +187,6 @@ def doctest_hist_f(): """ -@dec.skip_without('sqlite3') def doctest_hist_r(): """Test %hist -r @@ -207,7 +205,6 @@ def doctest_hist_r(): """ -@dec.skip_without('sqlite3') def doctest_hist_op(): """Test %hist -op @@ -285,7 +282,6 @@ def test_hist_pof(): assert os.path.isfile(tf) -@dec.skip_without('sqlite3') def test_macro(): ip = get_ipython() ip.history_manager.reset() # Clear any existing history. @@ -299,7 +295,6 @@ def test_macro(): nt.assert_in("test", ip.magic("macro")) -@dec.skip_without('sqlite3') def test_macro_run(): """Test that we can run a multi-line macro successfully.""" ip = get_ipython() From 383f73aeffcc4e57b500b3f492b42784feb551e5 Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Tue, 25 Feb 2020 22:27:21 -0800 Subject: [PATCH 0052/1407] Remove sqlite3 checks in test setup. --- IPython/testing/iptest.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index 8efcc97201c..a713071b9ea 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -137,7 +137,7 @@ def test_for(item, min_version=None, callback=extract_version): # have available at test run time have = {'matplotlib': test_for('matplotlib'), 'pygments': test_for('pygments'), - 'sqlite3': test_for('sqlite3')} + } #----------------------------------------------------------------------------- # Test suite definitions @@ -176,9 +176,6 @@ def will_run(self): # core: sec = test_sections['core'] -if not have['sqlite3']: - sec.exclude('tests.test_history') - sec.exclude('history') if not have['matplotlib']: sec.exclude('pylabtools'), sec.exclude('tests.test_pylabtools') From 499e491ec57e15c65c7a5274d4d863a069a33f2e Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Tue, 25 Feb 2020 22:40:23 -0800 Subject: [PATCH 0053/1407] remove broken test. Got error: "IPython.core.error.UsageError: unrecognized arguments: -rl" `-r` doesn't appear to be a valid option for %history/%hist. --- IPython/core/tests/test_magic.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 3d7012c62cd..5f58b110e01 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -187,24 +187,6 @@ def doctest_hist_f(): """ -def doctest_hist_r(): - """Test %hist -r - - XXX - This test is not recording the output correctly. For some reason, in - testing mode the raw history isn't getting populated. No idea why. - Disabling the output checking for now, though at least we do run it. - - In [1]: 'hist' in _ip.lsmagic() - Out[1]: True - - In [2]: x=1 - - In [3]: %hist -rl 2 - x=1 # random - %hist -r 2 - """ - - def doctest_hist_op(): """Test %hist -op From b43008515d506c0a8cabe307cf38fe8a25cfd346 Mon Sep 17 00:00:00 2001 From: Terry Davis <16829776+terrdavis@users.noreply.github.com> Date: Tue, 25 Feb 2020 23:04:58 -0800 Subject: [PATCH 0054/1407] remove __future__ import of print function --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 9ce9f37c992..252fe11fa3d 100755 --- a/setup.py +++ b/setup.py @@ -17,8 +17,6 @@ # The full license is in the file COPYING.rst, distributed with this software. #----------------------------------------------------------------------------- -from __future__ import print_function - import os import sys From 023e5b5d3ba3d2f04fbbdd1f911ff2b7f11cefbb Mon Sep 17 00:00:00 2001 From: linar-jether Date: Thu, 27 Feb 2020 02:05:45 -0600 Subject: [PATCH 0055/1407] Fix locals collisions with shell variables Fixes issues with namespace collisions, e.g. : ```python cfg = 'trap' %config IPCompleter.use_jedi = False ``` Causes: `AttributeError: 'str' object has no attribute 'IPCompleter'` fixes https://github.com/ipython/ipython/issues/11835 --- IPython/core/magics/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/magics/config.py b/IPython/core/magics/config.py index 97b13df02e6..b582bdd6eac 100644 --- a/IPython/core/magics/config.py +++ b/IPython/core/magics/config.py @@ -149,7 +149,7 @@ def config(self, s): # leave quotes on args when splitting, because we want # unquoted args to eval in user_ns cfg = Config() - exec("cfg."+line, locals(), self.shell.user_ns) + exec("cfg."+line, self.shell.user_ns, locals()) for configurable in configurables: try: From af3efd58f06cbd5701f267c99f375ff6fda71e57 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Thu, 27 Feb 2020 20:49:47 +0200 Subject: [PATCH 0056/1407] Highlight the executing node with stack_data --- IPython/core/ultratb.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 512594736e7..bea9a518033 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -98,6 +98,7 @@ import stack_data from pygments.formatters.terminal256 import Terminal256Formatter +from pygments.styles import get_style_by_name # IPython's own modules from IPython import get_ipython @@ -714,7 +715,9 @@ def get_records(self, etb, number_of_lines_of_context, tb_offset): after = context // 2 before = context - after if self.has_colors: - formatter = Terminal256Formatter() + style = get_style_by_name('default') + style = stack_data.style_with_executing_node(style, 'bg:#00005f') + formatter = Terminal256Formatter(style=style) else: formatter = None options = stack_data.Options( From 9bc3eae8511f533882cbe8bcbf55b080a782863b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 28 Feb 2020 14:04:55 -0800 Subject: [PATCH 0057/1407] Whats new in 7.13 --- docs/source/whatsnew/version7.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index e9d4ef3ce66..f54f0e5ef0c 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -2,6 +2,28 @@ 7.x Series ============ +.. _version 713: + +IPython 7.13 +============ + +IPython 7.13 is the first release of the 7.x branch since master is diverging +toward an 8.0. Exiting new features have already been merged in 8.0 and will +not be available on the 7.x branch. All the changes bellow have been backported +from the master branch. + + + - Fix inability to run PDB when inside an event loop :ghpull:`12141` + - Fix ability to interrupt some processes on windows :ghpull:`12137` + - Fix debugger shortcuts :ghpull:`12132` + - improve tab completion when inside a string by removing irrelevant elements :ghpull:`12128` + - Fix display of filename tab completion when the path is long :ghpull:`12122` + - Many removal of Python 2 specific code path :ghpull:`12110` + - displaying wav files do not require NumPy anymore, and is 5x to 30x faster :ghpull:`12113` + +See the list of all closed issues and pull request on `github +`_. + .. _version 712: IPython 7.12 From be199d33e191535d7b907cbef9aec0c4fd192597 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 28 Feb 2020 16:36:36 -0800 Subject: [PATCH 0058/1407] update stats --- docs/source/whatsnew/github-stats-7.rst | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/source/whatsnew/github-stats-7.rst b/docs/source/whatsnew/github-stats-7.rst index c2abed72017..91955bb2afa 100644 --- a/docs/source/whatsnew/github-stats-7.rst +++ b/docs/source/whatsnew/github-stats-7.rst @@ -1,6 +1,32 @@ Issues closed in the 7.x development cycle ========================================== +Issues closed in 8.12 +--------------------- + + +GitHub stats for 2020/02/01 - 2020/02/28 (tag: 7.12.0) + +These lists are automatically generated, and may be incomplete or contain duplicates. + +We closed 1 issues and merged 24 pull requests. +The full list can be seen `on GitHub `__ + +The following 12 authors contributed 108 commits. + +* Alex Hall +* Augusto +* Coon, Ethan T +* Daniel Hahler +* Inception95 +* Itamar Turner-Trauring +* Jonas Haag +* Jonathan Slenders +* linar-jether +* Matthias Bussonnier +* Nathan Goldbaum +* Terry Davis + Issues closed in 7.12 --------------------- From 2921bf257484fb0cd8d9e90f3f5e761f9c0e7204 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 29 Feb 2020 15:10:53 -0800 Subject: [PATCH 0059/1407] remove deprecated aliases --- IPython/core/excolors.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/IPython/core/excolors.py b/IPython/core/excolors.py index 487bde18c88..c47ce922c4e 100644 --- a/IPython/core/excolors.py +++ b/IPython/core/excolors.py @@ -164,21 +164,3 @@ def exception_colors(): ex_colors.add_scheme(ex_colors['Linux'].copy('Neutral')) return ex_colors - -class Deprec(object): - - def __init__(self, wrapped_obj): - self.wrapped=wrapped_obj - - def __getattr__(self, name): - val = getattr(self.wrapped, name) - warnings.warn("Using ExceptionColors global is deprecated and will be removed in IPython 6.0", - DeprecationWarning, stacklevel=2) - # using getattr after warnings break ipydoctest in weird way for 3.5 - return val - -# For backwards compatibility, keep around a single global object. Note that -# this should NOT be used, the factory function should be used instead, since -# these objects are stateful and it's very easy to get strange bugs if any code -# modifies the module-level object's state. -ExceptionColors = Deprec(exception_colors()) From 5d39b579262dda44b321fe5b6f4a3dd42fd29f0c Mon Sep 17 00:00:00 2001 From: foobarbyte <61650320+foobarbyte@users.noreply.github.com> Date: Sun, 1 Mar 2020 10:45:52 +0000 Subject: [PATCH 0060/1407] Pass tempfile_suffix argument to PromptSession. This ensures that when the open_in_editor() method of the current buffer (a prompt_toolkit.buffer.Buffer) is called, the tempfile opened in the editor will always have a .py extension, to support editor syntax highlighting. The open_input_in_editor(event) function associated with the f2 keybinding manually sets the tempfile_suffix property of the current buffer before calling its open_in_editor() method. The vi keybinding (v) and emacs mode keybinding (c-x c-e) are handled by prompt_toolkit directly, bypassing this function, so the tempfile created when they are used is a .txt file. Passing the tempfile_suffix argument to the PrompSession constructor ensures that .py extension is always used. --- IPython/terminal/interactiveshell.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 9f7d335ead8..7f18ac2d6bd 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -322,6 +322,7 @@ def prompt(): mouse_support=self.mouse_support, enable_open_in_editor=self.extra_open_editor_shortcuts, color_depth=self.color_depth, + tempfile_suffix=".py", **self._extra_prompt_options()) def _make_style_from_name_or_cls(self, name_or_cls): From ae0cfb2d1cbcf52e992359d2dad1e8cb7ce96286 Mon Sep 17 00:00:00 2001 From: foobarbyte <61650320+foobarbyte@users.noreply.github.com> Date: Sun, 1 Mar 2020 10:48:15 +0000 Subject: [PATCH 0061/1407] Remove redundant setting of tempfile_suffix. Since tempfile_suffix is set in the PromptSession constructor, it does not need to be set when calling the open_input_in_editor function. --- IPython/terminal/shortcuts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py index e44e34277e2..24224d4f804 100644 --- a/IPython/terminal/shortcuts.py +++ b/IPython/terminal/shortcuts.py @@ -250,7 +250,6 @@ def newline_autoindent(event): def open_input_in_editor(event): - event.app.current_buffer.tempfile_suffix = ".py" event.app.current_buffer.open_in_editor() From a0e4b1b4ef4d79e7f9a7cb5d3b089a9fe22fd909 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 2 Mar 2020 14:30:01 -0500 Subject: [PATCH 0062/1407] =?UTF-8?q?Fix=20built-in=20and=20standard=20lib?= =?UTF-8?q?rary=20pdb=20(=F0=9F=99=88)=20so=20they=20are=20interruptible?= =?UTF-8?q?=20from=20Jupyter.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IPython/core/debugger.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 0d0b71e88d8..fff875f2cd0 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -290,7 +290,7 @@ def interaction(self, frame, traceback): try: OldPdb.interaction(self, frame, traceback) except KeyboardInterrupt: - self.stdout.write('\n' + self.shell.get_exception_only()) + raise def new_do_up(self, arg): OldPdb.do_up(self, arg) @@ -627,6 +627,19 @@ def do_where(self, arg): do_w = do_where + def _cmdloop(self): + # Variant that doesn't catch KeyboardInterrupts. + while True: + try: + # keyboard interrupts allow for an easy way to cancel + # the current command, so allow them during interactive input + self.allow_kbdint = True + self.cmdloop() + self.allow_kbdint = False + break + except KeyboardInterrupt: + raise + def set_trace(frame=None): """ @@ -635,3 +648,10 @@ def set_trace(frame=None): If frame is not specified, debugging starts from caller's frame. """ Pdb().set_trace(frame or sys._getframe().f_back) + + +# Override built-in Pdb, since that version doesn't allow interrupting: +import pdb +pdb.set_trace = set_trace +pdb.Pdb = Pdb +del pdb From 901a6507cd0d9cf63d8764ff7285db0baf241176 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 2 Mar 2020 15:36:12 -0500 Subject: [PATCH 0063/1407] Test for debugger interruption. --- IPython/core/debugger.py | 7 ----- IPython/core/tests/test_debugger.py | 43 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index fff875f2cd0..8502874c86b 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -648,10 +648,3 @@ def set_trace(frame=None): If frame is not specified, debugging starts from caller's frame. """ Pdb().set_trace(frame or sys._getframe().f_back) - - -# Override built-in Pdb, since that version doesn't allow interrupting: -import pdb -pdb.set_trace = set_trace -pdb.Pdb = Pdb -del pdb diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 665d8f84fa0..1dfefe9cb46 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -6,6 +6,8 @@ import sys import warnings +from tempfile import NamedTemporaryFile +from subprocess import check_output import nose.tools as nt @@ -220,3 +222,44 @@ def can_exit(): >>> sys.settrace(old_trace) ''' + + +interruptible_debugger = """\ +import threading +import time +from os import _exit + +from IPython.core.debugger import set_trace + +def interrupt(): + time.sleep(0.1) + import os, signal + os.kill(os.getpid(), signal.SIGINT) +threading.Thread(target=interrupt).start() + +# Timeout if the interrupt doesn't happen: +def interrupt(): + time.sleep(2) + _exit(7) +threading.Thread(target=interrupt, daemon=True).start() + +def main(): + set_trace() + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + print("PASSED") +""" + + +def test_interruptible_core_debugger(): + """The debugger can be interrupted.""" + with NamedTemporaryFile("w") as f: + f.write(interruptible_debugger) + f.flush() + result = check_output([sys.executable, f.name], + encoding=sys.getdefaultencoding()) + # Wait for it to start: + assert "PASSED" in result From acf7e595dbc2743f5642018e5ec1f044be697a44 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 6 Mar 2020 09:13:48 -0500 Subject: [PATCH 0064/1407] Add some debugging prints. --- IPython/core/tests/test_debugger.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 1dfefe9cb46..60aca503abf 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -239,7 +239,10 @@ def interrupt(): # Timeout if the interrupt doesn't happen: def interrupt(): - time.sleep(2) + try: + time.sleep(2) + except KeyboardInterrupt: + return _exit(7) threading.Thread(target=interrupt, daemon=True).start() @@ -248,9 +251,14 @@ def main(): if __name__ == '__main__': try: + print("Starting debugger...") main() + print("Debugger exited without error.") except KeyboardInterrupt: - print("PASSED") + print("Caught KeyboardInterrupt, PASSED") + except Exception as e: + print("Got wrong exception...") + raise e """ From f0c34badc558ff1bf54ea3f8b9ea6fff975ff396 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 6 Mar 2020 09:32:35 -0500 Subject: [PATCH 0065/1407] A BdbQuit error is fine too. --- IPython/core/tests/test_debugger.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 60aca503abf..74a14c8b4cb 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -228,6 +228,7 @@ def can_exit(): import threading import time from os import _exit +from bdb import BdbQuit from IPython.core.debugger import set_trace @@ -251,11 +252,11 @@ def main(): if __name__ == '__main__': try: - print("Starting debugger...") + #print("Starting debugger...") main() print("Debugger exited without error.") - except KeyboardInterrupt: - print("Caught KeyboardInterrupt, PASSED") + except (KeyboardInterrupt, BdbQuit): + print("Caught KeyboardInterrupt or BdbQuit, PASSED") except Exception as e: print("Got wrong exception...") raise e From 3eb14a5d04a2c5dd2ded3fe2145116bf992fe9bd Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 6 Mar 2020 09:39:13 -0500 Subject: [PATCH 0066/1407] Windows doesn't let you read files open for writing, I think. --- IPython/core/tests/test_debugger.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 74a14c8b4cb..88eee102de6 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -265,10 +265,10 @@ def main(): def test_interruptible_core_debugger(): """The debugger can be interrupted.""" - with NamedTemporaryFile("w") as f: + with NamedTemporaryFile("w", delete=False) as f: f.write(interruptible_debugger) f.flush() - result = check_output([sys.executable, f.name], - encoding=sys.getdefaultencoding()) - # Wait for it to start: - assert "PASSED" in result + result = check_output([sys.executable, f.name], + encoding=sys.getdefaultencoding()) + # Wait for it to start: + assert "PASSED" in result From 4b28af2bcec563f3c2512a01ff5d4379873bbcf8 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 6 Mar 2020 09:51:31 -0500 Subject: [PATCH 0067/1407] Include stdout in output if the test fails. --- IPython/core/tests/test_debugger.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 88eee102de6..da087a1a06f 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -7,7 +7,7 @@ import sys import warnings from tempfile import NamedTemporaryFile -from subprocess import check_output +from subprocess import check_output, CalledProcessError import nose.tools as nt @@ -268,7 +268,12 @@ def test_interruptible_core_debugger(): with NamedTemporaryFile("w", delete=False) as f: f.write(interruptible_debugger) f.flush() - result = check_output([sys.executable, f.name], - encoding=sys.getdefaultencoding()) + try: + result = check_output([sys.executable, f.name], + encoding=sys.getdefaultencoding()) + except CalledProcessError as e: + print("STDOUT FROM SUBPROCESS:\n{}\n".format(e.stdout), + file=sys.stderr) + raise # Wait for it to start: assert "PASSED" in result From 66dafa1ab81457c09866d6a603ba6706bf0f513b Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 6 Mar 2020 09:59:41 -0500 Subject: [PATCH 0068/1407] Interrupt process in way that ipykernel does it on Windows. --- IPython/core/tests/test_debugger.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index da087a1a06f..0d7612dcffb 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -225,6 +225,7 @@ def can_exit(): interruptible_debugger = """\ +import sys import threading import time from os import _exit @@ -233,9 +234,14 @@ def can_exit(): from IPython.core.debugger import set_trace def interrupt(): + # Try to emulate the way interruption works in ipykernel time.sleep(0.1) - import os, signal - os.kill(os.getpid(), signal.SIGINT) + if sys.platform == "win32": + from _thread import interrupt_main + interrupt_main() + else: + import os, signal + os.kill(os.getpid(), signal.SIGINT) threading.Thread(target=interrupt).start() # Timeout if the interrupt doesn't happen: From ffbdfae99a2b3428ca152259f95c87f423eb5eab Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 10 Mar 2020 12:20:02 -0700 Subject: [PATCH 0069/1407] exclude matpltlib in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4139cc2b6f7..88111acc470 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ install: - pip install setuptools --upgrade - pip install -e file://$PWD#egg=ipython[test] --upgrade - pip install trio curio --upgrade --upgrade-strategy eager - - pip install pytest matplotlib + - pip install pytest 'matplotlib !=3.2.0' - pip install codecov check-manifest --upgrade script: From 6e079503b806fca8d28959d06e6ee087fec5a5e6 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 5 Mar 2020 12:30:22 -0700 Subject: [PATCH 0070/1407] support using '%run -m' for modules importable via an import hook --- IPython/core/magics/execution.py | 5 ++++ IPython/core/tests/test_magic.py | 46 ++++++++++++++++++++++++++++++++ IPython/utils/module_paths.py | 3 +++ 3 files changed, 54 insertions(+) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 2b26ebe9688..fb2df90b5d5 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -692,6 +692,11 @@ def run(self, parameter_s='', runner=None, warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"') error(msg) return + except TypeError: + if fpath in sys.meta_path: + filename = "" + else: + raise if filename.lower().endswith(('.ipy', '.ipynb')): with preserve_keys(self.shell.user_ns, '__file__'): diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 5f58b110e01..e5ad820c18a 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -1198,3 +1198,49 @@ def test_timeit_arguments(): # 3.7 optimize no-op statement like above out, and complain there is # nothing in the for loop. _ip.magic("timeit -n1 -r1 a=('#')") + + +TEST_MODULE = """ +print('Loaded my_tmp') +if __name__ == "__main__": + print('I just ran a script') +""" + + +def test_run_module_from_import_hook(): + "Test that a module can be loaded via an import hook" + with TemporaryDirectory() as tmpdir: + fullpath = os.path.join(tmpdir, 'my_tmp.py') + with open(fullpath, 'w') as f: + f.write(TEST_MODULE) + + class MyTempImporter(object): + def __init__(self): + pass + + def find_module(self, fullname, path=None): + if 'my_tmp' in fullname: + return self + return None + + def load_module(self, name): + import imp + return imp.load_source('my_tmp', fullpath) + + def get_code(self, fullname): + with open(fullpath, 'r') as f: + return compile(f.read(), 'foo', 'exec') + + def is_package(self, __): + return False + + sys.meta_path.insert(0, MyTempImporter()) + + with capture_output() as captured: + _ip.magic("run -m my_tmp") + _ip.run_cell("import my_tmp") + + output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n" + nt.assert_equal(output, captured.stdout) + + sys.meta_path.pop(0) diff --git a/IPython/utils/module_paths.py b/IPython/utils/module_paths.py index 0570c322e6a..32ecab8501d 100644 --- a/IPython/utils/module_paths.py +++ b/IPython/utils/module_paths.py @@ -20,6 +20,7 @@ # Stdlib imports import importlib import os +import sys # Third-party imports @@ -61,6 +62,8 @@ def find_mod(module_name): loader = importlib.util.find_spec(module_name) module_path = loader.origin if module_path is None: + if loader.loader in sys.meta_path: + return loader.loader return None else: split_path = module_path.split(".") From 3c2bf1f13742d02e5b927a770750fcbfe604ff3b Mon Sep 17 00:00:00 2001 From: luttik Date: Tue, 10 Mar 2020 19:11:35 +0100 Subject: [PATCH 0071/1407] Fixed objs in the display.py docstrings. --- IPython/core/display.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index 465c000c55a..3addc1d012b 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -57,7 +57,7 @@ def _display_mimetype(mimetype, objs, raw=False, metadata=None): ---------- mimetype : str The mimetype to be published (e.g. 'image/png') - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw text data to display. raw : bool @@ -139,7 +139,7 @@ def display(*objs, include=None, exclude=None, metadata=None, transient=None, di Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display. raw : bool, optional Are the objects to be displayed already mimetype-keyed dicts of raw display data, @@ -398,7 +398,7 @@ def display_pretty(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw text data to display. raw : bool @@ -418,7 +418,7 @@ def display_html(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw HTML data to display. raw : bool @@ -435,7 +435,7 @@ def display_markdown(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw markdown data to display. raw : bool @@ -453,7 +453,7 @@ def display_svg(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw svg data to display. raw : bool @@ -470,7 +470,7 @@ def display_png(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw png data to display. raw : bool @@ -487,7 +487,7 @@ def display_jpeg(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw JPEG data to display. raw : bool @@ -504,7 +504,7 @@ def display_latex(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw latex data to display. raw : bool @@ -523,7 +523,7 @@ def display_json(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw json data to display. raw : bool @@ -540,7 +540,7 @@ def display_javascript(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw javascript data to display. raw : bool @@ -557,7 +557,7 @@ def display_pdf(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw javascript data to display. raw : bool From 66a30dc18f9397027d8de04c7af05635684c0760 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 16 Mar 2020 14:30:41 -0500 Subject: [PATCH 0072/1407] Sketch of working interrupt mechanism for Windows. Will need to be implemented in ipykernel. --- IPython/core/tests/test_debugger.py | 71 +++++++++++++++++------------ 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 0d7612dcffb..78991999d16 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -4,10 +4,13 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import signal import sys +import time import warnings from tempfile import NamedTemporaryFile -from subprocess import check_output, CalledProcessError +from subprocess import check_output, CalledProcessError, PIPE +import subprocess import nose.tools as nt @@ -233,32 +236,24 @@ def can_exit(): from IPython.core.debugger import set_trace -def interrupt(): - # Try to emulate the way interruption works in ipykernel - time.sleep(0.1) - if sys.platform == "win32": - from _thread import interrupt_main - interrupt_main() - else: - import os, signal - os.kill(os.getpid(), signal.SIGINT) -threading.Thread(target=interrupt).start() - # Timeout if the interrupt doesn't happen: -def interrupt(): - try: - time.sleep(2) - except KeyboardInterrupt: - return +def timeout(): + time.sleep(5) _exit(7) -threading.Thread(target=interrupt, daemon=True).start() +threading.Thread(target=timeout, daemon=True).start() + +def break_handler(*args): + print("BREAK!") + raise KeyboardInterrupt() def main(): + import signal + signal.signal(signal.SIGBREAK, break_handler) set_trace() if __name__ == '__main__': try: - #print("Starting debugger...") + print("Starting debugger...") main() print("Debugger exited without error.") except (KeyboardInterrupt, BdbQuit): @@ -270,16 +265,34 @@ def main(): def test_interruptible_core_debugger(): - """The debugger can be interrupted.""" + """The debugger can be interrupted. + + See https://stackoverflow.com/a/35792192 for details on Windows. + """ with NamedTemporaryFile("w", delete=False) as f: f.write(interruptible_debugger) f.flush() - try: - result = check_output([sys.executable, f.name], - encoding=sys.getdefaultencoding()) - except CalledProcessError as e: - print("STDOUT FROM SUBPROCESS:\n{}\n".format(e.stdout), - file=sys.stderr) - raise - # Wait for it to start: - assert "PASSED" in result + start = time.time() + + p = subprocess.Popen([sys.executable, "-u", f.name], + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, # TODO disable on posix + encoding=sys.getdefaultencoding(), + stderr=PIPE, stdout=PIPE) + time.sleep(1) # wait for it to hit pdb + if sys.platform == "win32": + # Yes, this has to happen once. I have no idea why. + p.send_signal(signal.CTRL_BREAK_EVENT) + p.send_signal(signal.CTRL_BREAK_EVENT) + else: + p.send_signal(signal.SIGINT) + exit_code = p.wait() + stdout = p.stdout.read() + stderr = p.stderr.read() + print("STDOUT", stdout, file=sys.__stderr__) + print("STDERR", stderr, file=sys.__stderr__) + assert exit_code == 0 + print("SUCCESS!", file=sys.__stderr__) + # Make sure it exited cleanly, and quickly: + end = time.time() + assert end - start < 2 # timeout is 5 seconds + assert "PASSED" in stdout \ No newline at end of file From b2bbf3d9929448bbd5e40e4b02397dea23e41657 Mon Sep 17 00:00:00 2001 From: Theo Ouzhinski <32401143+theo-o@users.noreply.github.com> Date: Fri, 20 Mar 2020 12:36:39 -0400 Subject: [PATCH 0073/1407] Fix typo in 7.x series release notes 7.13 is the _final_ release of the 7.x series --- docs/source/whatsnew/version7.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index f54f0e5ef0c..7d122b9f998 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -7,7 +7,7 @@ IPython 7.13 ============ -IPython 7.13 is the first release of the 7.x branch since master is diverging +IPython 7.13 is the final release of the 7.x branch since master is diverging toward an 8.0. Exiting new features have already been merged in 8.0 and will not be available on the 7.x branch. All the changes bellow have been backported from the master branch. From 0b9d5ae4079ba5f9bb0ee74a2f5434306c2856de Mon Sep 17 00:00:00 2001 From: Theo Ouzhinski <32401143+theo-o@users.noreply.github.com> Date: Fri, 20 Mar 2020 12:39:57 -0400 Subject: [PATCH 0074/1407] Fix typo in 7.13 release notes bellow -> below --- docs/source/whatsnew/version7.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 7d122b9f998..62990dfc1e7 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -9,7 +9,7 @@ IPython 7.13 IPython 7.13 is the final release of the 7.x branch since master is diverging toward an 8.0. Exiting new features have already been merged in 8.0 and will -not be available on the 7.x branch. All the changes bellow have been backported +not be available on the 7.x branch. All the changes below have been backported from the master branch. From 1e79c167a41d13eee673b8bb54798cb2f920f957 Mon Sep 17 00:00:00 2001 From: Diego Fernandez Date: Thu, 26 Mar 2020 14:07:22 -0600 Subject: [PATCH 0075/1407] Bump jedi to at least 0.16.0 and fix deprecated function usage --- IPython/core/completer.py | 5 +++-- setup.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 985ac5ab9eb..fd3939f1e18 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -988,7 +988,8 @@ def _make_signature(completion)-> str: """ - return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for p in completion.params) if f]) + return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures() + for p in signature.defined_names()) if f]) class IPCompleter(Completer): """Extension of the completer class with IPython-specific features""" @@ -1398,7 +1399,7 @@ def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str): if not try_jedi: return [] try: - return filter(completion_filter, interpreter.completions()) + return filter(completion_filter, interpreter.complete()) except Exception as e: if self.debug: return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))] diff --git a/setup.py b/setup.py index 252fe11fa3d..9ec67cb7c8e 100755 --- a/setup.py +++ b/setup.py @@ -184,7 +184,7 @@ install_requires = [ 'setuptools>=18.5', - 'jedi>=0.10', + 'jedi>=0.16', 'decorator', 'pickleshare', 'traitlets>=4.2', From d9c0e690a2015544bff683cd2403491a0fa974ba Mon Sep 17 00:00:00 2001 From: Markus Wageringel Date: Thu, 26 Mar 2020 21:04:20 +0100 Subject: [PATCH 0076/1407] support for unicode identifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This rewrites some of the regular expressions that are used to match Python identifiers, so that they are unicode compatible. In Python 3, identifiers can contain unicode characters as long as the first character is not numeric. Examples for the changes: • inputtransformer: ``` In [1]: π = 3.14 In [2]: π.is_integer? Object `is_integer` not found. ``` ---------- • namespace: ``` π.is_integ*? ``` or ``` In [1]: %psearch π.is_integ Python identifiers can only contain ascii characters. ``` ---------- • prefilter: ``` %autocall 1 φ = float get_ipython().prefilter("φ 3") # should be 'φ(3)', but returns 'φ 3' ``` ---------- • completerlib: If there is a file e.g. named `π.py` in the current directory, then ``` import IPython IPython.core.completerlib.module_list('.') # should contain module 'π' ``` --- IPython/core/completerlib.py | 2 +- IPython/core/inputtransformer.py | 4 ++-- IPython/core/inputtransformer2.py | 4 ++-- IPython/core/magics/namespace.py | 6 ------ IPython/core/prefilter.py | 2 +- IPython/core/tests/test_inputtransformer.py | 1 + IPython/core/tests/test_inputtransformer2.py | 8 ++++++++ IPython/core/tests/test_magic.py | 2 ++ IPython/core/tests/test_prefilter.py | 10 ++++++++++ 9 files changed, 27 insertions(+), 12 deletions(-) diff --git a/IPython/core/completerlib.py b/IPython/core/completerlib.py index 9e592b0817e..7860cb67dcb 100644 --- a/IPython/core/completerlib.py +++ b/IPython/core/completerlib.py @@ -52,7 +52,7 @@ TIMEOUT_GIVEUP = 20 # Regular expression for the python import statement -import_re = re.compile(r'(?P[a-zA-Z_][a-zA-Z0-9_]*?)' +import_re = re.compile(r'(?P[^\W\d]\w*?)' r'(?P[/\\]__init__)?' r'(?P%s)$' % r'|'.join(re.escape(s) for s in _suffixes)) diff --git a/IPython/core/inputtransformer.py b/IPython/core/inputtransformer.py index 1c35eb64f32..afeca93cc0e 100644 --- a/IPython/core/inputtransformer.py +++ b/IPython/core/inputtransformer.py @@ -278,8 +278,8 @@ def escaped_commands(line): _initial_space_re = re.compile(r'\s*') _help_end_re = re.compile(r"""(%{0,2} - [a-zA-Z_*][\w*]* # Variable name - (\.[a-zA-Z_*][\w*]*)* # .etc.etc + (?!\d)[\w*]+ # Variable name + (\.(?!\d)[\w*]+)* # .etc.etc ) (\?\??)$ # ? or ?? """, diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 4562fe01d2c..0443e6829b4 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -405,8 +405,8 @@ def transform(self, lines): return lines_before + [new_line] + lines_after _help_end_re = re.compile(r"""(%{0,2} - [a-zA-Z_*][\w*]* # Variable name - (\.[a-zA-Z_*][\w*]*)* # .etc.etc + (?!\d)[\w*]+ # Variable name + (\.(?!\d)[\w*]+)* # .etc.etc ) (\?\??)$ # ? or ?? """, diff --git a/IPython/core/magics/namespace.py b/IPython/core/magics/namespace.py index cef6ddba8d7..acc4620549b 100644 --- a/IPython/core/magics/namespace.py +++ b/IPython/core/magics/namespace.py @@ -208,12 +208,6 @@ def psearch(self, parameter_s=''): %psearch -l list all available object types """ - try: - parameter_s.encode('ascii') - except UnicodeEncodeError: - print('Python identifiers can only contain ascii characters.') - return - # default namespaces to be searched def_search = ['user_local', 'user_global', 'builtin'] diff --git a/IPython/core/prefilter.py b/IPython/core/prefilter.py index dbf185e6a42..bf801f999c4 100644 --- a/IPython/core/prefilter.py +++ b/IPython/core/prefilter.py @@ -37,7 +37,7 @@ class PrefilterError(Exception): # RegExp to identify potential function names -re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$') +re_fun_name = re.compile(r'[^\W\d]([\w.]*) *$') # RegExp to exclude strings with this start from autocalling. In # particular, all binary operators should be excluded, so that if foo is diff --git a/IPython/core/tests/test_inputtransformer.py b/IPython/core/tests/test_inputtransformer.py index 90a1d5afd1e..0d97fd4d6b1 100644 --- a/IPython/core/tests/test_inputtransformer.py +++ b/IPython/core/tests/test_inputtransformer.py @@ -113,6 +113,7 @@ def transform_checker(tests, transformer, **kwargs): (u'%hist2??', "get_ipython().run_line_magic('pinfo2', '%hist2')"), (u'%%hist3?', "get_ipython().run_line_magic('pinfo', '%%hist3')"), (u'%%hist4??', "get_ipython().run_line_magic('pinfo2', '%%hist4')"), + (u'π.foo?', "get_ipython().run_line_magic('pinfo', 'π.foo')"), (u'f*?', "get_ipython().run_line_magic('psearch', 'f*')"), (u'ax.*aspe*?', "get_ipython().run_line_magic('psearch', 'ax.*aspe*')"), (u'a = abc?', "get_ipython().set_next_input('a = abc');" diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index cde9eca0d06..b29a0196d3f 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -119,6 +119,11 @@ def test(): [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"] ) +HELP_UNICODE = ( + ["π.foo?\n"], (1, 0), + ["get_ipython().run_line_magic('pinfo', 'π.foo')\n"] +) + def null_cleanup_transformer(lines): """ @@ -223,6 +228,9 @@ def test_transform_help(): tf = ipt2.HelpEnd((1, 0), (2, 8)) nt.assert_equal(tf.transform(HELP_MULTILINE[0]), HELP_MULTILINE[2]) + tf = ipt2.HelpEnd((1, 0), (1, 0)) + nt.assert_equal(tf.transform(HELP_UNICODE[0]), HELP_UNICODE[2]) + def test_find_assign_op_dedent(): """ be careful that empty token like dedent are not counted as parens diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index e5ad820c18a..56feb82af71 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -602,6 +602,8 @@ def doctest_precision(): def test_psearch(): with tt.AssertPrints("dict.fromkeys"): _ip.run_cell("dict.fr*?") + with tt.AssertPrints("π.is_integer"): + _ip.run_cell("π = 3.14;\nπ.is_integ*?") def test_timeit_shlex(): """test shlex issues with timeit (#1109)""" diff --git a/IPython/core/tests/test_prefilter.py b/IPython/core/tests/test_prefilter.py index 0e61b4693f7..ca447b3d0b7 100644 --- a/IPython/core/tests/test_prefilter.py +++ b/IPython/core/tests/test_prefilter.py @@ -115,3 +115,13 @@ def __call__(self, x): finally: del ip.user_ns['x'] ip.magic('autocall 0') + + +def test_autocall_should_support_unicode(): + ip.magic('autocall 2') + ip.user_ns['π'] = lambda x: x + try: + nt.assert_equal(ip.prefilter('π 3'),'π(3)') + finally: + ip.magic('autocall 0') + del ip.user_ns['π'] From d394d7e451e94ada5a876262e7f61ec3e48a0668 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 28 Mar 2020 11:49:23 -0700 Subject: [PATCH 0077/1407] update link to rpy2 object inventory --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index afdff72b5c4..3dde0f3ecfb 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -223,7 +223,7 @@ def is_stable(extra): htmlhelp_basename = 'ipythondoc' intersphinx_mapping = {'python': ('https://docs.python.org/3/', None), - 'rpy2': ('https://rpy2.readthedocs.io/en/latest/', None), + 'rpy2': ('https://rpy2.github.io/doc/latest/html/', None), 'jupyterclient': ('https://jupyter-client.readthedocs.io/en/latest/', None), 'jupyter': ('https://jupyter.readthedocs.io/en/latest/', None), 'jedi': ('https://jedi.readthedocs.io/en/latest/', None), From 3f0f9d507593edbbb6348a05976faea5bef1ed22 Mon Sep 17 00:00:00 2001 From: Matthieu Ancellin Date: Mon, 30 Mar 2020 14:29:43 +0200 Subject: [PATCH 0078/1407] Add option for video html display. --- IPython/core/display.py | 18 ++++++++++++------ .../whatsnew/pr/video-display-attributes.rst | 4 ++++ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 docs/source/whatsnew/pr/video-display-attributes.rst diff --git a/IPython/core/display.py b/IPython/core/display.py index 3addc1d012b..507fd304df5 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -1308,7 +1308,7 @@ def _find_ext(self, s): class Video(DisplayObject): def __init__(self, data=None, url=None, filename=None, embed=False, - mimetype=None, width=None, height=None): + mimetype=None, width=None, height=None, html_attributes="controls"): """Create a video object given raw data or an URL. When this object is returned by an input cell or passed to the @@ -1346,6 +1346,11 @@ def __init__(self, data=None, url=None, filename=None, embed=False, height : int Height in pixels to which to constrain the video in html. If not supplied, defaults to the height of the video. + html_attributes : str + Attributes for the HTML `