Skip to content

[2.7] bpo-30365: Backport warnings and fix bugs in ElementTree. #1581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 17, 2017
Merged
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
107 changes: 58 additions & 49 deletions Lib/test/test_xml_etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import cgi
import copy
import functools
import io
import pickle
import StringIO
Expand Down Expand Up @@ -86,6 +87,16 @@
"""


def checkwarnings(*filters):
def decorator(test):
def newtest(*args, **kwargs):
with support.check_warnings(*filters):
test(*args, **kwargs)
functools.update_wrapper(newtest, test)
return newtest
return decorator


class ModuleTest(unittest.TestCase):
# TODO: this should be removed once we get rid of the global module vars

Expand Down Expand Up @@ -656,6 +667,10 @@ def comment(self, data):
])


# Element.getchildren() and ElementTree.getiterator() are deprecated.
@checkwarnings(("This method will be removed in future versions. "
"Use .+ instead.",
(DeprecationWarning, PendingDeprecationWarning)))
def test_getchildren(self):
# Test Element.getchildren()

Expand Down Expand Up @@ -1356,9 +1371,15 @@ def test_bug_200708_close(self):

# Test custom builder.
class EchoTarget:
def start(self, tag, attrib):
pass
def end(self, tag):
pass
def data(self, text):
pass
def close(self):
return ET.Element("element") # simulate root
parser = ET.XMLParser(EchoTarget())
parser = ET.XMLParser(target=EchoTarget())
parser.feed("<element>some text</element>")
self.assertEqual(parser.close().tag, 'element')

Expand Down Expand Up @@ -1908,7 +1929,12 @@ def test_find_through_ElementTree(self):
e = ET.XML(SAMPLE_XML)
self.assertEqual(ET.ElementTree(e).find('tag').tag, 'tag')
self.assertEqual(ET.ElementTree(e).find('./tag').tag, 'tag')
self.assertEqual(ET.ElementTree(e).find('/tag').tag, 'tag')
# this produces a warning
msg = ("This search is broken in 1.3 and earlier, and will be fixed "
"in a future version. If you rely on the current behaviour, "
"change it to '.+'")
with support.check_warnings((msg, FutureWarning)):
self.assertEqual(ET.ElementTree(e).find('/tag').tag, 'tag')
e[2] = ET.XML(SAMPLE_SECTION)
self.assertEqual(ET.ElementTree(e).find('section/tag').tag, 'tag')
self.assertIsNone(ET.ElementTree(e).find('tog'))
Expand All @@ -1919,14 +1945,15 @@ def test_find_through_ElementTree(self):
self.assertEqual(ET.ElementTree(e).findtext('tog/foo', 'default'),
'default')
self.assertEqual(ET.ElementTree(e).findtext('./tag'), 'text')
self.assertEqual(ET.ElementTree(e).findtext('/tag'), 'text')
with support.check_warnings((msg, FutureWarning)):
self.assertEqual(ET.ElementTree(e).findtext('/tag'), 'text')
self.assertEqual(ET.ElementTree(e).findtext('section/tag'), 'subtext')

self.assertEqual(summarize_list(ET.ElementTree(e).findall('./tag')),
['tag'] * 2)
# this produces a warning
self.assertEqual(summarize_list(ET.ElementTree(e).findall('/tag')),
['tag'] * 2)
with support.check_warnings((msg, FutureWarning)):
it = ET.ElementTree(e).findall('/tag')
self.assertEqual(summarize_list(it), ['tag'] * 2)


class ElementIterTest(unittest.TestCase):
Expand Down Expand Up @@ -2014,6 +2041,15 @@ def test_iter_by_tag(self):
self.assertEqual(self._ilist(doc, '*'), all_tags)

def test_getiterator(self):
# Element.getiterator() is deprecated.
if sys.py3kwarning or ET is pyET:
with support.check_warnings(("This method will be removed in future versions. "
"Use .+ instead.", PendingDeprecationWarning)):
self._test_getiterator()
else:
self._test_getiterator()

def _test_getiterator(self):
doc = ET.XML('''
<document>
<house>
Expand Down Expand Up @@ -2178,13 +2214,13 @@ def _check_sample_element(self, e):
def test_constructor_args(self):
# Positional args. The first (html) is not supported, but should be
# nevertheless correctly accepted.
parser = ET.XMLParser(None, ET.TreeBuilder(), 'utf-8')
with support.check_py3k_warnings((r'.*\bhtml\b', DeprecationWarning)):
parser = ET.XMLParser(None, ET.TreeBuilder(), 'utf-8')
parser.feed(self.sample1)
self._check_sample_element(parser.close())

# Now as keyword args.
parser2 = ET.XMLParser(encoding='utf-8',
html=[{}],
target=ET.TreeBuilder())
parser2.feed(self.sample1)
self._check_sample_element(parser2.close())
Expand Down Expand Up @@ -2593,44 +2629,6 @@ def test_correct_import_pyET(self):
# --------------------------------------------------------------------


class CleanContext(object):
"""Provide default namespace mapping and path cache."""
checkwarnings = None

def __init__(self, quiet=False):
deprecations = (
("This method of XMLParser is deprecated. Define doctype\(\) "
"method on the TreeBuilder target.", DeprecationWarning),
# Search behaviour is broken if search path starts with "/".
("This search is broken in 1.3 and earlier, and will be fixed "
"in a future version. If you rely on the current behaviour, "
"change it to '.+'", FutureWarning),
# Element.getchildren() and Element.getiterator() are deprecated.
("This method will be removed in future versions. "
"Use .+ instead.", DeprecationWarning),
("This method will be removed in future versions. "
"Use .+ instead.", PendingDeprecationWarning))
self.checkwarnings = support.check_warnings(*deprecations, quiet=quiet)

def __enter__(self):
from xml.etree import ElementPath
self._nsmap = pyET._namespace_map
# Copy the default namespace mapping
self._nsmap_copy = self._nsmap.copy()
# Copy the path cache (should be empty)
self._path_cache = ElementPath._cache
ElementPath._cache = self._path_cache.copy()
self.checkwarnings.__enter__()

def __exit__(self, *args):
from xml.etree import ElementPath
# Restore mapping and path cache
self._nsmap.clear()
self._nsmap.update(self._nsmap_copy)
ElementPath._cache = self._path_cache
self.checkwarnings.__exit__(*args)


def test_main(module=None):
# When invoked without a module, runs the Python ET tests by loading pyET.
# Otherwise, uses the given module as the ET.
Expand Down Expand Up @@ -2666,11 +2664,22 @@ def test_main(module=None):
NoAcceleratorTest,
])

# Provide default namespace mapping and path cache.
from xml.etree import ElementPath
nsmap = pyET._namespace_map
# Copy the default namespace mapping
nsmap_copy = nsmap.copy()
# Copy the path cache (should be empty)
path_cache = ElementPath._cache
ElementPath._cache = path_cache.copy()
try:
# XXX the C module should give the same warnings as the Python module
with CleanContext(quiet=(pyET is not ET)):
support.run_unittest(*test_classes)
support.run_unittest(*test_classes)
finally:
from xml.etree import ElementPath
# Restore mapping and path cache
nsmap.clear()
nsmap.update(nsmap_copy)
ElementPath._cache = path_cache
# don't interfere with subsequent tests
ET = None

Expand Down
10 changes: 8 additions & 2 deletions Lib/xml/etree/ElementTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,8 @@ def end(self, tag):
self._tail = 1
return self._last

_sentinel = ['sentinel']

##
# Element structure builder for XML source data, based on the
# <b>expat</b> parser.
Expand All @@ -1465,7 +1467,11 @@ def end(self, tag):

class XMLParser(object):

def __init__(self, html=0, target=None, encoding=None):
def __init__(self, html=_sentinel, target=None, encoding=None):
if html is not _sentinel:
warnings.warnpy3k(
"The html argument of XMLParser() is deprecated",
DeprecationWarning, stacklevel=2)
try:
from xml.parsers import expat
except ImportError:
Expand Down Expand Up @@ -1617,7 +1623,7 @@ def _default(self, text):
pubid = pubid[1:-1]
if hasattr(self.target, "doctype"):
self.target.doctype(name, pubid, system[1:-1])
elif self.doctype is not self._XMLParser__doctype:
elif self.doctype != self._XMLParser__doctype:
# warn about deprecated call
self._XMLParser__doctype(name, pubid, system[1:-1])
self.doctype(name, pubid, system[1:-1])
Expand Down
9 changes: 9 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ Extension Modules
Library
-------

- bpo-30365: Running Python with the -3 option now emits deprecation warnings
for getchildren() and getiterator() methods of the Element class in the
xml.etree.cElementTree module and when pass the html argument to
xml.etree.ElementTree.XMLParser().

- bpo-30365: Fixed a deprecation warning about the doctype() method of the
xml.etree.ElementTree.XMLParser class. Now it is emitted only when define
the doctype() method in the subclass of XMLParser.

- bpo-30329: imaplib now catchs the Windows socket WSAEINVAL error
(code 10022) on shutdown(SHUT_RDWR): An invalid operation was attempted.
This error occurs sometimes on SSL connections.
Expand Down
43 changes: 36 additions & 7 deletions Modules/_elementtree.c
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,11 @@ element_getchildren(ElementObject* self, PyObject* args)
int i;
PyObject* list;

/* FIXME: report as deprecated? */
if (PyErr_WarnPy3k("This method will be removed in future versions. "
"Use 'list(elem)' or iteration over elem instead.",
1) < 0) {
return NULL;
}

if (!PyArg_ParseTuple(args, ":getchildren"))
return NULL;
Expand All @@ -984,13 +988,10 @@ element_getchildren(ElementObject* self, PyObject* args)
}

static PyObject*
element_iter(ElementObject* self, PyObject* args)
element_iter_impl(ElementObject* self, PyObject* tag)
{
PyObject* args;
PyObject* result;

PyObject* tag = Py_None;
if (!PyArg_ParseTuple(args, "|O:iter", &tag))
return NULL;

if (!elementtree_iter_obj) {
PyErr_SetString(
Expand All @@ -1014,6 +1015,34 @@ element_iter(ElementObject* self, PyObject* args)
return result;
}

static PyObject*
element_iter(ElementObject* self, PyObject* args)
{
PyObject* tag = Py_None;
if (!PyArg_ParseTuple(args, "|O:iter", &tag))
return NULL;

return element_iter_impl(self, tag);
}

static PyObject*
element_getiterator(ElementObject* self, PyObject* args)
{
PyObject* tag = Py_None;
if (!PyArg_ParseTuple(args, "|O:getiterator", &tag))
return NULL;

/* Change for a DeprecationWarning in 1.4 */
if (Py_Py3kWarningFlag &&
PyErr_WarnEx(PyExc_PendingDeprecationWarning,
"This method will be removed in future versions. "
"Use 'tree.iter()' or 'list(tree.iter())' instead.",
1) < 0) {
return NULL;
}
return element_iter_impl(self, tag);
}


static PyObject*
element_itertext(ElementObject* self, PyObject* args)
Expand Down Expand Up @@ -1510,7 +1539,7 @@ static PyMethodDef element_methods[] = {
{"itertext", (PyCFunction) element_itertext, METH_VARARGS},
{"iterfind", (PyCFunction) element_iterfind, METH_VARARGS},

{"getiterator", (PyCFunction) element_iter, METH_VARARGS},
{"getiterator", (PyCFunction) element_getiterator, METH_VARARGS},
{"getchildren", (PyCFunction) element_getchildren, METH_VARARGS},

{"items", (PyCFunction) element_items, METH_VARARGS},
Expand Down