Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
77 changes: 69 additions & 8 deletions Lib/test/test_xml_etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,28 +692,33 @@ def test_iterparse(self):
it = iterparse(TESTFN)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'document'))
with warnings_helper.check_no_resource_warning(self):
with self.assertRaises(ET.ParseError) as cm:
next(it)
self.assertEqual(str(cm.exception),
'junk after document element: line 1, column 12')
with self.assertRaises(ET.ParseError) as cm:
next(it)
self.assertEqual(str(cm.exception),
'junk after document element: line 1, column 12')
with self.assertWarns(ResourceWarning):
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
del cm, it
gc_collect()

# Not exhausting the iterator still closes the resource (bpo-43292)
with warnings_helper.check_no_resource_warning(self):
# Deleting iterator without close() should emit ResourceWarning (bpo-43292)
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
with self.assertWarns(ResourceWarning):
it = iterparse(SIMPLE_XMLFILE)
del it
gc_collect()

# 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):
# Not closing before del should emit ResourceWarning
with self.assertWarns(ResourceWarning):
it = iterparse(SIMPLE_XMLFILE)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
del it, elem
gc_collect()

with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
Expand All @@ -725,6 +730,62 @@ def test_iterparse(self):
with self.assertRaises(FileNotFoundError):
iterparse("nonexistent")

def test_iterparse_resource_warning(self):
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
# Test ResourceWarning when iterparse with filename is not closed
import warnings

# Should emit warning when not closed
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always", ResourceWarning)

def create_unclosed():
context = ET.iterparse(SIMPLE_XMLFILE)
next(context)
# Don't close - should warn

create_unclosed()
gc_collect()

resource_warnings = [x for x in w
if issubclass(x.category, ResourceWarning)]
self.assertGreater(len(resource_warnings), 0,
"Expected ResourceWarning when iterparse is not closed")

# Should NOT warn when explicitly closed
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always", ResourceWarning)

def create_closed():
context = ET.iterparse(SIMPLE_XMLFILE)
next(context)
context.close()

create_closed()
gc_collect()
Comment thread
cmaloney marked this conversation as resolved.
Outdated

resource_warnings = [x for x in w
if issubclass(x.category, ResourceWarning)]
self.assertEqual(len(resource_warnings), 0,
"No warning expected when iterparse is properly closed")

# Should NOT warn for file objects (externally managed)
with open(SIMPLE_XMLFILE, 'rb') as source:
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always", ResourceWarning)

def create_with_fileobj():
context = ET.iterparse(source)
next(context)
# Don't close - file object managed externally

create_with_fileobj()
gc_collect()

resource_warnings = [x for x in w
if issubclass(x.category, ResourceWarning)]
self.assertEqual(len(resource_warnings), 0,
"No warning for file objects managed externally")

def test_iterparse_close(self):
iterparse = ET.iterparse

Expand Down
9 changes: 6 additions & 3 deletions Lib/xml/etree/ElementTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -1261,15 +1261,18 @@ def iterator(source):
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:
_warn(f"unclosed file {source!r}",
ResourceWarning, source=self)
source.close()

it = IterParseIterator()
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