From 6bc9941f2eb5590e23ffbbdeb6ed4ba1520f273c Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Thu, 27 Jun 2024 22:42:34 +0100 Subject: [PATCH 01/41] Fix completion removing prefix --- IPython/core/completer.py | 90 ++++++++++++++++++++-------- IPython/core/tests/test_completer.py | 32 ++++++---- 2 files changed, 87 insertions(+), 35 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 4eef2b55880..9dfa3e4ed9f 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -898,7 +898,7 @@ def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> new_text = text[new_start:c.start] + c.text + text[c.end:new_end] if c._origin == 'jedi': seen_jedi.add(new_text) - elif c._origin == 'IPCompleter.python_matches': + elif c._origin == "IPCompleter.python_matcher": seen_python_matches.add(new_text) yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature) diff = seen_python_matches.difference(seen_jedi) @@ -1139,6 +1139,9 @@ def attr_matches(self, text): with a __getattr__ hook is evaluated. """ + return self._attr_matches(text)[0] + + def _attr_matches(self, text, include_prefix=True): m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) if not m2: return [] @@ -1170,28 +1173,36 @@ def attr_matches(self, text): # reconciliator would know that we intend to append to rather than # replace the input text; this requires refactoring to return range # which ought to be replaced (as does jedi). - tokens = _parse_tokens(expr) - rev_tokens = reversed(tokens) - skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE} - name_turn = True - - parts = [] - for token in rev_tokens: - if token.type in skip_over: - continue - if token.type == tokenize.NAME and name_turn: - parts.append(token.string) - name_turn = False - elif token.type == tokenize.OP and token.string == "." and not name_turn: - parts.append(token.string) - name_turn = True - else: - # short-circuit if not empty nor name token - break + if include_prefix: + tokens = _parse_tokens(expr) + rev_tokens = reversed(tokens) + skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE} + name_turn = True + + parts = [] + for token in rev_tokens: + if token.type in skip_over: + continue + if token.type == tokenize.NAME and name_turn: + parts.append(token.string) + name_turn = False + elif ( + token.type == tokenize.OP and token.string == "." and not name_turn + ): + parts.append(token.string) + name_turn = True + else: + # short-circuit if not empty nor name token + break - prefix_after_space = "".join(reversed(parts)) + prefix_after_space = "".join(reversed(parts)) + else: + prefix_after_space = "" - return ["%s.%s" % (prefix_after_space, w) for w in words if w[:n] == attr] + return ( + ["%s.%s" % (prefix_after_space, w) for w in words if w[:n] == attr], + "." + attr, + ) def _evaluate_expr(self, expr): obj = not_found @@ -1973,9 +1984,8 @@ def matchers(self) -> List[Matcher]: *self.magic_arg_matchers, self.custom_completer_matcher, self.dict_key_matcher, - # TODO: convert python_matches to v2 API self.magic_matcher, - self.python_matches, + self.python_matcher, self.file_matcher, self.python_func_kw_matcher, ] @@ -2316,9 +2326,41 @@ def _jedi_matches( else: return iter([]) + @context_matcher() + def python_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match attributes or global python names""" + text = context.line_with_cursor + if "." in text: + try: + matches, fragment = self._attr_matches(text, include_prefix=False) + if text.endswith(".") and self.omit__names: + if self.omit__names == 1: + # true if txt is _not_ a __ name, false otherwise: + no__name = lambda txt: re.match(r".*\.__.*?__", txt) is None + else: + # true if txt is _not_ a _ name, false otherwise: + no__name = ( + lambda txt: re.match(r"\._.*?", txt[txt.rindex(".") :]) + is None + ) + matches = filter(no__name, matches) + return _convert_matcher_v1_result_to_v2( + matches, type="attribute", fragment=fragment + ) + except NameError: + # catches . + matches = [] + return _convert_matcher_v1_result_to_v2(matches, type="attribute") + else: + matches = self.global_matches(context.token) + return _convert_matcher_v1_result_to_v2(matches, type="variable") + @completion_matcher(api_version=1) def python_matches(self, text: str) -> Iterable[str]: - """Match attributes or global python names""" + """Match attributes or global python names. + + .. deprecated:: 8.27 + You can use :meth:`python_matcher` instead.""" if "." in text: try: matches = self.attr_matches(text) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 87c561dc06b..e20d9b88a89 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -462,7 +462,10 @@ def test_all_completions_dups(self): matches = c.all_completions("TestClass.") assert len(matches) > 2, (jedi_status, matches) matches = c.all_completions("TestClass.a") - assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status + if jedi_status: + assert matches == ["TestClass.a", "TestClass.a1"], jedi_status + else: + assert matches == [".a", ".a1"], jedi_status @pytest.mark.xfail( sys.version_info.releaselevel in ("alpha",), @@ -594,7 +597,7 @@ def _(line, cursor_pos, expect, message, completion): ip.Completer.use_jedi = True with provisionalcompleter(): completions = ip.Completer.completions(line, cursor_pos) - self.assertIn(completion, completions) + self.assertIn(completion, list(completions)) with provisionalcompleter(): _( @@ -622,7 +625,7 @@ def _(line, cursor_pos, expect, message, completion): _( "assert str.star", 14, - "str.startswith", + ".startswith", "Should have completed on `assert str.star`: %s", Completion(11, 14, "startswith"), ) @@ -633,6 +636,13 @@ def _(line, cursor_pos, expect, message, completion): "Should have completed on `d['a b'].str`: %s", Completion(9, 12, "strip"), ) + _( + "a.app", + 4, + ".append", + "Should have completed on `a.app`: %s", + Completion(2, 4, "append"), + ) def test_omit__names(self): # also happens to test IPCompleter as a configurable @@ -647,8 +657,8 @@ def test_omit__names(self): with provisionalcompleter(): c.use_jedi = False s, matches = c.complete("ip.") - self.assertIn("ip.__str__", matches) - self.assertIn("ip._hidden_attr", matches) + self.assertIn(".__str__", matches) + self.assertIn("._hidden_attr", matches) # c.use_jedi = True # completions = set(c.completions('ip.', 3)) @@ -661,7 +671,7 @@ def test_omit__names(self): with provisionalcompleter(): c.use_jedi = False s, matches = c.complete("ip.") - self.assertNotIn("ip.__str__", matches) + self.assertNotIn(".__str__", matches) # self.assertIn('ip._hidden_attr', matches) # c.use_jedi = True @@ -675,8 +685,8 @@ def test_omit__names(self): with provisionalcompleter(): c.use_jedi = False s, matches = c.complete("ip.") - self.assertNotIn("ip.__str__", matches) - self.assertNotIn("ip._hidden_attr", matches) + self.assertNotIn(".__str__", matches) + self.assertNotIn("._hidden_attr", matches) # c.use_jedi = True # completions = set(c.completions('ip.', 3)) @@ -686,7 +696,7 @@ def test_omit__names(self): with provisionalcompleter(): c.use_jedi = False s, matches = c.complete("ip._x.") - self.assertIn("ip._x.keys", matches) + self.assertIn(".keys", matches) # c.use_jedi = True # completions = set(c.completions('ip._x.', 6)) @@ -697,7 +707,7 @@ def test_omit__names(self): def test_limit_to__all__False_ok(self): """ - Limit to all is deprecated, once we remove it this test can go away. + Limit to all is deprecated, once we remove it this test can go away. """ ip = get_ipython() c = ip.Completer @@ -708,7 +718,7 @@ def test_limit_to__all__False_ok(self): cfg.IPCompleter.limit_to__all__ = False c.update_config(cfg) s, matches = c.complete("d.") - self.assertIn("d.x", matches) + self.assertIn(".x", matches) def test_get__all__entries_ok(self): class A: From a499dbd507a92ea0087eca0f53e550fc838f0580 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Fri, 28 Jun 2024 10:39:06 +0200 Subject: [PATCH 02/41] back to dev --- 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 b72524d6ff8..a491faf4d15 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -16,11 +16,11 @@ # release. 'dev' as a _version_extra string means this is a development # version _version_major = 8 -_version_minor = 26 +_version_minor = 27 _version_patch = 0 _version_extra = ".dev" # _version_extra = "rc1" -_version_extra = "" # Uncomment this for full releases +# _version_extra = "" # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor, _version_patch] From 4d2c9e32619254c1ea170e897f2c3220ab3419e8 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 28 Jun 2024 09:54:05 +0100 Subject: [PATCH 03/41] Don't force matplotlib backend names to be lowercase --- IPython/core/magics/pylab.py | 2 +- IPython/core/tests/test_pylabtools.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/pylab.py b/IPython/core/magics/pylab.py index 265f860063a..423498d404c 100644 --- a/IPython/core/magics/pylab.py +++ b/IPython/core/magics/pylab.py @@ -100,7 +100,7 @@ def matplotlib(self, line=''): % _list_matplotlib_backends_and_gui_loops() ) else: - gui, backend = self.shell.enable_matplotlib(args.gui.lower() if isinstance(args.gui, str) else args.gui) + gui, backend = self.shell.enable_matplotlib(args.gui) self._show_matplotlib_backend(args.gui, backend) @skip_doctest diff --git a/IPython/core/tests/test_pylabtools.py b/IPython/core/tests/test_pylabtools.py index 6bddb348077..e9d98ff4646 100644 --- a/IPython/core/tests/test_pylabtools.py +++ b/IPython/core/tests/test_pylabtools.py @@ -350,3 +350,27 @@ def test_backend_entry_point(): def test_backend_unknown(): with pytest.raises(RuntimeError if pt._matplotlib_manages_backends() else KeyError): pt.find_gui_and_backend("name-does-not-exist") + + +@dec.skipif(not pt._matplotlib_manages_backends()) +def test_backend_module_name_case_sensitive(): + # Matplotlib backend names are case insensitive unless explicitly specified using + # "module://some_module.some_name" syntax which are case sensitive for mpl >= 3.9.1 + all_lowercase = "module://matplotlib_inline.backend_inline" + some_uppercase = "module://matplotlib_inline.Backend_inline" + ip = get_ipython() + mpl3_9_1 = matplotlib.__version_info__ >= (3, 9, 1) + + ip.enable_matplotlib(all_lowercase) + if mpl3_9_1: + with pytest.raises(RuntimeError): + ip.enable_matplotlib(some_uppercase) + else: + ip.enable_matplotlib(some_uppercase) + + ip.run_line_magic("matplotlib", all_lowercase) + if mpl3_9_1: + with pytest.raises(RuntimeError): + ip.run_line_magic("matplotlib", some_uppercase) + else: + ip.run_line_magic("matplotlib", some_uppercase) From 95b627553645dada0ba9e338b0da7005ecfe3fd4 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 28 Jun 2024 11:02:26 +0100 Subject: [PATCH 04/41] Isolate tests so state doesn't leak to other tests --- IPython/core/tests/test_pylabtools.py | 47 +++++++++++++-------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/IPython/core/tests/test_pylabtools.py b/IPython/core/tests/test_pylabtools.py index e9d98ff4646..31d3dbe21f2 100644 --- a/IPython/core/tests/test_pylabtools.py +++ b/IPython/core/tests/test_pylabtools.py @@ -258,6 +258,29 @@ def test_qt_gtk(self): assert gui == "qt" assert s.pylab_gui_select == "qt" + @dec.skipif(not pt._matplotlib_manages_backends()) + def test_backend_module_name_case_sensitive(self): + # Matplotlib backend names are case insensitive unless explicitly specified using + # "module://some_module.some_name" syntax which are case sensitive for mpl >= 3.9.1 + all_lowercase = "module://matplotlib_inline.backend_inline" + some_uppercase = "module://matplotlib_inline.Backend_inline" + mpl3_9_1 = matplotlib.__version_info__ >= (3, 9, 1) + + s = self.Shell() + s.enable_matplotlib(all_lowercase) + if mpl3_9_1: + with pytest.raises(RuntimeError): + s.enable_matplotlib(some_uppercase) + else: + s.enable_matplotlib(some_uppercase) + + s.run_line_magic("matplotlib", all_lowercase) + if mpl3_9_1: + with pytest.raises(RuntimeError): + s.run_line_magic("matplotlib", some_uppercase) + else: + s.run_line_magic("matplotlib", some_uppercase) + def test_no_gui_backends(): for k in ['agg', 'svg', 'pdf', 'ps']: @@ -350,27 +373,3 @@ def test_backend_entry_point(): def test_backend_unknown(): with pytest.raises(RuntimeError if pt._matplotlib_manages_backends() else KeyError): pt.find_gui_and_backend("name-does-not-exist") - - -@dec.skipif(not pt._matplotlib_manages_backends()) -def test_backend_module_name_case_sensitive(): - # Matplotlib backend names are case insensitive unless explicitly specified using - # "module://some_module.some_name" syntax which are case sensitive for mpl >= 3.9.1 - all_lowercase = "module://matplotlib_inline.backend_inline" - some_uppercase = "module://matplotlib_inline.Backend_inline" - ip = get_ipython() - mpl3_9_1 = matplotlib.__version_info__ >= (3, 9, 1) - - ip.enable_matplotlib(all_lowercase) - if mpl3_9_1: - with pytest.raises(RuntimeError): - ip.enable_matplotlib(some_uppercase) - else: - ip.enable_matplotlib(some_uppercase) - - ip.run_line_magic("matplotlib", all_lowercase) - if mpl3_9_1: - with pytest.raises(RuntimeError): - ip.run_line_magic("matplotlib", some_uppercase) - else: - ip.run_line_magic("matplotlib", some_uppercase) From 273ea6fe5f4c47658ea6f7e6d7a5bc8b6c2c0c4d Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:12:07 +0100 Subject: [PATCH 05/41] Fix exception on missing attribute matches --- IPython/core/completer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 9dfa3e4ed9f..bac9e1d8f44 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -1141,16 +1141,16 @@ def attr_matches(self, text): """ return self._attr_matches(text)[0] - def _attr_matches(self, text, include_prefix=True): + def _attr_matches(self, text, include_prefix=True) -> Tuple[Sequence[str], str]: m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) if not m2: - return [] + return [], "" expr, attr = m2.group(1, 2) obj = self._evaluate_expr(expr) if obj is not_found: - return [] + return [], "" if self.limit_to__all__ and hasattr(obj, '__all__'): words = get__all__entries(obj) From 5827cc8b7a69a67076bbf0c903dfcf0617ca96af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:27:17 +0100 Subject: [PATCH 06/41] Add a TODO comment to remember to narrow `global_matches` type --- IPython/core/completer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index bac9e1d8f44..440953d0690 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -2353,6 +2353,7 @@ def python_matcher(self, context: CompletionContext) -> SimpleMatcherResult: return _convert_matcher_v1_result_to_v2(matches, type="attribute") else: matches = self.global_matches(context.token) + # TODO: maybe distinguish between functions, modules and just "variables" return _convert_matcher_v1_result_to_v2(matches, type="variable") @completion_matcher(api_version=1) From a634eec3e425389573561f0f0bbcd3c0a93f5c14 Mon Sep 17 00:00:00 2001 From: chalggg <24186302+chalggg@users.noreply.github.com> Date: Mon, 8 Jul 2024 21:32:08 +0200 Subject: [PATCH 07/41] fix docs of custommagics --- docs/source/config/custommagics.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/config/custommagics.rst b/docs/source/config/custommagics.rst index 0a37b858a4c..4854970ef31 100644 --- a/docs/source/config/custommagics.rst +++ b/docs/source/config/custommagics.rst @@ -141,7 +141,7 @@ Accessing user namespace and local scope When creating line magics, you may need to access surrounding scope to get user variables (e.g when called inside functions). IPython provides the ``@needs_local_scope`` decorator that can be imported from -``IPython.core.magics``. When decorated with ``@needs_local_scope`` a magic will +``IPython.core.magic``. When decorated with ``@needs_local_scope`` a magic will be passed ``local_ns`` as an argument. As a convenience ``@needs_local_scope`` can also be applied to cell magics even if cell magics cannot appear at local scope context. @@ -153,7 +153,7 @@ Sometimes it may be useful to define a magic that can be silenced the same way that non-magic expressions can, i.e., by appending a semicolon at the end of the Python code to be executed. That can be achieved by decorating the magic function with the decorator ``@output_can_be_silenced`` that can be imported from -``IPython.core.magics``. When this decorator is used, IPython will parse the Python +``IPython.core.magic``. When this decorator is used, IPython will parse the Python code used by the magic and, if the last token is a ``;``, the output created by the magic will not show up on the screen. If you want to see an example of this decorator in action, take a look on the ``time`` magic defined in From 79fd033e34e4ac1bdf3740877b156e922081b7e6 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Mon, 22 Jul 2024 14:32:14 +0200 Subject: [PATCH 08/41] Do not upload to archive.ipython.org anymore Server is notworking anymore. --- tools/release | 82 ++--------------------------------------- tools/release_helper.sh | 6 +-- 2 files changed, 6 insertions(+), 82 deletions(-) diff --git a/tools/release b/tools/release index ffc7e63cb65..5164a2bb699 100755 --- a/tools/release +++ b/tools/release @@ -1,86 +1,10 @@ #!/usr/bin/env python3 """IPython release script. -This should ONLY be run at real release time. -""" -from __future__ import print_function - -import os -from glob import glob -from pathlib import Path -from subprocess import call -import sys - -from toollib import (get_ipdir, cd, execfile, sh, archive, - archive_user, archive_dir) - -# Get main ipython dir, this will raise if it doesn't pass some checks -ipdir = get_ipdir() -tooldir = ipdir / 'tools' -distdir = ipdir / 'dist' - -# Where I keep static backups of each release -ipbackupdir = Path('~/ipython/backup').expanduser() -if not ipbackupdir.exists(): - ipbackupdir.mkdir(parents=True, exist_ok=True) - -# Start in main IPython dir -cd(ipdir) - -# Load release info -version = None -execfile(Path('IPython','core','release.py'), globals()) - -# Build site addresses for file uploads -release_site = '%s/release/%s' % (archive, version) -backup_site = '%s/backup/' % archive - -# Start actual release process -print() -print('Releasing IPython') -print('=================') -print() -print('Version:', version) -print() -print('Source IPython directory:', ipdir) -print() - -# Perform local backup, go to tools dir to run it. -cd(tooldir) +Deprecated -if 'upload' in sys.argv: - cd(distdir) - - # do not upload OS specific files like .DS_Store - to_upload = glob('*.whl')+glob('*.tar.gz') - - # Make target dir if it doesn't exist - print('1. Uploading IPython to archive.ipython.org') - sh('ssh %s "mkdir -p %s/release/%s" ' % (archive_user, archive_dir, version)) - sh('scp *.tar.gz *.whl %s' % release_site) - - print('2. Uploading backup files...') - cd(ipbackupdir) - sh('scp `ls -1tr *tgz | tail -1` %s' % backup_site) - - print('3. Uploading to PyPI using twine') - cd(distdir) - call(['twine', 'upload', '--verbose'] + to_upload) - -else: - # Build, but don't upload - - # Make backup tarball - sh('python make_tarball.py') - sh('mv ipython-*.tgz %s' % ipbackupdir) - - # Build release files - sh('./build_release') - - cd(ipdir) - - print("`./release upload` to upload source distribution on PyPI and ipython archive") - sys.exit(0) +""" +sys.exit("deprecated") diff --git a/tools/release_helper.sh b/tools/release_helper.sh index df1ca0d48d2..08c8f6b8571 100644 --- a/tools/release_helper.sh +++ b/tools/release_helper.sh @@ -231,7 +231,7 @@ then echo echo $BLUE"Attempting to build package..."$NOR - tools/release + tools/build_release echo $RED'$ shasum -a 256 dist/*' @@ -245,7 +245,7 @@ then echo echo $BLUE"Attempting to build package..."$NOR - tools/release + tools/build_release echo $RED"Check the shasum for SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" echo $RED'$ shasum -a 256 dist/*' @@ -254,6 +254,6 @@ then if ask_section "upload packages ?" then - tools/release upload + twine upload --verbose dist/*.tar.gz dist/*.whl fi fi From 8fb51573248f2996fd03150a16c4f5439d925f99 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Mon, 22 Jul 2024 15:05:02 +0200 Subject: [PATCH 09/41] Fix autocall runs on getitem. Closes #14483 --- IPython/core/prefilter.py | 6 +- IPython/core/splitinput.py | 11 ++- IPython/core/tests/test_handlers.py | 108 ++++++++++---------------- IPython/core/tests/test_prefilter.py | 34 +++++--- IPython/core/tests/test_splitinput.py | 12 +-- 5 files changed, 86 insertions(+), 85 deletions(-) diff --git a/IPython/core/prefilter.py b/IPython/core/prefilter.py index e611b4b8340..a29df0c27ad 100644 --- a/IPython/core/prefilter.py +++ b/IPython/core/prefilter.py @@ -476,8 +476,8 @@ def check(self, line_info): any python operator, we should simply execute the line (regardless of whether or not there's a possible autocall expansion). This avoids spurious (and very confusing) geattr() accesses.""" - if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|': - return self.prefilter_manager.get_handler_by_name('normal') + if line_info.the_rest and line_info.the_rest[0] in "!=()<>,+*/%^&|": + return self.prefilter_manager.get_handler_by_name("normal") else: return None @@ -512,6 +512,8 @@ def check(self, line_info): callable(oinfo.obj) and (not self.exclude_regexp.match(line_info.the_rest)) and self.function_name_regexp.match(line_info.ifun) + and line_info.raw_the_rest.startswith(" ") + or not line_info.raw_the_rest.strip() ): return self.prefilter_manager.get_handler_by_name("auto") else: diff --git a/IPython/core/splitinput.py b/IPython/core/splitinput.py index 0cd70ec9100..33e462b3b80 100644 --- a/IPython/core/splitinput.py +++ b/IPython/core/splitinput.py @@ -76,7 +76,7 @@ def split_user_input(line, pattern=None): # print('line:<%s>' % line) # dbg # print('pre <%s> ifun <%s> rest <%s>' % (pre,ifun.strip(),the_rest)) # dbg - return pre, esc or '', ifun.strip(), the_rest.lstrip() + return pre, esc or "", ifun.strip(), the_rest class LineInfo(object): @@ -107,11 +107,15 @@ class LineInfo(object): the_rest Everything else on the line. + + raw_the_rest + the_rest without whitespace stripped. """ def __init__(self, line, continue_prompt=False): self.line = line self.continue_prompt = continue_prompt - self.pre, self.esc, self.ifun, self.the_rest = split_user_input(line) + self.pre, self.esc, self.ifun, self.raw_the_rest = split_user_input(line) + self.the_rest = self.raw_the_rest.lstrip() self.pre_char = self.pre.strip() if self.pre_char: @@ -136,3 +140,6 @@ def ofind(self, ip) -> OInfo: def __str__(self): return "LineInfo [%s|%s|%s|%s]" %(self.pre, self.esc, self.ifun, self.the_rest) + + def __repr__(self): + return "<" + str(self) + ">" diff --git a/IPython/core/tests/test_handlers.py b/IPython/core/tests/test_handlers.py index 604dadee1ab..905d9abe07b 100644 --- a/IPython/core/tests/test_handlers.py +++ b/IPython/core/tests/test_handlers.py @@ -7,17 +7,13 @@ # our own packages from IPython.core import autocall from IPython.testing import tools as tt +import pytest +from collections.abc import Callable #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- -# Get the public instance of IPython - -failures = [] -num_tests = 0 - -#----------------------------------------------------------------------------- # Test functions #----------------------------------------------------------------------------- @@ -31,67 +27,49 @@ def __call__(self): return "called" -def run(tests): - """Loop through a list of (pre, post) inputs, where pre is the string - handed to ipython, and post is how that string looks after it's been - transformed (i.e. ipython's notion of _i)""" - tt.check_pairs(ip.prefilter_manager.prefilter_lines, tests) - +@pytest.mark.parametrize( + "autocall, input, output", + [ + # For many of the below, we're also checking that leading whitespace + # turns off the esc char, which it should unless there is a continuation + # line. + ("1", '"no change"', '"no change"'), # normal + ("1", "lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), # magic + # Only explicit escapes or instances of IPyAutocallable should get + # expanded + ("0", 'len "abc"', 'len "abc"'), + ("0", "autocallable", "autocallable()"), + # Don't add extra brackets (gh-1117) + ("0", "autocallable()", "autocallable()"), + ("1", 'len "abc"', 'len("abc")'), + ("1", 'len "abc";', 'len("abc");'), # ; is special -- moves out of parens + # Autocall is turned off if first arg is [] and the object + # is both callable and indexable. Like so: + ("1", "len [1,2]", "len([1,2])"), # len doesn't support __getitem__... + ("1", "call_idx [1]", "call_idx [1]"), # call_idx *does*.. + ("1", "call_idx 1", "call_idx(1)"), + ("1", "len", "len"), # only at 2 does it auto-call on single args + ("2", 'len "abc"', 'len("abc")'), + ("2", 'len "abc";', 'len("abc");'), + ("2", "len [1,2]", "len([1,2])"), + ("2", "call_idx [1]", "call_idx [1]"), + ("2", "call_idx 1", "call_idx(1)"), + # T his is what's different: + ("2", "len", "len()"), # only at 2 does it auto-call on single args + ("0", "Callable[[int], None]", "Callable[[int], None]"), + ("1", "Callable[[int], None]", "Callable[[int], None]"), + ("1", "Callable[[int], None]", "Callable[[int], None]"), + ], +) +def test_handlers_I(autocall, input, output): + autocallable = Autocallable() + ip.user_ns["autocallable"] = autocallable -def test_handlers(): call_idx = CallableIndexable() - ip.user_ns['call_idx'] = call_idx + ip.user_ns["call_idx"] = call_idx - # For many of the below, we're also checking that leading whitespace - # turns off the esc char, which it should unless there is a continuation - # line. - run( - [('"no change"', '"no change"'), # normal - (u"lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), # magic - #("a = b # PYTHON-MODE", '_i'), # emacs -- avoids _in cache - ]) + ip.user_ns["Callable"] = Callable - # Objects which are instances of IPyAutocall are *always* autocalled - autocallable = Autocallable() - ip.user_ns['autocallable'] = autocallable - - # auto - ip.run_line_magic("autocall", "0") - # Only explicit escapes or instances of IPyAutocallable should get - # expanded - run( - [ - ('len "abc"', 'len "abc"'), - ("autocallable", "autocallable()"), - # Don't add extra brackets (gh-1117) - ("autocallable()", "autocallable()"), - ] - ) + ip.run_line_magic("autocall", autocall) + assert ip.prefilter_manager.prefilter_lines(input) == output ip.run_line_magic("autocall", "1") - run( - [ - ('len "abc"', 'len("abc")'), - ('len "abc";', 'len("abc");'), # ; is special -- moves out of parens - # Autocall is turned off if first arg is [] and the object - # is both callable and indexable. Like so: - ("len [1,2]", "len([1,2])"), # len doesn't support __getitem__... - ("call_idx [1]", "call_idx [1]"), # call_idx *does*.. - ("call_idx 1", "call_idx(1)"), - ("len", "len"), # only at 2 does it auto-call on single args - ] - ) - ip.run_line_magic("autocall", "2") - run( - [ - ('len "abc"', 'len("abc")'), - ('len "abc";', 'len("abc");'), - ("len [1,2]", "len([1,2])"), - ("call_idx [1]", "call_idx [1]"), - ("call_idx 1", "call_idx(1)"), - # This is what's different: - ("len", "len()"), # only at 2 does it auto-call on single args - ] - ) - ip.run_line_magic("autocall", "1") - - assert failures == [] diff --git a/IPython/core/tests/test_prefilter.py b/IPython/core/tests/test_prefilter.py index 91c3c868821..999cd43e6e8 100644 --- a/IPython/core/tests/test_prefilter.py +++ b/IPython/core/tests/test_prefilter.py @@ -48,7 +48,7 @@ def dummy_magic(line): pass def test_autocall_binops(): """See https://github.com/ipython/ipython/issues/81""" - ip.magic('autocall 2') + ip.run_line_magic("autocall", "2") f = lambda x: x ip.user_ns['f'] = f try: @@ -71,8 +71,8 @@ def test_autocall_binops(): finally: pm.unregister_checker(ac) finally: - ip.magic('autocall 0') - del ip.user_ns['f'] + ip.run_line_magic("autocall", "0") + del ip.user_ns["f"] def test_issue_114(): @@ -105,23 +105,35 @@ def __call__(self, x): return x # Create a callable broken object - ip.user_ns['x'] = X() - ip.magic('autocall 2') + ip.user_ns["x"] = X() + ip.run_line_magic("autocall", "2") try: # Even if x throws an attribute error when looking at its rewrite # attribute, we should not crash. So the test here is simply making # the prefilter call and not having an exception. ip.prefilter('x 1') finally: - del ip.user_ns['x'] - ip.magic('autocall 0') + del ip.user_ns["x"] + ip.run_line_magic("autocall", "0") + + +def test_autocall_type_ann(): + ip.run_cell("import collections.abc") + ip.run_line_magic("autocall", "1") + try: + assert ( + ip.prefilter("collections.abc.Callable[[int], None]") + == "collections.abc.Callable[[int], None]" + ) + finally: + ip.run_line_magic("autocall", "0") def test_autocall_should_support_unicode(): - ip.magic('autocall 2') - ip.user_ns['π'] = lambda x: x + ip.run_line_magic("autocall", "2") + ip.user_ns["π"] = lambda x: x try: assert ip.prefilter("π 3") == "π(3)" finally: - ip.magic('autocall 0') - del ip.user_ns['π'] + ip.run_line_magic("autocall", "0") + del ip.user_ns["π"] diff --git a/IPython/core/tests/test_splitinput.py b/IPython/core/tests/test_splitinput.py index 1462e7fa033..f5fc53fafe3 100644 --- a/IPython/core/tests/test_splitinput.py +++ b/IPython/core/tests/test_splitinput.py @@ -1,7 +1,8 @@ # coding: utf-8 from IPython.core.splitinput import split_user_input, LineInfo -from IPython.testing import tools as tt + +import pytest tests = [ ("x=1", ("", "", "x", "=1")), @@ -19,18 +20,19 @@ (";ls", ("", ";", "ls", "")), (" ;ls", (" ", ";", "ls", "")), ("f.g(x)", ("", "", "f.g", "(x)")), - ("f.g (x)", ("", "", "f.g", "(x)")), + ("f.g (x)", ("", "", "f.g", " (x)")), ("?%hist1", ("", "?", "%hist1", "")), ("?%%hist2", ("", "?", "%%hist2", "")), ("??%hist3", ("", "??", "%hist3", "")), ("??%%hist4", ("", "??", "%%hist4", "")), ("?x*", ("", "?", "x*", "")), + ("Pérez Fernando", ("", "", "Pérez", " Fernando")), ] -tests.append(("Pérez Fernando", ("", "", "Pérez", "Fernando"))) -def test_split_user_input(): - return tt.check_pairs(split_user_input, tests) +@pytest.mark.parametrize("input, output", tests) +def test_split_user_input(input, output): + assert split_user_input(input) == output def test_LineInfo(): From 75fe23b1c851c0988a5d4ae4ff61a760ce2b70c2 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Thu, 15 Aug 2024 13:50:16 +0200 Subject: [PATCH 10/41] Only copy files in startup dir if we just created it. Closes #14496 --- IPython/core/profiledir.py | 45 +++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/IPython/core/profiledir.py b/IPython/core/profiledir.py index 1e33b552fb7..95bc59ff2be 100644 --- a/IPython/core/profiledir.py +++ b/IPython/core/profiledir.py @@ -14,6 +14,8 @@ from ..utils.path import expand_path, ensure_dir_exists from traitlets import Unicode, Bool, observe +from typing import Optional + #----------------------------------------------------------------------------- # Module errors #----------------------------------------------------------------------------- @@ -68,18 +70,31 @@ def _location_changed(self, change): self.pid_dir = os.path.join(new, self.pid_dir_name) self.static_dir = os.path.join(new, self.static_dir_name) self.check_dirs() - - def _mkdir(self, path, mode=None): + + def _mkdir(self, path: str, mode: Optional[int] = None) -> bool: """ensure a directory exists at a given path This is a version of os.mkdir, with the following differences: - - returns True if it created the directory, False otherwise + - returns wether the directory has been created or not. - ignores EEXIST, protecting against race conditions where the dir may have been created in between the check and the creation - sets permissions if requested and the dir already exists + + Parameters + ---------- + path: str + path of the dir to create + mode: int + see `mode` of `os.mkdir` + + Returns + ------- + bool: + returns True if it created the directory, False otherwise """ + if os.path.exists(path): if mode and os.stat(path).st_mode != mode: try: @@ -109,16 +124,20 @@ def check_log_dir(self, change=None): @observe('startup_dir') def check_startup_dir(self, change=None): - self._mkdir(self.startup_dir) - - readme = os.path.join(self.startup_dir, 'README') - src = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'README_STARTUP') - - if not os.path.exists(src): - self.log.warning("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src) - - if os.path.exists(src) and not os.path.exists(readme): - shutil.copy(src, readme) + if self._mkdir(self.startup_dir): + readme = os.path.join(self.startup_dir, "README") + src = os.path.join( + get_ipython_package_dir(), "core", "profile", "README_STARTUP" + ) + + if not os.path.exists(src): + self.log.warning( + "Could not copy README_STARTUP to startup dir. Source file %s does not exist.", + src, + ) + + if os.path.exists(src) and not os.path.exists(readme): + shutil.copy(src, readme) @observe('security_dir') def check_security_dir(self, change=None): From 01e08c9c10cf5fdf898bcc413c770e76839b7e6e Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 20 Aug 2024 00:33:17 -0700 Subject: [PATCH 11/41] Apply suggestions from code review Co-authored-by: James Beith --- IPython/core/profiledir.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/IPython/core/profiledir.py b/IPython/core/profiledir.py index 95bc59ff2be..a1b94f2da33 100644 --- a/IPython/core/profiledir.py +++ b/IPython/core/profiledir.py @@ -76,7 +76,7 @@ def _mkdir(self, path: str, mode: Optional[int] = None) -> bool: This is a version of os.mkdir, with the following differences: - - returns wether the directory has been created or not. + - returns whether the directory has been created or not. - ignores EEXIST, protecting against race conditions where the dir may have been created in between the check and the creation @@ -130,15 +130,15 @@ def check_startup_dir(self, change=None): get_ipython_package_dir(), "core", "profile", "README_STARTUP" ) - if not os.path.exists(src): + if os.path.exists(src): + if not os.path.exists(readme): + shutil.copy(src, readme) + else: self.log.warning( "Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src, ) - if os.path.exists(src) and not os.path.exists(readme): - shutil.copy(src, readme) - @observe('security_dir') def check_security_dir(self, change=None): self._mkdir(self.security_dir, 0o40700) From ed2b9275249c2fa00de0ec2bc5e8e3f91032f03b Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 26 Aug 2024 12:46:52 +0100 Subject: [PATCH 12/41] Fix showing SystemExit exception raise inside except handler --- IPython/core/tests/test_ultratb.py | 12 +++++++++ IPython/core/ultratb.py | 43 ++++++++++++++++-------------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py index e167d99506a..8ed73873aa1 100644 --- a/IPython/core/tests/test_ultratb.py +++ b/IPython/core/tests/test_ultratb.py @@ -298,6 +298,13 @@ class Python3ChainedExceptionsTest(unittest.TestCase): raise ValueError("Yikes") from None """ + SYS_EXIT_WITH_CONTEXT_CODE = """ +try: + 1/0 +except Exception as e: + raise SystemExit(1) + """ + def test_direct_cause_error(self): with tt.AssertPrints(["KeyError", "NameError", "direct cause"]): ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE) @@ -306,6 +313,11 @@ def test_exception_during_handling_error(self): with tt.AssertPrints(["KeyError", "NameError", "During handling"]): ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) + def test_sysexit_while_handling_error(self): + with tt.AssertPrints(["SystemExit", "to see the full traceback"]): + with tt.AssertNotPrints(["another exception"], suppress=False): + ip.run_cell(self.SYS_EXIT_WITH_CONTEXT_CODE) + def test_suppress_exception_chaining(self): with tt.AssertNotPrints("ZeroDivisionError"), \ tt.AssertPrints("ValueError", suppress=False): diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index cc139b1e257..66c9ce9108b 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -552,28 +552,31 @@ def structured_traceback( lines = ''.join(self._format_exception_only(etype, evalue)) out_list.append(lines) - exception = self.get_parts_of_chained_exception(evalue) + # Find chained exceptions if we have a traceback (not for exception-only mode) + if etb is not None: + exception = self.get_parts_of_chained_exception(evalue) - if exception and (id(exception[1]) not in chained_exc_ids): - chained_exception_message = ( - self.prepare_chained_exception_message(evalue.__cause__)[0] - if evalue is not None - else "" - ) - etype, evalue, etb = exception - # Trace exception to avoid infinite 'cause' loop - chained_exc_ids.add(id(exception[1])) - chained_exceptions_tb_offset = 0 - out_list = ( - self.structured_traceback( - etype, - evalue, - (etb, chained_exc_ids), # type: ignore - chained_exceptions_tb_offset, - context, + if exception and (id(exception[1]) not in chained_exc_ids): + chained_exception_message = ( + self.prepare_chained_exception_message(evalue.__cause__)[0] + if evalue is not None + else "" + ) + etype, evalue, etb = exception + # Trace exception to avoid infinite 'cause' loop + chained_exc_ids.add(id(exception[1])) + chained_exceptions_tb_offset = 0 + out_list = ( + self.structured_traceback( + etype, + evalue, + (etb, chained_exc_ids), # type: ignore + chained_exceptions_tb_offset, + context, + ) + + chained_exception_message + + out_list ) - + chained_exception_message - + out_list) return out_list From 473f4f32b7fe5fb36f8b77db565e5a5ffc45395d Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 26 Aug 2024 16:19:16 +0100 Subject: [PATCH 13/41] Fix test_ipdb_magics doctest for Python 3.13 --- IPython/core/tests/test_debugger.py | 70 +++++++++++++++-------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index b22be9231dc..8de500d0b60 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -59,40 +59,43 @@ def test_ipdb_magics(): First, set up some test functions and classes which we can inspect. - >>> class ExampleClass(object): - ... """Docstring for ExampleClass.""" - ... def __init__(self): - ... """Docstring for ExampleClass.__init__""" - ... pass - ... def __str__(self): - ... return "ExampleClass()" - - >>> def example_function(x, y, z="hello"): - ... """Docstring for example_function.""" - ... pass + In [1]: class ExampleClass(object): + ...: """Docstring for ExampleClass.""" + ...: def __init__(self): + ...: """Docstring for ExampleClass.__init__""" + ...: pass + ...: def __str__(self): + ...: return "ExampleClass()" - >>> old_trace = sys.gettrace() + In [2]: def example_function(x, y, z="hello"): + ...: """Docstring for example_function.""" + ...: pass - Create a function which triggers ipdb. + In [3]: old_trace = sys.gettrace() - >>> def trigger_ipdb(): - ... a = ExampleClass() - ... debugger.Pdb().set_trace() + Create a function which triggers ipdb. - >>> with PdbTestInput([ - ... 'pdef example_function', - ... 'pdoc ExampleClass', - ... 'up', - ... 'down', - ... 'list', - ... 'pinfo a', - ... 'll', - ... 'continue', - ... ]): - ... trigger_ipdb() - --Return-- - None - > (3)trigger_ipdb() + In [4]: def trigger_ipdb(): + ...: a = ExampleClass() + ...: debugger.Pdb().set_trace() + + Run ipdb with faked input & check output. Because of a difference between + Python 3.13 & older versions, the first bit of the output is inconsistent. + We need to use ... to accommodate that, so the examples have to use IPython + prompts so that ... is distinct from the Python PS2 prompt. + + In [5]: with PdbTestInput([ + ...: 'pdef example_function', + ...: 'pdoc ExampleClass', + ...: 'up', + ...: 'down', + ...: 'list', + ...: 'pinfo a', + ...: 'll', + ...: 'continue', + ...: ]): + ...: trigger_ipdb() + ...> (3)trigger_ipdb() 1 def trigger_ipdb(): 2 a = ExampleClass() ----> 3 debugger.Pdb().set_trace() @@ -112,8 +115,7 @@ def test_ipdb_magics(): 10 ]): ---> 11 trigger_ipdb() - ipdb> down - None + ipdb> down... > (3)trigger_ipdb() 1 def trigger_ipdb(): 2 a = ExampleClass() @@ -136,10 +138,10 @@ def test_ipdb_magics(): ----> 3 debugger.Pdb().set_trace() ipdb> continue - + Restore previous trace function, e.g. for coverage.py - >>> sys.settrace(old_trace) + In [6]: sys.settrace(old_trace) ''' def test_ipdb_magics2(): From 7ea81cd8b4fce0b75f3cab58b636cf8235663db7 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 26 Aug 2024 16:57:24 +0100 Subject: [PATCH 14/41] Fix test_decorator_skip_with_breakpoint() on Python 3.13 --- IPython/core/tests/test_debugger.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 8de500d0b60..9c507cf79ff 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -497,11 +497,18 @@ def test_decorator_skip_with_breakpoint(): child.expect_exact(line) child.sendline("") - # as the filename does not exists, we'll rely on the filename prompt - child.expect_exact("47 bar(3, 4)") + # From 3.13, set_trace()/breakpoint() stop on the line where they're + # called, instead of the next line. + if sys.version_info >= (3, 13): + child.expect_exact("--> 46 ipdb.set_trace()") + extra_step = [("step", "--> 47 bar(3, 4)")] + else: + child.expect_exact("--> 47 bar(3, 4)") + extra_step = [] for input_, expected in [ (f"b {name}.py:3", ""), + ] + extra_step + [ ("step", "1---> 3 pass # should not stop here except"), ("step", "---> 38 @pdb_skipped_decorator"), ("continue", ""), From 44bffab7fab0b9b855ed5c55ad6b8049198f200e Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 26 Aug 2024 17:09:02 +0100 Subject: [PATCH 15/41] Relax string checks in test_pinfo_docstring_dynamic Allows this test to pass on Python 3.13 --- IPython/core/tests/test_oinspect.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/IPython/core/tests/test_oinspect.py b/IPython/core/tests/test_oinspect.py index 3decb8be04b..ac7c3656099 100644 --- a/IPython/core/tests/test_oinspect.py +++ b/IPython/core/tests/test_oinspect.py @@ -516,23 +516,23 @@ def prop(self, v): ip.run_line_magic("pinfo", "b.prop") captured = capsys.readouterr() - assert "Docstring: cdoc for prop" in captured.out + assert re.search(r"Docstring:\s+cdoc for prop", captured.out) ip.run_line_magic("pinfo", "b.non_exist") captured = capsys.readouterr() - assert "Docstring: cdoc for non_exist" in captured.out + assert re.search(r"Docstring:\s+cdoc for non_exist", captured.out) ip.run_cell("b.prop?") captured = capsys.readouterr() - assert "Docstring: cdoc for prop" in captured.out + assert re.search(r"Docstring:\s+cdoc for prop", captured.out) ip.run_cell("b.non_exist?") captured = capsys.readouterr() - assert "Docstring: cdoc for non_exist" in captured.out + assert re.search(r"Docstring:\s+cdoc for non_exist", captured.out) ip.run_cell("b.undefined?") captured = capsys.readouterr() - assert "Docstring: " in captured.out + assert re.search(r"Type:\s+NoneType", captured.out) def test_pinfo_magic(): From 2e34c770413ab283033f2edd757c45477ca6d1b3 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 26 Aug 2024 17:33:13 +0100 Subject: [PATCH 16/41] Autoformat with darker --- IPython/core/tests/test_debugger.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 9c507cf79ff..6113ff920c4 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -139,8 +139,8 @@ def test_ipdb_magics(): ipdb> continue - Restore previous trace function, e.g. for coverage.py - + Restore previous trace function, e.g. for coverage.py + In [6]: sys.settrace(old_trace) ''' @@ -506,13 +506,17 @@ def test_decorator_skip_with_breakpoint(): child.expect_exact("--> 47 bar(3, 4)") extra_step = [] - for input_, expected in [ - (f"b {name}.py:3", ""), - ] + extra_step + [ - ("step", "1---> 3 pass # should not stop here except"), - ("step", "---> 38 @pdb_skipped_decorator"), - ("continue", ""), - ]: + for input_, expected in ( + [ + (f"b {name}.py:3", ""), + ] + + extra_step + + [ + ("step", "1---> 3 pass # should not stop here except"), + ("step", "---> 38 @pdb_skipped_decorator"), + ("continue", ""), + ] + ): child.expect("ipdb>") child.sendline(input_) child.expect_exact(input_) From ef9d41eab01bdcbd7f40c0d09775c792381cabbd Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Fri, 30 Aug 2024 10:22:40 +0200 Subject: [PATCH 17/41] Whats new 8.27 --- docs/source/whatsnew/version8.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/source/whatsnew/version8.rst b/docs/source/whatsnew/version8.rst index 490bec850ea..ba09695c266 100644 --- a/docs/source/whatsnew/version8.rst +++ b/docs/source/whatsnew/version8.rst @@ -1,6 +1,37 @@ ============ 8.x Series ============ +.. _version 8.27: + +IPython 8.27 +============ + +New release of IPython after a month off (not enough changes). We can see a few +important changes for this release. + + - autocall was beeing call getitem, :ghpull:`14486` + - Only copy files in startup dir if we just created it. :ghpull:`14497` + - Fix some tests on Python 3.13 RC1 :ghpull:`14504`; this one I guess make this + the first IPython release officially compatible with Python 3.13; you will + need the most recent ``executing`` and ``stack_data``, we won't pin to avoid + forcing user of older Python version to upgrade. + + +As usual you can find the full list of PRs on GitHub under `the 8.27 +`__ milestone. + +Thanks +------ + +Many thanks to `@Kleirre `__ our June intern for +doing her first contribution to open source, doing the releases notes and +release. I guess you didn't even notice it was not me who released :-). I wish +her all the best in her future endeavor and look forward for her work in +astrophysics. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + .. _version 8.26: IPython 8.26 From 82690a067bc47ae3e82de2dc832cbf9840cbf5ea Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Fri, 30 Aug 2024 10:50:16 +0200 Subject: [PATCH 18/41] release 8.27.0 --- IPython/core/release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index a491faf4d15..c77f561096b 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -20,7 +20,7 @@ _version_patch = 0 _version_extra = ".dev" # _version_extra = "rc1" -# _version_extra = "" # Uncomment this for full releases +_version_extra = "" # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor, _version_patch] From b90943a07ff91f5ef6a8ca6528fea8f35495b308 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Fri, 30 Aug 2024 10:50:48 +0200 Subject: [PATCH 19/41] back to dev --- 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 c77f561096b..75d6173334e 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -16,11 +16,11 @@ # release. 'dev' as a _version_extra string means this is a development # version _version_major = 8 -_version_minor = 27 +_version_minor = 28 _version_patch = 0 _version_extra = ".dev" # _version_extra = "rc1" -_version_extra = "" # Uncomment this for full releases +# _version_extra = "" # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor, _version_patch] From 59ab72174daaf94579a68f3e338fd49c4386403c Mon Sep 17 00:00:00 2001 From: Stavros Ntentos <133706+stdedos@users.noreply.github.com> Date: Fri, 5 Jul 2024 21:12:27 +0300 Subject: [PATCH 20/41] `AssertionError`: `assert _xterm_term_title_saved` In some (unknown) situation, it is possible that the `_xterm_term_title_saved` is unset, but the code would make a call to `_restore_term_title_xterm`, resulting in `AssertionError`. At least on replicatable reproduction is returning from `ipython` to `pudb` via `^D^D` on an empty cell. See more details in https://github.com/ipython/ipython/pull/14480 Signed-off-by: Stavros Ntentos <133706+stdedos@users.noreply.github.com> --- IPython/utils/terminal.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/IPython/utils/terminal.py b/IPython/utils/terminal.py index b09cfe0d22d..10d73fce586 100644 --- a/IPython/utils/terminal.py +++ b/IPython/utils/terminal.py @@ -80,7 +80,13 @@ def _set_term_title_xterm(title): def _restore_term_title_xterm(): # Make sure the restore has at least one accompanying set. global _xterm_term_title_saved - assert _xterm_term_title_saved + if not _xterm_term_title_saved: + warnings.warn( + "Expecting xterm_term_title_saved to be True, but is not; will not restore terminal title.", + stacklevel=1, + ) + return + sys.stdout.write('\033[23;0t') _xterm_term_title_saved = False From 4931481095fca4a02ceb7a3e74b24c4725907899 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Mon, 2 Sep 2024 12:01:17 +0200 Subject: [PATCH 21/41] more infor about ipdb configuration --- IPython/core/debugger.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 0a524ebe401..937b6e88141 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -14,14 +14,35 @@ - hide frames in tracebacks based on `__tracebackhide__` - allows to skip frames based on `__debuggerskip__` + +Global Configuration +-------------------- + +The IPython debugger will by read the global ``~/.pdbrc`` file. +That is to say you can list all comands supported by ipdb in your `~/.pdbrc` +configuration file, to globally configure pdb. + +Example:: + + # ~/.pdbrc + skip_predicates debuggerskip false + skip_hidden false + context 25 + +Features +-------- + +The IPython debugger can hide and skip frames when printing or moving through +the stack. This can have a performance impact, so can be configures. + The skipping and hiding frames are configurable via the `skip_predicates` command. By default, frames from readonly files will be hidden, frames containing -``__tracebackhide__=True`` will be hidden. +``__tracebackhide__ = True`` will be hidden. -Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent -frames value of ``__debuggerskip__`` is ``True`` will be skipped. +Frames containing ``__debuggerskip__`` will be stepped over, frames whose parent +frames value of ``__debuggerskip__`` is ``True`` will also be skipped. >>> def helpers_helper(): ... pass @@ -1070,7 +1091,9 @@ def do_context(self, context): raise ValueError() self.context = new_context except ValueError: - self.error("The 'context' command requires a positive integer argument.") + self.error( + f"The 'context' command requires a positive integer argument (current value {self.context})." + ) class InterruptiblePdb(Pdb): From 02160bc77b4e556930e92cb261c5f0c688acff26 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 2 Sep 2024 14:21:34 +0100 Subject: [PATCH 22/41] Fix use of pyside6 >= 6.7.0 --- IPython/external/qt_loaders.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/IPython/external/qt_loaders.py b/IPython/external/qt_loaders.py index 1486cf9d773..76301946913 100644 --- a/IPython/external/qt_loaders.py +++ b/IPython/external/qt_loaders.py @@ -302,13 +302,24 @@ def import_pyside6(): ImportErrors raised within this function are non-recoverable """ + def get_attrs(module): + return { + name: getattr(module, name) + for name in dir(module) + if not name.startswith("_") + } + from PySide6 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport # Join QtGui and QtWidgets for Qt4 compatibility. QtGuiCompat = types.ModuleType("QtGuiCompat") QtGuiCompat.__dict__.update(QtGui.__dict__) - QtGuiCompat.__dict__.update(QtWidgets.__dict__) - QtGuiCompat.__dict__.update(QtPrintSupport.__dict__) + if QtCore.__version_info__ < (6, 7): + QtGuiCompat.__dict__.update(QtWidgets.__dict__) + QtGuiCompat.__dict__.update(QtPrintSupport.__dict__) + else: + QtGuiCompat.__dict__.update(get_attrs(QtWidgets)) + QtGuiCompat.__dict__.update(get_attrs(QtPrintSupport)) return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE6 From eca325081f8033bbc2abd74bbc2e3ae5d7571f20 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 2 Sep 2024 15:15:09 +0100 Subject: [PATCH 23/41] Linting --- IPython/external/qt_loaders.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/external/qt_loaders.py b/IPython/external/qt_loaders.py index 76301946913..6058ee5a9a8 100644 --- a/IPython/external/qt_loaders.py +++ b/IPython/external/qt_loaders.py @@ -302,6 +302,7 @@ def import_pyside6(): ImportErrors raised within this function are non-recoverable """ + def get_attrs(module): return { name: getattr(module, name) From e153b0fb226966be80662d801d7ff3f20eeb4313 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 3 Sep 2024 11:48:29 +0200 Subject: [PATCH 24/41] Get back to "Executing" stable It should now support Python 3.13 --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 925754b383a..1935286a831 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,7 +79,6 @@ jobs: python -m pip install --pre --upgrade pip setuptools wheel build python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --no-binary curio --upgrade -e .[${{ matrix.deps }}] python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --upgrade check-manifest pytest-cov pytest-json-report - python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple git+https://github.com/alexmojaki/executing.git@3.13 - name: Try building with Python build if: runner.os != 'Windows' # setup.py does not support sdist on Windows run: | From 0e5bd036dd461fad895581f9ff822374206067c1 Mon Sep 17 00:00:00 2001 From: Shaun Walbridge Date: Wed, 11 Sep 2024 22:01:44 -0400 Subject: [PATCH 25/41] Use environment variable to identify conda / mamba Conda and mamba both set an environment variable which refers to the base environment's executable path, use that in preference to less reliable methods, but fall back on the other approaches if unable to locate the executable this way. Additionally, change the search to look for the bare command name rather than the command within the top level of the active environment, I'm dubious this approach works with any current conda / mamba version which usually place their executable links in a `condabin` directory or elsewhere not at the same level as the Python executable. --- IPython/core/magics/packaging.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/IPython/core/magics/packaging.py b/IPython/core/magics/packaging.py index 093b0a2ec10..b335e7e24a0 100644 --- a/IPython/core/magics/packaging.py +++ b/IPython/core/magics/packaging.py @@ -9,6 +9,7 @@ #----------------------------------------------------------------------------- import functools +import os import re import shlex import sys @@ -41,6 +42,16 @@ def _get_conda_like_executable(command): executable: string Value should be: conda, mamba or micromamba """ + # Check for a environment variable bound to the base executable, both conda and mamba + # set these when activating an environment. + base_executable = "CONDA_EXE" + if 'mamba' in command.lower(): + base_executable = "MAMBA_EXE" + if base_executable in os.environ: + executable = Path(os.environ[base_executable]) + if executable.is_file(): + return str(executable.resolve()) + # Check if there is a conda executable in the same directory as the Python executable. # This is the case within conda's root environment. executable = Path(sys.executable).parent / command @@ -48,10 +59,12 @@ def _get_conda_like_executable(command): return str(executable) # Otherwise, attempt to extract the executable from conda history. - # This applies in any conda environment. + # This applies in any conda environment. Parsing this way is error prone because + # different versions of conda and mamba include differing cmd values such as + # `conda`, `conda-script.py`, or `path/to/conda`, here use the raw command provided. history = Path(sys.prefix, "conda-meta", "history").read_text(encoding="utf-8") match = re.search( - rf"^#\s*cmd:\s*(?P.*{executable})\s[create|install]", + rf"^#\s*cmd:\s*(?P.*{command})\s[create|install]", history, flags=re.MULTILINE, ) From 578ae2b7f47bb9aed8ff0110f0a98bed17660142 Mon Sep 17 00:00:00 2001 From: Shaun Walbridge Date: Wed, 11 Sep 2024 22:44:43 -0400 Subject: [PATCH 26/41] fix formatting --- IPython/core/magics/packaging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/magics/packaging.py b/IPython/core/magics/packaging.py index b335e7e24a0..09d4117270f 100644 --- a/IPython/core/magics/packaging.py +++ b/IPython/core/magics/packaging.py @@ -45,7 +45,7 @@ def _get_conda_like_executable(command): # Check for a environment variable bound to the base executable, both conda and mamba # set these when activating an environment. base_executable = "CONDA_EXE" - if 'mamba' in command.lower(): + if "mamba" in command.lower(): base_executable = "MAMBA_EXE" if base_executable in os.environ: executable = Path(os.environ[base_executable]) From 039827387b71b8b2932e3c2e272340bd6337980b Mon Sep 17 00:00:00 2001 From: jsbautista Date: Mon, 16 Sep 2024 00:55:15 -0500 Subject: [PATCH 27/41] Make values public --- IPython/core/ultratb.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 66c9ce9108b..a8a38f165b7 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -830,8 +830,8 @@ class VerboseTB(TBTools): traceback, to be used with alternate interpreters (because their own code would appear in the traceback).""" - _tb_highlight = "bg:ansiyellow" - _tb_highlight_style = "default" + tb_highlight = "bg:ansiyellow" + tb_highlight_style = "default" def __init__( self, @@ -1133,8 +1133,8 @@ def get_records( after = context // 2 before = context - after if self.has_colors: - style = get_style_by_name(self._tb_highlight_style) - style = stack_data.style_with_executing_node(style, self._tb_highlight) + style = get_style_by_name(self.tb_highlight_style) + style = stack_data.style_with_executing_node(style, self.tb_highlight) formatter = Terminal256Formatter(style=style) else: formatter = None From cf006c5b191bd0f0d4dd21bbd8e070533ff356ce Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 4 Mar 2022 10:49:46 +0100 Subject: [PATCH 28/41] Start migrating somethings to pytest's monkeypatch --- IPython/utils/tests/test_path.py | 82 ++++++++++++++++---------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py index 5ea27e08693..ea580755d68 100644 --- a/IPython/utils/tests/test_path.py +++ b/IPython/utils/tests/test_path.py @@ -113,11 +113,11 @@ def environment(): @skip_if_not_win32 @with_environment -def test_get_home_dir_1(): +def test_get_home_dir_1(monkeypatch): """Testcase for py2exe logic, un-compressed lib """ unfrozen = path.get_home_dir() - sys.frozen = True + monkeypatch.setattr(sys, "frozen", True) #fake filename for IPython.__init__ IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py")) @@ -128,13 +128,15 @@ def test_get_home_dir_1(): @skip_if_not_win32 @with_environment -def test_get_home_dir_2(): +def test_get_home_dir_2(monkeypatch): """Testcase for py2exe logic, compressed lib """ unfrozen = path.get_home_dir() - sys.frozen = True - #fake filename for IPython.__init__ - IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower() + monkeypatch.setattr(sys, "frozen", True) + # fake filename for IPython.__init__ + IPython.__file__ = abspath( + join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py") + ).lower() home_dir = path.get_home_dir(True) assert home_dir == unfrozen @@ -160,22 +162,22 @@ def test_get_home_dir_4(): @skip_win32 @with_environment -def test_get_home_dir_5(): +def test_get_home_dir_5(monkeypatch): """raise HomeDirError if $HOME is specified, but not a writable dir""" env['HOME'] = abspath(HOME_TEST_DIR+'garbage') # set os.name = posix, to prevent My Documents fallback on Windows - os.name = 'posix' + monkeypatch.setattr(os, "name", "posix") pytest.raises(path.HomeDirError, path.get_home_dir, True) # Should we stub wreg fully so we can run the test on all platforms? @skip_if_not_win32 @with_environment -def test_get_home_dir_8(): +def test_get_home_dir_8(monkeypatch): """Using registry hack for 'My Documents', os=='nt' HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing. """ - os.name = 'nt' + monkeypatch.setattr(os, "name", "nt") # Remove from stub environment all keys that may be set for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']: env.pop(key, None) @@ -194,13 +196,12 @@ def __exit__(*args, **kwargs): assert home_dir == abspath(HOME_TEST_DIR) @with_environment -def test_get_xdg_dir_0(): +def test_get_xdg_dir_0(monkeypatch): """test_get_xdg_dir_0, check xdg_dir""" - reload(path) - path._writable_dir = lambda path: True - path.get_home_dir = lambda : 'somewhere' - os.name = "posix" - sys.platform = "linux2" + monkeypatch.setattr(path, "_writable_dir", lambda path: True) + monkeypatch.setattr(path, "get_home_dir", lambda: "somewhere") + monkeypatch.setattr(os, "name", "posix") + monkeypatch.setattr(sys, "platform", "linux2") env.pop('IPYTHON_DIR', None) env.pop('IPYTHONDIR', None) env.pop('XDG_CONFIG_HOME', None) @@ -209,44 +210,41 @@ def test_get_xdg_dir_0(): @with_environment -def test_get_xdg_dir_1(): +def test_get_xdg_dir_1(monkeypatch): """test_get_xdg_dir_1, check nonexistent xdg_dir""" - reload(path) - path.get_home_dir = lambda : HOME_TEST_DIR - os.name = "posix" - sys.platform = "linux2" - env.pop('IPYTHON_DIR', None) - env.pop('IPYTHONDIR', None) - env.pop('XDG_CONFIG_HOME', None) + monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR) + monkeypatch.setattr(os, "name", "posix") + monkeypatch.setattr(sys, "platform", "linux2") + env.pop("IPYTHON_DIR", None) + env.pop("IPYTHONDIR", None) + env.pop("XDG_CONFIG_HOME", None) assert path.get_xdg_dir() is None @with_environment -def test_get_xdg_dir_2(): +def test_get_xdg_dir_2(monkeypatch): """test_get_xdg_dir_2, check xdg_dir default to ~/.config""" - reload(path) - path.get_home_dir = lambda : HOME_TEST_DIR - os.name = "posix" - sys.platform = "linux2" - env.pop('IPYTHON_DIR', None) - env.pop('IPYTHONDIR', None) - env.pop('XDG_CONFIG_HOME', None) - cfgdir=os.path.join(path.get_home_dir(), '.config') + monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR) + monkeypatch.setattr(os, "name", "posix") + monkeypatch.setattr(sys, "platform", "linux2") + env.pop("IPYTHON_DIR", None) + env.pop("IPYTHONDIR", None) + env.pop("XDG_CONFIG_HOME", None) + cfgdir = os.path.join(path.get_home_dir(), ".config") if not os.path.exists(cfgdir): os.makedirs(cfgdir) assert path.get_xdg_dir() == cfgdir @with_environment -def test_get_xdg_dir_3(): +def test_get_xdg_dir_3(monkeypatch): """test_get_xdg_dir_3, check xdg_dir not used on non-posix systems""" - reload(path) - path.get_home_dir = lambda : HOME_TEST_DIR - os.name = "nt" - sys.platform = "win32" - env.pop('IPYTHON_DIR', None) - env.pop('IPYTHONDIR', None) - env.pop('XDG_CONFIG_HOME', None) - cfgdir=os.path.join(path.get_home_dir(), '.config') + monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR) + monkeypatch.setattr(os, "name", "nt") + monkeypatch.setattr(sys, "platform", "win32") + env.pop("IPYTHON_DIR", None) + env.pop("IPYTHONDIR", None) + env.pop("XDG_CONFIG_HOME", None) + cfgdir = os.path.join(path.get_home_dir(), ".config") os.makedirs(cfgdir, exist_ok=True) assert path.get_xdg_dir() is None From 46979aade049562e114fb160167bde871090667a Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Thu, 19 Sep 2024 10:52:00 +0200 Subject: [PATCH 29/41] more pytest cleanup --- IPython/utils/tests/test_path.py | 95 ++++++++++++++++---------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py index ea580755d68..92794b690de 100644 --- a/IPython/utils/tests/test_path.py +++ b/IPython/utils/tests/test_path.py @@ -75,20 +75,29 @@ def teardown_module(): shutil.rmtree(TMP_TEST_DIR) -def setup_environment(): - """Setup testenvironment for some functions that are tested - in this module. In particular this functions stores attributes - and other things that we need to stub in some test functions. - This needs to be done on a function level and not module level because - each testfunction needs a pristine environment. - """ +# Build decorator that uses the setup_environment/setup_environment +@pytest.fixture +def environment(): global oldstuff, platformstuff - oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd()) + oldstuff = ( + env.copy(), + os.name, + sys.platform, + path.get_home_dir, + IPython.__file__, + os.getcwd(), + ) -def teardown_environment(): - """Restore things that were remembered by the setup_environment function - """ - (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff + yield + + ( + oldenv, + os.name, + sys.platform, + path.get_home_dir, + IPython.__file__, + old_wd, + ) = oldstuff os.chdir(old_wd) reload(path) @@ -96,16 +105,7 @@ def teardown_environment(): if key not in oldenv: del env[key] env.update(oldenv) - if hasattr(sys, 'frozen'): - del sys.frozen - - -# Build decorator that uses the setup_environment/setup_environment -@pytest.fixture -def environment(): - setup_environment() - yield - teardown_environment() + assert not hasattr(sys, "frozen") with_environment = pytest.mark.usefixtures("environment") @@ -117,7 +117,7 @@ def test_get_home_dir_1(monkeypatch): """Testcase for py2exe logic, un-compressed lib """ unfrozen = path.get_home_dir() - monkeypatch.setattr(sys, "frozen", True) + monkeypatch.setattr(sys, "frozen", True, raising=False) #fake filename for IPython.__init__ IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py")) @@ -132,7 +132,7 @@ def test_get_home_dir_2(monkeypatch): """Testcase for py2exe logic, compressed lib """ unfrozen = path.get_home_dir() - monkeypatch.setattr(sys, "frozen", True) + monkeypatch.setattr(sys, "frozen", True, raising=False) # fake filename for IPython.__init__ IPython.__file__ = abspath( join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py") @@ -279,31 +279,30 @@ def test_get_long_path_name(): assert p == "/usr/local" -class TestRaiseDeprecation(unittest.TestCase): +@dec.skip_win32 # can't create not-user-writable dir on win +@with_environment +def test_not_writable_ipdir(): + tmpdir = tempfile.mkdtemp() + os.name = "posix" + env.pop("IPYTHON_DIR", None) + env.pop("IPYTHONDIR", None) + env.pop("XDG_CONFIG_HOME", None) + env["HOME"] = tmpdir + ipdir = os.path.join(tmpdir, ".ipython") + os.mkdir(ipdir, 0o555) + try: + open(os.path.join(ipdir, "_foo_"), "w", encoding="utf-8").close() + except IOError: + pass + else: + # I can still write to an unwritable dir, + # assume I'm root and skip the test + pytest.skip("I can't create directories that I can't write to") + + with pytest.warns(UserWarning, match="is not a writable location"): + ipdir = paths.get_ipython_dir() + env.pop("IPYTHON_DIR", None) - @dec.skip_win32 # can't create not-user-writable dir on win - @with_environment - def test_not_writable_ipdir(self): - tmpdir = tempfile.mkdtemp() - os.name = "posix" - env.pop('IPYTHON_DIR', None) - env.pop('IPYTHONDIR', None) - env.pop('XDG_CONFIG_HOME', None) - env['HOME'] = tmpdir - ipdir = os.path.join(tmpdir, '.ipython') - os.mkdir(ipdir, 0o555) - try: - open(os.path.join(ipdir, "_foo_"), "w", encoding="utf-8").close() - except IOError: - pass - else: - # I can still write to an unwritable dir, - # assume I'm root and skip the test - pytest.skip("I can't create directories that I can't write to") - - with self.assertWarnsRegex(UserWarning, 'is not a writable location'): - ipdir = paths.get_ipython_dir() - env.pop('IPYTHON_DIR', None) @with_environment def test_get_py_filename(): From be117b750461990c450de7ee19e9aa12ef2b2bc1 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Thu, 19 Sep 2024 11:14:49 +0200 Subject: [PATCH 30/41] stop tesing sage for now --- .github/workflows/downstream.yml | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 00f19d36777..04429d513eb 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -53,19 +53,21 @@ jobs: pytest - name: Install sagemath-repl run: | - cd .. - git clone --depth 1 https://github.com/sagemath/sage - cd sage - # We cloned it for the tests, but for simplicity we install the - # wheels from PyPI. - # (Avoid 10.3b6 because of https://github.com/sagemath/sage/pull/37178) - pip install --pre sagemath-repl sagemath-environment - # Install optionals that make more tests pass - pip install pillow - pip install --pre sagemath-categories - cd .. + # Sept 2024, sage has been failing for a while, + # Skipping. + # cd .. + # git clone --depth 1 https://github.com/sagemath/sage + # cd sage + # # We cloned it for the tests, but for simplicity we install the + # # wheels from PyPI. + # # (Avoid 10.3b6 because of https://github.com/sagemath/sage/pull/37178) + # pip install --pre sagemath-repl sagemath-environment + # # Install optionals that make more tests pass + # pip install pillow + # pip install --pre sagemath-categories + # cd .. - name: Test sagemath-repl run: | - cd ../sage/ - # From https://github.com/sagemath/sage/blob/develop/pkgs/sagemath-repl/tox.ini - sage-runtests -p --environment=sage.all__sagemath_repl --baseline-stats-path=pkgs/sagemath-repl/known-test-failures.json --initial --optional=sage src/sage/repl src/sage/doctest src/sage/misc/sage_input.py src/sage/misc/sage_eval.py + # cd ../sage/ + # # From https://github.com/sagemath/sage/blob/develop/pkgs/sagemath-repl/tox.ini + # sage-runtests -p --environment=sage.all__sagemath_repl --baseline-stats-path=pkgs/sagemath-repl/known-test-failures.json --initial --optional=sage src/sage/repl src/sage/doctest src/sage/misc/sage_input.py src/sage/misc/sage_eval.py From f51bfd63162703b53015c1f23900bdecbef88024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Thu, 19 Sep 2024 21:02:28 -0700 Subject: [PATCH 31/41] DOC: fix typo in deprecation warning --- IPython/core/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index 20e2e34b8f6..04d56ab3582 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -41,7 +41,7 @@ def __getattr__(name): if name in _deprecated_names: - warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2) + warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython.display", DeprecationWarning, stacklevel=2) return getattr(display_functions, name) if name in globals().keys(): From 7fe23a1961e01be9c4d7429179f08d06017740ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Thu, 19 Sep 2024 21:40:55 -0700 Subject: [PATCH 32/41] MAINT: linter --- IPython/core/display.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index 04d56ab3582..5c4557b150f 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -41,7 +41,11 @@ def __getattr__(name): if name in _deprecated_names: - warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython.display", DeprecationWarning, stacklevel=2) + warn( + f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython.display", + DeprecationWarning, + stacklevel=2, + ) return getattr(display_functions, name) if name in globals().keys(): From 189346703e7751c717fe513578441d373dab8905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Thu, 19 Sep 2024 21:46:44 -0700 Subject: [PATCH 33/41] CI: update actions version --- .github/workflows/docs.yml | 6 +++--- .github/workflows/downstream.yml | 4 ++-- .github/workflows/mypy.yml | 4 ++-- .github/workflows/nightly-wheel-build.yml | 4 ++-- .github/workflows/python-package.yml | 4 ++-- .github/workflows/test.yml | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f18fb39392f..5513b16930b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,9 +10,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.x - name: Install Graphviz @@ -34,6 +34,6 @@ jobs: run: | coverage combine `find . -name .coverage\*` && coverage xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v4 with: name: Docs diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 04429d513eb..3340c0182b2 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -25,9 +25,9 @@ jobs: python-version: "3.10" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Update Python installer diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index c65682efb1d..d2bffdb607e 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -18,9 +18,9 @@ jobs: python-version: ["3.x"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/nightly-wheel-build.yml b/.github/workflows/nightly-wheel-build.yml index 64a41e32eda..6f0eaddca53 100644 --- a/.github/workflows/nightly-wheel-build.yml +++ b/.github/workflows/nightly-wheel-build.yml @@ -13,9 +13,9 @@ jobs: if: github.event_name != 'pull_request' && (github.event_name != 'schedule' || github.repository_owner == 'ipython') steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" cache: pip diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index e65f2e5b3a2..786d00b9509 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -18,11 +18,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.x - name: Install dependencies diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1935286a831..8bcc2d3a55d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,9 +56,9 @@ jobs: want-latest-entry-point-code: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: pip @@ -102,14 +102,14 @@ jobs: COLUMNS: 120 run: | pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }} --json-report --json-report-file=./report-${{ matrix.python-version }}-${{runner.os}}.json --maxfail=15 - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: upload pytest timing reports as json path: | ./report-*.json - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: name: Test files: /home/runner/work/ipython/ipython/coverage.xml From 80f7077d1beb93a5ac2909cd69787a6779f74a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Thu, 19 Sep 2024 22:00:09 -0700 Subject: [PATCH 34/41] CI: downgrade upload-artifact version to avoid (409) error --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8bcc2d3a55d..eb8b2b31720 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -102,7 +102,7 @@ jobs: COLUMNS: 120 run: | pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }} --json-report --json-report-file=./report-${{ matrix.python-version }}-${{runner.os}}.json --maxfail=15 - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v3 with: name: upload pytest timing reports as json path: | From 8f54e26cff0e96536e61ce33a237f54bd1d2102b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Fri, 20 Sep 2024 10:35:55 -0700 Subject: [PATCH 35/41] CI: adding dependabot for actions update --- .github/dependabot.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..d1fed9f3d5f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + groups: + actions: + patterns: + - "*" From 11b984c1abba9a6aba012c0750ec1adf519d69a6 Mon Sep 17 00:00:00 2001 From: Wes Turner Date: Mon, 30 Sep 2024 02:23:16 -0400 Subject: [PATCH 36/41] DOC: whatsnew/v8: Update release notes for the Mamba and Micromamba magic commands #14191 --- docs/source/whatsnew/version8.rst | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/source/whatsnew/version8.rst b/docs/source/whatsnew/version8.rst index ba09695c266..8ab6c5d1b14 100644 --- a/docs/source/whatsnew/version8.rst +++ b/docs/source/whatsnew/version8.rst @@ -392,16 +392,29 @@ Reverted in 8.17.1: - :ghpull:`14190` remove support for python 2 in lexers (reverted in 8.17.1 as it is imported by qtconsole/spyder) -Mamba and Micromamba magic -~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _ref: mamba-and-micromamba-magic +.. _ref: mamba-and-micromamba-magic-commands + +Mamba and Micromamba magic commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In addition to the ``%conda`` magic command for calling ``conda`` in IPython, +the ``%mamba`` and ``%micromamba`` magic commands now +call ``mamba`` and ``micromamba`` if they are on ``sys.path``. + +.. code:: python + + %mamba install pkgname + %micromamba install pkgname + %conda install pkgname + %pip install pkgname -In addition to the conda command to manage conda environment, mamba and -micromamba can now be used using the corresponding magic in IPython. -Since these commands are compatible with conda, they are following the -same logic. + %mamba --help + %micromamba --help + %conda --help + %pip --help # works w/ JupyterLite + !pip --help -These two magic require to have the corresponding commands available -either in the conda environment or system wide. :ghpull:`14191` From ab2053a3d9069f5c30faf23baef4724c4782dfc5 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 1 Oct 2024 09:37:53 +0200 Subject: [PATCH 37/41] remove json report files --- .github/workflows/test.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eb8b2b31720..3543bdbe2da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,13 +72,13 @@ jobs: run: | python -m pip install --only-binary ':all:' --upgrade pip setuptools wheel build python -m pip install --only-binary ':all:' --no-binary curio --upgrade -e .[${{ matrix.deps }}] - python -m pip install --only-binary ':all:' --upgrade check-manifest pytest-cov pytest-json-report 'pytest<8' + python -m pip install --only-binary ':all:' --upgrade check-manifest pytest-cov 'pytest<8' - name: Install and update Python dependencies (dev?) if: ${{ contains( matrix.python-version, 'dev' ) }} run: | python -m pip install --pre --upgrade pip setuptools wheel build python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --no-binary curio --upgrade -e .[${{ matrix.deps }}] - python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --upgrade check-manifest pytest-cov pytest-json-report + python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --upgrade check-manifest pytest-cov - name: Try building with Python build if: runner.os != 'Windows' # setup.py does not support sdist on Windows run: | @@ -101,13 +101,7 @@ jobs: env: COLUMNS: 120 run: | - pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }} --json-report --json-report-file=./report-${{ matrix.python-version }}-${{runner.os}}.json --maxfail=15 - - uses: actions/upload-artifact@v3 - with: - name: upload pytest timing reports as json - path: | - ./report-*.json - + pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }} --maxfail=15 - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: From 06f266d213e12ecf8f0b96ca2efd8a74b67103ee Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 1 Oct 2024 09:59:08 +0200 Subject: [PATCH 38/41] Update docs/source/whatsnew/version8.rst --- docs/source/whatsnew/version8.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/source/whatsnew/version8.rst b/docs/source/whatsnew/version8.rst index 8ab6c5d1b14..006ac09e628 100644 --- a/docs/source/whatsnew/version8.rst +++ b/docs/source/whatsnew/version8.rst @@ -392,8 +392,6 @@ Reverted in 8.17.1: - :ghpull:`14190` remove support for python 2 in lexers (reverted in 8.17.1 as it is imported by qtconsole/spyder) -.. _ref: mamba-and-micromamba-magic -.. _ref: mamba-and-micromamba-magic-commands Mamba and Micromamba magic commands ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From b91233156fe0d295fc29b95ede9d177ede548734 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 1 Oct 2024 10:04:53 +0200 Subject: [PATCH 39/41] Update docs/source/whatsnew/version8.rst --- docs/source/whatsnew/version8.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/whatsnew/version8.rst b/docs/source/whatsnew/version8.rst index 006ac09e628..865322d7477 100644 --- a/docs/source/whatsnew/version8.rst +++ b/docs/source/whatsnew/version8.rst @@ -400,7 +400,7 @@ In addition to the ``%conda`` magic command for calling ``conda`` in IPython, the ``%mamba`` and ``%micromamba`` magic commands now call ``mamba`` and ``micromamba`` if they are on ``sys.path``. -.. code:: python +.. code:: %mamba install pkgname %micromamba install pkgname From 9b8d0c387597c739a6a5be3d33e8be106385c939 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Wed, 2 Oct 2024 11:56:08 +0200 Subject: [PATCH 40/41] What's new and update release tools --- docs/source/whatsnew/version8.rst | 37 +++++++++++++ tools/release_helper.sh | 88 +++++++++++++++---------------- 2 files changed, 81 insertions(+), 44 deletions(-) diff --git a/docs/source/whatsnew/version8.rst b/docs/source/whatsnew/version8.rst index 865322d7477..c231777a26a 100644 --- a/docs/source/whatsnew/version8.rst +++ b/docs/source/whatsnew/version8.rst @@ -1,6 +1,43 @@ ============ 8.x Series ============ +.. _version 8.28: + +IPython 8.28 +============ + +Slight delay of this September release as I was busy at Pydata Paris last week. +Not many user visible changes for this release, a couple of bug fixes and +workaround: + + - :ghpull:`14480` AssertionError: assert _xterm_term_title_saved in WSL – It is + unclear why the terminal title is not saved in WSL, if you've WSL experience + we'd love your feedback and help to not just ignore an error + - :ghpull:`14510` Fix use of pyside6 >= 6.7.0 + - :ghpull:`14518` Make values public (_tb_highlight & _tb_highlight_style) + - :ghpull:`14515` Use environment variable to identify conda / mamba + + +As usual you can find the full list of PRs on GitHub under `the 8.28 +`__ milestone. + +For something completely different +---------------------------------- + +One of the first works of Science Fiction (`Frankenstein +`__), was written by `Mary Shelley +`__ when she was 18, before being +published in London on 1 January 1818 when she was 20. This is often overlooked, +and the role of founders of science fiction attribute to Edgar Allan Poe and +Jules Verne despite being published later. + +Thanks +------ + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + + .. _version 8.27: IPython 8.27 diff --git a/tools/release_helper.sh b/tools/release_helper.sh index 08c8f6b8571..973baacf14f 100644 --- a/tools/release_helper.sh +++ b/tools/release_helper.sh @@ -30,12 +30,12 @@ python -c 'import matplotlib' echo "Will use $BLUE'$EDITOR'$NOR to edit files when necessary" -echo -n "PREV_RELEASE (X.y.z) [$PREV_RELEASE]: " -read input -PREV_RELEASE=${input:-$PREV_RELEASE} -echo -n "MILESTONE (X.y) [$MILESTONE]: " -read input -MILESTONE=${input:-$MILESTONE} +# echo -n "PREV_RELEASE (X.y.z) [$PREV_RELEASE]: " +# read input +# PREV_RELEASE=${input:-$PREV_RELEASE} +# echo -n "MILESTONE (X.y) [$MILESTONE]: " +# read input +# MILESTONE=${input:-$MILESTONE} echo -n "VERSION (X.y.z) [$VERSION]:" read input VERSION=${input:-$VERSION} @@ -90,44 +90,44 @@ then read fi -if ask_section "Gen Stats, and authors" -then - - echo - echo $BLUE"here are all the authors that contributed to this release:"$NOR - git log --format="%aN <%aE>" $PREV_RELEASE... | sort -u -f - - echo - echo $BLUE"If you see any duplicates cancel (Ctrl-C), then edit .mailmap." - echo $GREEN"Press enter to continue:"$NOR - read - - echo $BLUE"generating stats"$NOR - python tools/github_stats.py --milestone $MILESTONE > stats.rst - - echo $BLUE"stats.rst files generated."$NOR - echo $GREEN"Please merge it with the right file (github-stats-X.rst) and commit."$NOR - echo $GREEN"press enter to continue."$NOR - read - -fi - -if ask_section "Generate API difference (using frapuccino)" -then - echo $BLUE"Checking out $PREV_RELEASE"$NOR - git checkout tags/$PREV_RELEASE - sleep 1 - echo $BLUE"Saving API to file $PREV_RELEASE"$NOR - frappuccino IPython IPython.kernel IPython.lib IPython.qt IPython.lib.kernel IPython.html IPython.frontend IPython.external --save IPython-$PREV_RELEASE.json - echo $BLUE"coming back to $BRANCH"$NOR - git switch $BRANCH - sleep 1 - echo $BLUE"comparing ..."$NOR - frappuccino IPython IPython.kernel IPython.lib --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 +# if ask_section "Gen Stats, and authors" +# then +# +# echo +# echo $BLUE"here are all the authors that contributed to this release:"$NOR +# git log --format="%aN <%aE>" $PREV_RELEASE... | sort -u -f +# +# echo +# echo $BLUE"If you see any duplicates cancel (Ctrl-C), then edit .mailmap." +# echo $GREEN"Press enter to continue:"$NOR +# read +# +# echo $BLUE"generating stats"$NOR +# python tools/github_stats.py --milestone $MILESTONE > stats.rst +# +# echo $BLUE"stats.rst files generated."$NOR +# echo $GREEN"Please merge it with the right file (github-stats-X.rst) and commit."$NOR +# echo $GREEN"press enter to continue."$NOR +# read +# +# fi + +# if ask_section "Generate API difference (using frapuccino)" +# then +# echo $BLUE"Checking out $PREV_RELEASE"$NOR +# git checkout tags/$PREV_RELEASE +# sleep 1 +# echo $BLUE"Saving API to file $PREV_RELEASE"$NOR +# frappuccino IPython IPython.kernel IPython.lib IPython.qt IPython.lib.kernel IPython.html IPython.frontend IPython.external --save IPython-$PREV_RELEASE.json +# echo $BLUE"coming back to $BRANCH"$NOR +# git switch $BRANCH +# sleep 1 +# echo $BLUE"comparing ..."$NOR +# frappuccino IPython IPython.kernel IPython.lib --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 From a9c7369d725c7ab50e92f8f84329a0d7048dd6c8 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Wed, 2 Oct 2024 13:28:31 +0200 Subject: [PATCH 41/41] release 8.28.0 --- IPython/core/release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index 75d6173334e..fb5a54da6ab 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -20,7 +20,7 @@ _version_patch = 0 _version_extra = ".dev" # _version_extra = "rc1" -# _version_extra = "" # Uncomment this for full releases +_version_extra = "" # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor, _version_patch]