Issue #14128: Exposing Element as an actual type from _elementtree, rather than a factory function.

This makes the C implementation more aligned with the Python implementation.
Also added some tests to ensure that Element is now a type and that it can
be subclassed.
This commit is contained in:
Eli Bendersky 2012-03-04 07:14:03 +02:00
parent c9590ad745
commit 092af1fc5c
4 changed files with 198 additions and 91 deletions

View File

@ -1901,16 +1901,51 @@ class CleanContext(object):
class TestAcceleratorNotImported(unittest.TestCase):
# Test that the C accelerator was not imported for pyET
def test_correct_import_pyET(self):
self.assertEqual(pyET.Element.__module__, 'xml.etree.ElementTree')
self.assertEqual(pyET.SubElement.__module__, 'xml.etree.ElementTree')
class TestElementClass(unittest.TestCase):
def test_Element_is_a_type(self):
self.assertIsInstance(ET.Element, type)
def test_Element_subclass_trivial(self):
class MyElement(ET.Element):
pass
mye = MyElement('foo')
self.assertIsInstance(mye, ET.Element)
self.assertIsInstance(mye, MyElement)
self.assertEqual(mye.tag, 'foo')
def test_Element_subclass_constructor(self):
class MyElement(ET.Element):
def __init__(self, tag, attrib={}, **extra):
super(MyElement, self).__init__(tag + '__', attrib, **extra)
mye = MyElement('foo', {'a': 1, 'b': 2}, c=3, d=4)
self.assertEqual(mye.tag, 'foo__')
self.assertEqual(sorted(mye.items()),
[('a', 1), ('b', 2), ('c', 3), ('d', 4)])
def test_Element_subclass_new_method(self):
class MyElement(ET.Element):
def newmethod(self):
return self.tag
mye = MyElement('joe')
self.assertEqual(mye.newmethod(), 'joe')
def test_main(module=pyET):
from test import test_xml_etree
# Run the tests specific to the Python implementation
support.run_unittest(TestAcceleratorNotImported)
# The same doctests are used for both the Python and the C implementations
test_xml_etree.ET = module
support.run_unittest(TestAcceleratorNotImported)
support.run_unittest(TestElementClass)
# XXX the C module should give the same warnings as the Python module
with CleanContext(quiet=(module is not pyET)):

View File

@ -46,14 +46,22 @@ class MiscTests(unittest.TestCase):
finally:
data = None
@unittest.skipUnless(cET, 'requires _elementtree')
class TestAliasWorking(unittest.TestCase):
# Test that the cET alias module is alive
def test_alias_working(self):
e = cET_alias.Element('foo')
self.assertEqual(e.tag, 'foo')
@unittest.skipUnless(cET, 'requires _elementtree')
class TestAcceleratorImported(unittest.TestCase):
# Test that the C accelerator was imported, as expected
def test_correct_import_cET(self):
self.assertEqual(cET.Element.__module__, '_elementtree')
self.assertEqual(cET.SubElement.__module__, '_elementtree')
def test_correct_import_cET_alias(self):
self.assertEqual(cET_alias.Element.__module__, '_elementtree')
self.assertEqual(cET_alias.SubElement.__module__, '_elementtree')
def test_main():
@ -61,13 +69,15 @@ def test_main():
# Run the tests specific to the C implementation
support.run_doctest(test_xml_etree_c, verbosity=True)
support.run_unittest(MiscTests, TestAcceleratorImported)
support.run_unittest(
MiscTests,
TestAliasWorking,
TestAcceleratorImported
)
# Run the same test suite as the Python module
test_xml_etree.test_main(module=cET)
# Exercise the deprecated alias
test_xml_etree.test_main(module=cET_alias)
if __name__ == '__main__':
test_main()

View File

@ -101,7 +101,6 @@ import sys
import re
import warnings
class _SimpleElementPath:
# emulate pre-1.2 find/findtext/findall behaviour
def find(self, element, tag, namespaces=None):

View File

@ -191,7 +191,7 @@ list_join(PyObject* list)
}
/* -------------------------------------------------------------------- */
/* the element type */
/* the Element type */
typedef struct {
@ -236,10 +236,10 @@ static PyTypeObject Element_Type;
#define Element_CheckExact(op) (Py_TYPE(op) == &Element_Type)
/* -------------------------------------------------------------------- */
/* element constructor and destructor */
/* Element constructors and destructor */
LOCAL(int)
element_new_extra(ElementObject* self, PyObject* attrib)
create_extra(ElementObject* self, PyObject* attrib)
{
self->extra = PyObject_Malloc(sizeof(ElementObjectExtra));
if (!self->extra)
@ -259,7 +259,7 @@ element_new_extra(ElementObject* self, PyObject* attrib)
}
LOCAL(void)
element_dealloc_extra(ElementObject* self)
dealloc_extra(ElementObject* self)
{
int i;
@ -274,8 +274,11 @@ element_dealloc_extra(ElementObject* self)
PyObject_Free(self->extra);
}
/* Convenience internal function to create new Element objects with the given
* tag and attributes.
*/
LOCAL(PyObject*)
element_new(PyObject* tag, PyObject* attrib)
create_new_element(PyObject* tag, PyObject* attrib)
{
ElementObject* self;
@ -290,16 +293,10 @@ element_new(PyObject* tag, PyObject* attrib)
self->extra = NULL;
if (attrib != Py_None) {
if (element_new_extra(self, attrib) < 0) {
if (create_extra(self, attrib) < 0) {
PyObject_Del(self);
return NULL;
}
self->extra->length = 0;
self->extra->allocated = STATIC_CHILDREN;
self->extra->children = self->extra->_children;
}
Py_INCREF(tag);
@ -316,6 +313,86 @@ element_new(PyObject* tag, PyObject* attrib)
return (PyObject*) self;
}
static PyObject *
element_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
ElementObject *e = (ElementObject *)type->tp_alloc(type, 0);
if (e != NULL) {
Py_INCREF(Py_None);
e->tag = Py_None;
Py_INCREF(Py_None);
e->text = Py_None;
Py_INCREF(Py_None);
e->tail = Py_None;
e->extra = NULL;
}
return (PyObject *)e;
}
static int
element_init(PyObject *self, PyObject *args, PyObject *kwds)
{
PyObject *tag;
PyObject *tmp;
PyObject *attrib = NULL;
ElementObject *self_elem;
if (!PyArg_ParseTuple(args, "O|O!:Element", &tag, &PyDict_Type, &attrib))
return -1;
if (attrib || kwds) {
attrib = (attrib) ? PyDict_Copy(attrib) : PyDict_New();
if (!attrib)
return -1;
if (kwds)
PyDict_Update(attrib, kwds);
} else {
Py_INCREF(Py_None);
attrib = Py_None;
}
self_elem = (ElementObject *)self;
/* Use None for empty dictionaries */
if (PyDict_CheckExact(attrib) && PyDict_Size(attrib) == 0) {
Py_INCREF(Py_None);
attrib = Py_None;
}
if (attrib != Py_None) {
if (create_extra(self_elem, attrib) < 0) {
PyObject_Del(self_elem);
return -1;
}
}
/* If create_extra needed attrib, it took a reference to it, so we can
* release ours anyway.
*/
Py_DECREF(attrib);
/* Replace the objects already pointed to by tag, text and tail. */
tmp = self_elem->tag;
self_elem->tag = tag;
Py_INCREF(tag);
Py_DECREF(tmp);
tmp = self_elem->text;
self_elem->text = Py_None;
Py_INCREF(Py_None);
Py_DECREF(JOIN_OBJ(tmp));
tmp = self_elem->tail;
self_elem->tail = Py_None;
Py_INCREF(Py_None);
Py_DECREF(JOIN_OBJ(tmp));
return 0;
}
LOCAL(int)
element_resize(ElementObject* self, int extra)
{
@ -326,7 +403,7 @@ element_resize(ElementObject* self, int extra)
elements. set an exception and return -1 if allocation failed */
if (!self->extra)
element_new_extra(self, NULL);
create_extra(self, NULL);
size = self->extra->length + extra;
@ -443,35 +520,6 @@ element_get_tail(ElementObject* self)
return res;
}
static PyObject*
element(PyObject* self, PyObject* args, PyObject* kw)
{
PyObject* elem;
PyObject* tag;
PyObject* attrib = NULL;
if (!PyArg_ParseTuple(args, "O|O!:Element", &tag,
&PyDict_Type, &attrib))
return NULL;
if (attrib || kw) {
attrib = (attrib) ? PyDict_Copy(attrib) : PyDict_New();
if (!attrib)
return NULL;
if (kw)
PyDict_Update(attrib, kw);
} else {
Py_INCREF(Py_None);
attrib = Py_None;
}
elem = element_new(tag, attrib);
Py_DECREF(attrib);
return elem;
}
static PyObject*
subelement(PyObject* self, PyObject* args, PyObject* kw)
{
@ -496,7 +544,7 @@ subelement(PyObject* self, PyObject* args, PyObject* kw)
attrib = Py_None;
}
elem = element_new(tag, attrib);
elem = create_new_element(tag, attrib);
Py_DECREF(attrib);
@ -512,7 +560,7 @@ static void
element_dealloc(ElementObject* self)
{
if (self->extra)
element_dealloc_extra(self);
dealloc_extra(self);
/* discard attributes */
Py_DECREF(self->tag);
@ -521,7 +569,7 @@ element_dealloc(ElementObject* self)
RELEASE(sizeof(ElementObject), "destroy element");
PyObject_Del(self);
Py_TYPE(self)->tp_free((PyObject *)self);
}
/* -------------------------------------------------------------------- */
@ -547,7 +595,7 @@ element_clear(ElementObject* self, PyObject* args)
return NULL;
if (self->extra) {
element_dealloc_extra(self);
dealloc_extra(self);
self->extra = NULL;
}
@ -571,7 +619,7 @@ element_copy(ElementObject* self, PyObject* args)
if (!PyArg_ParseTuple(args, ":__copy__"))
return NULL;
element = (ElementObject*) element_new(
element = (ElementObject*) create_new_element(
self->tag, (self->extra) ? self->extra->attrib : Py_None
);
if (!element)
@ -634,7 +682,7 @@ element_deepcopy(ElementObject* self, PyObject* args)
attrib = Py_None;
}
element = (ElementObject*) element_new(tag, attrib);
element = (ElementObject*) create_new_element(tag, attrib);
Py_DECREF(tag);
Py_DECREF(attrib);
@ -1029,7 +1077,7 @@ element_insert(ElementObject* self, PyObject* args)
return NULL;
if (!self->extra)
element_new_extra(self, NULL);
create_extra(self, NULL);
if (index < 0) {
index += self->extra->length;
@ -1100,7 +1148,7 @@ element_makeelement(PyObject* self, PyObject* args, PyObject* kw)
if (!attrib)
return NULL;
elem = element_new(tag, attrib);
elem = create_new_element(tag, attrib);
Py_DECREF(attrib);
@ -1154,7 +1202,10 @@ element_remove(ElementObject* self, PyObject* args)
static PyObject*
element_repr(ElementObject* self)
{
if (self->tag)
return PyUnicode_FromFormat("<Element %R at %p>", self->tag, self);
else
return PyUnicode_FromFormat("<Element at %p>", self);
}
static PyObject*
@ -1168,7 +1219,7 @@ element_set(ElementObject* self, PyObject* args)
return NULL;
if (!self->extra)
element_new_extra(self, NULL);
create_extra(self, NULL);
attrib = element_get_attrib(self);
if (!attrib)
@ -1284,7 +1335,7 @@ element_ass_subscr(PyObject* self_, PyObject* item, PyObject* value)
PyObject* seq = NULL;
if (!self->extra)
element_new_extra(self, NULL);
create_extra(self, NULL);
if (PySlice_GetIndicesEx(item,
self->extra->length,
@ -1448,7 +1499,7 @@ element_getattro(ElementObject* self, PyObject* nameobj)
} else if (strcmp(name, "attrib") == 0) {
PyErr_Clear();
if (!self->extra)
element_new_extra(self, NULL);
create_extra(self, NULL);
res = element_get_attrib(self);
}
@ -1484,7 +1535,7 @@ element_setattr(ElementObject* self, const char* name, PyObject* value)
Py_INCREF(self->tail);
} else if (strcmp(name, "attrib") == 0) {
if (!self->extra)
element_new_extra(self, NULL);
create_extra(self, NULL);
Py_DECREF(self->extra->attrib);
self->extra->attrib = value;
Py_INCREF(self->extra->attrib);
@ -1531,7 +1582,7 @@ static PyTypeObject Element_Type = {
(getattrofunc)element_getattro, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
@ -1541,6 +1592,16 @@ static PyTypeObject Element_Type = {
0, /* tp_iternext */
element_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)element_init, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */
element_new, /* tp_new */
0, /* tp_free */
};
/* ==================================================================== */
@ -1666,7 +1727,7 @@ treebuilder_handle_start(TreeBuilderObject* self, PyObject* tag,
self->data = NULL;
}
node = element_new(tag, attrib);
node = create_new_element(tag, attrib);
if (!node)
return NULL;
@ -2801,7 +2862,6 @@ static PyTypeObject XMLParser_Type = {
/* python module interface */
static PyMethodDef _functions[] = {
{"Element", (PyCFunction) element, METH_VARARGS|METH_KEYWORDS},
{"SubElement", (PyCFunction) subelement, METH_VARARGS|METH_KEYWORDS},
{"TreeBuilder", (PyCFunction) treebuilder, METH_VARARGS},
#if defined(USE_EXPAT)
@ -2911,5 +2971,8 @@ PyInit__elementtree(void)
Py_INCREF(elementtree_parseerror_obj);
PyModule_AddObject(m, "ParseError", elementtree_parseerror_obj);
Py_INCREF((PyObject *)&Element_Type);
PyModule_AddObject(m, "Element", (PyObject *)&Element_Type);
return m;
}