mirror of
https://github.com/python/cpython
synced 2024-10-06 13:57:57 +00:00
gh-69893: Add the close() method for xml.etree.ElementTree.iterparse() iterator (GH-114534)
This commit is contained in:
parent
fc06096911
commit
ca715e56a1
|
@ -625,6 +625,8 @@ Functions
|
|||
target. Returns an :term:`iterator` providing ``(event, elem)`` pairs;
|
||||
it has a ``root`` attribute that references the root element of the
|
||||
resulting XML tree once *source* is fully read.
|
||||
The iterator has the :meth:`!close` method that closes the internal
|
||||
file object if *source* is a filename.
|
||||
|
||||
Note that while :func:`iterparse` builds the tree incrementally, it issues
|
||||
blocking reads on *source* (or the file it names). As such, it's unsuitable
|
||||
|
@ -647,6 +649,9 @@ Functions
|
|||
.. versionchanged:: 3.8
|
||||
The ``comment`` and ``pi`` events were added.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Added the :meth:`!close` method.
|
||||
|
||||
|
||||
.. function:: parse(source, parser=None)
|
||||
|
||||
|
|
|
@ -472,6 +472,14 @@ warnings
|
|||
warning may also be emitted when a decorated function or class is used at runtime.
|
||||
See :pep:`702`. (Contributed by Jelle Zijlstra in :gh:`104003`.)
|
||||
|
||||
xml.etree.ElementTree
|
||||
---------------------
|
||||
|
||||
* Add the :meth:`!close` method for the iterator returned by
|
||||
:func:`~xml.etree.ElementTree.iterparse` for explicit cleaning up.
|
||||
(Contributed by Serhiy Storchaka in :gh:`69893`.)
|
||||
|
||||
|
||||
Optimizations
|
||||
=============
|
||||
|
||||
|
|
|
@ -555,6 +555,17 @@ def test_iterparse(self):
|
|||
('end', '{namespace}root'),
|
||||
])
|
||||
|
||||
with open(SIMPLE_XMLFILE, 'rb') as source:
|
||||
context = iterparse(source)
|
||||
action, elem = next(context)
|
||||
self.assertEqual((action, elem.tag), ('end', 'element'))
|
||||
self.assertEqual([(action, elem.tag) for action, elem in context], [
|
||||
('end', 'element'),
|
||||
('end', 'empty-element'),
|
||||
('end', 'root'),
|
||||
])
|
||||
self.assertEqual(context.root.tag, 'root')
|
||||
|
||||
events = ()
|
||||
context = iterparse(SIMPLE_XMLFILE, events)
|
||||
self.assertEqual([(action, elem.tag) for action, elem in context], [])
|
||||
|
@ -646,12 +657,81 @@ def test_iterparse(self):
|
|||
|
||||
# Not exhausting the iterator still closes the resource (bpo-43292)
|
||||
with warnings_helper.check_no_resource_warning(self):
|
||||
it = iterparse(TESTFN)
|
||||
it = iterparse(SIMPLE_XMLFILE)
|
||||
del it
|
||||
|
||||
with warnings_helper.check_no_resource_warning(self):
|
||||
it = iterparse(SIMPLE_XMLFILE)
|
||||
it.close()
|
||||
del it
|
||||
|
||||
with warnings_helper.check_no_resource_warning(self):
|
||||
it = iterparse(SIMPLE_XMLFILE)
|
||||
action, elem = next(it)
|
||||
self.assertEqual((action, elem.tag), ('end', 'element'))
|
||||
del it, elem
|
||||
|
||||
with warnings_helper.check_no_resource_warning(self):
|
||||
it = iterparse(SIMPLE_XMLFILE)
|
||||
action, elem = next(it)
|
||||
it.close()
|
||||
self.assertEqual((action, elem.tag), ('end', 'element'))
|
||||
del it, elem
|
||||
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
iterparse("nonexistent")
|
||||
|
||||
def test_iterparse_close(self):
|
||||
iterparse = ET.iterparse
|
||||
|
||||
it = iterparse(SIMPLE_XMLFILE)
|
||||
it.close()
|
||||
with self.assertRaises(StopIteration):
|
||||
next(it)
|
||||
it.close() # idempotent
|
||||
|
||||
with open(SIMPLE_XMLFILE, 'rb') as source:
|
||||
it = iterparse(source)
|
||||
it.close()
|
||||
self.assertFalse(source.closed)
|
||||
with self.assertRaises(StopIteration):
|
||||
next(it)
|
||||
it.close() # idempotent
|
||||
|
||||
it = iterparse(SIMPLE_XMLFILE)
|
||||
action, elem = next(it)
|
||||
self.assertEqual((action, elem.tag), ('end', 'element'))
|
||||
it.close()
|
||||
with self.assertRaises(StopIteration):
|
||||
next(it)
|
||||
it.close() # idempotent
|
||||
|
||||
with open(SIMPLE_XMLFILE, 'rb') as source:
|
||||
it = iterparse(source)
|
||||
action, elem = next(it)
|
||||
self.assertEqual((action, elem.tag), ('end', 'element'))
|
||||
it.close()
|
||||
self.assertFalse(source.closed)
|
||||
with self.assertRaises(StopIteration):
|
||||
next(it)
|
||||
it.close() # idempotent
|
||||
|
||||
it = iterparse(SIMPLE_XMLFILE)
|
||||
list(it)
|
||||
it.close()
|
||||
with self.assertRaises(StopIteration):
|
||||
next(it)
|
||||
it.close() # idempotent
|
||||
|
||||
with open(SIMPLE_XMLFILE, 'rb') as source:
|
||||
it = iterparse(source)
|
||||
list(it)
|
||||
it.close()
|
||||
self.assertFalse(source.closed)
|
||||
with self.assertRaises(StopIteration):
|
||||
next(it)
|
||||
it.close() # idempotent
|
||||
|
||||
def test_writefile(self):
|
||||
elem = ET.Element("tag")
|
||||
elem.text = "text"
|
||||
|
@ -3044,8 +3124,7 @@ def test_basic(self):
|
|||
# With an explicit parser too (issue #9708)
|
||||
sourcefile = serialize(doc, to_string=False)
|
||||
parser = ET.XMLParser(target=ET.TreeBuilder())
|
||||
self.assertEqual(next(ET.iterparse(sourcefile, parser=parser))[0],
|
||||
'end')
|
||||
self.assertEqual(next(ET.iterparse(sourcefile, parser=parser))[0], 'end')
|
||||
|
||||
tree = ET.ElementTree(None)
|
||||
self.assertRaises(AttributeError, tree.iter)
|
||||
|
|
|
@ -1248,10 +1248,17 @@ def iterator(source):
|
|||
if close_source:
|
||||
source.close()
|
||||
|
||||
gen = iterator(source)
|
||||
class IterParseIterator(collections.abc.Iterator):
|
||||
__next__ = iterator(source).__next__
|
||||
__next__ = gen.__next__
|
||||
def close(self):
|
||||
if close_source:
|
||||
source.close()
|
||||
gen.close()
|
||||
|
||||
def __del__(self):
|
||||
# TODO: Emit a ResourceWarning if it was not explicitly closed.
|
||||
# (When the close() method will be supported in all maintained Python versions.)
|
||||
if close_source:
|
||||
source.close()
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Add the :meth:`!close` method for the iterator returned by
|
||||
:func:`xml.etree.ElementTree.iterparse`.
|
Loading…
Reference in a new issue