From 21bb3743d92e2c70f54bdce107cba20ae21688b3 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Sat, 4 Jan 2025 15:59:32 +0530 Subject: [PATCH 01/35] Update and Add docstrings for functions and methods in minidom.py module --- Lib/xml/dom/minidom.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index db51f350ea0153..3f3944e7a935c8 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -32,6 +32,7 @@ class Node(xml.dom.Node): + """Define properties accessible on a DOM node.""" namespaceURI = None # this is non-null only for elements and attributes parentNode = None ownerDocument = None @@ -44,6 +45,7 @@ def __bool__(self): return True def toxml(self, encoding=None, standalone=None): + """Generate a string representation of a DOM.""" return self.toprettyxml("", "", encoding, standalone) def toprettyxml(self, indent="\t", newl="\n", encoding=None, @@ -80,10 +82,16 @@ def _get_lastChild(self): return self.childNodes[-1] def insertBefore(self, newChild, refChild): + """Insert a new DOM Node before an existing Node. + https://dom.spec.whatwg.org/#dom-node-insertbefore + The insertBefore(node, child) method, when invoked, + must return the result of pre-inserting node into + this before child. + See also https://dom.spec.whatwg.org/#concept-node-pre-insert + """ if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: for c in tuple(newChild.childNodes): self.insertBefore(c, refChild) - ### The DOM does not clearly specify what to return in this case return newChild if newChild.nodeType not in self._child_node_types: raise xml.dom.HierarchyRequestErr( @@ -112,6 +120,7 @@ def insertBefore(self, newChild, refChild): return newChild def appendChild(self, node): + """Append a child node to an existing node.""" if node.nodeType == self.DOCUMENT_FRAGMENT_NODE: for c in tuple(node.childNodes): self.appendChild(c) @@ -129,6 +138,7 @@ def appendChild(self, node): return node def replaceChild(self, newChild, oldChild): + """Replace an existing node with a new node.""" if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: refChild = oldChild.nextSibling self.removeChild(oldChild) @@ -161,6 +171,7 @@ def replaceChild(self, newChild, oldChild): return oldChild def removeChild(self, oldChild): + """Remove an existing child.""" try: self.childNodes.remove(oldChild) except ValueError: @@ -172,11 +183,15 @@ def removeChild(self, oldChild): oldChild.nextSibling = oldChild.previousSibling = None if oldChild.nodeType in _nodeTypes_with_children: _clear_id_cache(self) - oldChild.parentNode = None return oldChild def normalize(self): + """Transform a node into its normalized form. + Removes empty exclusive Text nodes and concatenates the data of + remaining contiguous exclusive Text nodes into the first of + their nodes. + """ L = [] for child in self.childNodes: if child.nodeType == Node.TEXT_NODE: @@ -204,6 +219,7 @@ def normalize(self): self.childNodes[:] = L def cloneNode(self, deep): + """The Node.cloneNode() method returns a duplicate of the node.""" return _clone_node(self, deep, self.ownerDocument or self) def isSupported(self, feature, version): @@ -882,7 +898,7 @@ def getElementsByTagNameNS(self, namespaceURI, localName): self, namespaceURI, localName, NodeList()) def __repr__(self): - return "" % (self.tagName, id(self)) + return f"" def writexml(self, writer, indent="", addindent="", newl=""): """Write an XML element to a file-like object @@ -1341,6 +1357,7 @@ def _get_internalSubset(self): return self.internalSubset def cloneNode(self, deep): + """The Node.cloneNode() method returns a duplicate of the node.""" if self.ownerDocument is None: # it's ok clone = DocumentType(None) @@ -1904,7 +1921,8 @@ def renameNode(self, n, namespaceURI, name): def _clone_node(node, deep, newOwnerDocument): """ - Clone a node and give it the new owner document. + Returns a copy of node. + If deep is true, the copy also includes the node’s descendants. Called by Node.cloneNode and Document.importNode """ if node.ownerDocument.isSameNode(newOwnerDocument): From f16761bb441e00fc4378923e8ab15f35cd0e5b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Srinivas=20Reddy=20Thatiparthy=20=28=E0=B0=A4=E0=B0=BE?= =?UTF-8?q?=E0=B0=9F=E0=B0=BF=E0=B0=AA=E0=B0=B0=E0=B1=8D=E0=B0=A4=E0=B0=BF?= =?UTF-8?q?=20=E0=B0=B6=E0=B1=8D=E0=B0=B0=E0=B1=80=E0=B0=A8=E0=B0=BF?= =?UTF-8?q?=E0=B0=B5=E0=B0=BE=E0=B0=B8=E0=B1=8D=20=20=E0=B0=B0=E0=B1=86?= =?UTF-8?q?=E0=B0=A1=E0=B1=8D=E0=B0=A1=E0=B0=BF=29?= Date: Mon, 6 Jan 2025 11:34:47 +0530 Subject: [PATCH 02/35] Update Lib/xml/dom/minidom.py Co-authored-by: Terry Jan Reedy --- Lib/xml/dom/minidom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 3f3944e7a935c8..0c0b5bea393759 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -138,7 +138,7 @@ def appendChild(self, node): return node def replaceChild(self, newChild, oldChild): - """Replace an existing node with a new node.""" + """Replace child node *oldChild* with *newChild*.""" if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: refChild = oldChild.nextSibling self.removeChild(oldChild) From eb0208b0e15ae52eca3b9899c7ee95219a876f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Srinivas=20Reddy=20Thatiparthy=20=28=E0=B0=A4=E0=B0=BE?= =?UTF-8?q?=E0=B0=9F=E0=B0=BF=E0=B0=AA=E0=B0=B0=E0=B1=8D=E0=B0=A4=E0=B0=BF?= =?UTF-8?q?=20=E0=B0=B6=E0=B1=8D=E0=B0=B0=E0=B1=80=E0=B0=A8=E0=B0=BF?= =?UTF-8?q?=E0=B0=B5=E0=B0=BE=E0=B0=B8=E0=B1=8D=20=20=E0=B0=B0=E0=B1=86?= =?UTF-8?q?=E0=B0=A1=E0=B1=8D=E0=B0=A1=E0=B0=BF=29?= Date: Mon, 6 Jan 2025 11:34:55 +0530 Subject: [PATCH 03/35] Update Lib/xml/dom/minidom.py Co-authored-by: Terry Jan Reedy --- Lib/xml/dom/minidom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 0c0b5bea393759..6615a798194076 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -83,6 +83,7 @@ def _get_lastChild(self): def insertBefore(self, newChild, refChild): """Insert a new DOM Node before an existing Node. + https://dom.spec.whatwg.org/#dom-node-insertbefore The insertBefore(node, child) method, when invoked, must return the result of pre-inserting node into From f4f633445ae05e8e085315f742f3f82a0c6daa90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Srinivas=20Reddy=20Thatiparthy=20=28=E0=B0=A4=E0=B0=BE?= =?UTF-8?q?=E0=B0=9F=E0=B0=BF=E0=B0=AA=E0=B0=B0=E0=B1=8D=E0=B0=A4=E0=B0=BF?= =?UTF-8?q?=20=E0=B0=B6=E0=B1=8D=E0=B0=B0=E0=B1=80=E0=B0=A8=E0=B0=BF?= =?UTF-8?q?=E0=B0=B5=E0=B0=BE=E0=B0=B8=E0=B1=8D=20=20=E0=B0=B0=E0=B1=86?= =?UTF-8?q?=E0=B0=A1=E0=B1=8D=E0=B0=A1=E0=B0=BF=29?= Date: Mon, 6 Jan 2025 11:35:07 +0530 Subject: [PATCH 04/35] Update Lib/xml/dom/minidom.py Co-authored-by: Terry Jan Reedy --- Lib/xml/dom/minidom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 6615a798194076..3bd78e997e6c48 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -189,6 +189,7 @@ def removeChild(self, oldChild): def normalize(self): """Transform a node into its normalized form. + Removes empty exclusive Text nodes and concatenates the data of remaining contiguous exclusive Text nodes into the first of their nodes. From 70617eafc2bc9e76b2775d1e327116abb7a0d0a8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 4 Jan 2025 13:31:01 +0300 Subject: [PATCH 05/35] Add `check-readthedocs` pre-commit hook (#128453) --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 107f3b255735f4..af6accd89b5bd4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,6 +55,7 @@ repos: hooks: - id: check-dependabot - id: check-github-workflows + - id: check-readthedocs - repo: https://github.com/rhysd/actionlint rev: v1.7.4 From 90b344f2119f88d562757931386bf0621a1562bd Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 4 Jan 2025 11:46:04 +0100 Subject: [PATCH 06/35] gh-128152: Argument Clinic: ignore pre-processor directives inside C comments (#128464) --- Lib/test/test_clinic.py | 10 ++++++++++ .../2025-01-03-23-51-07.gh-issue-128152.IhzElS.rst | 2 ++ Tools/clinic/libclinic/cpp.py | 3 +++ 3 files changed, 15 insertions(+) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2025-01-03-23-51-07.gh-issue-128152.IhzElS.rst diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 11054963b6ff03..b45b9ee89ee3de 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -740,6 +740,16 @@ def test_cloned_forced_text_signature_illegal(self): err = "Cannot use @text_signature when cloning a function" self.expect_failure(block, err, lineno=11) + def test_ignore_preprocessor_in_comments(self): + for dsl in "clinic", "python": + raw = dedent(f"""\ + /*[{dsl} input] + # CPP directives, valid or not, should be ignored in C comments. + # + [{dsl} start generated code]*/ + """) + self.clinic.parse(raw) + class ParseFileUnitTest(TestCase): def expect_parsing_failure( diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-01-03-23-51-07.gh-issue-128152.IhzElS.rst b/Misc/NEWS.d/next/Tools-Demos/2025-01-03-23-51-07.gh-issue-128152.IhzElS.rst new file mode 100644 index 00000000000000..9657e138e9911b --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2025-01-03-23-51-07.gh-issue-128152.IhzElS.rst @@ -0,0 +1,2 @@ +Fix a bug where Argument Clinic's C pre-processor parser tried to parse +pre-processor directives inside C comments. Patch by Erlend Aasland. diff --git a/Tools/clinic/libclinic/cpp.py b/Tools/clinic/libclinic/cpp.py index e115d65a88e1b6..3cfe99b712641d 100644 --- a/Tools/clinic/libclinic/cpp.py +++ b/Tools/clinic/libclinic/cpp.py @@ -132,6 +132,9 @@ def pop_stack() -> TokenAndCondition: if line_comment: line = before.rstrip() + if self.in_comment: + return + if not line.startswith('#'): return From 34c317a024432a6bd38886b1ee82e3b8c0b42440 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 4 Jan 2025 12:53:51 +0000 Subject: [PATCH 07/35] GH-127381: pathlib ABCs: remove `PathBase.move()` and `move_into()` (#128337) These methods combine `_delete()` and `copy()`, but `_delete()` isn't part of the public interface, and it's unlikely to be added until the pathlib ABCs are made official, or perhaps even later. --- Lib/pathlib/_abc.py | 27 ----- Lib/pathlib/_local.py | 38 +++++-- Lib/test/test_pathlib/test_pathlib.py | 118 ++++++++++++++++++++++ Lib/test/test_pathlib/test_pathlib_abc.py | 118 ---------------------- 4 files changed, 148 insertions(+), 153 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index e6ff3fe1187512..7de2bb066f8f99 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -573,30 +573,3 @@ def copy_into(self, target_dir, *, follow_symlinks=True, return self.copy(target, follow_symlinks=follow_symlinks, dirs_exist_ok=dirs_exist_ok, preserve_metadata=preserve_metadata) - - def _delete(self): - """ - Delete this file or directory (including all sub-directories). - """ - raise NotImplementedError - - def move(self, target): - """ - Recursively move this file or directory tree to the given destination. - """ - target = self.copy(target, follow_symlinks=False, preserve_metadata=True) - self._delete() - return target - - def move_into(self, target_dir): - """ - Move this file or directory tree into the given existing directory. - """ - name = self.name - if not name: - raise ValueError(f"{self!r} has an empty name") - elif isinstance(target_dir, PathBase): - target = target_dir / name - else: - target = self.with_segments(target_dir, name) - return self.move(target) diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py index c5721a69d00470..1da85ddea24376 100644 --- a/Lib/pathlib/_local.py +++ b/Lib/pathlib/_local.py @@ -1128,16 +1128,38 @@ def move(self, target): """ Recursively move this file or directory tree to the given destination. """ - if not isinstance(target, PathBase): - target = self.with_segments(target) - target.copy._ensure_different_file(self) + # Use os.replace() if the target is os.PathLike and on the same FS. try: - return self.replace(target) - except OSError as err: - if err.errno != EXDEV: - raise + target_str = os.fspath(target) + except TypeError: + pass + else: + if not isinstance(target, PathBase): + target = self.with_segments(target_str) + target.copy._ensure_different_file(self) + try: + os.replace(self, target_str) + return target + except OSError as err: + if err.errno != EXDEV: + raise # Fall back to copy+delete. - return PathBase.move(self, target) + target = self.copy(target, follow_symlinks=False, preserve_metadata=True) + self._delete() + return target + + def move_into(self, target_dir): + """ + Move this file or directory tree into the given existing directory. + """ + name = self.name + if not name: + raise ValueError(f"{self!r} has an empty name") + elif isinstance(target_dir, PathBase): + target = target_dir / name + else: + target = self.with_segments(target_dir, name) + return self.move(target) if hasattr(os, "symlink"): def symlink_to(self, target, target_is_directory=False): diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index d13daf8ac8cb07..a67a1c531630a1 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -1423,26 +1423,97 @@ def test_move_dangling_symlink(self): self.assertTrue(target.is_symlink()) self.assertEqual(source_readlink, target.readlink()) + def test_move_file(self): + base = self.cls(self.base) + source = base / 'fileA' + source_text = source.read_text() + target = base / 'fileA_moved' + result = source.move(target) + self.assertEqual(result, target) + self.assertFalse(source.exists()) + self.assertTrue(target.exists()) + self.assertEqual(source_text, target.read_text()) + @patch_replace def test_move_file_other_fs(self): self.test_move_file() + def test_move_file_to_file(self): + base = self.cls(self.base) + source = base / 'fileA' + source_text = source.read_text() + target = base / 'dirB' / 'fileB' + result = source.move(target) + self.assertEqual(result, target) + self.assertFalse(source.exists()) + self.assertTrue(target.exists()) + self.assertEqual(source_text, target.read_text()) + @patch_replace def test_move_file_to_file_other_fs(self): self.test_move_file_to_file() + def test_move_file_to_dir(self): + base = self.cls(self.base) + source = base / 'fileA' + target = base / 'dirB' + self.assertRaises(OSError, source.move, target) + @patch_replace def test_move_file_to_dir_other_fs(self): self.test_move_file_to_dir() + def test_move_file_to_itself(self): + base = self.cls(self.base) + source = base / 'fileA' + self.assertRaises(OSError, source.move, source) + + def test_move_dir(self): + base = self.cls(self.base) + source = base / 'dirC' + target = base / 'dirC_moved' + result = source.move(target) + self.assertEqual(result, target) + self.assertFalse(source.exists()) + self.assertTrue(target.is_dir()) + self.assertTrue(target.joinpath('dirD').is_dir()) + self.assertTrue(target.joinpath('dirD', 'fileD').is_file()) + self.assertEqual(target.joinpath('dirD', 'fileD').read_text(), + "this is file D\n") + self.assertTrue(target.joinpath('fileC').is_file()) + self.assertTrue(target.joinpath('fileC').read_text(), + "this is file C\n") + @patch_replace def test_move_dir_other_fs(self): self.test_move_dir() + def test_move_dir_to_dir(self): + base = self.cls(self.base) + source = base / 'dirC' + target = base / 'dirB' + self.assertRaises(OSError, source.move, target) + self.assertTrue(source.exists()) + self.assertTrue(target.exists()) + @patch_replace def test_move_dir_to_dir_other_fs(self): self.test_move_dir_to_dir() + def test_move_dir_to_itself(self): + base = self.cls(self.base) + source = base / 'dirC' + self.assertRaises(OSError, source.move, source) + self.assertTrue(source.exists()) + + def test_move_dir_into_itself(self): + base = self.cls(self.base) + source = base / 'dirC' + target = base / 'dirC' / 'bar' + self.assertRaises(OSError, source.move, target) + self.assertTrue(source.exists()) + self.assertFalse(target.exists()) + @patch_replace def test_move_dir_into_itself_other_fs(self): self.test_move_dir_into_itself() @@ -1472,10 +1543,26 @@ def test_move_dir_symlink_to_itself_other_fs(self): def test_move_dangling_symlink_other_fs(self): self.test_move_dangling_symlink() + def test_move_into(self): + base = self.cls(self.base) + source = base / 'fileA' + source_text = source.read_text() + target_dir = base / 'dirA' + result = source.move_into(target_dir) + self.assertEqual(result, target_dir / 'fileA') + self.assertFalse(source.exists()) + self.assertTrue(result.exists()) + self.assertEqual(source_text, result.read_text()) + @patch_replace def test_move_into_other_os(self): self.test_move_into() + def test_move_into_empty_name(self): + source = self.cls('') + target_dir = self.base + self.assertRaises(ValueError, source.move_into, target_dir) + @patch_replace def test_move_into_empty_name_other_os(self): self.test_move_into_empty_name() @@ -1794,6 +1881,37 @@ def test_rmdir(self): self.assertFileNotFound(p.stat) self.assertFileNotFound(p.unlink) + def test_delete_file(self): + p = self.cls(self.base) / 'fileA' + p._delete() + self.assertFalse(p.exists()) + self.assertFileNotFound(p._delete) + + def test_delete_dir(self): + base = self.cls(self.base) + base.joinpath('dirA')._delete() + self.assertFalse(base.joinpath('dirA').exists()) + self.assertFalse(base.joinpath('dirA', 'linkC').exists( + follow_symlinks=False)) + base.joinpath('dirB')._delete() + self.assertFalse(base.joinpath('dirB').exists()) + self.assertFalse(base.joinpath('dirB', 'fileB').exists()) + self.assertFalse(base.joinpath('dirB', 'linkD').exists( + follow_symlinks=False)) + base.joinpath('dirC')._delete() + self.assertFalse(base.joinpath('dirC').exists()) + self.assertFalse(base.joinpath('dirC', 'dirD').exists()) + self.assertFalse(base.joinpath('dirC', 'dirD', 'fileD').exists()) + self.assertFalse(base.joinpath('dirC', 'fileC').exists()) + self.assertFalse(base.joinpath('dirC', 'novel.txt').exists()) + + def test_delete_missing(self): + tmp = self.cls(self.base, 'delete') + tmp.mkdir() + # filename is guaranteed not to exist + filename = tmp / 'foo' + self.assertRaises(FileNotFoundError, filename._delete) + @needs_symlinks def test_delete_symlink(self): tmp = self.cls(self.base, 'delete') diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index d588442bd11785..0762f224fc9ac4 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -1345,93 +1345,6 @@ def test_copy_into_empty_name(self): target_dir = self.base self.assertRaises(ValueError, source.copy_into, target_dir) - def test_move_file(self): - base = self.cls(self.base) - source = base / 'fileA' - source_text = source.read_text() - target = base / 'fileA_moved' - result = source.move(target) - self.assertEqual(result, target) - self.assertFalse(source.exists()) - self.assertTrue(target.exists()) - self.assertEqual(source_text, target.read_text()) - - def test_move_file_to_file(self): - base = self.cls(self.base) - source = base / 'fileA' - source_text = source.read_text() - target = base / 'dirB' / 'fileB' - result = source.move(target) - self.assertEqual(result, target) - self.assertFalse(source.exists()) - self.assertTrue(target.exists()) - self.assertEqual(source_text, target.read_text()) - - def test_move_file_to_dir(self): - base = self.cls(self.base) - source = base / 'fileA' - target = base / 'dirB' - self.assertRaises(OSError, source.move, target) - - def test_move_file_to_itself(self): - base = self.cls(self.base) - source = base / 'fileA' - self.assertRaises(OSError, source.move, source) - - def test_move_dir(self): - base = self.cls(self.base) - source = base / 'dirC' - target = base / 'dirC_moved' - result = source.move(target) - self.assertEqual(result, target) - self.assertFalse(source.exists()) - self.assertTrue(target.is_dir()) - self.assertTrue(target.joinpath('dirD').is_dir()) - self.assertTrue(target.joinpath('dirD', 'fileD').is_file()) - self.assertEqual(target.joinpath('dirD', 'fileD').read_text(), - "this is file D\n") - self.assertTrue(target.joinpath('fileC').is_file()) - self.assertTrue(target.joinpath('fileC').read_text(), - "this is file C\n") - - def test_move_dir_to_dir(self): - base = self.cls(self.base) - source = base / 'dirC' - target = base / 'dirB' - self.assertRaises(OSError, source.move, target) - self.assertTrue(source.exists()) - self.assertTrue(target.exists()) - - def test_move_dir_to_itself(self): - base = self.cls(self.base) - source = base / 'dirC' - self.assertRaises(OSError, source.move, source) - self.assertTrue(source.exists()) - - def test_move_dir_into_itself(self): - base = self.cls(self.base) - source = base / 'dirC' - target = base / 'dirC' / 'bar' - self.assertRaises(OSError, source.move, target) - self.assertTrue(source.exists()) - self.assertFalse(target.exists()) - - def test_move_into(self): - base = self.cls(self.base) - source = base / 'fileA' - source_text = source.read_text() - target_dir = base / 'dirA' - result = source.move_into(target_dir) - self.assertEqual(result, target_dir / 'fileA') - self.assertFalse(source.exists()) - self.assertTrue(result.exists()) - self.assertEqual(source_text, result.read_text()) - - def test_move_into_empty_name(self): - source = self.cls('') - target_dir = self.base - self.assertRaises(ValueError, source.move_into, target_dir) - def test_iterdir(self): P = self.cls p = P(self.base) @@ -1660,37 +1573,6 @@ def test_is_symlink(self): self.assertIs((P / 'linkA\udfff').is_file(), False) self.assertIs((P / 'linkA\x00').is_file(), False) - def test_delete_file(self): - p = self.cls(self.base) / 'fileA' - p._delete() - self.assertFalse(p.exists()) - self.assertFileNotFound(p._delete) - - def test_delete_dir(self): - base = self.cls(self.base) - base.joinpath('dirA')._delete() - self.assertFalse(base.joinpath('dirA').exists()) - self.assertFalse(base.joinpath('dirA', 'linkC').exists( - follow_symlinks=False)) - base.joinpath('dirB')._delete() - self.assertFalse(base.joinpath('dirB').exists()) - self.assertFalse(base.joinpath('dirB', 'fileB').exists()) - self.assertFalse(base.joinpath('dirB', 'linkD').exists( - follow_symlinks=False)) - base.joinpath('dirC')._delete() - self.assertFalse(base.joinpath('dirC').exists()) - self.assertFalse(base.joinpath('dirC', 'dirD').exists()) - self.assertFalse(base.joinpath('dirC', 'dirD', 'fileD').exists()) - self.assertFalse(base.joinpath('dirC', 'fileC').exists()) - self.assertFalse(base.joinpath('dirC', 'novel.txt').exists()) - - def test_delete_missing(self): - tmp = self.cls(self.base, 'delete') - tmp.mkdir() - # filename is guaranteed not to exist - filename = tmp / 'foo' - self.assertRaises(FileNotFoundError, filename._delete) - class DummyPathWalkTest(unittest.TestCase): cls = DummyPath From 5f7ba82888b31005598f225096b35647e68e9763 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 4 Jan 2025 16:44:34 +0100 Subject: [PATCH 08/35] Docs: mark up json.dump() using parameter list (#128482) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/library/json.rst | 118 +++++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/Doc/library/json.rst b/Doc/library/json.rst index 758d47462b6e12..f11109fb0ecfed 100644 --- a/Doc/library/json.rst +++ b/Doc/library/json.rst @@ -151,69 +151,91 @@ Basic Usage sort_keys=False, **kw) Serialize *obj* as a JSON formatted stream to *fp* (a ``.write()``-supporting - :term:`file-like object`) using this :ref:`conversion table + :term:`file-like object`) using this :ref:`Python-to-JSON conversion table `. - If *skipkeys* is true (default: ``False``), then dict keys that are not - of a basic type (:class:`str`, :class:`int`, :class:`float`, :class:`bool`, - ``None``) will be skipped instead of raising a :exc:`TypeError`. - - The :mod:`json` module always produces :class:`str` objects, not - :class:`bytes` objects. Therefore, ``fp.write()`` must support :class:`str` - input. - - If *ensure_ascii* is true (the default), the output is guaranteed to - have all incoming non-ASCII characters escaped. If *ensure_ascii* is - false, these characters will be output as-is. + To use a custom :class:`JSONEncoder` subclass (for example, one that overrides the + :meth:`~JSONEncoder.default` method to serialize additional types), specify it with the + *cls* keyword argument; otherwise :class:`JSONEncoder` is used. - If *check_circular* is false (default: ``True``), then the circular - reference check for container types will be skipped and a circular reference - will result in a :exc:`RecursionError` (or worse). + .. note:: - If *allow_nan* is false (default: ``True``), then it will be a - :exc:`ValueError` to serialize out of range :class:`float` values (``nan``, - ``inf``, ``-inf``) in strict compliance of the JSON specification. - If *allow_nan* is true, their JavaScript equivalents (``NaN``, - ``Infinity``, ``-Infinity``) will be used. + Unlike :mod:`pickle` and :mod:`marshal`, JSON is not a framed protocol, + so trying to serialize multiple objects with repeated calls to + :func:`dump` using the same *fp* will result in an invalid JSON file. - If *indent* is a non-negative integer or string, then JSON array elements and - object members will be pretty-printed with that indent level. An indent level - of 0, negative, or ``""`` will only insert newlines. ``None`` (the default) - selects the most compact representation. Using a positive integer indent - indents that many spaces per level. If *indent* is a string (such as ``"\t"``), - that string is used to indent each level. + :param object obj: + The Python object to be serialized. + + :param fp: + The file-like object *obj* will be serialized to. + The :mod:`!json` module always produces :class:`str` objects, + not :class:`bytes` objects, + therefore ``fp.write()`` must support :class:`str` input. + :type fp: :term:`file-like object` + + :param bool skipkeys: + If ``True``, keys that are not of a basic type + (:class:`str`, :class:`int`, :class:`float`, :class:`bool`, ``None``) + will be skipped instead of raising a :exc:`TypeError`. + Default ``False``. + + :param bool ensure_ascii: + If ``True`` (the default), the output is guaranteed to + have all incoming non-ASCII characters escaped. + If ``False``, these characters will be outputted as-is. + + :param bool check_circular: + If ``False``, the circular reference check for container types is skipped + and a circular reference will result in a :exc:`RecursionError` (or worse). + Default ``True``. + + :param bool allow_nan: + If ``False``, serialization of out-of-range :class:`float` values + (``nan``, ``inf``, ``-inf``) will result in a :exc:`ValueError`, + in strict compliance with the JSON specification. + If ``True`` (the default), their JavaScript equivalents + (``NaN``, ``Infinity``, ``-Infinity``) are used. + + :param indent: + If a positive integer or string, JSON array elements and + object members will be pretty-printed with that indent level. + A positive integer indents that many spaces per level; + a string (such as ``"\t"``) is used to indent each level. + If zero, negative, or ``""`` (the empty string), + only newlines are inserted. + If ``None`` (the default), the most compact representation is used. + :type indent: int | str | None + + :param separators: + A two-tuple: ``(item_separator, key_separator)``. + If ``None`` (the default), *separators* defaults to + ``(', ', ': ')`` if *indent* is ``None``, + and ``(',', ': ')`` otherwise. + For the most compact JSON, + specify ``(',', ':')`` to eliminate whitespace. + :type separators: tuple | None + + :param default: + A function that is called for objects that can't otherwise be serialized. + It should return a JSON encodable version of the object + or raise a :exc:`TypeError`. + If ``None`` (the default), :exc:`!TypeError` is raised. + :type default: :term:`callable` | None + + :param sort_keys: + If ``True``, dictionaries will be outputted sorted by key. + Default ``False``. .. versionchanged:: 3.2 Allow strings for *indent* in addition to integers. - If specified, *separators* should be an ``(item_separator, key_separator)`` - tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and - ``(',', ': ')`` otherwise. To get the most compact JSON representation, - you should specify ``(',', ':')`` to eliminate whitespace. - .. versionchanged:: 3.4 Use ``(',', ': ')`` as default if *indent* is not ``None``. - If specified, *default* should be a function that gets called for objects that - can't otherwise be serialized. It should return a JSON encodable version of - the object or raise a :exc:`TypeError`. If not specified, :exc:`TypeError` - is raised. - - If *sort_keys* is true (default: ``False``), then the output of - dictionaries will be sorted by key. - - To use a custom :class:`JSONEncoder` subclass (e.g. one that overrides the - :meth:`~JSONEncoder.default` method to serialize additional types), specify it with the - *cls* kwarg; otherwise :class:`JSONEncoder` is used. - .. versionchanged:: 3.6 All optional parameters are now :ref:`keyword-only `. - .. note:: - - Unlike :mod:`pickle` and :mod:`marshal`, JSON is not a framed protocol, - so trying to serialize multiple objects with repeated calls to - :func:`dump` using the same *fp* will result in an invalid JSON file. .. function:: dumps(obj, *, skipkeys=False, ensure_ascii=True, \ check_circular=True, allow_nan=True, cls=None, \ From 23d11bea93ba5a7f7ade25edf3fd6a96ca41a4f6 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 4 Jan 2025 15:45:24 +0000 Subject: [PATCH 09/35] pathlib tests: create `walk()` test hierarchy without using class under test (#128338) In the tests for `pathlib.Path.walk()`, avoid using the path class under test (`self.cls`) in test setup. Instead we use `os` functions in `test_pathlib`, and direct manipulation of `DummyPath` internal data in `test_pathlib_abc`. --- Lib/test/test_pathlib/test_pathlib.py | 38 ++++++++++++++- Lib/test/test_pathlib/test_pathlib_abc.py | 59 ++++++++--------------- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index a67a1c531630a1..6548577f4de12c 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -3029,6 +3029,42 @@ def setUp(self): if name in _tests_needing_symlinks and not self.can_symlink: self.skipTest('requires symlinks') super().setUp() + + def createTestHierarchy(self): + # Build: + # TESTFN/ + # TEST1/ a file kid and two directory kids + # tmp1 + # SUB1/ a file kid and a directory kid + # tmp2 + # SUB11/ no kids + # SUB2/ a file kid and a dirsymlink kid + # tmp3 + # link/ a symlink to TEST2 + # broken_link + # broken_link2 + # TEST2/ + # tmp4 a lone file + t2_path = self.cls(self.base, "TEST2") + os.makedirs(self.sub11_path) + os.makedirs(self.sub2_path) + os.makedirs(t2_path) + + tmp1_path = self.walk_path / "tmp1" + tmp2_path = self.sub1_path / "tmp2" + tmp3_path = self.sub2_path / "tmp3" + tmp4_path = self.cls(self.base, "TEST2", "tmp4") + for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path: + with open(path, "w", encoding='utf-8') as f: + f.write(f"I'm {path} and proud of it. Blame test_pathlib.\n") + + if self.can_symlink: + broken_link_path = self.sub2_path / "broken_link" + broken_link2_path = self.sub2_path / "broken_link2" + os.symlink(t2_path, self.link_path, target_is_directory=True) + os.symlink('broken', broken_link_path) + os.symlink(os.path.join('tmp3', 'broken'), broken_link2_path) + self.sub2_tree = (self.sub2_path, [], ["broken_link", "broken_link2", "link", "tmp3"]) sub21_path= self.sub2_path / "SUB21" tmp5_path = sub21_path / "tmp3" broken_link3_path = self.sub2_path / "broken_link3" @@ -3052,7 +3088,7 @@ def setUp(self): def tearDown(self): if 'SUB21' in self.sub2_tree[1]: os.chmod(self.sub2_path / "SUB21", stat.S_IRWXU) - super().tearDown() + os_helper.rmtree(self.base) def test_walk_bad_dir(self): errors = [] diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 0762f224fc9ac4..87aef0c130cf9e 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -1580,52 +1580,35 @@ class DummyPathWalkTest(unittest.TestCase): can_symlink = False def setUp(self): - # Build: - # TESTFN/ - # TEST1/ a file kid and two directory kids - # tmp1 - # SUB1/ a file kid and a directory kid - # tmp2 - # SUB11/ no kids - # SUB2/ a file kid and a dirsymlink kid - # tmp3 - # link/ a symlink to TEST2 - # broken_link - # broken_link2 - # TEST2/ - # tmp4 a lone file self.walk_path = self.cls(self.base, "TEST1") self.sub1_path = self.walk_path / "SUB1" self.sub11_path = self.sub1_path / "SUB11" self.sub2_path = self.walk_path / "SUB2" - tmp1_path = self.walk_path / "tmp1" - tmp2_path = self.sub1_path / "tmp2" - tmp3_path = self.sub2_path / "tmp3" self.link_path = self.sub2_path / "link" - t2_path = self.cls(self.base, "TEST2") - tmp4_path = self.cls(self.base, "TEST2", "tmp4") - broken_link_path = self.sub2_path / "broken_link" - broken_link2_path = self.sub2_path / "broken_link2" - - self.sub11_path.mkdir(parents=True) - self.sub2_path.mkdir(parents=True) - t2_path.mkdir(parents=True) - - for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path: - with path.open("w", encoding='utf-8') as f: - f.write(f"I'm {path} and proud of it. Blame test_pathlib.\n") + self.sub2_tree = (self.sub2_path, [], ["tmp3"]) + self.createTestHierarchy() - if self.can_symlink: - self.link_path.symlink_to(t2_path, target_is_directory=True) - broken_link_path.symlink_to('broken') - broken_link2_path.symlink_to(self.cls('tmp3', 'broken')) - self.sub2_tree = (self.sub2_path, [], ["broken_link", "broken_link2", "link", "tmp3"]) - else: - self.sub2_tree = (self.sub2_path, [], ["tmp3"]) + def createTestHierarchy(self): + cls = self.cls + cls._files = { + f'{self.base}/TEST1/tmp1': b'this is tmp1\n', + f'{self.base}/TEST1/SUB1/tmp2': b'this is tmp2\n', + f'{self.base}/TEST1/SUB2/tmp3': b'this is tmp3\n', + f'{self.base}/TEST2/tmp4': b'this is tmp4\n', + } + cls._directories = { + f'{self.base}': {'TEST1', 'TEST2'}, + f'{self.base}/TEST1': {'SUB1', 'SUB2', 'tmp1'}, + f'{self.base}/TEST1/SUB1': {'SUB11', 'tmp2'}, + f'{self.base}/TEST1/SUB1/SUB11': set(), + f'{self.base}/TEST1/SUB2': {'tmp3'}, + f'{self.base}/TEST2': {'tmp4'}, + } def tearDown(self): - base = self.cls(self.base) - base._delete() + cls = self.cls + cls._files.clear() + cls._directories.clear() def test_walk_topdown(self): walker = self.walk_path.walk() From e6a8d3885a20fcd0c6516601063cad0c15193769 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Sat, 4 Jan 2025 17:38:57 +0000 Subject: [PATCH 10/35] gh-126719: Clarify math.fmod docs (#127741) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/math.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/math.rst b/Doc/library/math.rst index bf79b23a72bbf9..c78b313db5152d 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -248,7 +248,8 @@ Floating point arithmetic .. function:: fmod(x, y) - Return ``fmod(x, y)``, as defined by the platform C library. Note that the + Return the floating-point remainder of ``x / y``, + as defined by the platform C library function ``fmod(x, y)``. Note that the Python expression ``x % y`` may not return the same result. The intent of the C standard is that ``fmod(x, y)`` be exactly (mathematically; to infinite precision) equal to ``x - n*y`` for some integer *n* such that the result has From 1592145e4f95d1b393a80b3f64e139cdde97e2b6 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 4 Jan 2025 19:57:59 +0100 Subject: [PATCH 11/35] Docs: amend json.dump() post gh-128482 (#128489) --- Doc/library/json.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Doc/library/json.rst b/Doc/library/json.rst index f11109fb0ecfed..169291f74f44a5 100644 --- a/Doc/library/json.rst +++ b/Doc/library/json.rst @@ -154,10 +154,6 @@ Basic Usage :term:`file-like object`) using this :ref:`Python-to-JSON conversion table `. - To use a custom :class:`JSONEncoder` subclass (for example, one that overrides the - :meth:`~JSONEncoder.default` method to serialize additional types), specify it with the - *cls* keyword argument; otherwise :class:`JSONEncoder` is used. - .. note:: Unlike :mod:`pickle` and :mod:`marshal`, JSON is not a framed protocol, @@ -197,6 +193,13 @@ Basic Usage If ``True`` (the default), their JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``) are used. + :param cls: + If set, a custom JSON encoder with the + :meth:`~JSONEncoder.default` method overridden, + for serializing into custom datatypes. + If ``None`` (the default), :class:`!JSONEncoder` is used. + :type cls: a :class:`JSONEncoder` subclass + :param indent: If a positive integer or string, JSON array elements and object members will be pretty-printed with that indent level. @@ -223,7 +226,7 @@ Basic Usage If ``None`` (the default), :exc:`!TypeError` is raised. :type default: :term:`callable` | None - :param sort_keys: + :param bool sort_keys: If ``True``, dictionaries will be outputted sorted by key. Default ``False``. From 1a8ee69dfe4bd4e50da08e68961190c5450fdd47 Mon Sep 17 00:00:00 2001 From: "RUANG (James Roy)" Date: Sun, 5 Jan 2025 04:48:20 +0800 Subject: [PATCH 12/35] gh-127954: Document PyObject_DelItemString (#127986) --- Doc/c-api/object.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index a137688fe07545..3a434a4173eafa 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -493,6 +493,13 @@ Object Protocol on failure. This is equivalent to the Python statement ``del o[key]``. +.. c:function:: int PyObject_DelItemString(PyObject *o, const char *key) + + This is the same as :c:func:`PyObject_DelItem`, but *key* is + specified as a :c:expr:`const char*` UTF-8 encoded bytes string, + rather than a :c:expr:`PyObject*`. + + .. c:function:: PyObject* PyObject_Dir(PyObject *o) This is equivalent to the Python expression ``dir(o)``, returning a (possibly From 61c3e8ace542d7b98e19cfb2fa201162736170b4 Mon Sep 17 00:00:00 2001 From: Beomsoo Kim Date: Sun, 5 Jan 2025 07:38:49 +0900 Subject: [PATCH 13/35] gh-127553: Remove outdated TODO comment in _pydatetime (#127564) --- Lib/_pydatetime.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index ed01670cfece43..be90c9b1315d53 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -2392,7 +2392,6 @@ def __reduce__(self): def _isoweek1monday(year): # Helper to calculate the day number of the Monday starting week 1 - # XXX This could be done more efficiently THURSDAY = 3 firstday = _ymd2ord(year, 1, 1) firstweekday = (firstday + 6) % 7 # See weekday() above From 69f8e4b9cbc82bce1e480560d348ea7a55b0e514 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 5 Jan 2025 00:38:46 +0100 Subject: [PATCH 14/35] gh-115765: Document and enforce Autoconf 2.72 requirement (#128502) --- Doc/using/configure.rst | 5 ++++- configure.ac | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index e7733a6dc11451..82df8dfc948ed2 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -29,7 +29,7 @@ Features and minimum versions required to build CPython: * Tcl/Tk 8.5.12 for the :mod:`tkinter` module. -* Autoconf 2.71 and aclocal 1.16.5 are required to regenerate the +* Autoconf 2.72 and aclocal 1.16.5 are required to regenerate the :file:`configure` script. .. versionchanged:: 3.1 @@ -58,6 +58,9 @@ Features and minimum versions required to build CPython: .. versionchanged:: 3.13 Autoconf 2.71, aclocal 1.16.5 and SQLite 3.15.2 are now required. +.. versionchanged:: next + Autoconf 2.72 is now required. + See also :pep:`7` "Style Guide for C Code" and :pep:`11` "CPython platform support". diff --git a/configure.ac b/configure.ac index 9e131ed1a2dc98..1def41176337e3 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl ************************************************************ dnl * Please run autoreconf -ivf -Werror to test your changes! * dnl ************************************************************ dnl -dnl Python's configure script requires autoconf 2.71, autoconf-archive, +dnl Python's configure script requires autoconf 2.72, autoconf-archive, dnl aclocal 1.16, and pkg-config. dnl dnl It is recommended to use the Tools/build/regen-configure.sh shell script @@ -12,7 +12,7 @@ dnl # Set VERSION so we only need to edit in one place (i.e., here) m4_define([PYTHON_VERSION], [3.14]) -AC_PREREQ([2.71]) +AC_PREREQ([2.72]) AC_INIT([python],[PYTHON_VERSION],[https://github.com/python/cpython/issues/]) From 7f767cdd61e9a3d302982b24ef52cb497de44282 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 4 Jan 2025 22:16:30 -0600 Subject: [PATCH 15/35] gh-128437: Add `BOLT_COMMON_FLAGS` with `-update-debug-sections` (gh-128455) Add `BOLT_COMMON_FLAGS` with `-update-debug-sections` Co-authored-by: Gregory Szorc --- configure | 18 ++++++++++++++++-- configure.ac | 18 ++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/configure b/configure index aa88c74c61156c..61ee51c4b36473 100755 --- a/configure +++ b/configure @@ -907,6 +907,7 @@ CFLAGS_ALIASING OPT BOLT_APPLY_FLAGS BOLT_INSTRUMENT_FLAGS +BOLT_COMMON_FLAGS BOLT_BINARIES MERGE_FDATA LLVM_BOLT @@ -1142,6 +1143,7 @@ LIBS CPPFLAGS CPP PROFILE_TASK +BOLT_COMMON_FLAGS BOLT_INSTRUMENT_FLAGS BOLT_APPLY_FLAGS LIBUUID_CFLAGS @@ -1963,6 +1965,8 @@ Some influential environment variables: CPP C preprocessor PROFILE_TASK Python args for PGO generation task + BOLT_COMMON_FLAGS + Common arguments to llvm-bolt when instrumenting and applying BOLT_INSTRUMENT_FLAGS Arguments to llvm-bolt when instrumenting binaries BOLT_APPLY_FLAGS @@ -9389,11 +9393,21 @@ then : fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking BOLT_COMMON_FLAGS" >&5 +printf %s "checking BOLT_COMMON_FLAGS... " >&6; } +if test -z "${BOLT_COMMON_FLAGS}" +then + BOLT_COMMON_FLAGS=-update-debug-sections + +fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking BOLT_INSTRUMENT_FLAGS" >&5 printf %s "checking BOLT_INSTRUMENT_FLAGS... " >&6; } if test -z "${BOLT_INSTRUMENT_FLAGS}" then - BOLT_INSTRUMENT_FLAGS= + BOLT_INSTRUMENT_FLAGS="${BOLT_COMMON_FLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $BOLT_INSTRUMENT_FLAGS" >&5 printf "%s\n" "$BOLT_INSTRUMENT_FLAGS" >&6; } @@ -9403,7 +9417,7 @@ printf "%s\n" "$BOLT_INSTRUMENT_FLAGS" >&6; } printf %s "checking BOLT_APPLY_FLAGS... " >&6; } if test -z "${BOLT_APPLY_FLAGS}" then - BOLT_APPLY_FLAGS=" -update-debug-sections -reorder-blocks=ext-tsp -reorder-functions=cdsort -split-functions -icf=1 -inline-all -split-eh -reorder-functions-use-hot-size -peepholes=none -jump-tables=aggressive -inline-ap -indirect-call-promotion=all -dyno-stats -use-gnu-stack -frame-opt=hot " + BOLT_APPLY_FLAGS=" ${BOLT_COMMON_FLAGS} -reorder-blocks=ext-tsp -reorder-functions=cdsort -split-functions -icf=1 -inline-all -split-eh -reorder-functions-use-hot-size -peepholes=none -jump-tables=aggressive -inline-ap -indirect-call-promotion=all -dyno-stats -use-gnu-stack -frame-opt=hot " fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $BOLT_APPLY_FLAGS" >&5 diff --git a/configure.ac b/configure.ac index 1def41176337e3..172e8a1a842010 100644 --- a/configure.ac +++ b/configure.ac @@ -2160,6 +2160,20 @@ AS_VAR_IF([enable_shared], [yes], [ BOLT_BINARIES="${BOLT_BINARIES} \$(INSTSONAME)" ]) +AC_ARG_VAR( + [BOLT_COMMON_FLAGS], + [Common arguments to llvm-bolt when instrumenting and applying] +) + +AC_MSG_CHECKING([BOLT_COMMON_FLAGS]) +if test -z "${BOLT_COMMON_FLAGS}" +then + AS_VAR_SET( + [BOLT_COMMON_FLAGS], + [-update-debug-sections] + ) +fi + AC_ARG_VAR( [BOLT_INSTRUMENT_FLAGS], [Arguments to llvm-bolt when instrumenting binaries] @@ -2167,7 +2181,7 @@ AC_ARG_VAR( AC_MSG_CHECKING([BOLT_INSTRUMENT_FLAGS]) if test -z "${BOLT_INSTRUMENT_FLAGS}" then - BOLT_INSTRUMENT_FLAGS= + BOLT_INSTRUMENT_FLAGS="${BOLT_COMMON_FLAGS}" fi AC_MSG_RESULT([$BOLT_INSTRUMENT_FLAGS]) @@ -2181,7 +2195,7 @@ then AS_VAR_SET( [BOLT_APPLY_FLAGS], [m4_normalize(" - -update-debug-sections + ${BOLT_COMMON_FLAGS} -reorder-blocks=ext-tsp -reorder-functions=cdsort -split-functions From 2f8b072ee64eedf429313eee058bda18c2771d30 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Sun, 5 Jan 2025 18:17:06 +0900 Subject: [PATCH 16/35] gh-128137: Update PyASCIIObject to handle interned field with the atomic operation (gh-128196) --- Include/cpython/unicodeobject.h | 20 ++++++++++++------- ...-12-24-01-40-12.gh-issue-128137.gsTwr_.rst | 2 ++ Objects/unicodeobject.c | 6 +++--- 3 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-01-40-12.gh-issue-128137.gsTwr_.rst diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 91799137101280..46a01c8e591709 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -109,7 +109,7 @@ typedef struct { 3: Interned, Immortal, and Static This categorization allows the runtime to determine the right cleanup mechanism at runtime shutdown. */ - unsigned int interned:2; + uint16_t interned; /* Character size: - PyUnicode_1BYTE_KIND (1): @@ -132,21 +132,23 @@ typedef struct { * all characters are in the range U+0000-U+10FFFF * at least one character is in the range U+10000-U+10FFFF */ - unsigned int kind:3; + unsigned short kind:3; /* Compact is with respect to the allocation scheme. Compact unicode objects only require one memory block while non-compact objects use one block for the PyUnicodeObject struct and another for its data buffer. */ - unsigned int compact:1; + unsigned short compact:1; /* The string only contains characters in the range U+0000-U+007F (ASCII) and the kind is PyUnicode_1BYTE_KIND. If ascii is set and compact is set, use the PyASCIIObject structure. */ - unsigned int ascii:1; + unsigned short ascii:1; /* The object is statically allocated. */ - unsigned int statically_allocated:1; + unsigned short statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to - 4 bytes (see issue #19537 on m68k). */ - unsigned int :24; + 4 bytes (see issue #19537 on m68k) and we use unsigned short to avoid + the extra four bytes on 32-bit Windows. This is restricted features + for specific compilers including GCC, MSVC, Clang and IBM's XL compiler. */ + unsigned short :10; } state; } PyASCIIObject; @@ -195,7 +197,11 @@ typedef struct { /* Use only if you know it's a string */ static inline unsigned int PyUnicode_CHECK_INTERNED(PyObject *op) { +#ifdef Py_GIL_DISABLED + return _Py_atomic_load_uint16_relaxed(&_PyASCIIObject_CAST(op)->state.interned); +#else return _PyASCIIObject_CAST(op)->state.interned; +#endif } #define PyUnicode_CHECK_INTERNED(op) PyUnicode_CHECK_INTERNED(_PyObject_CAST(op)) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-01-40-12.gh-issue-128137.gsTwr_.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-01-40-12.gh-issue-128137.gsTwr_.rst new file mode 100644 index 00000000000000..a3b7cde7f67676 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-01-40-12.gh-issue-128137.gsTwr_.rst @@ -0,0 +1,2 @@ +Update :c:type:`PyASCIIObject` layout to handle interned field with the +atomic operation. Patch by Donghee Na. diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 5e532ce0f348c4..3eafa2381c1a4d 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -15729,7 +15729,7 @@ immortalize_interned(PyObject *s) _Py_DecRefTotal(_PyThreadState_GET()); } #endif - _PyUnicode_STATE(s).interned = SSTATE_INTERNED_IMMORTAL; + FT_ATOMIC_STORE_UINT16_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_INTERNED_IMMORTAL); _Py_SetImmortal(s); } @@ -15848,7 +15848,7 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */, _Py_DecRefTotal(_PyThreadState_GET()); #endif } - _PyUnicode_STATE(s).interned = SSTATE_INTERNED_MORTAL; + FT_ATOMIC_STORE_UINT16_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_INTERNED_MORTAL); /* INTERNED_MORTAL -> INTERNED_IMMORTAL (if needed) */ @@ -15984,7 +15984,7 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp) Py_UNREACHABLE(); } if (!shared) { - _PyUnicode_STATE(s).interned = SSTATE_NOT_INTERNED; + FT_ATOMIC_STORE_UINT16_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_NOT_INTERNED); } } #ifdef INTERNED_STATS From 539b63849193dbc9e0bee8226c40e4cc1bb81570 Mon Sep 17 00:00:00 2001 From: Damien <81557462+Damien-Chen@users.noreply.github.com> Date: Sun, 5 Jan 2025 20:07:18 +0800 Subject: [PATCH 17/35] gh-128504: Upgrade doctest to ubuntu-24.04 (#128506) Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/reusable-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index 3962d12403919a..88da55bf08b8fe 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -99,7 +99,7 @@ jobs: # Run "doctest" on HEAD as new syntax doesn't exist in the latest stable release doctest: name: 'Doctest' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 60 steps: - uses: actions/checkout@v4 From a4c5f4ffad41556412423d245cbdde92fabd7f66 Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi Date: Mon, 6 Jan 2025 06:58:31 +0900 Subject: [PATCH 18/35] Docs: fix `MessageDefect` references in email.policy docs (#128468) --- Doc/library/email.policy.rst | 4 ++-- Doc/tools/.nitignore | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Doc/library/email.policy.rst b/Doc/library/email.policy.rst index 314767d0802a08..6b997ee784f6e4 100644 --- a/Doc/library/email.policy.rst +++ b/Doc/library/email.policy.rst @@ -267,7 +267,7 @@ added matters. To illustrate:: Handle a *defect* found on *obj*. When the email package calls this method, *defect* will always be a subclass of - :class:`~email.errors.Defect`. + :class:`~email.errors.MessageDefect`. The default implementation checks the :attr:`raise_on_defect` flag. If it is ``True``, *defect* is raised as an exception. If it is ``False`` @@ -277,7 +277,7 @@ added matters. To illustrate:: .. method:: register_defect(obj, defect) Register a *defect* on *obj*. In the email package, *defect* will always - be a subclass of :class:`~email.errors.Defect`. + be a subclass of :class:`~email.errors.MessageDefect`. The default implementation calls the ``append`` method of the ``defects`` attribute of *obj*. When the email package calls :attr:`handle_defect`, diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 7d50aec56a9bf7..6940c95ab2c9a1 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -23,7 +23,6 @@ Doc/library/email.charset.rst Doc/library/email.compat32-message.rst Doc/library/email.errors.rst Doc/library/email.parser.rst -Doc/library/email.policy.rst Doc/library/exceptions.rst Doc/library/functools.rst Doc/library/http.cookiejar.rst From c612744439e3b9dcd3d508d4569f3570ffeecbf4 Mon Sep 17 00:00:00 2001 From: RanKKI Date: Mon, 6 Jan 2025 12:32:16 +1100 Subject: [PATCH 19/35] gh-98188: Fix EmailMessage.get_payload to decode data when CTE value has extra text (#127547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Up to this point message handling has been very strict with regards to content encoding values: mixed case was accepted, but trailing blanks or other text would cause decoding failure, even if the first token was a valid encoding. By Postel's Rule we should go ahead and decode as long as we can recognize that first token. We have not thought of any security or backward compatibility concerns with this fix. This fix does introduce a new technique/pattern to the Message code: we look to see if the header has a 'cte' attribute, and if so we use that. This effectively promotes the header API exposed by HeaderRegistry to an API that any header parser "should" support. This seems like a reasonable thing to do. It is not, however, a requirement, as the string value of the header is still used if there is no cte attribute. The full fix (ignore any trailing blanks or blank-separated trailing text) applies only to the non-compat32 API. compat32 is only fixed to the extent that it now ignores trailing spaces. Note that the HeaderRegistry parsing still records a HeaderDefect if there is extra text. Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/email/message.py | 8 +++- Lib/test/test_email/test_email.py | 44 +++++++++++++++++++ Lib/test/test_email/test_headerregistry.py | 5 +++ Misc/ACKS | 1 + ...4-12-03-14-45-16.gh-issue-98188.GX9i2b.rst | 3 ++ 5 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-12-03-14-45-16.gh-issue-98188.GX9i2b.rst diff --git a/Lib/email/message.py b/Lib/email/message.py index a58afc5fe5f68e..87fcab68868d46 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -286,8 +286,12 @@ def get_payload(self, i=None, decode=False): if i is not None and not isinstance(self._payload, list): raise TypeError('Expected list, got %s' % type(self._payload)) payload = self._payload - # cte might be a Header, so for now stringify it. - cte = str(self.get('content-transfer-encoding', '')).lower() + cte = self.get('content-transfer-encoding', '') + if hasattr(cte, 'cte'): + cte = cte.cte + else: + # cte might be a Header, so for now stringify it. + cte = str(cte).strip().lower() # payload may be bytes here. if not decode: if isinstance(payload, str) and utils._has_surrogates(payload): diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index abe9ef2e94409f..2deb35721576b8 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -810,6 +810,16 @@ def test_unicode_body_defaults_to_utf8_encoding(self): w4kgdGVzdGFiYwo= """)) + def test_string_payload_with_base64_cte(self): + msg = email.message_from_string(textwrap.dedent("""\ + Content-Transfer-Encoding: base64 + + SGVsbG8uIFRlc3Rpbmc= + """), policy=email.policy.default) + self.assertEqual(msg.get_payload(decode=True), b"Hello. Testing") + self.assertDefectsEqual(msg['content-transfer-encoding'].defects, []) + + # Test the email.encoders module class TestEncoders(unittest.TestCase): @@ -2352,6 +2362,40 @@ def test_missing_header_body_separator(self): self.assertDefectsEqual(msg.defects, [errors.MissingHeaderBodySeparatorDefect]) + def test_string_payload_with_extra_space_after_cte(self): + # https://github.com/python/cpython/issues/98188 + cte = "base64 " + msg = email.message_from_string(textwrap.dedent(f"""\ + Content-Transfer-Encoding: {cte} + + SGVsbG8uIFRlc3Rpbmc= + """), policy=email.policy.default) + self.assertEqual(msg.get_payload(decode=True), b"Hello. Testing") + self.assertDefectsEqual(msg['content-transfer-encoding'].defects, []) + + def test_string_payload_with_extra_text_after_cte(self): + msg = email.message_from_string(textwrap.dedent("""\ + Content-Transfer-Encoding: base64 some text + + SGVsbG8uIFRlc3Rpbmc= + """), policy=email.policy.default) + self.assertEqual(msg.get_payload(decode=True), b"Hello. Testing") + cte = msg['content-transfer-encoding'] + self.assertDefectsEqual(cte.defects, [email.errors.InvalidHeaderDefect]) + + def test_string_payload_with_extra_space_after_cte_compat32(self): + cte = "base64 " + msg = email.message_from_string(textwrap.dedent(f"""\ + Content-Transfer-Encoding: {cte} + + SGVsbG8uIFRlc3Rpbmc= + """), policy=email.policy.compat32) + pasted_cte = msg['content-transfer-encoding'] + self.assertEqual(pasted_cte, cte) + self.assertEqual(msg.get_payload(decode=True), b"Hello. Testing") + self.assertDefectsEqual(msg.defects, []) + + # Test RFC 2047 header encoding and decoding class TestRFC2047(TestEmailBase): diff --git a/Lib/test/test_email/test_headerregistry.py b/Lib/test/test_email/test_headerregistry.py index 4c0523f410332f..ff7a6da644d572 100644 --- a/Lib/test/test_email/test_headerregistry.py +++ b/Lib/test/test_email/test_headerregistry.py @@ -837,6 +837,11 @@ def cte_as_value(self, '7bit', [errors.InvalidHeaderDefect]), + 'extra_space_after_cte': ( + 'base64 ', + 'base64', + []), + } diff --git a/Misc/ACKS b/Misc/ACKS index c6e53317b37d78..d7585c16c8169c 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1129,6 +1129,7 @@ Gregor Lingl Everett Lipman Mirko Liss Alexander Liu +Hui Liu Yuan Liu Nick Lockwood Stephanie Lockwood diff --git a/Misc/NEWS.d/next/Library/2024-12-03-14-45-16.gh-issue-98188.GX9i2b.rst b/Misc/NEWS.d/next/Library/2024-12-03-14-45-16.gh-issue-98188.GX9i2b.rst new file mode 100644 index 00000000000000..30ab8cfc3f0bc6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-12-03-14-45-16.gh-issue-98188.GX9i2b.rst @@ -0,0 +1,3 @@ +Fix an issue in :meth:`email.message.Message.get_payload` where data +cannot be decoded if the Content Transfer Encoding mechanism contains +trailing whitespaces or additional junk text. Patch by Hui Liu. From b0a91293f002b2df9eeb91a9f491e7b72303127c Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 11:36:20 +0530 Subject: [PATCH 20/35] Revert __repr__ change --- Lib/xml/dom/minidom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 3bd78e997e6c48..f02247d1ee1da2 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -900,7 +900,7 @@ def getElementsByTagNameNS(self, namespaceURI, localName): self, namespaceURI, localName, NodeList()) def __repr__(self): - return f"" + return "" % (self.tagName, id(self)) def writexml(self, writer, indent="", addindent="", newl=""): """Write an XML element to a file-like object From 8b7ff8ec5810927208e5890b2f61ccec621b7ed1 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 12:42:06 +0530 Subject: [PATCH 21/35] Update the docstring for _clone_node(...) --- Lib/xml/dom/minidom.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index f02247d1ee1da2..c45866a0275015 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -1922,10 +1922,16 @@ def renameNode(self, n, namespaceURI, name): def _clone_node(node, deep, newOwnerDocument): - """ - Returns a copy of node. - If deep is true, the copy also includes the node’s descendants. - Called by Node.cloneNode and Document.importNode + """Create and return a clone of a DOM node. + + Args: + node: The DOM node to clone + deep (bool): If True, recursively clone the node's descendants. + If False, only clone the node itself. + newOwnerDocument: The document that will own the cloned node + + Returns: + The cloned node """ if node.ownerDocument.isSameNode(newOwnerDocument): operation = xml.dom.UserDataHandler.NODE_CLONED From afa51efa54d2898e938a22eb11bf340bef0203cd Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 12:45:40 +0530 Subject: [PATCH 22/35] Update docstring for cloneNode(...) --- Lib/xml/dom/minidom.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index c45866a0275015..419d37a3f520ef 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -221,7 +221,12 @@ def normalize(self): self.childNodes[:] = L def cloneNode(self, deep): - """The Node.cloneNode() method returns a duplicate of the node.""" + """Create and return a duplicate of this node. + + Args: + deep (bool): If True, recursively clone this node's descendants. + If False, clone only this node. + """ return _clone_node(self, deep, self.ownerDocument or self) def isSupported(self, feature, version): From 14bb77e4b594098553c5b79ac3e2d25422ceebed Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 12:48:09 +0530 Subject: [PATCH 23/35] Update docstrings for cloneNode(...) --- Lib/xml/dom/minidom.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 419d37a3f520ef..44ab77627169c2 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -1364,7 +1364,12 @@ def _get_internalSubset(self): return self.internalSubset def cloneNode(self, deep): - """The Node.cloneNode() method returns a duplicate of the node.""" + """Create and return a duplicate of this node. + + Args: + deep (bool): If True, recursively clone this node's descendants. + If False, clone only this node. + """ if self.ownerDocument is None: # it's ok clone = DocumentType(None) From a4840f33d3d3c3292663a53e3a205feb1b8e3210 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 14:50:55 +0530 Subject: [PATCH 24/35] Convert doc string to imperative mode --- Lib/xml/dom/minidom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 44ab77627169c2..1592ef792b3b11 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -188,9 +188,9 @@ def removeChild(self, oldChild): return oldChild def normalize(self): - """Transform a node into its normalized form. + """Transform this node into its normalized form. - Removes empty exclusive Text nodes and concatenates the data of + Remove empty exclusive Text nodes and concatenate data of remaining contiguous exclusive Text nodes into the first of their nodes. """ From 38be045d4d2369393410b580db3d69afd405639e Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 15:06:00 +0530 Subject: [PATCH 25/35] Update docstring as recommended by argument clinic --- Lib/xml/dom/minidom.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 1592ef792b3b11..aa3aaa4083eeb2 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -223,9 +223,8 @@ def normalize(self): def cloneNode(self, deep): """Create and return a duplicate of this node. - Args: - deep (bool): If True, recursively clone this node's descendants. - If False, clone only this node. + deep: If True, recursively clone this node's descendants. + If False, clone only this node. """ return _clone_node(self, deep, self.ownerDocument or self) From cca0fda656f99389a1644b048f1bcde1b4e2b3d1 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 15:09:19 +0530 Subject: [PATCH 26/35] Update docstring as recommended by argument clinic --- Lib/xml/dom/minidom.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index aa3aaa4083eeb2..ad547b3583865c 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -1365,9 +1365,8 @@ def _get_internalSubset(self): def cloneNode(self, deep): """Create and return a duplicate of this node. - Args: - deep (bool): If True, recursively clone this node's descendants. - If False, clone only this node. + deep: If True, recursively clone this node's descendants. + If False, clone only this node. """ if self.ownerDocument is None: # it's ok From 8df67955262cfc6ad1556217cd2772f24c1bf40a Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 15:10:44 +0530 Subject: [PATCH 27/35] Undo removing comment --- Lib/xml/dom/minidom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index ad547b3583865c..9d358894dbe3de 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -93,6 +93,7 @@ def insertBefore(self, newChild, refChild): if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: for c in tuple(newChild.childNodes): self.insertBefore(c, refChild) + ### The DOM does not clearly specify what to return in this case return newChild if newChild.nodeType not in self._child_node_types: raise xml.dom.HierarchyRequestErr( From 727af86547291334a16a44ccd6b7c8c9f31f20e0 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 15:13:00 +0530 Subject: [PATCH 28/35] Update docstrings as it was done in argument clinic --- Lib/xml/dom/minidom.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 9d358894dbe3de..a032375d0b3a23 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -1933,14 +1933,10 @@ def renameNode(self, n, namespaceURI, name): def _clone_node(node, deep, newOwnerDocument): """Create and return a clone of a DOM node. - Args: - node: The DOM node to clone - deep (bool): If True, recursively clone the node's descendants. - If False, only clone the node itself. - newOwnerDocument: The document that will own the cloned node - - Returns: - The cloned node + node: The DOM node to clone + deep: If True, recursively clone the node's descendants. + If False, only clone the node itself. + newOwnerDocument: The document that will own the cloned node """ if node.ownerDocument.isSameNode(newOwnerDocument): operation = xml.dom.UserDataHandler.NODE_CLONED From 2da171f4c519808ffd03a7cfab5c2a0814cfae9d Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 15:14:19 +0530 Subject: [PATCH 29/35] Add space back --- Lib/xml/dom/minidom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index a032375d0b3a23..561d3d8fc3e8d6 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -185,6 +185,7 @@ def removeChild(self, oldChild): oldChild.nextSibling = oldChild.previousSibling = None if oldChild.nodeType in _nodeTypes_with_children: _clear_id_cache(self) + oldChild.parentNode = None return oldChild From ecb4a54d051d24f6bb8344d92b07a4c7170c6fa3 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 6 Jan 2025 18:08:01 +0530 Subject: [PATCH 30/35] Update doc strings --- Lib/xml/dom/minidom.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 561d3d8fc3e8d6..e0b3e4d64051d8 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -225,8 +225,9 @@ def normalize(self): def cloneNode(self, deep): """Create and return a duplicate of this node. - deep: If True, recursively clone this node's descendants. - If False, clone only this node. + deep + If True, recursively clone this node's descendants. + If False, clone only this node. """ return _clone_node(self, deep, self.ownerDocument or self) @@ -1367,7 +1368,8 @@ def _get_internalSubset(self): def cloneNode(self, deep): """Create and return a duplicate of this node. - deep: If True, recursively clone this node's descendants. + deep + If True, recursively clone this node's descendants. If False, clone only this node. """ if self.ownerDocument is None: @@ -1934,10 +1936,13 @@ def renameNode(self, n, namespaceURI, name): def _clone_node(node, deep, newOwnerDocument): """Create and return a clone of a DOM node. - node: The DOM node to clone - deep: If True, recursively clone the node's descendants. + node + The DOM node to clone + deep + If True, recursively clone the node's descendants. If False, only clone the node itself. - newOwnerDocument: The document that will own the cloned node + newOwnerDocument + The document that will own the cloned node """ if node.ownerDocument.isSameNode(newOwnerDocument): operation = xml.dom.UserDataHandler.NODE_CLONED From 8b6373060d7bdb067fd96bb917a9d23e7cc9bd45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Srinivas=20Reddy=20Thatiparthy=20=28=E0=B0=A4=E0=B0=BE?= =?UTF-8?q?=E0=B0=9F=E0=B0=BF=E0=B0=AA=E0=B0=B0=E0=B1=8D=E0=B0=A4=E0=B0=BF?= =?UTF-8?q?=20=E0=B0=B6=E0=B1=8D=E0=B0=B0=E0=B1=80=E0=B0=A8=E0=B0=BF?= =?UTF-8?q?=E0=B0=B5=E0=B0=BE=E0=B0=B8=E0=B1=8D=20=20=E0=B0=B0=E0=B1=86?= =?UTF-8?q?=E0=B0=A1=E0=B1=8D=E0=B0=A1=E0=B0=BF=29?= Date: Mon, 6 Jan 2025 18:13:31 +0530 Subject: [PATCH 31/35] Update Lib/xml/dom/minidom.py Co-authored-by: Erlend E. Aasland --- Lib/xml/dom/minidom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index e0b3e4d64051d8..7ccd367f01680b 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -1937,7 +1937,7 @@ def _clone_node(node, deep, newOwnerDocument): """Create and return a clone of a DOM node. node - The DOM node to clone + The DOM node to clone. deep If True, recursively clone the node's descendants. If False, only clone the node itself. From 49a12e6b3c09eff510e756a9562c9d3e6d0aeda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Srinivas=20Reddy=20Thatiparthy=20=28=E0=B0=A4=E0=B0=BE?= =?UTF-8?q?=E0=B0=9F=E0=B0=BF=E0=B0=AA=E0=B0=B0=E0=B1=8D=E0=B0=A4=E0=B0=BF?= =?UTF-8?q?=20=E0=B0=B6=E0=B1=8D=E0=B0=B0=E0=B1=80=E0=B0=A8=E0=B0=BF?= =?UTF-8?q?=E0=B0=B5=E0=B0=BE=E0=B0=B8=E0=B1=8D=20=20=E0=B0=B0=E0=B1=86?= =?UTF-8?q?=E0=B0=A1=E0=B1=8D=E0=B0=A1=E0=B0=BF=29?= Date: Mon, 6 Jan 2025 18:13:38 +0530 Subject: [PATCH 32/35] Update Lib/xml/dom/minidom.py Co-authored-by: Erlend E. Aasland --- Lib/xml/dom/minidom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 7ccd367f01680b..a32d4242a49d23 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -1942,7 +1942,7 @@ def _clone_node(node, deep, newOwnerDocument): If True, recursively clone the node's descendants. If False, only clone the node itself. newOwnerDocument - The document that will own the cloned node + The document that will own the cloned node. """ if node.ownerDocument.isSameNode(newOwnerDocument): operation = xml.dom.UserDataHandler.NODE_CLONED From b97d812e6dde095b004d72465ed3f8837e46b96c Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 16 Jan 2025 18:30:52 +0530 Subject: [PATCH 33/35] Address review comments --- Lib/xml/dom/minidom.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index a32d4242a49d23..54e89cf06db769 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -32,7 +32,9 @@ class Node(xml.dom.Node): - """Define properties accessible on a DOM node.""" + """Base class representing a node in the DOM tree. + Provides core properties and methods that all DOM nodes must implement. + """ namespaceURI = None # this is non-null only for elements and attributes parentNode = None ownerDocument = None @@ -84,11 +86,11 @@ def _get_lastChild(self): def insertBefore(self, newChild, refChild): """Insert a new DOM Node before an existing Node. - https://dom.spec.whatwg.org/#dom-node-insertbefore - The insertBefore(node, child) method, when invoked, - must return the result of pre-inserting node into - this before child. - See also https://dom.spec.whatwg.org/#concept-node-pre-insert + newChild + The new node to insert + refChild + The existing node that will be the next sibling of newChild. + If None, newChild is appended to the end. """ if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: for c in tuple(newChild.childNodes): From 19d245ca44864f2e9cd1d3a7b2ba5e1191bd7521 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 16 Jan 2025 18:57:35 +0530 Subject: [PATCH 34/35] Update docstrings --- Lib/xml/dom/minidom.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 54e89cf06db769..f7e3c9f326480f 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -1370,9 +1370,9 @@ def _get_internalSubset(self): def cloneNode(self, deep): """Create and return a duplicate of this node. - deep - If True, recursively clone this node's descendants. - If False, clone only this node. + deep + If True, recursively clone this node's descendants. + If False, clone only this node. """ if self.ownerDocument is None: # it's ok @@ -1938,13 +1938,13 @@ def renameNode(self, n, namespaceURI, name): def _clone_node(node, deep, newOwnerDocument): """Create and return a clone of a DOM node. - node - The DOM node to clone. - deep - If True, recursively clone the node's descendants. - If False, only clone the node itself. - newOwnerDocument - The document that will own the cloned node. + node + The DOM node to clone. + deep + If True, recursively clone the node's descendants. + If False, only clone the node itself. + newOwnerDocument + The document that will own the cloned node. """ if node.ownerDocument.isSameNode(newOwnerDocument): operation = xml.dom.UserDataHandler.NODE_CLONED From 7b1666d73d2272abd9987d0be4faf0e36dbe5e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Srinivas=20Reddy=20Thatiparthy=20=28=E0=B0=A4=E0=B0=BE?= =?UTF-8?q?=E0=B0=9F=E0=B0=BF=E0=B0=AA=E0=B0=B0=E0=B1=8D=E0=B0=A4=E0=B0=BF?= =?UTF-8?q?=20=E0=B0=B6=E0=B1=8D=E0=B0=B0=E0=B1=80=E0=B0=A8=E0=B0=BF?= =?UTF-8?q?=E0=B0=B5=E0=B0=BE=E0=B0=B8=E0=B1=8D=20=20=E0=B0=B0=E0=B1=86?= =?UTF-8?q?=E0=B0=A1=E0=B1=8D=E0=B0=A1=E0=B0=BF=29?= Date: Tue, 13 May 2025 09:45:46 +0530 Subject: [PATCH 35/35] Update Lib/xml/dom/minidom.py Co-authored-by: Erlend E. Aasland --- Lib/xml/dom/minidom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index f7e3c9f326480f..478779a51cb5f5 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -33,6 +33,7 @@ class Node(xml.dom.Node): """Base class representing a node in the DOM tree. + Provides core properties and methods that all DOM nodes must implement. """ namespaceURI = None # this is non-null only for elements and attributes