From 09b52471f39ba280d836b945d47719c697af0b45 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 17 May 2017 10:08:11 +0300 Subject: [PATCH] bpo-30365: Backport warnings and fix bugs in ElementTree. (#1581) 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(). 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. Fixed a bug in the test_bug_200708_close test method. An EchoTarget instance was incorrectly passed to XMLParser() as the html argument and silently ignored. Tests no longer failed when use the -m option for running only selected test methods. Checking warnings now is more specific, warnings are expected only when use deprecated features. --- Lib/test/test_xml_etree.py | 107 +++++++++++++++++++---------------- Lib/xml/etree/ElementTree.py | 10 +++- Misc/NEWS | 9 +++ Modules/_elementtree.c | 43 +++++++++++--- 4 files changed, 111 insertions(+), 58 deletions(-) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 201266a12d..60b26ead7a 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -8,6 +8,7 @@ import cgi import copy +import functools import io import pickle import StringIO @@ -86,6 +87,16 @@ ENTITY_XML = """\ """ +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 @@ -656,6 +667,10 @@ class ElementTreeTest(unittest.TestCase): ]) + # 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() @@ -1356,9 +1371,15 @@ class BugsTest(unittest.TestCase): # 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("some text") self.assertEqual(parser.close().tag, 'element') @@ -1908,7 +1929,12 @@ class ElementFindTest(unittest.TestCase): 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')) @@ -1919,14 +1945,15 @@ class ElementFindTest(unittest.TestCase): 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): @@ -2014,6 +2041,15 @@ class ElementIterTest(unittest.TestCase): 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(''' @@ -2178,13 +2214,13 @@ class XMLParserTest(unittest.TestCase): 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()) @@ -2593,44 +2629,6 @@ class NoAcceleratorTest(unittest.TestCase): # -------------------------------------------------------------------- -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. @@ -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 diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index cf6402f8c5..dca69106d1 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1450,6 +1450,8 @@ class TreeBuilder(object): self._tail = 1 return self._last +_sentinel = ['sentinel'] + ## # Element structure builder for XML source data, based on the # expat parser. @@ -1465,7 +1467,11 @@ class TreeBuilder(object): 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: @@ -1617,7 +1623,7 @@ class XMLParser(object): 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]) diff --git a/Misc/NEWS b/Misc/NEWS index c3fb3c8900..dd6ec1b419 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -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. diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 0a01c3cfad..b9e9b3aff8 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -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; @@ -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( @@ -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) @@ -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}, -- 2.50.1