diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 89eae383dd2..a330baa450e 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -280,26 +280,31 @@ def __init__(self, color_scheme=None, completekey=None, # Set the prompt - the default prompt is '(Pdb)' self.prompt = prompt + self.skip_hidden = True def set_colors(self, scheme): """Shorthand access to the color table scheme selector method.""" self.color_scheme_table.set_active_scheme(scheme) self.parser.style = scheme + + def hidden_frames(self, stack): + """ + Given an index in the stack return wether it should be skipped. + + This is used in up/down and where to skip frames. + """ + ip_hide = [s[0].f_locals.get("__tracebackhide__", False) for s in stack] + ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"] + if ip_start: + ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)] + return ip_hide + def interaction(self, frame, traceback): try: OldPdb.interaction(self, frame, traceback) except KeyboardInterrupt: - self.stdout.write('\n' + self.shell.get_exception_only()) - - def new_do_up(self, arg): - OldPdb.do_up(self, arg) - do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up) - - def new_do_down(self, arg): - OldPdb.do_down(self, arg) - - do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down) + self.stdout.write("\n" + self.shell.get_exception_only()) def new_do_frame(self, arg): OldPdb.do_frame(self, arg) @@ -320,6 +325,8 @@ def new_do_restart(self, arg): return self.do_quit(arg) def print_stack_trace(self, context=None): + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal if context is None: context = self.context try: @@ -329,8 +336,21 @@ def print_stack_trace(self, context=None): except (TypeError, ValueError): raise ValueError("Context must be a positive integer") try: - for frame_lineno in self.stack: + skipped = 0 + for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack): + if hidden and self.skip_hidden: + skipped += 1 + continue + if skipped: + print( + f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + skipped = 0 self.print_stack_entry(frame_lineno, context=context) + if skipped: + print( + f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" + ) except KeyboardInterrupt: pass @@ -487,6 +507,16 @@ def print_list_lines(self, filename, first, last): except KeyboardInterrupt: pass + def do_skip_hidden(self, arg): + """ + Change whether or not we should skip frames with the + __tracebackhide__ attribute. + """ + if arg.strip().lower() in ("true", "yes"): + self.skip_hidden = True + elif arg.strip().lower() in ("false", "no"): + self.skip_hidden = False + def do_list(self, arg): """Print lines of code from the current stack frame """ @@ -633,6 +663,109 @@ def do_where(self, arg): do_w = do_where + def stop_here(self, frame): + hidden = False + if self.skip_hidden: + hidden = frame.f_locals.get("__tracebackhide__", False) + if hidden: + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + print(f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n") + + return super().stop_here(frame) + + def do_up(self, arg): + """u(p) [count] + Move the current frame count (default one) levels up in the + stack trace (to an older frame). + + Will skip hidden frames. + """ + ## modified version of upstream that skips + # frames with __tracebackide__ + if self.curindex == 0: + self.error("Oldest frame") + return + try: + count = int(arg or 1) + except ValueError: + self.error("Invalid frame count (%s)" % arg) + return + skipped = 0 + if count < 0: + _newframe = 0 + else: + _newindex = self.curindex + counter = 0 + hidden_frames = self.hidden_frames(self.stack) + for i in range(self.curindex - 1, -1, -1): + frame = self.stack[i][0] + if hidden_frames[i] and self.skip_hidden: + skipped += 1 + continue + counter += 1 + if counter >= count: + break + else: + # if no break occured. + self.error("all frames above hidden") + return + + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + _newframe = i + self._select_frame(_newframe) + if skipped: + print( + f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + + def do_down(self, arg): + """d(own) [count] + Move the current frame count (default one) levels down in the + stack trace (to a newer frame). + + Will skip hidden frames. + """ + if self.curindex + 1 == len(self.stack): + self.error("Newest frame") + return + try: + count = int(arg or 1) + except ValueError: + self.error("Invalid frame count (%s)" % arg) + return + if count < 0: + _newframe = len(self.stack) - 1 + else: + _newindex = self.curindex + counter = 0 + skipped = 0 + hidden_frames = self.hidden_frames(self.stack) + for i in range(self.curindex + 1, len(self.stack)): + frame = self.stack[i][0] + if hidden_frames[i] and self.skip_hidden: + skipped += 1 + continue + counter += 1 + if counter >= count: + break + else: + self.error("all frames bellow hidden") + return + + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + if skipped: + print( + f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + _newframe = i + + self._select_frame(_newframe) + + do_d = do_down + do_u = do_up class InterruptiblePdb(Pdb): """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index f5fdba6692b..ddb1b64ea78 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2211,11 +2211,20 @@ def complete(self, text, line=None, cursor_pos=None): with self.builtin_trap: return self.Completer.complete(text, line, cursor_pos) - def set_custom_completer(self, completer, pos=0): + def set_custom_completer(self, completer, pos=0) -> None: """Adds a new custom completer function. The position argument (defaults to 0) is the index in the completers - list where you want the completer to be inserted.""" + list where you want the completer to be inserted. + + `completer` should have the following signature:: + + def completion(self: Completer, text: string) -> List[str]: + raise NotImplementedError + + It will be bound to the current Completer instance and pass some text + and return a list with current completions to suggest to the user. + """ newcomp = types.MethodType(completer, self.Completer) self.Completer.custom_matchers.insert(pos,newcomp) @@ -3310,6 +3319,9 @@ async def run_code(self, code_obj, result=None, *, async_=False): False : successful execution. True : an error occurred. """ + # special value to say that anything above is IPython and should be + # hidden. + __tracebackhide__ = "__ipython_bottom__" # Set our own excepthook in case the user code tries to call it # directly, so that the IPython crash handler doesn't get triggered old_excepthook, sys.excepthook = sys.excepthook, self.excepthook diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index 5c8649b5408..a8feb755386 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -364,13 +364,25 @@ def xmode(self, parameter_s=''): Valid modes: Plain, Context, Verbose, and Minimal. - If called without arguments, acts as a toggle.""" + If called without arguments, acts as a toggle. + + When in verbose mode the value --show (and --hide) + will respectively show (or hide) frames with ``__tracebackhide__ = + True`` value set. + """ def xmode_switch_err(name): warn('Error changing %s exception modes.\n%s' % (name,sys.exc_info()[1])) shell = self.shell + if parameter_s.strip() == "--show": + shell.InteractiveTB.skip_hidden = False + return + if parameter_s.strip() == "--hide": + shell.InteractiveTB.skip_hidden = True + return + new_mode = parameter_s.strip().capitalize() try: shell.InteractiveTB.set_mode(mode=new_mode) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 438d0b5f0ad..dc6cdf00e29 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -688,17 +688,16 @@ def run(self, parameter_s='', runner=None, modulename = opts["m"][0] modpath = find_mod(modulename) if modpath is None: - warn('%r is not a valid modulename on sys.path'%modulename) - return + msg = '%r is not a valid modulename on sys.path'%modulename + raise Exception(msg) arg_lst = [modpath] + arg_lst try: fpath = None # initialize to make sure fpath is in scope later fpath = arg_lst[0] filename = file_finder(fpath) except IndexError: - warn('you must provide at least a filename.') - print('\n%run:\n', oinspect.getdoc(self.run)) - return + msg = 'you must provide at least a filename.' + raise Exception(msg) except IOError as e: try: msg = str(e) @@ -706,13 +705,17 @@ def run(self, parameter_s='', runner=None, msg = e.message if os.name == 'nt' and re.match(r"^'.*'$",fpath): warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"') - error(msg) - return + raise Exception(msg) + 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__'): self.shell.user_ns['__file__'] = filename - self.shell.safe_execfile_ipy(filename) + self.shell.safe_execfile_ipy(filename, raise_exceptions=True) return # Control the response to exit() calls made by the script being run diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index ddc9b6f5874..90da7e22803 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -25,6 +25,7 @@ from IPython.utils.process import abbrev_cwd from IPython.utils.terminal import set_term_title from traitlets import Bool +from warnings import warn @magics_class @@ -48,8 +49,15 @@ def __init__(self, shell=None, **kwargs): winext = os.environ['pathext'].replace(';','|').replace('.','') except KeyError: winext = 'exe|com|bat|py' - - self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) + try: + self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) + except re.error: + warn("Seems like your pathext environmental " + "variable is malformed. Please check it to " + "enable a proper handle of file extensions " + "managed for your system") + winext = 'exe|com|bat|py' + self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) # call up the chain super().__init__(shell=shell, **kwargs) diff --git a/IPython/core/release.py b/IPython/core/release.py index aae948bbb1b..4e50e18b838 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -20,7 +20,7 @@ # release. 'dev' as a _version_extra string means this is a development # version _version_major = 7 -_version_minor = 15 +_version_minor = 16 _version_patch = 0 _version_extra = '.dev' # _version_extra = 'b1' diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 7f3720a5ab9..9fdc944e4d0 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -4,20 +4,24 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import bdb +import builtins +import os import signal +import subprocess import sys import time import warnings +from subprocess import PIPE, CalledProcessError, check_output from tempfile import NamedTemporaryFile -from subprocess import check_output, CalledProcessError, PIPE -import subprocess +from textwrap import dedent from unittest.mock import patch -import builtins -import bdb import nose.tools as nt from IPython.core import debugger +from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE +from IPython.testing.decorators import skip_win32 #----------------------------------------------------------------------------- # Helper classes, from CPython's Pdb test suite @@ -254,3 +258,69 @@ def raising_input(msg="", called=[0]): # implementation would involve a subprocess, but that adds issues with # interrupting subprocesses that are rather complex, so it's simpler # just to do it this way. + +@skip_win32 +def test_xmode_skip(): + """that xmode skip frames + + Not as a doctest as pytest does not run doctests. + """ + import pexpect + env = os.environ.copy() + env["IPY_TEST_SIMPLE_PROMPT"] = "1" + + child = pexpect.spawn( + sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env + ) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + + child.expect("IPython") + child.expect("\n") + child.expect_exact("In [1]") + + block = dedent( + """ +def f(): + __tracebackhide__ = True + g() + +def g(): + raise ValueError + +f() + """ + ) + + for line in block.splitlines(): + child.sendline(line) + child.expect_exact(line) + child.expect_exact("skipping") + + block = dedent( + """ +def f(): + __tracebackhide__ = True + g() + +def g(): + from IPython.core.debugger import set_trace + set_trace() + +f() + """ + ) + + for line in block.splitlines(): + child.sendline(line) + child.expect_exact(line) + + child.expect("ipdb>") + child.sendline("w") + child.expect("hidden") + child.expect("ipdb>") + child.sendline("skip_hidden false") + child.sendline("w") + child.expect("__traceba") + child.expect("ipdb>") + + child.close() diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index 38d71b31740..eff832b3fc0 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -402,6 +402,25 @@ def test_run_nb(self): nt.assert_equal(_ip.user_ns['answer'], 42) + def test_run_nb_error(self): + """Test %run notebook.ipynb error""" + from nbformat import v4, writes + # %run when a file name isn't provided + nt.assert_raises(Exception, _ip.magic, "run") + + # %run when a file doesn't exist + nt.assert_raises(Exception, _ip.magic, "run foobar.ipynb") + + # %run on a notebook with an error + nb = v4.new_notebook( + cells=[ + v4.new_code_cell("0/0") + ] + ) + src = writes(nb, version=4) + self.mktmp(src, ext='.ipynb') + nt.assert_raises(Exception, _ip.magic, "run %s" % self.fname) + def test_file_options(self): src = ('import sys\n' 'a = " ".join(sys.argv[1:])\n') diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 9e7e8fb390d..45e22bd7b94 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -879,13 +879,36 @@ def __init__(self, color_scheme='Linux', call_pdb=False, ostream=None, self.check_cache = check_cache self.debugger_cls = debugger_cls or debugger.Pdb + self.skip_hidden = True def format_records(self, records, last_unique, recursion_repeat): """Format the stack frames of the traceback""" frames = [] + + skipped = 0 for r in records[:last_unique+recursion_repeat+1]: - #print '*** record:',file,lnum,func,lines,index # dbg + if self.skip_hidden: + if r[0].f_locals.get("__tracebackhide__", 0): + skipped += 1 + continue + if skipped: + Colors = self.Colors # just a shorthand + quicker name lookup + ColorsNormal = Colors.Normal # used a lot + frames.append( + " %s[... skipping hidden %s frame]%s\n" + % (Colors.excName, skipped, ColorsNormal) + ) + skipped = 0 + frames.append(self.format_record(*r)) + + if skipped: + Colors = self.Colors # just a shorthand + quicker name lookup + ColorsNormal = Colors.Normal # used a lot + frames.append( + " %s[... skipping hidden %s frame]%s\n" + % (Colors.excName, skipped, ColorsNormal) + ) if recursion_repeat: frames.append('... last %d frames repeated, from the frame below ...\n' % recursion_repeat) @@ -1123,8 +1146,6 @@ 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) diff --git a/IPython/terminal/debugger.py b/IPython/terminal/debugger.py index f3268e70bd6..ffd53c65e78 100644 --- a/IPython/terminal/debugger.py +++ b/IPython/terminal/debugger.py @@ -41,6 +41,16 @@ def get_prompt_tokens(): global_namespace={}, parent=self.shell, ) + # add a completer for all the do_ methods + methods_names = [m[3:] for m in dir(self) if m.startswith("do_")] + + def gen_comp(self, text): + return [m for m in methods_names if m.startswith(text)] + import types + newcomp = types.MethodType(gen_comp, compl) + compl.custom_matchers.insert(0, newcomp) + # end add completer. + self._ptcomp = IPythonPTCompleter(compl) options = dict( diff --git a/IPython/terminal/pt_inputhooks/qt.py b/IPython/terminal/pt_inputhooks/qt.py index 1504c3bde72..9f5e1b41dcd 100644 --- a/IPython/terminal/pt_inputhooks/qt.py +++ b/IPython/terminal/pt_inputhooks/qt.py @@ -45,7 +45,7 @@ def inputhook(context): QtCore.QSocketNotifier.Read) try: # connect the callback we care about before we turn it on - notifier.activated.connect(event_loop.exit) + notifier.activated.connect(lambda: event_loop.exit()) notifier.setEnabled(True) # only start the event loop we are not already flipped if not context.input_is_ready(): diff --git a/IPython/terminal/pt_inputhooks/wx.py b/IPython/terminal/pt_inputhooks/wx.py index 618f092b4f9..a0f4442c771 100644 --- a/IPython/terminal/pt_inputhooks/wx.py +++ b/IPython/terminal/pt_inputhooks/wx.py @@ -177,11 +177,13 @@ def inputhook_wxphoenix(context): # Use a wx.Timer to periodically check whether input is ready - as soon as # it is, we exit the main loop + timer = wx.Timer() + def poll(ev): if context.input_is_ready(): + timer.Stop() app.ExitMainLoop() - timer = wx.Timer() timer.Start(poll_interval) timer.Bind(wx.EVT_TIMER, poll) diff --git a/IPython/utils/_process_posix.py b/IPython/utils/_process_posix.py index f3f93f774d6..a11cad7697c 100644 --- a/IPython/utils/_process_posix.py +++ b/IPython/utils/_process_posix.py @@ -59,11 +59,12 @@ class ProcessHandler(object): @property def sh(self): - if self._sh is None: - self._sh = pexpect.which('sh') + if self._sh is None: + shell_name = os.environ.get("SHELL", "sh") + self._sh = pexpect.which(shell_name) if self._sh is None: - raise OSError('"sh" shell not found') - + raise OSError('"{}" shell not found'.format(shell_name)) + return self._sh def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None): diff --git a/appveyor.yml b/appveyor.yml index b79c7f26ff4..d20effdf815 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,13 +4,6 @@ matrix: environment: matrix: - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.x" - PYTHON_ARCH: "32" - - - PYTHON: "C:\\Python36-x64" - PYTHON_VERSION: "3.6.x" - PYTHON_ARCH: "64" - PYTHON: "C:\\Python37-x64" PYTHON_VERSION: "3.7.x" diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index f87379d2ca0..e4ed965a13e 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -8,7 +8,7 @@ Asynchronous in REPL: Autoawait This feature is experimental and behavior can change between python and IPython version without prior deprecation. -Starting with IPython 7.0, and when user Python 3.6 and above, IPython offer the +Starting with IPython 7.0, and when using Python 3.6 and above, IPython offer the ability to run asynchronous code from the REPL. Constructs which are :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython. @@ -19,7 +19,7 @@ will differ between IPython, IPykernel and their versions. When a supported library is used, IPython will automatically allow Futures and Coroutines in the REPL to be ``await`` ed. This will happen if an :ref:`await -` (or any other async constructs like async-with, async-for) is use at +` (or any other async constructs like async-with, async-for) is used at top level scope, or if any structure valid only in `async def `_ function context are present. For example, the following being a syntax error in the @@ -73,7 +73,7 @@ By default IPython will assume integration with Python's provided :mod:`asyncio`, but integration with other libraries is provided. In particular we provide experimental integration with the ``curio`` and ``trio`` library. -You can switch current integration by using the +You can switch the current integration by using the ``c.InteractiveShell.loop_runner`` option or the ``autoawait `` magic. @@ -118,7 +118,7 @@ to your code. When using command line IPython, the default loop (or runner) does not process in the background, so top level asynchronous code must finish for the REPL to -allow you to enter more code. As with usual Python semantic, the awaitables are +allow you to enter more code. As with usual Python semantics, the awaitables are started only when awaited for the first time. That is to say, in first example, no network request is done between ``In[1]`` and ``In[2]``. @@ -131,8 +131,8 @@ a loop to run. By default IPython will use a fake coroutine runner which should allow ``IPython.embed()`` to be nested. Though this will prevent usage of the :magic:`%autoawait` feature when using IPython embed. -You can set explicitly a coroutine runner for ``embed()`` if you desire to run -asynchronous code, the exact behavior is though undefined. +You can set a coroutine runner explicitly for ``embed()`` if you want to run +asynchronous code, though the exact behavior is undefined. Effects on Magics ----------------- @@ -140,14 +140,14 @@ Effects on Magics A couple of magics (``%%timeit``, ``%timeit``, ``%%time``, ``%%prun``) have not yet been updated to work with asynchronous code and will raise syntax errors when trying to use top-level ``await``. We welcome any contribution to help fix -those, and extra cases we haven't caught yet. We hope for better support in Cor +those, and extra cases we haven't caught yet. We hope for better support in Core Python for top-level Async code. Internals --------- As running asynchronous code is not supported in interactive REPL (as of Python -3.7) we have to rely to a number of complex workaround and heuristic to allow +3.7) we have to rely to a number of complex workarounds and heuristics to allow this to happen. It is interesting to understand how this works in order to comprehend potential bugs, or provide a custom runner. @@ -179,16 +179,16 @@ significant overhead to this kind of code. By default the generated coroutine function will be consumed by Asyncio's ``loop_runner = asyncio.get_evenloop().run_until_complete()`` method if ``async`` mode is deemed necessary, otherwise the coroutine will just be -exhausted in a simple runner. It is though possible to change the default +exhausted in a simple runner. It is possible, though, to change the default runner. A loop runner is a *synchronous* function responsible from running a coroutine object. -The runner is responsible from ensuring that ``coroutine`` run to completion, -and should return the result of executing the coroutine. Let's write a +The runner is responsible for ensuring that ``coroutine`` runs to completion, +and it should return the result of executing the coroutine. Let's write a runner for ``trio`` that print a message when used as an exercise, ``trio`` is -special as it usually prefer to run a function object and make a coroutine by +special as it usually prefers to run a function object and make a coroutine by itself, we can get around this limitation by wrapping it in an async-def without parameters and passing this value to ``trio``:: @@ -246,8 +246,8 @@ Difference between terminal IPython and IPykernel The exact asynchronous code running behavior varies between Terminal IPython and IPykernel. The root cause of this behavior is due to IPykernel having a *persistent* `asyncio` loop running, while Terminal IPython starts and stops a -loop for each code block. This can lead to surprising behavior in some case if -you are used to manipulate asyncio loop yourself, see for example +loop for each code block. This can lead to surprising behavior in some cases if +you are used to manipulating asyncio loop yourself, see for example :ghissue:`11303` for a longer discussion but here are some of the astonishing cases. diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index f2bde112f66..9e9b2984c6c 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -23,6 +23,7 @@ Need to be updated: pr/* + .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. As a reminder, IPython master has diverged from the 7.x branch, thus master may diff --git a/docs/source/whatsnew/github-stats-7.rst b/docs/source/whatsnew/github-stats-7.rst index d326771e8de..6bc36c0789c 100644 --- a/docs/source/whatsnew/github-stats-7.rst +++ b/docs/source/whatsnew/github-stats-7.rst @@ -1,6 +1,28 @@ Issues closed in the 7.x development cycle ========================================== + +Issues closed in 7.16 +--------------------- + +GitHub stats for 2020/05/29 - 2020/06/26 (tag: 7.15.0) + +These lists are automatically generated, and may be incomplete or contain duplicates. + +We closed 0 issues and merged 18 pull requests. +The full list can be seen `on GitHub `__ + +The following 7 authors contributed 22 commits. + +* Benjamin Ragan-Kelley +* dalthviz +* Frank Tobia +* Matthias Bussonnier +* palewire +* Paul McCarthy +* Talley Lambert + + Issues closed in 7.15 --------------------- diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 3d9abc96f6f..286f16fb9f4 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -2,6 +2,67 @@ 7.x Series ============ +.. _version 716: + +IPython 7.16 +============ + + +The default traceback mode will now skip frames that are marked with +``__tracebackhide__ = True`` and show how many traceback frames have been +skipped. This can be toggled by using :magic:`xmode` with the ``--show`` or +``--hide`` attribute. It will have no effect on non verbose traceback modes. + +The ipython debugger also now understands ``__tracebackhide__`` as well and will +skip hidden frames when displaying. Movement up and down the stack will skip the +hidden frames and will show how many frames were hidden. Internal IPython frames +are also now hidden by default. The behavior can be changed with the +``skip_hidden`` while in the debugger, command and accepts "yes", "no", "true" +and "false" case insensitive parameters. + + +Misc Noticeable changes: +------------------------ + +- Exceptions are now (re)raised when running notebooks via the :magic:`%run`, helping to catch issues in workflows and + pipelines. :ghpull:`12301` +- Fix inputhook for qt 5.15.0 :ghpull:`12355` +- Fix wx inputhook :ghpull:`12375` +- Add handling for malformed pathext env var (Windows) :ghpull:`12367` +- use $SHELL in system_piped :ghpull:`12360` for uniform behavior with + ipykernel. + +Reproducible Build +------------------ + +IPython 7.15 reproducible build did not work, so we try again this month +:ghpull:`12358`. + + +API Changes +----------- + +Change of API and exposed objects automatically detected using `frappuccino +`_ (still in beta): + + +The following items are new and mostly related to understanding ``__tracebackbhide__``:: + + + IPython.core.debugger.Pdb.do_down(self, arg) + + IPython.core.debugger.Pdb.do_skip_hidden(self, arg) + + IPython.core.debugger.Pdb.do_up(self, arg) + + IPython.core.debugger.Pdb.hidden_frames(self, stack) + + IPython.core.debugger.Pdb.stop_here(self, frame) + + +The following items have been removed:: + + - IPython.core.debugger.Pdb.new_do_down + - IPython.core.debugger.Pdb.new_do_up + +Those were implementation details. + + .. _version 715: IPython 7.15 diff --git a/tools/release_helper.sh b/tools/release_helper.sh index 6e2e8bf201a..dd700b56329 100644 --- a/tools/release_helper.sh +++ b/tools/release_helper.sh @@ -81,6 +81,21 @@ then fi +if ask_section "Generate API difference (using frapuccino)" +then + echo $BLUE"Checking out $PREV_RELEASE"$NOR + git checkout $PREV_RELEASE + echo $BLUE"Saving API to file $PREV_RELEASE"$NOR + frappuccino IPython --save IPython-$PREV_RELEASE.json + echo $BLUE"comming back to $BRANCH"$NOR + git checkout $BRANCH + echo $BLUE"comparing ..."$NOR + frappuccino IPython --compare IPython-$PREV_RELEASE.json + echo $GREEN"Use the above guideline to write an API changelog ..."$NOR + echo $GREEN"Press any keys to continue"$NOR + read +fi + echo "Cleaning repository" git clean -xfdi diff --git a/tools/retar.py b/tools/retar.py index dbdca0ed7f5..9b5e73c6baa 100644 --- a/tools/retar.py +++ b/tools/retar.py @@ -47,10 +47,10 @@ m2.type = m.type m2.linkname = m.linkname if m.isdir(): + new.addfile(m2) + else: data = old.extractfile(m) new.addfile(m2, data) - else: - new.addfile(m2) new.close() old.close() @@ -58,3 +58,7 @@ with open(sys.argv[1], "wb") as f: with gzip.GzipFile('', "wb", fileobj=f, mtime=timestamp) as gzf: gzf.write(buf.read()) + +# checks the archive is valid. +archive = tarfile.open(sys.argv[1]) +names = archive.getnames()