From bd2cdae3ce3de8e1ac912a11885fc98f1dc22bb4 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 14 Oct 2021 17:04:37 +0100 Subject: [PATCH 01/28] bpo-45447: Add syntax highlighting for `.pyi` files in IDLE This commit adds syntax highlighting for `.pyi` files in IDLE. It also adds an option to open Python stub files in the "File-open" dialogue, and an option to save a file as a `.pyi` file in the "File-save" dialogue. --- Lib/idlelib/README.txt | 1 + Lib/idlelib/browser.py | 17 +++++---- Lib/idlelib/editor.py | 12 +++---- Lib/idlelib/idle_test/test_iomenu.py | 18 ++++++++++ Lib/idlelib/idle_test/test_util.py | 15 ++++++++ Lib/idlelib/iomenu.py | 1 + Lib/idlelib/util.py | 36 +++++++++++++++++++ Misc/ACKS | 1 + .../2021-10-14-16-55-03.bpo-45447.FhiH5P.rst | 2 ++ 9 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 Lib/idlelib/idle_test/test_util.py create mode 100644 Lib/idlelib/util.py create mode 100644 Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst diff --git a/Lib/idlelib/README.txt b/Lib/idlelib/README.txt index bc3d978f43f1ad..54a603d59b5b29 100644 --- a/Lib/idlelib/README.txt +++ b/Lib/idlelib/README.txt @@ -81,6 +81,7 @@ statusbar.py # Define status bar for windows (nim). tabbedpages.py # Define tabbed pages widget (nim). textview.py # Define read-only text widget (nim). tree.py # Define tree widget, used in browsers (nim). +util.py # Define objects imported elsewhere with no dependencies (nim) undo.py # Manage undo stack. windows.py # Manage window list and define listed top level. zoomheight.py # Zoom window to full height of screen. diff --git a/Lib/idlelib/browser.py b/Lib/idlelib/browser.py index 3c3a53a6599a79..1168c49e240c9c 100644 --- a/Lib/idlelib/browser.py +++ b/Lib/idlelib/browser.py @@ -13,6 +13,7 @@ import pyclbr import sys +from idlelib.util import is_python_source, is_python_extension from idlelib.config import idleConf from idlelib import pyshell from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas @@ -76,7 +77,7 @@ def __init__(self, master, path, *, _htest=False, _utest=False): Instance variables: name: Module name. - file: Full path and module with .py extension. Used in + file: Full path and module with a valid Python extension. Used in creating ModuleBrowserTreeItem as the rootnode for the tree and subsequently in the children. """ @@ -161,21 +162,19 @@ def GetSubList(self): def OnDoubleClick(self): "Open a module in an editor window when double clicked." - if os.path.normcase(self.file[-3:]) != ".py": - return - if not os.path.exists(self.file): - return - file_open(self.file) + if self.IsExpandable() and os.path.exists(self.file): + file_open(self.file) def IsExpandable(self): - "Return True if Python (.py) file." - return os.path.normcase(self.file[-3:]) == ".py" + "Identify whether `self.file` is a valid Python file." + # see idlelib.util for currently supported Python extensions. + return is_python_source(self.file) def listchildren(self): "Return sequenced classes and functions in the module." dir, base = os.path.split(self.file) name, ext = os.path.splitext(base) - if os.path.normcase(ext) != ".py": + if not is_python_extension(os.path.normcase(ext)): return [] try: tree = pyclbr.readmodule_ex(name, [dir] + sys.path) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index fcc8a3f08ccfe3..b82277b0209f55 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -15,6 +15,7 @@ from tkinter import simpledialog from tkinter import messagebox +import idlelib.util from idlelib.config import idleConf from idlelib import configdialog from idlelib import grep @@ -754,13 +755,10 @@ def gotoline(self, lineno): self.center() def ispythonsource(self, filename): - if not filename or os.path.isdir(filename): - return True - base, ext = os.path.splitext(os.path.basename(filename)) - if os.path.normcase(ext) in (".py", ".pyw"): - return True - line = self.text.get('1.0', '1.0 lineend') - return line.startswith('#!') and 'python' in line + return idlelib.util.is_python_source( + filepath=filename, + firstline=self.text.get('1.0', '1.0 lineend') + ) def close_hook(self): if self.flist: diff --git a/Lib/idlelib/idle_test/test_iomenu.py b/Lib/idlelib/idle_test/test_iomenu.py index 99f40487967124..bb53b53808d9ae 100644 --- a/Lib/idlelib/idle_test/test_iomenu.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -44,6 +44,24 @@ def test_fixnewlines_end(self): eq(text.get('1.0', 'end-1c'), 'a\n') eq(fix(), 'a'+io.eol_convention) + def check_filetype(self, ext): + return any( + ext in filetype_tuple[1] for filetype_tuple in self.io.filetypes + ) + + def test_python_files(self): + self.assertTrue(self.check_filetype('*.py')) + self.assertTrue(self.check_filetype('*.pyw')) + + def test_python_stub_files(self): + self.assertTrue(self.check_filetype('*.pyi')) + + def test_text_files(self): + self.assertTrue(self.check_filetype('*.txt')) + + def test_all_files(self): + self.assertTrue(self.check_filetype('*')) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py new file mode 100644 index 00000000000000..246dd8b12d439b --- /dev/null +++ b/Lib/idlelib/idle_test/test_util.py @@ -0,0 +1,15 @@ +"Test constants and functions found in idlelib.util" + +import unittest +from idlelib import util + + +class ExtensionTest(unittest.TestCase): + def test_extensions(self): + self.assertIn('.pyi', util.PYTHON_EXTENSIONS) + self.assertIn('.py', util.PYTHON_EXTENSIONS) + self.assertIn('.pyw', util.PYTHON_EXTENSIONS) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/iomenu.py b/Lib/idlelib/iomenu.py index 5ebf7089fb9abe..57a41182349085 100644 --- a/Lib/idlelib/iomenu.py +++ b/Lib/idlelib/iomenu.py @@ -349,6 +349,7 @@ def print_window(self, event): filetypes = ( ("Python files", "*.py *.pyw", "TEXT"), + ("Python stub files", "*.pyi", "TEXT"), ("Text files", "*.txt", "TEXT"), ("All files", "*"), ) diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py new file mode 100644 index 00000000000000..af3714d40472f4 --- /dev/null +++ b/Lib/idlelib/util.py @@ -0,0 +1,36 @@ +""" +Idlelib objects with no external idlelib dependencies +which are needed in more than one idlelib module. + +They are included here because + a) they don't particularly belong elsewhere; or + b) because inclusion here simplifies the idlelib dependency graph. + +TODO: + * Python versions (editor and help_about), + * tk version and patchlevel (pyshell, help_about, maxos?, editor?), + * std streams (pyshell, run), + * warning stuff (ditto). +""" +from os import path + +# PYTHON_EXTENSIONS is used in editor, browser, and iomenu. +# .pyw is for Windows; .pyi is for stub files. +PYTHON_EXTENSIONS = frozenset({'.py', '.pyw', '.pyi'}) + + +def is_python_extension(extension, is_valid=PYTHON_EXTENSIONS.__contains__): + """Identify whether a given string is a valid Python file extension""" + return is_valid(extension) + + +def is_python_source(filepath, firstline=None): + """Identify whether a given path is valid as a Python module.""" + if not filepath or path.isdir(filepath): + return True + base, ext = path.splitext(path.basename(filepath)) + return is_python_extension(path.normcase(ext)) or all(( + firstline is not None, + firstline.startswith('#!'), + 'python' in firstline + )) diff --git a/Misc/ACKS b/Misc/ACKS index 23c92abb4d02a7..204293fa50d9c0 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1885,6 +1885,7 @@ Bob Watson Colin Watson David Watson Aaron Watters +Alex Waygood Henrik Weber Leon Weber Steve Weber diff --git a/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst b/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst new file mode 100644 index 00000000000000..6750e42b145c55 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst @@ -0,0 +1,2 @@ +IDLE now knows that `.pyi` files can only contain valid Python expressions, +and will apply syntax highlighting accordingly. Patch by Alex Waygood. From b12399d1772946106e29a75e69cb9bafb9efe388 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Oct 2021 11:36:30 +0100 Subject: [PATCH 02/28] Improve news entry. Co-authored-by: E-Paine <63801254+E-Paine@users.noreply.github.com> --- Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst b/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst index 6750e42b145c55..35ccd29487ce13 100644 --- a/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst +++ b/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst @@ -1,2 +1 @@ -IDLE now knows that `.pyi` files can only contain valid Python expressions, -and will apply syntax highlighting accordingly. Patch by Alex Waygood. +IDLE now applies syntax highlighting to `.pyi` files. Patch by Alex Waygood. From 96d5a8865e409a73929501623e4b1d39ed79e0e4 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Oct 2021 11:37:32 +0100 Subject: [PATCH 03/28] Improve docstring at top of util.py Co-authored-by: E-Paine <63801254+E-Paine@users.noreply.github.com> --- Lib/idlelib/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index af3714d40472f4..4f2539c742a32b 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -10,7 +10,7 @@ * Python versions (editor and help_about), * tk version and patchlevel (pyshell, help_about, maxos?, editor?), * std streams (pyshell, run), - * warning stuff (ditto). + * warning stuff (pyshell, run). """ from os import path From eee661edabccb57514fff6597f2e31173df38b1a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Oct 2021 12:32:34 +0100 Subject: [PATCH 04/28] Improve tests --- Lib/idlelib/idle_test/test_iomenu.py | 25 +++++----- Lib/idlelib/idle_test/test_util.py | 68 +++++++++++++++++++++++++++- Lib/idlelib/util.py | 17 ++++--- 3 files changed, 88 insertions(+), 22 deletions(-) diff --git a/Lib/idlelib/idle_test/test_iomenu.py b/Lib/idlelib/idle_test/test_iomenu.py index bb53b53808d9ae..7f22f664ab87f7 100644 --- a/Lib/idlelib/idle_test/test_iomenu.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -1,6 +1,6 @@ "Test , coverage 17%." -from idlelib import iomenu +from idlelib import iomenu, util import unittest from test.support import requires from tkinter import Tk @@ -44,23 +44,24 @@ def test_fixnewlines_end(self): eq(text.get('1.0', 'end-1c'), 'a\n') eq(fix(), 'a'+io.eol_convention) - def check_filetype(self, ext): - return any( - ext in filetype_tuple[1] for filetype_tuple in self.io.filetypes - ) - def test_python_files(self): - self.assertTrue(self.check_filetype('*.py')) - self.assertTrue(self.check_filetype('*.pyw')) +def _extension_is_in_IOBinding_dot_filetypes(extension): + return any( + extension in filetype_tuple[1] + for filetype_tuple in iomenu.IOBinding.filetypes + ) - def test_python_stub_files(self): - self.assertTrue(self.check_filetype('*.pyi')) + +class FiletypesTest(unittest.TestCase): + def test_python_source_files(self): + for extension in util.PYTHON_EXTENSIONS: + self.assertTrue(_extension_is_in_IOBinding_dot_filetypes(extension)) def test_text_files(self): - self.assertTrue(self.check_filetype('*.txt')) + self.assertTrue(_extension_is_in_IOBinding_dot_filetypes('*.txt')) def test_all_files(self): - self.assertTrue(self.check_filetype('*')) + self.assertTrue(_extension_is_in_IOBinding_dot_filetypes('*')) if __name__ == '__main__': diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index 246dd8b12d439b..09a1eea991a012 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -1,15 +1,81 @@ "Test constants and functions found in idlelib.util" import unittest +from unittest import mock from idlelib import util class ExtensionTest(unittest.TestCase): - def test_extensions(self): + def test_pyi(self): self.assertIn('.pyi', util.PYTHON_EXTENSIONS) + + def test_py(self): self.assertIn('.py', util.PYTHON_EXTENSIONS) + + def test_pyw(self): self.assertIn('.pyw', util.PYTHON_EXTENSIONS) +class IsPythonExtensionTest(unittest.TestCase): + def test_positives(self): + for extension in util.PYTHON_EXTENSIONS: + self.assertTrue(util.is_python_extension(extension)) + + def test_negatives(self): + NEGATIVES = '.pyc', '.p', 'py', '.pyy', '..py', '.ppy' + for bad_extension in NEGATIVES: + self.assertFalse(util.is_python_extension(bad_extension)) + + +class IsPythonSourceTest(unittest.TestCase): + def test_empty_string_filepath(self): + self.assertTrue(util.is_python_source('')) + + def test_directory_filepath(self): + with mock.patch('os.path.isdir', return_value=True): + self.assertTrue(util.is_python_source('asdfoijf9245-98-$%^&*.json')) + + def test_python_extension(self): + with mock.patch('idlelib.util.is_python_extension', return_value=True): + self.assertTrue(util.is_python_source('asdfe798723%^&*^%£(.docx')) + + def test_bad_filepath_no_firstline(self): + with ( + mock.patch('os.path.isdir', return_value=False), + mock.patch('idlelib.util.is_python_extension', return_value=False) + ): + self.assertFalse(util.is_python_source('%^*iupoukjcj&)9675889')) + + def test_bad_filepath_good_firstline(self): + with ( + mock.patch('os.path.isdir', return_value=False), + mock.patch('idlelib.util.is_python_extension', return_value=False) + ): + self.assertTrue(util.is_python_source( + 'ad^%^&*yoi724)', '#!python' + )) + self.assertTrue(util.is_python_source( + '7890J:IUJjhu435465$£^%£', '#! python' + )) + self.assertTrue(util.is_python_source( + '$%^&554657jhasfnmnbnnnm', '#!asdfkpoijeqrjiumnbjypythonasdfjlh' + )) + + def test_bad_filepath_bad_firstline(self): + with ( + mock.patch('os.path.isdir', return_value=False), + mock.patch('idlelib.util.is_python_extension', return_value=False) + ): + self.assertFalse(util.is_python_source( + 'tyyuiuoiu', '#python!' + )) + self.assertFalse(util.is_python_source( + '796923568%^&**$', '!#python' + )) + self.assertFalse(util.is_python_source( + '67879iy987kjl87', 'python#!' + )) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index 4f2539c742a32b..6ac0512c40d615 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -14,14 +14,13 @@ """ from os import path -# PYTHON_EXTENSIONS is used in editor, browser, and iomenu. # .pyw is for Windows; .pyi is for stub files. PYTHON_EXTENSIONS = frozenset({'.py', '.pyw', '.pyi'}) -def is_python_extension(extension, is_valid=PYTHON_EXTENSIONS.__contains__): - """Identify whether a given string is a valid Python file extension""" - return is_valid(extension) +def is_python_extension(extension, valid_extensions=PYTHON_EXTENSIONS): + """Identify whether a given string is a valid Python file extension.""" + return extension in valid_extensions def is_python_source(filepath, firstline=None): @@ -29,8 +28,8 @@ def is_python_source(filepath, firstline=None): if not filepath or path.isdir(filepath): return True base, ext = path.splitext(path.basename(filepath)) - return is_python_extension(path.normcase(ext)) or all(( - firstline is not None, - firstline.startswith('#!'), - 'python' in firstline - )) + return is_python_extension(path.normcase(ext)) or ( + firstline is not None + and firstline.startswith('#!') + and 'python' in firstline + ) From b9ef62ba5340aad75a4baa0a2fff7693fe868273 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Oct 2021 12:58:52 +0100 Subject: [PATCH 05/28] Use keyword arguments in test_util.py --- Lib/idlelib/idle_test/test_util.py | 38 ++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index 09a1eea991a012..582d742108cd61 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -22,9 +22,11 @@ def test_positives(self): self.assertTrue(util.is_python_extension(extension)) def test_negatives(self): - NEGATIVES = '.pyc', '.p', 'py', '.pyy', '..py', '.ppy' - for bad_extension in NEGATIVES: - self.assertFalse(util.is_python_extension(bad_extension)) + self.assertFalse(util.is_python_extension('.pyc')) + self.assertFalse(util.is_python_extension('.p')) + self.assertFalse(util.is_python_extension('.pyy')) + self.assertFalse(util.is_python_extension('..py')) + self.assertFalse(util.is_python_extension('.ppy')) class IsPythonSourceTest(unittest.TestCase): @@ -33,18 +35,24 @@ def test_empty_string_filepath(self): def test_directory_filepath(self): with mock.patch('os.path.isdir', return_value=True): - self.assertTrue(util.is_python_source('asdfoijf9245-98-$%^&*.json')) + self.assertTrue( + util.is_python_source(filepath='asdfoijf9245-98-$%^&*.json') + ) def test_python_extension(self): with mock.patch('idlelib.util.is_python_extension', return_value=True): - self.assertTrue(util.is_python_source('asdfe798723%^&*^%£(.docx')) + self.assertTrue( + util.is_python_source(filepath='asdfe798723%^&*^%£(.docx') + ) def test_bad_filepath_no_firstline(self): with ( mock.patch('os.path.isdir', return_value=False), mock.patch('idlelib.util.is_python_extension', return_value=False) ): - self.assertFalse(util.is_python_source('%^*iupoukjcj&)9675889')) + self.assertFalse( + util.is_python_source(filepath='%^*iupoukjcj&)9675889') + ) def test_bad_filepath_good_firstline(self): with ( @@ -52,13 +60,16 @@ def test_bad_filepath_good_firstline(self): mock.patch('idlelib.util.is_python_extension', return_value=False) ): self.assertTrue(util.is_python_source( - 'ad^%^&*yoi724)', '#!python' + filepath='ad^%^&*yoi724)', + firstline='#!python' )) self.assertTrue(util.is_python_source( - '7890J:IUJjhu435465$£^%£', '#! python' + filepath='7890J:IUJjhu435465$£^%£', + firstline='#! python' )) self.assertTrue(util.is_python_source( - '$%^&554657jhasfnmnbnnnm', '#!asdfkpoijeqrjiumnbjypythonasdfjlh' + filepath='$%^&554657jhasfnmnbnnnm', + firstline='#!asdfkpoijeqrjiumnbjypythonasdfjlh' )) def test_bad_filepath_bad_firstline(self): @@ -67,13 +78,16 @@ def test_bad_filepath_bad_firstline(self): mock.patch('idlelib.util.is_python_extension', return_value=False) ): self.assertFalse(util.is_python_source( - 'tyyuiuoiu', '#python!' + filepath='tyyuiuoiu', + firstline='#python!' )) self.assertFalse(util.is_python_source( - '796923568%^&**$', '!#python' + filepath='796923568%^&**$', + firstline='!#python' )) self.assertFalse(util.is_python_source( - '67879iy987kjl87', 'python#!' + filepath='67879iy987kjl87', + firstline='python#!' )) From 44fb0f1e7e27bdf44aaa5d8dbe39ae632b4483a1 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Oct 2021 14:10:44 +0100 Subject: [PATCH 06/28] Correct alphabetical order in README.txt... --- Lib/idlelib/README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/README.txt b/Lib/idlelib/README.txt index 54a603d59b5b29..8870fda315e392 100644 --- a/Lib/idlelib/README.txt +++ b/Lib/idlelib/README.txt @@ -81,8 +81,8 @@ statusbar.py # Define status bar for windows (nim). tabbedpages.py # Define tabbed pages widget (nim). textview.py # Define read-only text widget (nim). tree.py # Define tree widget, used in browsers (nim). -util.py # Define objects imported elsewhere with no dependencies (nim) undo.py # Manage undo stack. +util.py # Define objects imported elsewhere with no dependencies (nim) windows.py # Manage window list and define listed top level. zoomheight.py # Zoom window to full height of screen. From 2d8e0e8cb8ed8f57b025e2bc237a109655b1f64b Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Oct 2021 15:03:58 +0100 Subject: [PATCH 07/28] Update coverage statistics in test files --- Lib/idlelib/idle_test/test_browser.py | 2 +- Lib/idlelib/idle_test/test_editor.py | 2 +- Lib/idlelib/idle_test/test_iomenu.py | 2 +- Lib/idlelib/idle_test/test_util.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/idle_test/test_browser.py b/Lib/idlelib/idle_test/test_browser.py index 03a50f22ca1e82..cd99a763e0a91b 100644 --- a/Lib/idlelib/idle_test/test_browser.py +++ b/Lib/idlelib/idle_test/test_browser.py @@ -1,4 +1,4 @@ -"Test browser, coverage 90%." +"Test browser, coverage 81%." from idlelib import browser from test.support import requires diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index 8665d680c0118f..20a3bd04ee7e75 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -1,4 +1,4 @@ -"Test editor, coverage 35%." +"Test editor, coverage 54%." from idlelib import editor import unittest diff --git a/Lib/idlelib/idle_test/test_iomenu.py b/Lib/idlelib/idle_test/test_iomenu.py index 7f22f664ab87f7..f9079446866479 100644 --- a/Lib/idlelib/idle_test/test_iomenu.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -1,4 +1,4 @@ -"Test , coverage 17%." +"Test, coverage 22%." from idlelib import iomenu, util import unittest diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index 582d742108cd61..93ec728d0abd86 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -1,4 +1,4 @@ -"Test constants and functions found in idlelib.util" +"Test constants and functions found in idlelib.util, coverage 100%" import unittest from unittest import mock From 4bba476b5107819b47499d61aed0e842dcdc09c0 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 16 Oct 2021 17:20:24 +0100 Subject: [PATCH 08/28] Add __main__ block for util.py, tweak added test for iomenu.py, tweak docstrings to keep line length within 72 chars --- Lib/idlelib/browser.py | 6 +++--- Lib/idlelib/idle_test/test_iomenu.py | 6 +++--- Lib/idlelib/util.py | 7 ++++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Lib/idlelib/browser.py b/Lib/idlelib/browser.py index 1168c49e240c9c..35736d0b9fd1ce 100644 --- a/Lib/idlelib/browser.py +++ b/Lib/idlelib/browser.py @@ -77,9 +77,9 @@ def __init__(self, master, path, *, _htest=False, _utest=False): Instance variables: name: Module name. - file: Full path and module with a valid Python extension. Used in - creating ModuleBrowserTreeItem as the rootnode for - the tree and subsequently in the children. + file: Full path and module with a valid Python extension. + Used in creating ModuleBrowserTreeItem as the rootnode + for the tree and subsequently in the children. """ self.master = master self.path = path diff --git a/Lib/idlelib/idle_test/test_iomenu.py b/Lib/idlelib/idle_test/test_iomenu.py index f9079446866479..602d3c42799057 100644 --- a/Lib/idlelib/idle_test/test_iomenu.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -47,7 +47,7 @@ def test_fixnewlines_end(self): def _extension_is_in_IOBinding_dot_filetypes(extension): return any( - extension in filetype_tuple[1] + f'*{extension}' in filetype_tuple[1] for filetype_tuple in iomenu.IOBinding.filetypes ) @@ -58,10 +58,10 @@ def test_python_source_files(self): self.assertTrue(_extension_is_in_IOBinding_dot_filetypes(extension)) def test_text_files(self): - self.assertTrue(_extension_is_in_IOBinding_dot_filetypes('*.txt')) + self.assertTrue(_extension_is_in_IOBinding_dot_filetypes('.txt')) def test_all_files(self): - self.assertTrue(_extension_is_in_IOBinding_dot_filetypes('*')) + self.assertTrue(_extension_is_in_IOBinding_dot_filetypes('')) if __name__ == '__main__': diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index 6ac0512c40d615..a003ffeacf5ef4 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -19,7 +19,7 @@ def is_python_extension(extension, valid_extensions=PYTHON_EXTENSIONS): - """Identify whether a given string is a valid Python file extension.""" + "Identify whether a given string is a valid Python file extension." return extension in valid_extensions @@ -33,3 +33,8 @@ def is_python_source(filepath, firstline=None): and firstline.startswith('#!') and 'python' in firstline ) + + +if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_util', verbosity=2) From be0043e56f02984e5a9d040bef3f4ab04a2ba771 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 18 Oct 2021 10:56:49 +0100 Subject: [PATCH 09/28] Tweak test_util.py --- Lib/idlelib/idle_test/test_util.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index 93ec728d0abd86..ee56b9455762c5 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -39,8 +39,11 @@ def test_directory_filepath(self): util.is_python_source(filepath='asdfoijf9245-98-$%^&*.json') ) - def test_python_extension(self): - with mock.patch('idlelib.util.is_python_extension', return_value=True): + def test_filepath_with_python_extension(self): + with ( + mock.patch('os.path.isdir', return_value=False), + mock.patch('idlelib.util.is_python_extension', return_value=True) + ): self.assertTrue( util.is_python_source(filepath='asdfe798723%^&*^%£(.docx') ) From 5dc03fa2206329d95f315948114dfa132b25bde6 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 16 Jan 2022 09:19:13 +0000 Subject: [PATCH 10/28] Update Lib/idlelib/util.py Co-authored-by: Terry Jan Reedy --- Lib/idlelib/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index a003ffeacf5ef4..f13c2e3788548e 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -15,7 +15,7 @@ from os import path # .pyw is for Windows; .pyi is for stub files. -PYTHON_EXTENSIONS = frozenset({'.py', '.pyw', '.pyi'}) +PYTHON_EXTENSIONS = frozenset(('.py', '.pyw', '.pyi')) def is_python_extension(extension, valid_extensions=PYTHON_EXTENSIONS): From 4b2c36cba3225bb843ed57884a29d6215ff66a50 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 16 Jan 2022 14:09:10 +0000 Subject: [PATCH 11/28] Add example `.pyi` file --- Lib/idlelib/idle_test/example.pyi | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Lib/idlelib/idle_test/example.pyi diff --git a/Lib/idlelib/idle_test/example.pyi b/Lib/idlelib/idle_test/example.pyi new file mode 100644 index 00000000000000..a9811a78d10a6e --- /dev/null +++ b/Lib/idlelib/idle_test/example.pyi @@ -0,0 +1,2 @@ +class Example: + def method(self, argument1: str, argument2: list[int]) -> None: ... From e9dfeb131f659297df4f453e7f61a57961b7b67b Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 24 Jan 2022 18:32:06 +0000 Subject: [PATCH 12/28] Revert changes to coverage statistics --- Lib/idlelib/idle_test/test_browser.py | 2 +- Lib/idlelib/idle_test/test_editor.py | 2 +- Lib/idlelib/idle_test/test_iomenu.py | 2 +- Lib/idlelib/idle_test/test_util.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/idle_test/test_browser.py b/Lib/idlelib/idle_test/test_browser.py index cd99a763e0a91b..03a50f22ca1e82 100644 --- a/Lib/idlelib/idle_test/test_browser.py +++ b/Lib/idlelib/idle_test/test_browser.py @@ -1,4 +1,4 @@ -"Test browser, coverage 81%." +"Test browser, coverage 90%." from idlelib import browser from test.support import requires diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index 20a3bd04ee7e75..8665d680c0118f 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -1,4 +1,4 @@ -"Test editor, coverage 54%." +"Test editor, coverage 35%." from idlelib import editor import unittest diff --git a/Lib/idlelib/idle_test/test_iomenu.py b/Lib/idlelib/idle_test/test_iomenu.py index 602d3c42799057..ae0c5d0c625d7b 100644 --- a/Lib/idlelib/idle_test/test_iomenu.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -1,4 +1,4 @@ -"Test, coverage 22%." +"Test , coverage 17%." from idlelib import iomenu, util import unittest diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index ee56b9455762c5..076edc2439c9ee 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -1,4 +1,4 @@ -"Test constants and functions found in idlelib.util, coverage 100%" +"Test constants and functions found in idlelib.util" import unittest from unittest import mock From a197c93d82dab0457edb8e5465a16a8e51404285 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 24 Jan 2022 18:35:44 +0000 Subject: [PATCH 13/28] Rename `example.pyi` to `example_stub.pyi` --- Lib/idlelib/idle_test/{example.pyi => example_stub.pyi} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Lib/idlelib/idle_test/{example.pyi => example_stub.pyi} (100%) diff --git a/Lib/idlelib/idle_test/example.pyi b/Lib/idlelib/idle_test/example_stub.pyi similarity index 100% rename from Lib/idlelib/idle_test/example.pyi rename to Lib/idlelib/idle_test/example_stub.pyi From eb754bdfadb0d746dd8186114e7f41c1e73dee5b Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 24 Jan 2022 18:38:43 +0000 Subject: [PATCH 14/28] Have stub files combined with other Python files in `iomenu.py` --- Lib/idlelib/iomenu.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/idlelib/iomenu.py b/Lib/idlelib/iomenu.py index 57a41182349085..14c4e97cfcb379 100644 --- a/Lib/idlelib/iomenu.py +++ b/Lib/idlelib/iomenu.py @@ -348,8 +348,7 @@ def print_window(self, event): savedialog = None filetypes = ( - ("Python files", "*.py *.pyw", "TEXT"), - ("Python stub files", "*.pyi", "TEXT"), + ("Python files", "*.py *pyi *.pyw", "TEXT"), ("Text files", "*.txt", "TEXT"), ("All files", "*"), ) From c6bd52479fe996d7d74cfbffb16d73a6253cca4d Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 24 Jan 2022 23:22:48 +0000 Subject: [PATCH 15/28] Fix broken test, improve other tests --- Lib/idlelib/idle_test/test_iomenu.py | 5 +- Lib/idlelib/idle_test/test_util.py | 73 +++++++++++++--------------- Lib/idlelib/iomenu.py | 2 +- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/Lib/idlelib/idle_test/test_iomenu.py b/Lib/idlelib/idle_test/test_iomenu.py index ae0c5d0c625d7b..5ac1cc44d37ea3 100644 --- a/Lib/idlelib/idle_test/test_iomenu.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -55,7 +55,10 @@ def _extension_is_in_IOBinding_dot_filetypes(extension): class FiletypesTest(unittest.TestCase): def test_python_source_files(self): for extension in util.PYTHON_EXTENSIONS: - self.assertTrue(_extension_is_in_IOBinding_dot_filetypes(extension)) + with self.subTest(extension=extension): + self.assertTrue( + _extension_is_in_IOBinding_dot_filetypes(extension) + ) def test_text_files(self): self.assertTrue(_extension_is_in_IOBinding_dot_filetypes('.txt')) diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index 076edc2439c9ee..ed48314e1d9004 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -6,27 +6,22 @@ class ExtensionTest(unittest.TestCase): - def test_pyi(self): - self.assertIn('.pyi', util.PYTHON_EXTENSIONS) - - def test_py(self): - self.assertIn('.py', util.PYTHON_EXTENSIONS) - - def test_pyw(self): - self.assertIn('.pyw', util.PYTHON_EXTENSIONS) + def test_extensions(self): + for extension in {'.pyi', '.py', '.pyw'}: + with self.subTest(extension=extension): + self.assertIn(extension, util.PYTHON_EXTENSIONS) class IsPythonExtensionTest(unittest.TestCase): def test_positives(self): for extension in util.PYTHON_EXTENSIONS: - self.assertTrue(util.is_python_extension(extension)) + with self.subTest(extension=extension): + self.assertTrue(util.is_python_extension(extension)) def test_negatives(self): - self.assertFalse(util.is_python_extension('.pyc')) - self.assertFalse(util.is_python_extension('.p')) - self.assertFalse(util.is_python_extension('.pyy')) - self.assertFalse(util.is_python_extension('..py')) - self.assertFalse(util.is_python_extension('.ppy')) + for bad_extension in {'.pyc', '.p', '.pyy', '..py', '.ppy'}: + with self.subTest(bad_extension=bad_extension): + self.assertFalse(util.is_python_extension(bad_extension)) class IsPythonSourceTest(unittest.TestCase): @@ -57,41 +52,41 @@ def test_bad_filepath_no_firstline(self): util.is_python_source(filepath='%^*iupoukjcj&)9675889') ) - def test_bad_filepath_good_firstline(self): + def test_bad_filepath_good_firstline(self): + test_cases = ( + ('ad^%^&*yoi724)', '#!python'), + ('7890J:IUJjhu435465$£^%£', '#! python'), + ('$%^&554657jhasfnmnbnnnm', '#!asdfkpoijeqrjiumnbjypythonasdfjlh') + ) + with ( mock.patch('os.path.isdir', return_value=False), mock.patch('idlelib.util.is_python_extension', return_value=False) ): - self.assertTrue(util.is_python_source( - filepath='ad^%^&*yoi724)', - firstline='#!python' - )) - self.assertTrue(util.is_python_source( - filepath='7890J:IUJjhu435465$£^%£', - firstline='#! python' - )) - self.assertTrue(util.is_python_source( - filepath='$%^&554657jhasfnmnbnnnm', - firstline='#!asdfkpoijeqrjiumnbjypythonasdfjlh' - )) + for filepath, firstline in test_cases: + with self.subTest(filepath=filepath, firstline=firstline): + self.assertTrue(util.is_python_source( + filepath=filepath, + firstline=firstline + )) def test_bad_filepath_bad_firstline(self): + test_cases = ( + ('tyyuiuoiu', '#python!'), + ('796923568%^&**$', '!#python'), + ('67879iy987kjl87', 'python#!') + ) + with ( mock.patch('os.path.isdir', return_value=False), mock.patch('idlelib.util.is_python_extension', return_value=False) ): - self.assertFalse(util.is_python_source( - filepath='tyyuiuoiu', - firstline='#python!' - )) - self.assertFalse(util.is_python_source( - filepath='796923568%^&**$', - firstline='!#python' - )) - self.assertFalse(util.is_python_source( - filepath='67879iy987kjl87', - firstline='python#!' - )) + for filepath, firstline in test_cases: + with self.subTest(filepath=filepath, firstline=firstline): + self.assertFalse(util.is_python_source( + filepath=filepath, + firstline=firstline + )) if __name__ == '__main__': diff --git a/Lib/idlelib/iomenu.py b/Lib/idlelib/iomenu.py index 14c4e97cfcb379..70de9aa08b470d 100644 --- a/Lib/idlelib/iomenu.py +++ b/Lib/idlelib/iomenu.py @@ -348,7 +348,7 @@ def print_window(self, event): savedialog = None filetypes = ( - ("Python files", "*.py *pyi *.pyw", "TEXT"), + ("Python files", "*.py *.pyi *.pyw", "TEXT"), ("Text files", "*.txt", "TEXT"), ("All files", "*"), ) From 5a74935ba1ab610953a31bcb364f5eef70ca04fd Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 24 Jan 2022 23:26:35 +0000 Subject: [PATCH 16/28] Add `example_noext` file --- Lib/idlelib/idle_test/example_noext | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Lib/idlelib/idle_test/example_noext diff --git a/Lib/idlelib/idle_test/example_noext b/Lib/idlelib/idle_test/example_noext new file mode 100644 index 00000000000000..7d2510e30bc0ae --- /dev/null +++ b/Lib/idlelib/idle_test/example_noext @@ -0,0 +1,4 @@ +#!usr/bin/env python + +def example_function(some_argument): + pass From 84b925186e98f10ad9e37e14347a18d211c0b4ba Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 24 Jan 2022 23:32:41 +0000 Subject: [PATCH 17/28] Add entry to 'What's new in 3.11' --- Doc/whatsnew/3.11.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 4328ee6a5030cd..dd4daa52933aee 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -226,6 +226,13 @@ fractions (Contributed by Mark Dickinson in :issue:`44547`.) +IDLE and idlelib +---------------- + +* IDLE now applies syntax highlighting to `.pyi` files. (Contributed by Alex + Waygood in :issue:`45447`.) + + inspect ------- * Add :func:`inspect.getmembers_static`: return all members without From 58fef8e4d8c0bba3a7e54e486435ae2d643b7639 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 10 Feb 2022 23:56:51 -0500 Subject: [PATCH 18/28] Revert changes in browser.py --- Lib/idlelib/browser.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Lib/idlelib/browser.py b/Lib/idlelib/browser.py index 35736d0b9fd1ce..3c3a53a6599a79 100644 --- a/Lib/idlelib/browser.py +++ b/Lib/idlelib/browser.py @@ -13,7 +13,6 @@ import pyclbr import sys -from idlelib.util import is_python_source, is_python_extension from idlelib.config import idleConf from idlelib import pyshell from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas @@ -77,9 +76,9 @@ def __init__(self, master, path, *, _htest=False, _utest=False): Instance variables: name: Module name. - file: Full path and module with a valid Python extension. - Used in creating ModuleBrowserTreeItem as the rootnode - for the tree and subsequently in the children. + file: Full path and module with .py extension. Used in + creating ModuleBrowserTreeItem as the rootnode for + the tree and subsequently in the children. """ self.master = master self.path = path @@ -162,19 +161,21 @@ def GetSubList(self): def OnDoubleClick(self): "Open a module in an editor window when double clicked." - if self.IsExpandable() and os.path.exists(self.file): - file_open(self.file) + if os.path.normcase(self.file[-3:]) != ".py": + return + if not os.path.exists(self.file): + return + file_open(self.file) def IsExpandable(self): - "Identify whether `self.file` is a valid Python file." - # see idlelib.util for currently supported Python extensions. - return is_python_source(self.file) + "Return True if Python (.py) file." + return os.path.normcase(self.file[-3:]) == ".py" def listchildren(self): "Return sequenced classes and functions in the module." dir, base = os.path.split(self.file) name, ext = os.path.splitext(base) - if not is_python_extension(os.path.normcase(ext)): + if os.path.normcase(ext) != ".py": return [] try: tree = pyclbr.readmodule_ex(name, [dir] + sys.path) From 00a51b279ffacffabc733659d8f48984b038f3e5 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 10 Feb 2022 23:57:41 -0500 Subject: [PATCH 19/28] Revert changes to editor.EditorWindow.ispythonsource. --- Lib/idlelib/editor.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index b82277b0209f55..e660830b94e561 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -755,10 +755,13 @@ def gotoline(self, lineno): self.center() def ispythonsource(self, filename): - return idlelib.util.is_python_source( - filepath=filename, - firstline=self.text.get('1.0', '1.0 lineend') - ) + if not filename or os.path.isdir(filename): + return True + base, ext = os.path.splitext(os.path.basename(filename)) + if os.path.normcase(ext) in (".py", ".pyw"): + return True + line = self.text.get('1.0', '1.0 lineend') + return line.startswith('#!') and 'python' in line def close_hook(self): if self.flist: From a5890997d835a9840ecbada3bb896dd773db4ccd Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Fri, 11 Feb 2022 00:04:18 -0500 Subject: [PATCH 20/28] Revise util, removing new functions. --- Lib/idlelib/util.py | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index f13c2e3788548e..9483d5a7c118a0 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -15,25 +15,7 @@ from os import path # .pyw is for Windows; .pyi is for stub files. -PYTHON_EXTENSIONS = frozenset(('.py', '.pyw', '.pyi')) - - -def is_python_extension(extension, valid_extensions=PYTHON_EXTENSIONS): - "Identify whether a given string is a valid Python file extension." - return extension in valid_extensions - - -def is_python_source(filepath, firstline=None): - """Identify whether a given path is valid as a Python module.""" - if not filepath or path.isdir(filepath): - return True - base, ext = path.splitext(path.basename(filepath)) - return is_python_extension(path.normcase(ext)) or ( - firstline is not None - and firstline.startswith('#!') - and 'python' in firstline - ) - +py_extensions = frozenset(('.py', '.pyw', '.pyi')) if __name__ == '__main__': from unittest import main From 647ae1adce11853c3bfa0fda2c342e57b1601038 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Fri, 11 Feb 2022 00:13:30 -0500 Subject: [PATCH 21/28] Revise test_util, deleting most tests. --- Lib/idlelib/idle_test/test_util.py | 86 ++---------------------------- 1 file changed, 4 insertions(+), 82 deletions(-) diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index ed48314e1d9004..29e9b9a588cffe 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -1,92 +1,14 @@ -"Test constants and functions found in idlelib.util" +"""Test util, coverage 100%""" import unittest -from unittest import mock +#from unittest import mock from idlelib import util -class ExtensionTest(unittest.TestCase): +class UtilTest(unittest.TestCase): def test_extensions(self): for extension in {'.pyi', '.py', '.pyw'}: - with self.subTest(extension=extension): - self.assertIn(extension, util.PYTHON_EXTENSIONS) - - -class IsPythonExtensionTest(unittest.TestCase): - def test_positives(self): - for extension in util.PYTHON_EXTENSIONS: - with self.subTest(extension=extension): - self.assertTrue(util.is_python_extension(extension)) - - def test_negatives(self): - for bad_extension in {'.pyc', '.p', '.pyy', '..py', '.ppy'}: - with self.subTest(bad_extension=bad_extension): - self.assertFalse(util.is_python_extension(bad_extension)) - - -class IsPythonSourceTest(unittest.TestCase): - def test_empty_string_filepath(self): - self.assertTrue(util.is_python_source('')) - - def test_directory_filepath(self): - with mock.patch('os.path.isdir', return_value=True): - self.assertTrue( - util.is_python_source(filepath='asdfoijf9245-98-$%^&*.json') - ) - - def test_filepath_with_python_extension(self): - with ( - mock.patch('os.path.isdir', return_value=False), - mock.patch('idlelib.util.is_python_extension', return_value=True) - ): - self.assertTrue( - util.is_python_source(filepath='asdfe798723%^&*^%£(.docx') - ) - - def test_bad_filepath_no_firstline(self): - with ( - mock.patch('os.path.isdir', return_value=False), - mock.patch('idlelib.util.is_python_extension', return_value=False) - ): - self.assertFalse( - util.is_python_source(filepath='%^*iupoukjcj&)9675889') - ) - - def test_bad_filepath_good_firstline(self): - test_cases = ( - ('ad^%^&*yoi724)', '#!python'), - ('7890J:IUJjhu435465$£^%£', '#! python'), - ('$%^&554657jhasfnmnbnnnm', '#!asdfkpoijeqrjiumnbjypythonasdfjlh') - ) - - with ( - mock.patch('os.path.isdir', return_value=False), - mock.patch('idlelib.util.is_python_extension', return_value=False) - ): - for filepath, firstline in test_cases: - with self.subTest(filepath=filepath, firstline=firstline): - self.assertTrue(util.is_python_source( - filepath=filepath, - firstline=firstline - )) - - def test_bad_filepath_bad_firstline(self): - test_cases = ( - ('tyyuiuoiu', '#python!'), - ('796923568%^&**$', '!#python'), - ('67879iy987kjl87', 'python#!') - ) - - with ( - mock.patch('os.path.isdir', return_value=False), - mock.patch('idlelib.util.is_python_extension', return_value=False) - ): - for filepath, firstline in test_cases: - with self.subTest(filepath=filepath, firstline=firstline): - self.assertFalse(util.is_python_source( - filepath=filepath, - firstline=firstline - )) + self.assertIn(extension, util.py_extensions) if __name__ == '__main__': From a090db71e93d714a1c7f4c77ea1c0fcd327088cc Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Fri, 11 Feb 2022 01:08:27 -0500 Subject: [PATCH 22/28] Use tuple instead of frozen set. This enables computing text for open/save dialogs. .py must be first to make it default on non-macOS. --- Lib/idlelib/editor.py | 4 ++-- Lib/idlelib/idle_test/test_iomenu.py | 2 +- Lib/idlelib/iomenu.py | 5 ++++- Lib/idlelib/util.py | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index e660830b94e561..6c52efd655e121 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -15,7 +15,6 @@ from tkinter import simpledialog from tkinter import messagebox -import idlelib.util from idlelib.config import idleConf from idlelib import configdialog from idlelib import grep @@ -28,6 +27,7 @@ from idlelib import replace from idlelib import search from idlelib.tree import wheel_event +from idlelib.util import py_extensions from idlelib import window # The default tab setting for a Text widget, in average-width characters. @@ -758,7 +758,7 @@ def ispythonsource(self, filename): if not filename or os.path.isdir(filename): return True base, ext = os.path.splitext(os.path.basename(filename)) - if os.path.normcase(ext) in (".py", ".pyw"): + if os.path.normcase(ext) in py_extensions: return True line = self.text.get('1.0', '1.0 lineend') return line.startswith('#!') and 'python' in line diff --git a/Lib/idlelib/idle_test/test_iomenu.py b/Lib/idlelib/idle_test/test_iomenu.py index 5ac1cc44d37ea3..849863d9a90acb 100644 --- a/Lib/idlelib/idle_test/test_iomenu.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -54,7 +54,7 @@ def _extension_is_in_IOBinding_dot_filetypes(extension): class FiletypesTest(unittest.TestCase): def test_python_source_files(self): - for extension in util.PYTHON_EXTENSIONS: + for extension in util.py_extensions: with self.subTest(extension=extension): self.assertTrue( _extension_is_in_IOBinding_dot_filetypes(extension) diff --git a/Lib/idlelib/iomenu.py b/Lib/idlelib/iomenu.py index 70de9aa08b470d..ad3109df84096a 100644 --- a/Lib/idlelib/iomenu.py +++ b/Lib/idlelib/iomenu.py @@ -11,6 +11,9 @@ import idlelib from idlelib.config import idleConf +from idlelib.util import py_extensions + +py_extensions = ' '.join("*"+ext for ext in py_extensions) encoding = 'utf-8' if sys.platform == 'win32': @@ -348,7 +351,7 @@ def print_window(self, event): savedialog = None filetypes = ( - ("Python files", "*.py *.pyi *.pyw", "TEXT"), + ("Python files", py_extensions, "TEXT"), ("Text files", "*.txt", "TEXT"), ("All files", "*"), ) diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index 9483d5a7c118a0..5480219786bca6 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -15,7 +15,7 @@ from os import path # .pyw is for Windows; .pyi is for stub files. -py_extensions = frozenset(('.py', '.pyw', '.pyi')) +py_extensions = ('.py', '.pyw', '.pyi') # Order needed for open/save dialogs. if __name__ == '__main__': from unittest import main From fc9e587d41f094592d9f31509f7371e6b92e755c Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Fri, 11 Feb 2022 01:16:11 -0500 Subject: [PATCH 23/28] Update 2021-10-14-16-55-03.bpo-45447.FhiH5P.rst --- Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst b/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst index 35ccd29487ce13..bad30b0f6f31a1 100644 --- a/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst +++ b/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst @@ -1 +1,2 @@ -IDLE now applies syntax highlighting to `.pyi` files. Patch by Alex Waygood. +IDLE now applies syntax highlighting to `.pyi` files. Patch by Alex Waygood +and Terry Jan Reedy. From 70f06f1a7ffe4c5e23fd0ab2c0863af8d61eb381 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Fri, 11 Feb 2022 01:16:32 -0500 Subject: [PATCH 24/28] Create 3.11.rst --- Doc/whatsnew/3.11.rst | 139 ++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 93 deletions(-) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 19ee49a4e33cc4..061013d48cc85b 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -67,8 +67,6 @@ Summary -- Release highlights PEP-654: Exception Groups and ``except*``. (Contributed by Irit Katriel in :issue:`45292`.) -PEP-673: ``Self`` Type. -(Contributed by James Hilton-Balfe and Pradeep Kumar in :issue:`30924`.) New Features ============ @@ -232,7 +230,7 @@ IDLE and idlelib ---------------- * IDLE now applies syntax highlighting to `.pyi` files. (Contributed by Alex - Waygood in :issue:`45447`.) + Waygood and Terry Jan Reedy in :issue:`45447`.) inspect @@ -409,8 +407,7 @@ CPython bytecode changes * Replaced the three call instructions: :opcode:`CALL_FUNCTION`, :opcode:`CALL_FUNCTION_KW` and :opcode:`CALL_METHOD` with - :opcode:`PRECALL_FUNCTION`, :opcode:`PRECALL_METHOD`, :opcode:`CALL`, - and :opcode:`KW_NAMES`. + :opcode:`CALL_NO_KW`, :opcode:`CALL_KW` and :opcode:`PRECALL_METHOD`. This decouples the argument shifting for methods from the handling of keyword arguments and allows better specialization of calls. @@ -421,9 +418,8 @@ CPython bytecode changes indicate failure with :const:`None` (where a tuple of extracted values would otherwise be). -* Replace several stack manipulation instructions (``DUP_TOP``, ``DUP_TOP_TWO``, - ``ROT_TWO``, ``ROT_THREE``, ``ROT_FOUR``, and ``ROT_N``) with new - :opcode:`COPY` and :opcode:`SWAP` instructions. +* Added :opcode:`COPY`, which pushes the *i*-th item to the top of the stack. + The item is not removed from its original location. * Add :opcode:`POP_JUMP_IF_NOT_NONE` and :opcode:`POP_JUMP_IF_NONE` opcodes to speed up conditional jumps. @@ -467,22 +463,6 @@ Deprecated as deprecated, its docstring is now corrected). (Contributed by Hugo van Kemenade in :issue:`45837`.) -* The delegation of :func:`int` to :meth:`__trunc__` is now deprecated. Calling - ``int(a)`` when ``type(a)`` implements :meth:`__trunc__` but not - :meth:`__int__` or :meth:`__index__` now raises a :exc:`DeprecationWarning`. - (Contributed by Zackery Spytz in :issue:`44977`.) - -* The following have been deprecated in :mod:`configparser` since Python 3.2. - Their deprecation warnings have now been updated to note they will removed in - Python 3.12: - - * the :class:`configparser.SafeConfigParser` class - * the :attr:`configparser.ParsingError.filename` property - * the :meth:`configparser.ParsingError.readfp` method - - (Contributed by Hugo van Kemenade in :issue:`45173`.) - - Removed ======= @@ -515,6 +495,28 @@ Removed and :class:`fileinput.FileInput`, deprecated since Python 3.9. (Contributed by Hugo van Kemenade in :issue:`45132`.) +* Removed many old deprecated :mod:`unittest` features: + + - :class:`~unittest.TestCase` method aliases ``failUnlessEqual``, + ``failIfEqual``, ``failUnless``, ``failIf``, ``failUnlessRaises``, + ``failUnlessAlmostEqual``, ``failIfAlmostEqual`` (deprecated in Python 3.1), + ``assertEquals``, ``assertNotEquals``, ``assert_``, ``assertAlmostEquals``, + ``assertNotAlmostEquals``, ``assertRegexpMatches``, ``assertRaisesRegexp`` + (deprecated in Python 3.2), and ``assertNotRegexpMatches`` (deprecated in + Python 3.5). + + - Undocumented and broken :class:`~unittest.TestCase` method + ``assertDictContainsSubset`` (deprecated in Python 3.2). + + - Undocumented :meth:` + TestLoader.loadTestsFromModule` parameter *use_load_tests* (deprecated + and ignored since Python 3.2). + + - An alias of the :class:`~unittest.TextTestResult` class: + ``_TextTestResult`` (deprecated in Python 3.2). + + (Contributed by Serhiy Storchaka in :issue:`45162`.) + * The following deprecated functions and methods are removed in the :mod:`gettext` module: :func:`~gettext.lgettext`, :func:`~gettext.ldgettext`, :func:`~gettext.lngettext` and :func:`~gettext.ldngettext`. @@ -527,6 +529,13 @@ Removed the ``l*gettext()`` functions. (Contributed by Dong-hee Na and Serhiy Storchaka in :issue:`44235`.) +* Removed from the :mod:`configparser` module: + the :class:`SafeConfigParser` class, + the :attr:`filename` property of the :class:`ParsingError` class, + the :meth:`readfp` method of the :class:`ConfigParser` class, + deprecated since Python 3.2. + (Contributed by Hugo van Kemenade in :issue:`45173`.) + * The :func:`@asyncio.coroutine ` :term:`decorator` enabling legacy generator-based coroutines to be compatible with async/await code. The function has been deprecated since Python 3.8 and the removal was @@ -560,9 +569,6 @@ Removed Python 3.4 but has been broken since Python 3.7. (Contributed by Inada Naoki in :issue:`23882`.) -* Remove ``__class_getitem__`` method from :class:`pathlib.PurePath`, - because it was not used and added by mistake in previous versions. - (Contributed by Nikita Sobolev in :issue:`46483`.) Porting to Python 3.11 ====================== @@ -598,12 +604,6 @@ Changes in the Python API of sorting simply isn't well-defined in the absence of a total ordering on list elements. -* :mod:`calendar`: The :class:`calendar.LocaleTextCalendar` and - :class:`calendar.LocaleHTMLCalendar` classes now use - :func:`locale.getlocale`, instead of using :func:`locale.getdefaultlocale`, - if no locale is specified. - (Contributed by Victor Stinner in :issue:`46659`.) - Build Changes ============= @@ -619,12 +619,6 @@ Build Changes ``isinf()``, ``isnan()``, ``round()``. (Contributed by Victor Stinner in :issue:`45440`.) -* Building Python now requires a C99 ```` header file providing - a ``NAN`` constant, or the ``__builtin_nan()`` built-in function. If a - platform does not support Not-a-Number (NaN), the ``Py_NO_NAN`` macro can be - defined in the ``pyconfig.h`` file. - (Contributed by Victor Stinner in :issue:`46640`.) - * Freelists for object structs can now be disabled. A new :program:`configure` option :option:`!--without-freelists` can be used to disable all freelists except empty tuple singleton. @@ -671,11 +665,6 @@ C API Changes fields of the result from the exception instance (the ``value`` field). (Contributed by Irit Katriel in :issue:`45711`.) -* :c:struct:`_frozen` has a new ``is_package`` field to indicate whether - or not the frozen module is a package. Previously, a negative value - in the ``size`` field was the indicator. Now only non-negative values - be used for ``size``. - (Contributed by Kumar Aditya in :issue:`46608`.) New Features ------------ @@ -695,26 +684,6 @@ New Features :c:macro:`PY_VERSION_HEX`. (Contributed by Gabriele N. Tornetta in :issue:`43931`.) -* :c:type:`Py_buffer` and APIs are now part of the limited API and the stable - ABI: - - * :c:func:`PyObject_CheckBuffer` - * :c:func:`PyObject_GetBuffer` - * :c:func:`PyBuffer_GetPointer` - * :c:func:`PyBuffer_SizeFromFormat` - * :c:func:`PyBuffer_ToContiguous` - * :c:func:`PyBuffer_FromContiguous` - * :c:func:`PyBuffer_CopyData` - * :c:func:`PyBuffer_IsContiguous` - * :c:func:`PyBuffer_FillContiguousStrides` - * :c:func:`PyBuffer_FillInfo` - * :c:func:`PyBuffer_Release` - * :c:func:`PyMemoryView_FromBuffer` - * :c:member:`~PyBufferProcs.bf_getbuffer` and - :c:member:`~PyBufferProcs.bf_releasebuffer` type slots - - (Contributed by Christian Heimes in :issue:`45459`.) - Porting to Python 3.11 ---------------------- @@ -817,38 +786,27 @@ Porting to Python 3.11 which are not available in the limited C API. (Contributed by Victor Stinner in :issue:`46007`.) -* Changes of the private :c:type:`PyFrameObject` structure members. - - While the documentation notes that the fields of ``PyFrameObject`` are - subject to change at any time, they have been stable for a long time - and were used in several popular extensions. - In Python 3.11, the frame struct was reorganized to allow performance - optimizations. Rather than reading the fields directly, extensions should - use functions: +* Changes of the :c:type:`PyFrameObject` structure members: * ``f_code``: removed, use :c:func:`PyFrame_GetCode` instead. Warning: the function returns a :term:`strong reference`, need to call :c:func:`Py_DECREF`. - * ``f_back``: changed (see below), use :c:func:`PyFrame_GetBack`. + * ``f_back``: changed, use :c:func:`PyFrame_GetBack`. * ``f_builtins``: removed, - use ``PyObject_GetAttrString((PyObject*)frame, "f_builtins")``. + use ``PyObject_GetAttrString(frame, "f_builtins")``. * ``f_globals``: removed, - use ``PyObject_GetAttrString((PyObject*)frame, "f_globals")``. + use ``PyObject_GetAttrString(frame, "f_globals")``. * ``f_locals``: removed, - use ``PyObject_GetAttrString((PyObject*)frame, "f_locals")``. + use ``PyObject_GetAttrString(frame, "f_locals")``. * ``f_lasti``: removed, - use ``PyObject_GetAttrString((PyObject*)frame, "f_lasti")``. - - The following fields were removed entirely, as they were details - of the old implementation: - - * ``f_valuesstack`` - * ``f_stackdepth`` - * ``f_gen`` - * ``f_iblock`` - * ``f_state`` - * ``f_blockstack`` - * ``f_localsplus`` + use ``PyObject_GetAttrString(frame, "f_lasti")``. + * ``f_valuesstack``: removed. + * ``f_stackdepth``: removed. + * ``f_gen``: removed. + * ``f_iblock``: removed. + * ``f_state``: removed. + * ``f_blockstack``: removed. + * ``f_localsplus``: removed. The Python frame object is now created lazily. A side effect is that the ``f_back`` member must not be accessed directly, since its value is now also @@ -999,8 +957,3 @@ Removed worked since the :c:type:`PyWeakReference` structure is opaque in the limited C API. (Contributed by Victor Stinner in :issue:`35134`.) - -* Remove the ``PyHeapType_GET_MEMBERS()`` macro. It was exposed in the - public C API by mistake, it must only be used by Python internally. - Use the ``PyTypeObject.tp_members`` member instead. - (Contributed by Victor Stinner in :issue:`40170`.) From d4d3906b2b80c8fff950594d53c1d994c54be900 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Fri, 11 Feb 2022 01:18:14 -0500 Subject: [PATCH 25/28] Update 3.11.rst --- Doc/whatsnew/3.11.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 061013d48cc85b..e0aeaf10a3bbba 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -68,6 +68,10 @@ Summary -- Release highlights PEP-654: Exception Groups and ``except*``. (Contributed by Irit Katriel in :issue:`45292`.) +PEP-673: ``Self`` Type. +(Contributed by James Hilton-Balfe and Pradeep Kumar in :issue:`30924`.) + + New Features ============ From aafd8e29e2d06c645a004f1c0c0637f163114354 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Fri, 11 Feb 2022 01:27:43 -0500 Subject: [PATCH 26/28] Revert unwanted changed to what's new --- Doc/whatsnew/3.11.rst | 138 +++++++++++++++++++++++++++--------------- 1 file changed, 90 insertions(+), 48 deletions(-) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index e0aeaf10a3bbba..3d210ce6a9a6b2 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -67,11 +67,9 @@ Summary -- Release highlights PEP-654: Exception Groups and ``except*``. (Contributed by Irit Katriel in :issue:`45292`.) - PEP-673: ``Self`` Type. (Contributed by James Hilton-Balfe and Pradeep Kumar in :issue:`30924`.) - New Features ============ @@ -236,7 +234,6 @@ IDLE and idlelib * IDLE now applies syntax highlighting to `.pyi` files. (Contributed by Alex Waygood and Terry Jan Reedy in :issue:`45447`.) - inspect ------- * Add :func:`inspect.getmembers_static`: return all members without @@ -411,7 +408,8 @@ CPython bytecode changes * Replaced the three call instructions: :opcode:`CALL_FUNCTION`, :opcode:`CALL_FUNCTION_KW` and :opcode:`CALL_METHOD` with - :opcode:`CALL_NO_KW`, :opcode:`CALL_KW` and :opcode:`PRECALL_METHOD`. + :opcode:`PRECALL_FUNCTION`, :opcode:`PRECALL_METHOD`, :opcode:`CALL`, + and :opcode:`KW_NAMES`. This decouples the argument shifting for methods from the handling of keyword arguments and allows better specialization of calls. @@ -422,8 +420,9 @@ CPython bytecode changes indicate failure with :const:`None` (where a tuple of extracted values would otherwise be). -* Added :opcode:`COPY`, which pushes the *i*-th item to the top of the stack. - The item is not removed from its original location. +* Replace several stack manipulation instructions (``DUP_TOP``, ``DUP_TOP_TWO``, + ``ROT_TWO``, ``ROT_THREE``, ``ROT_FOUR``, and ``ROT_N``) with new + :opcode:`COPY` and :opcode:`SWAP` instructions. * Add :opcode:`POP_JUMP_IF_NOT_NONE` and :opcode:`POP_JUMP_IF_NONE` opcodes to speed up conditional jumps. @@ -467,6 +466,22 @@ Deprecated as deprecated, its docstring is now corrected). (Contributed by Hugo van Kemenade in :issue:`45837`.) +* The delegation of :func:`int` to :meth:`__trunc__` is now deprecated. Calling + ``int(a)`` when ``type(a)`` implements :meth:`__trunc__` but not + :meth:`__int__` or :meth:`__index__` now raises a :exc:`DeprecationWarning`. + (Contributed by Zackery Spytz in :issue:`44977`.) + +* The following have been deprecated in :mod:`configparser` since Python 3.2. + Their deprecation warnings have now been updated to note they will removed in + Python 3.12: + + * the :class:`configparser.SafeConfigParser` class + * the :attr:`configparser.ParsingError.filename` property + * the :meth:`configparser.ParsingError.readfp` method + + (Contributed by Hugo van Kemenade in :issue:`45173`.) + + Removed ======= @@ -499,28 +514,6 @@ Removed and :class:`fileinput.FileInput`, deprecated since Python 3.9. (Contributed by Hugo van Kemenade in :issue:`45132`.) -* Removed many old deprecated :mod:`unittest` features: - - - :class:`~unittest.TestCase` method aliases ``failUnlessEqual``, - ``failIfEqual``, ``failUnless``, ``failIf``, ``failUnlessRaises``, - ``failUnlessAlmostEqual``, ``failIfAlmostEqual`` (deprecated in Python 3.1), - ``assertEquals``, ``assertNotEquals``, ``assert_``, ``assertAlmostEquals``, - ``assertNotAlmostEquals``, ``assertRegexpMatches``, ``assertRaisesRegexp`` - (deprecated in Python 3.2), and ``assertNotRegexpMatches`` (deprecated in - Python 3.5). - - - Undocumented and broken :class:`~unittest.TestCase` method - ``assertDictContainsSubset`` (deprecated in Python 3.2). - - - Undocumented :meth:` - TestLoader.loadTestsFromModule` parameter *use_load_tests* (deprecated - and ignored since Python 3.2). - - - An alias of the :class:`~unittest.TextTestResult` class: - ``_TextTestResult`` (deprecated in Python 3.2). - - (Contributed by Serhiy Storchaka in :issue:`45162`.) - * The following deprecated functions and methods are removed in the :mod:`gettext` module: :func:`~gettext.lgettext`, :func:`~gettext.ldgettext`, :func:`~gettext.lngettext` and :func:`~gettext.ldngettext`. @@ -533,13 +526,6 @@ Removed the ``l*gettext()`` functions. (Contributed by Dong-hee Na and Serhiy Storchaka in :issue:`44235`.) -* Removed from the :mod:`configparser` module: - the :class:`SafeConfigParser` class, - the :attr:`filename` property of the :class:`ParsingError` class, - the :meth:`readfp` method of the :class:`ConfigParser` class, - deprecated since Python 3.2. - (Contributed by Hugo van Kemenade in :issue:`45173`.) - * The :func:`@asyncio.coroutine ` :term:`decorator` enabling legacy generator-based coroutines to be compatible with async/await code. The function has been deprecated since Python 3.8 and the removal was @@ -573,6 +559,9 @@ Removed Python 3.4 but has been broken since Python 3.7. (Contributed by Inada Naoki in :issue:`23882`.) +* Remove ``__class_getitem__`` method from :class:`pathlib.PurePath`, + because it was not used and added by mistake in previous versions. + (Contributed by Nikita Sobolev in :issue:`46483`.) Porting to Python 3.11 ====================== @@ -608,6 +597,12 @@ Changes in the Python API of sorting simply isn't well-defined in the absence of a total ordering on list elements. +* :mod:`calendar`: The :class:`calendar.LocaleTextCalendar` and + :class:`calendar.LocaleHTMLCalendar` classes now use + :func:`locale.getlocale`, instead of using :func:`locale.getdefaultlocale`, + if no locale is specified. + (Contributed by Victor Stinner in :issue:`46659`.) + Build Changes ============= @@ -623,6 +618,12 @@ Build Changes ``isinf()``, ``isnan()``, ``round()``. (Contributed by Victor Stinner in :issue:`45440`.) +* Building Python now requires a C99 ```` header file providing + a ``NAN`` constant, or the ``__builtin_nan()`` built-in function. If a + platform does not support Not-a-Number (NaN), the ``Py_NO_NAN`` macro can be + defined in the ``pyconfig.h`` file. + (Contributed by Victor Stinner in :issue:`46640`.) + * Freelists for object structs can now be disabled. A new :program:`configure` option :option:`!--without-freelists` can be used to disable all freelists except empty tuple singleton. @@ -669,6 +670,11 @@ C API Changes fields of the result from the exception instance (the ``value`` field). (Contributed by Irit Katriel in :issue:`45711`.) +* :c:struct:`_frozen` has a new ``is_package`` field to indicate whether + or not the frozen module is a package. Previously, a negative value + in the ``size`` field was the indicator. Now only non-negative values + be used for ``size``. + (Contributed by Kumar Aditya in :issue:`46608`.) New Features ------------ @@ -688,6 +694,26 @@ New Features :c:macro:`PY_VERSION_HEX`. (Contributed by Gabriele N. Tornetta in :issue:`43931`.) +* :c:type:`Py_buffer` and APIs are now part of the limited API and the stable + ABI: + + * :c:func:`PyObject_CheckBuffer` + * :c:func:`PyObject_GetBuffer` + * :c:func:`PyBuffer_GetPointer` + * :c:func:`PyBuffer_SizeFromFormat` + * :c:func:`PyBuffer_ToContiguous` + * :c:func:`PyBuffer_FromContiguous` + * :c:func:`PyBuffer_CopyData` + * :c:func:`PyBuffer_IsContiguous` + * :c:func:`PyBuffer_FillContiguousStrides` + * :c:func:`PyBuffer_FillInfo` + * :c:func:`PyBuffer_Release` + * :c:func:`PyMemoryView_FromBuffer` + * :c:member:`~PyBufferProcs.bf_getbuffer` and + :c:member:`~PyBufferProcs.bf_releasebuffer` type slots + + (Contributed by Christian Heimes in :issue:`45459`.) + Porting to Python 3.11 ---------------------- @@ -790,27 +816,38 @@ Porting to Python 3.11 which are not available in the limited C API. (Contributed by Victor Stinner in :issue:`46007`.) -* Changes of the :c:type:`PyFrameObject` structure members: +* Changes of the private :c:type:`PyFrameObject` structure members. + + While the documentation notes that the fields of ``PyFrameObject`` are + subject to change at any time, they have been stable for a long time + and were used in several popular extensions. + In Python 3.11, the frame struct was reorganized to allow performance + optimizations. Rather than reading the fields directly, extensions should + use functions: * ``f_code``: removed, use :c:func:`PyFrame_GetCode` instead. Warning: the function returns a :term:`strong reference`, need to call :c:func:`Py_DECREF`. - * ``f_back``: changed, use :c:func:`PyFrame_GetBack`. + * ``f_back``: changed (see below), use :c:func:`PyFrame_GetBack`. * ``f_builtins``: removed, - use ``PyObject_GetAttrString(frame, "f_builtins")``. + use ``PyObject_GetAttrString((PyObject*)frame, "f_builtins")``. * ``f_globals``: removed, - use ``PyObject_GetAttrString(frame, "f_globals")``. + use ``PyObject_GetAttrString((PyObject*)frame, "f_globals")``. * ``f_locals``: removed, - use ``PyObject_GetAttrString(frame, "f_locals")``. + use ``PyObject_GetAttrString((PyObject*)frame, "f_locals")``. * ``f_lasti``: removed, - use ``PyObject_GetAttrString(frame, "f_lasti")``. - * ``f_valuesstack``: removed. - * ``f_stackdepth``: removed. - * ``f_gen``: removed. - * ``f_iblock``: removed. - * ``f_state``: removed. - * ``f_blockstack``: removed. - * ``f_localsplus``: removed. + use ``PyObject_GetAttrString((PyObject*)frame, "f_lasti")``. + + The following fields were removed entirely, as they were details + of the old implementation: + + * ``f_valuesstack`` + * ``f_stackdepth`` + * ``f_gen`` + * ``f_iblock`` + * ``f_state`` + * ``f_blockstack`` + * ``f_localsplus`` The Python frame object is now created lazily. A side effect is that the ``f_back`` member must not be accessed directly, since its value is now also @@ -961,3 +998,8 @@ Removed worked since the :c:type:`PyWeakReference` structure is opaque in the limited C API. (Contributed by Victor Stinner in :issue:`35134`.) + +* Remove the ``PyHeapType_GET_MEMBERS()`` macro. It was exposed in the + public C API by mistake, it must only be used by Python internally. + Use the ``PyTypeObject.tp_members`` member instead. + (Contributed by Victor Stinner in :issue:`40170`.) From e4b289ac07fbc005198b09a379fd0f1f54aa2b8b Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 11 Feb 2022 09:37:10 +0000 Subject: [PATCH 27/28] Remove commented-out line --- Lib/idlelib/idle_test/test_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index 29e9b9a588cffe..20721fe980c784 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -1,7 +1,6 @@ """Test util, coverage 100%""" import unittest -#from unittest import mock from idlelib import util From 4931b3f17b9338cfe1045762208c24536d16c570 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 12 Feb 2022 14:24:21 -0500 Subject: [PATCH 28/28] Final tweaks. --- Lib/idlelib/NEWS.txt | 3 +++ Lib/idlelib/idle_test/test_iomenu.py | 8 ++++---- Lib/idlelib/idle_test/test_util.py | 1 - .../next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 0bfadfd81e2dd4..441ec41ed76b2c 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -4,6 +4,9 @@ Released on 2022-10-03 ========================= +bpo-28950: Apply IDLE syntax highlighting to `.pyi` files. Add util.py +for common components. Patch by Alex Waygood and Terry Jan Reedy. + bpo-46630: Make query dialogs on Windows start with a cursor in the entry box. diff --git a/Lib/idlelib/idle_test/test_iomenu.py b/Lib/idlelib/idle_test/test_iomenu.py index 849863d9a90acb..e338893c09e6a1 100644 --- a/Lib/idlelib/idle_test/test_iomenu.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -45,7 +45,7 @@ def test_fixnewlines_end(self): eq(fix(), 'a'+io.eol_convention) -def _extension_is_in_IOBinding_dot_filetypes(extension): +def _extension_in_filetypes(extension): return any( f'*{extension}' in filetype_tuple[1] for filetype_tuple in iomenu.IOBinding.filetypes @@ -57,14 +57,14 @@ def test_python_source_files(self): for extension in util.py_extensions: with self.subTest(extension=extension): self.assertTrue( - _extension_is_in_IOBinding_dot_filetypes(extension) + _extension_in_filetypes(extension) ) def test_text_files(self): - self.assertTrue(_extension_is_in_IOBinding_dot_filetypes('.txt')) + self.assertTrue(_extension_in_filetypes('.txt')) def test_all_files(self): - self.assertTrue(_extension_is_in_IOBinding_dot_filetypes('')) + self.assertTrue(_extension_in_filetypes('')) if __name__ == '__main__': diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index 29e9b9a588cffe..20721fe980c784 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -1,7 +1,6 @@ """Test util, coverage 100%""" import unittest -#from unittest import mock from idlelib import util diff --git a/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst b/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst index bad30b0f6f31a1..2b5170c7631d24 100644 --- a/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst +++ b/Misc/NEWS.d/next/IDLE/2021-10-14-16-55-03.bpo-45447.FhiH5P.rst @@ -1,2 +1,2 @@ -IDLE now applies syntax highlighting to `.pyi` files. Patch by Alex Waygood +Apply IDLE syntax highlighting to `.pyi` files. Patch by Alex Waygood and Terry Jan Reedy.