From 762ec97ea68a1126b8855996c61fa8239dc9fff7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 30 Mar 2017 18:12:06 +0300 Subject: [PATCH] bpo-29204: Emit warnings for already deprecated ElementTree features. (#773) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Element.getiterator() and the html parameter of XMLParser() were deprecated only in the documentation (since Python 3.2 and 3.4 correspondintly). Now using them emits a deprecation warning. * Don’t need check_warnings any more. --- Lib/test/test_xml_etree.py | 89 +++++++++++++++------------------ Lib/test/test_xml_etree_c.py | 3 +- Lib/xml/etree/ElementTree.py | 7 ++- Misc/NEWS | 4 ++ Modules/_elementtree.c | 39 ++++++++++++++- Modules/clinic/_elementtree.c.h | 31 +++++++++++- 6 files changed, 120 insertions(+), 53 deletions(-) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index dbdad23a742..952a4fd6060 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -6,6 +6,7 @@ # monkey-patched when running the "test_xml_etree_c" test suite. import copy +import functools import html import io import operator @@ -90,6 +91,16 @@ ENTITY_XML = """\ """ +def checkwarnings(*filters, quiet=False): + def decorator(test): + def newtest(*args, **kwargs): + with support.check_warnings(*filters, quiet=quiet): + test(*args, **kwargs) + functools.update_wrapper(newtest, test) + return newtest + return decorator + + class ModuleTest(unittest.TestCase): def test_sanity(self): # Import sanity. @@ -690,6 +701,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() @@ -1558,7 +1573,7 @@ class BugsTest(unittest.TestCase): class EchoTarget: 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') @@ -2225,8 +2240,12 @@ class ElementFindTest(unittest.TestCase): 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'] * 3) + 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 self.assertWarnsRegex(FutureWarning, msg): + it = ET.ElementTree(e).findall('//tag') + self.assertEqual(summarize_list(it), ['tag'] * 3) class ElementIterTest(unittest.TestCase): @@ -2311,6 +2330,9 @@ class ElementIterTest(unittest.TestCase): self.assertEqual(self._ilist(doc), all_tags) self.assertEqual(self._ilist(doc, '*'), all_tags) + # Element.getiterator() is deprecated. + @checkwarnings(("This method will be removed in future versions. " + "Use .+ instead.", PendingDeprecationWarning)) def test_getiterator(self): doc = ET.XML(''' @@ -2493,13 +2515,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 self.assertWarnsRegex(DeprecationWarning, r'\bhtml\b'): + 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()) @@ -3016,46 +3038,6 @@ class NoAcceleratorTest(unittest.TestCase): # -------------------------------------------------------------------- -class CleanContext(object): - """Provide default namespace mapping and path cache.""" - checkwarnings = None - - def __init__(self, quiet=False): - if sys.flags.optimize >= 2: - # under -OO, doctests cannot be run and therefore not all warnings - # will be emitted - quiet = True - deprecations = ( - # 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 = ET.register_namespace._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. @@ -3095,11 +3077,22 @@ def test_main(module=None): NoAcceleratorTest, ]) + # Provide default namespace mapping and path cache. + from xml.etree import ElementPath + nsmap = ET.register_namespace._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 = pyET = None diff --git a/Lib/test/test_xml_etree_c.py b/Lib/test/test_xml_etree_c.py index 7c60699f912..171a3f88b9a 100644 --- a/Lib/test/test_xml_etree_c.py +++ b/Lib/test/test_xml_etree_c.py @@ -8,7 +8,8 @@ import unittest cET = import_fresh_module('xml.etree.ElementTree', fresh=['_elementtree']) cET_alias = import_fresh_module('xml.etree.cElementTree', - fresh=['_elementtree', 'xml.etree']) + fresh=['_elementtree', 'xml.etree'], + deprecated=True) @unittest.skipUnless(cET, 'requires _elementtree') diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 735405681ff..7944cf100fa 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1430,6 +1430,7 @@ class TreeBuilder: self._tail = 1 return self._last +_sentinel = ['sentinel'] # also see ElementTree and TreeBuilder class XMLParser: @@ -1443,7 +1444,11 @@ class XMLParser: """ - def __init__(self, html=0, target=None, encoding=None): + def __init__(self, html=_sentinel, target=None, encoding=None): + if html is not _sentinel: + warnings.warn( + "The html argument of XMLParser() is deprecated", + DeprecationWarning, stacklevel=2) try: from xml.parsers import expat except ImportError: diff --git a/Misc/NEWS b/Misc/NEWS index 5fff581cfce..87b47829d71 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -298,6 +298,10 @@ Extension Modules Library ------- +- bpo-29204: Element.getiterator() and the html parameter of XMLParser() were + deprecated only in the documentation (since Python 3.2 and 3.4 correspondintly). + Now using them emits a deprecation warning. + - bpo-27863: Fixed multiple crashes in ElementTree caused by race conditions and wrong types. diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 36aa391609f..4e1750f9dd0 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -1366,7 +1366,12 @@ _elementtree_Element_getchildren_impl(ElementObject *self) Py_ssize_t i; PyObject* list; - /* FIXME: report as deprecated? */ + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "This method will be removed in future versions. " + "Use 'list(elem)' or iteration over elem instead.", + 1) < 0) { + return NULL; + } if (!self->extra) return PyList_New(0); @@ -1415,6 +1420,28 @@ _elementtree_Element_iter_impl(ElementObject *self, PyObject *tag) } +/*[clinic input] +_elementtree.Element.getiterator + + tag: object = None + +[clinic start generated code]*/ + +static PyObject * +_elementtree_Element_getiterator_impl(ElementObject *self, PyObject *tag) +/*[clinic end generated code: output=cb69ff4a3742dfa1 input=500da1a03f7b9e28]*/ +{ + /* Change for a DeprecationWarning in 1.4 */ + if (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 _elementtree_Element_iter_impl(self, tag); +} + + /*[clinic input] _elementtree.Element.itertext @@ -3244,6 +3271,14 @@ _elementtree_XMLParser___init___impl(XMLParserObject *self, PyObject *html, PyObject *target, const char *encoding) /*[clinic end generated code: output=d6a16c63dda54441 input=155bc5695baafffd]*/ { + if (html != NULL) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "The html argument of XMLParser() is deprecated", + 1) < 0) { + return -1; + } + } + self->entity = PyDict_New(); if (!self->entity) return -1; @@ -3716,7 +3751,7 @@ static PyMethodDef element_methods[] = { _ELEMENTTREE_ELEMENT_ITERTEXT_METHODDEF _ELEMENTTREE_ELEMENT_ITERFIND_METHODDEF - {"getiterator", (PyCFunction)_elementtree_Element_iter, METH_FASTCALL, _elementtree_Element_iter__doc__}, + _ELEMENTTREE_ELEMENT_GETITERATOR_METHODDEF _ELEMENTTREE_ELEMENT_GETCHILDREN_METHODDEF _ELEMENTTREE_ELEMENT_ITEMS_METHODDEF diff --git a/Modules/clinic/_elementtree.c.h b/Modules/clinic/_elementtree.c.h index c13cb35c708..7d1fd1813ca 100644 --- a/Modules/clinic/_elementtree.c.h +++ b/Modules/clinic/_elementtree.c.h @@ -333,6 +333,35 @@ exit: return return_value; } +PyDoc_STRVAR(_elementtree_Element_getiterator__doc__, +"getiterator($self, /, tag=None)\n" +"--\n" +"\n"); + +#define _ELEMENTTREE_ELEMENT_GETITERATOR_METHODDEF \ + {"getiterator", (PyCFunction)_elementtree_Element_getiterator, METH_FASTCALL, _elementtree_Element_getiterator__doc__}, + +static PyObject * +_elementtree_Element_getiterator_impl(ElementObject *self, PyObject *tag); + +static PyObject * +_elementtree_Element_getiterator(ElementObject *self, PyObject **args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"tag", NULL}; + static _PyArg_Parser _parser = {"|O:getiterator", _keywords, 0}; + PyObject *tag = Py_None; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &tag)) { + goto exit; + } + return_value = _elementtree_Element_getiterator_impl(self, tag); + +exit: + return return_value; +} + PyDoc_STRVAR(_elementtree_Element_itertext__doc__, "itertext($self, /)\n" "--\n" @@ -726,4 +755,4 @@ _elementtree_XMLParser__setevents(XMLParserObject *self, PyObject **args, Py_ssi exit: return return_value; } -/*[clinic end generated code: output=b69fa98c40917f58 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=fbc92d64735adec0 input=a9049054013a1b77]*/