Skip to content
Open
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
4 changes: 4 additions & 0 deletions Doc/library/xml.etree.elementtree.rst
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,10 @@ Functions
.. versionchanged:: 3.13
Added the :meth:`!close` method.

.. versionchanged:: 3.15
A :exc:`ResourceWarning` is now emitted if the iterator opened a file
and is not explicitly closed.


.. function:: parse(source, parser=None)

Expand Down
24 changes: 19 additions & 5 deletions Lib/test/test_xml_etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,22 +698,39 @@ def test_iterparse(self):
self.assertEqual(str(cm.exception),
'junk after document element: line 1, column 12')
del cm, it
gc_collect()

with self.assertRaises(FileNotFoundError):
iterparse("nonexistent")

def test_iterparse_not_close(self):
# Not closing before del should emit ResourceWarning
iterparse = ET.iterparse

# Not exhausting the iterator still closes the resource (bpo-43292)
with warnings_helper.check_no_resource_warning(self):
with self.assertWarns(ResourceWarning) as wm:
it = iterparse(SIMPLE_XMLFILE)
del it
gc_collect()
self.assertIn('unclosed file', str(wm.warning))
self.assertIn(repr(SIMPLE_XMLFILE), str(wm.warning))
self.assertEqual(wm.filename, __file__)

# Explicitly calling close() should not emit warning
with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
it.close()
del it

with warnings_helper.check_no_resource_warning(self):
with self.assertWarns(ResourceWarning) as wm:
it = iterparse(SIMPLE_XMLFILE)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
del it, elem
gc_collect()
self.assertIn('unclosed file', str(wm.warning))
self.assertIn(repr(SIMPLE_XMLFILE), str(wm.warning))
self.assertEqual(wm.filename, __file__)

with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
Expand All @@ -722,9 +739,6 @@ def test_iterparse(self):
self.assertEqual((action, elem.tag), ('end', 'element'))
del it, elem

with self.assertRaises(FileNotFoundError):
iterparse("nonexistent")

def test_iterparse_close(self):
iterparse = ET.iterparse

Expand Down
14 changes: 10 additions & 4 deletions Lib/xml/etree/ElementTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -1255,22 +1255,28 @@ def iterator(source):
if it is not None:
it.root = root
finally:
nonlocal close_source
if close_source:
source.close()
close_source = False

gen = iterator(source)
class IterParseIterator(collections.abc.Iterator):
__next__ = gen.__next__

def close(self):
nonlocal close_source
if close_source:
source.close()
gen.close()
close_source = False

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.)
def __del__(self, _warn=warnings.warn):
if close_source:
source.close()
try:
_warn(f"unclosed file {source.name!r}", ResourceWarning, stacklevel=2)
finally:
source.close()

it = IterParseIterator()
it.root = None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:func:`xml.etree.ElementTree.iterparse` now emits a :exc:`ResourceWarning`
when the iterator is not explicitly closed and was opened with a filename.
This helps developers identify and fix resource leaks. Patch by Osama
Abdelkader.
Loading