Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(ast)
STRUCT_FOR_ID(athrow)
STRUCT_FOR_ID(attr)
STRUCT_FOR_ID(attrib)
STRUCT_FOR_ID(attribute)
STRUCT_FOR_ID(autocommit)
STRUCT_FOR_ID(backtick)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1847,6 +1847,15 @@ def test_c_name(self):
""")
self.assertEqual("os_stat_fn", function.c_basename)

def test_var_keyword_c_name(self):
function = self.parse_function("""
module foo
foo.bar
**kwargs as kwds: dict
""")
p = function.parameters['kwargs_as_kwds']
self.assertEqual("kwds", p.converter.name)

def test_base_invalid_syntax(self):
block = """
module os
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_xml_etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -3022,6 +3022,10 @@ def f():

class BasicElementTest(ElementTestCase, unittest.TestCase):

def test_extra_keys_must_be_strings(self):
with self.assertRaisesRegex(TypeError, "keywords must be strings"):
ET.Element("tag", **{ET.QName("key"): "value"})

def test___init__(self):
tag = "foo"
attrib = { "zix": "wyp" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Calling :class:`xml.etree.ElementTree.Element` with non-string keys in
*extra* now raises :exc:`TypeError`, as the Python implementation already
did. Pass such keys in *attrib* instead.
99 changes: 51 additions & 48 deletions Modules/_elementtree.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "pycore_ceval.h" // _Py_EnterRecursiveCall()
#include "pycore_dict.h" // _PyDict_CopyAsDict()
#include "pycore_pyhash.h" // _Py_HashSecret
#include "pycore_runtime.h" // _Py_ID()
#include "pycore_tuple.h" // _PyTuple_FromPair
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()

Expand Down Expand Up @@ -377,7 +378,7 @@ static PyObject*
get_attrib_from_keywords(PyObject *kwds)
{
PyObject *attrib;
if (PyDict_PopString(kwds, "attrib", &attrib) < 0) {
if (PyDict_Pop(kwds, &_Py_ID(attrib), &attrib) < 0) {
return NULL;
}

Expand All @@ -393,16 +394,16 @@ get_attrib_from_keywords(PyObject *kwds)
return NULL;
}
Py_SETREF(attrib, _PyDict_CopyAsDict(attrib));
if (attrib == NULL) {
return NULL;
}
if (PyDict_Update(attrib, kwds) < 0) {
Py_DECREF(attrib);
return NULL;
}
return attrib;
}
else {
attrib = PyDict_New();
}

if (attrib != NULL && PyDict_Update(attrib, kwds) < 0) {
Py_DECREF(attrib);
return NULL;
}
return attrib;
return PyDict_Copy(kwds);
}

/*[clinic input]
Expand All @@ -413,15 +414,37 @@ class _elementtree.XMLParser "XMLParserObject *" "clinic_state()->XMLParser_Type
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6c83ea832d2b0ef1]*/

/*[clinic input]
@vectorcall
@text_signature "(tag, /, attrib={}, **extra)"
_elementtree.Element.__init__

tag: object
attrib: object(c_default="NULL") = {}
/
**extra: dict

An XML element.

This class is the reference implementation of the Element interface.

An element's length is its number of subelements. That means if you
want to check if an element is truly empty, you should check BOTH
its length AND its text attribute.

*tag* is the element name. *attrib* is an optional dictionary
containing element attributes. *extra* are additional element
attributes given as keyword arguments.

Example form:
<tag attrib>text<child/>...</tag>tail
[clinic start generated code]*/

static int
element_init(PyObject *self, PyObject *args, PyObject *kwds)
_elementtree_Element___init___impl(ElementObject *self, PyObject *tag,
PyObject *attrib, PyObject *extra)
/*[clinic end generated code: output=d10be095275d5d1a input=a66a3eacbc30aa64]*/
{
PyObject *tag;
PyObject *attrib = NULL;
ElementObject *self_elem;

if (!PyArg_ParseTuple(args, "O|O:Element", &tag, &attrib))
return -1;
if (attrib != NULL && !PyAnyDict_Check(attrib)) {
PyErr_Format(PyExc_TypeError,
"Element() argument 2 must be dict or frozendict, not %T",
Expand All @@ -434,23 +457,21 @@ element_init(PyObject *self, PyObject *args, PyObject *kwds)
attrib = _PyDict_CopyAsDict(attrib);
if (!attrib)
return -1;
if (kwds) {
if (PyDict_Update(attrib, kwds) < 0) {
if (extra) {
if (PyDict_Update(attrib, extra) < 0) {
Py_DECREF(attrib);
return -1;
}
}
} else if (kwds) {
} else if (extra) {
/* have keywords args */
attrib = get_attrib_from_keywords(kwds);
attrib = get_attrib_from_keywords(extra);
if (!attrib)
return -1;
}

self_elem = (ElementObject *)self;

if (attrib != NULL && !is_empty_dict(attrib)) {
if (create_extra(self_elem, attrib) < 0) {
if (create_extra(self, attrib) < 0) {
Py_DECREF(attrib);
return -1;
}
Expand All @@ -460,10 +481,10 @@ element_init(PyObject *self, PyObject *args, PyObject *kwds)
Py_XDECREF(attrib);

/* Replace the objects already pointed to by tag, text and tail. */
Py_XSETREF(self_elem->tag, Py_NewRef(tag));
Py_XSETREF(self->tag, Py_NewRef(tag));

_set_joined_ptr(&self_elem->text, Py_NewRef(Py_None));
_set_joined_ptr(&self_elem->tail, Py_NewRef(Py_None));
_set_joined_ptr(&self->text, Py_NewRef(Py_None));
_set_joined_ptr(&self->tail, Py_NewRef(Py_None));

return 0;
}
Expand Down Expand Up @@ -4451,27 +4472,8 @@ static PyGetSetDef element_getsetlist[] = {
{NULL},
};

PyDoc_STRVAR(element_doc,
"Element(tag, attrib={}, **extra)\n"
"--\n"
"\n"
"An XML element.\n"
"\n"
"This class is the reference implementation of the Element interface.\n"
"\n"
"An element's length is its number of subelements. That means if you\n"
"want to check if an element is truly empty, you should check BOTH\n"
"its length AND its text attribute.\n"
"\n"
"*tag* is the element name. *attrib* is an optional dictionary\n"
"containing element attributes. *extra* are additional element\n"
"attributes given as keyword arguments.\n"
"\n"
"Example form:\n"
" <tag attrib>text<child/>...</tag>tail");

static PyType_Slot element_slots[] = {
{Py_tp_doc, (void *)element_doc},
{Py_tp_doc, (void *)_elementtree_Element___init____doc__},
{Py_tp_dealloc, element_dealloc},
{Py_tp_repr, element_repr},
{Py_tp_getattro, PyObject_GenericGetAttr},
Expand All @@ -4480,7 +4482,8 @@ static PyType_Slot element_slots[] = {
{Py_tp_methods, element_methods},
{Py_tp_members, element_members},
{Py_tp_getset, element_getsetlist},
{Py_tp_init, element_init},
{Py_tp_init, _elementtree_Element___init__},
{Py_tp_vectorcall, _elementtree_Element_vectorcall},
{Py_tp_alloc, PyType_GenericAlloc},
{Py_tp_new, element_new},
{Py_sq_length, element_length},
Expand Down
104 changes: 102 additions & 2 deletions Modules/clinic/_elementtree.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 13 additions & 3 deletions Tools/clinic/libclinic/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,7 @@ class VarKeywordCConverter(CConverter):
def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
raise AssertionError('should never be called')

def parse_var_keyword(self) -> str:
def parse_var_keyword(self, *, stackargs: bool) -> str:
raise NotImplementedError


Expand All @@ -1448,6 +1448,16 @@ class var_keyword_dict_converter(VarKeywordCConverter):
def cleanup(self) -> str:
return f'Py_XDECREF({self.parser_name});\n'

def parse_var_keyword(self) -> str:
def parse_var_keyword(self, *, stackargs: bool) -> str:
# NULL when there are no keywords: no call allocates an empty dict.
return f"{self.parser_name} = Py_XNewRef(kwargs);\n"
param_name = self.parser_name
if stackargs:
return f"""
if (kwnames != NULL) {{{{
{param_name} = _PyStack_AsDict(args + nargs, kwnames);
if ({param_name} == NULL) {{{{
goto exit;
}}}}
}}}}
"""
return f"{param_name} = Py_XNewRef(kwargs);\n"
2 changes: 1 addition & 1 deletion Tools/clinic/libclinic/dsl_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,7 @@ def parse_parameter(self, line: str) -> None:

# handle "as" for parameters too
c_name = None
m = re.match(r'(?:\* *)?\w+( +as +(\w+))', line)
m = re.match(r'(?:\*{1,2} *)?\w+( +as +(\w+))', line)
if m:
c_name = m[2]
line = line[:m.start(1)] + line[m.end(1):]
Expand Down
Loading
Loading