Skip to content

Commit afd8113

Browse files
authored
gh-144270: Make SubElement parent and tag positional-only (GH-144845)
The C accelerator implementations use PyArg_ParseTuple, which inherently enforces positional-only parameters. The Python fallback allowed these as keyword arguments, creating a behavioral mismatch. Make the tag parameter of Element.__init__ and the parent and tag parameters of SubElement positional-only to align with the C accelerator.
1 parent 8e9d21c commit afd8113

File tree

4 files changed

+48
-4
lines changed

4 files changed

+48
-4
lines changed

Doc/library/xml.etree.elementtree.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ Functions
691691
.. versionadded:: 3.2
692692

693693

694-
.. function:: SubElement(parent, tag, attrib={}, **extra)
694+
.. function:: SubElement(parent, tag, /, attrib={}, **extra)
695695

696696
Subelement factory. This function creates an element instance, and appends
697697
it to an existing element.
@@ -705,6 +705,9 @@ Functions
705705
.. versionchanged:: 3.15
706706
*attrib* can now be a :class:`frozendict`.
707707

708+
.. versionchanged:: next
709+
*parent* and *tag* are now positional-only parameters.
710+
708711

709712
.. function:: tostring(element, encoding="us-ascii", method="xml", *, \
710713
xml_declaration=None, default_namespace=None, \
@@ -880,7 +883,7 @@ Element Objects
880883
:noindex:
881884
:no-index:
882885

883-
.. class:: Element(tag, attrib={}, **extra)
886+
.. class:: Element(tag, /, attrib={}, **extra)
884887

885888
Element class. This class defines the Element interface, and provides a
886889
reference implementation of this interface.
@@ -893,6 +896,9 @@ Element Objects
893896
.. versionchanged:: 3.15
894897
*attrib* can now be a :class:`frozendict`.
895898

899+
.. versionchanged:: next
900+
*tag* is now a positional-only parameter.
901+
896902

897903
.. attribute:: tag
898904

Lib/test/test_xml_etree.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,19 @@ def test_simpleops(self):
381381
self.serialize_check(element,
382382
'<tag key="value"><subtag /><subtag /></tag>')
383383

384+
def test_positional_only_parameter(self):
385+
# Test Element positional-only parameters (gh-144846).
386+
387+
# 'tag' is positional-only
388+
with self.assertRaises(TypeError):
389+
ET.Element(tag='fail')
390+
391+
# 'tag' and 'attrib' as kwarg/attribute names
392+
e = ET.Element('e', attrib={'attrib': 'foo'}, tag='bar')
393+
self.assertEqual(e.tag, 'e')
394+
self.assertEqual(e.get('attrib'), 'foo')
395+
self.assertEqual(e.get('tag'), 'bar')
396+
384397
def test_cdata(self):
385398
# Test CDATA handling (etc).
386399

@@ -484,6 +497,28 @@ def test_attrib(self):
484497
self.assertEqual(ET.tostring(elem),
485498
b'<test a="&#13;" b="&#13;&#10;" c="&#09;&#10;&#13; " d="&#10;&#10;&#13;&#13;&#09;&#09; " />')
486499

500+
def test_subelement_positional_only_parameter(self):
501+
# Test SubElement positional-only parameters (gh-144270).
502+
parent = ET.Element('parent')
503+
504+
# 'parent' and 'tag' are positional-only
505+
with self.assertRaises(TypeError):
506+
ET.SubElement(parent=parent, tag='fail')
507+
with self.assertRaises(TypeError):
508+
ET.SubElement(parent, tag='fail')
509+
510+
# 'attrib' can be passed as keyword
511+
sub1 = ET.SubElement(parent, 'sub1', attrib={'key': 'value'})
512+
self.assertEqual(sub1.get('key'), 'value')
513+
514+
# 'tag' and 'parent' as kwargs become XML attributes, not func params
515+
sub2 = ET.SubElement(parent, 'sub2', attrib={'attrib': 'foo'},
516+
tag='bar', parent='baz')
517+
self.assertEqual(sub2.tag, 'sub2')
518+
self.assertEqual(sub2.get('attrib'), 'foo')
519+
self.assertEqual(sub2.get('tag'), 'bar')
520+
self.assertEqual(sub2.get('parent'), 'baz')
521+
487522
def test_makeelement(self):
488523
# Test makeelement handling.
489524

Lib/xml/etree/ElementTree.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class Element:
164164
165165
"""
166166

167-
def __init__(self, tag, attrib={}, **extra):
167+
def __init__(self, tag, /, attrib={}, **extra):
168168
if not isinstance(attrib, (dict, frozendict)):
169169
raise TypeError("attrib must be dict or frozendict, not %s" % (
170170
attrib.__class__.__name__,))
@@ -416,7 +416,7 @@ def itertext(self):
416416
yield t
417417

418418

419-
def SubElement(parent, tag, attrib={}, **extra):
419+
def SubElement(parent, tag, /, attrib={}, **extra):
420420
"""Subelement factory which creates an element instance, and appends it
421421
to an existing parent.
422422
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Made the *tag* parameter of :class:`xml.etree.ElementTree.Element` and the
2+
*parent* and *tag* parameters of :func:`xml.etree.ElementTree.SubElement`
3+
positional-only, matching the behavior of the C accelerator.

0 commit comments

Comments
 (0)