Summary
BaseEngine.run_redline creates the output scratch file with
target_path = tempfile.NamedTemporaryFile(delete=False).name
This constructs a NamedTemporaryFile, takes its .name, and immediately drops the only reference to the file object. The object is never closed, so its finalizer runs while close_called is still false and CPython reports a ResourceWarning: Implicitly cleaning up <_TemporaryFileWrapper ...>.
One warning is emitted per call to run_redline. The file descriptor stays open until the garbage collector reaches the object.
_write_to_temp_file in the same class does this correctly — it keeps the object, writes, closes it, and then returns .name — so the fix is to make the two consistent.
Environment
- python-redlines 0.3.0 (
DocxodusEngine; XmlPowerToolsEngine shares the same BaseEngine.run_redline)
- Python 3.14.2, macOS
- Also present in the source on Python 3.12, but silent there:
tempfile._TemporaryFileCloser.__del__ only began emitting this warning in a later CPython release. On 3.14 the message comes from tempfile.py:484.
Location
python_redlines/engines.py, line 153, in BaseEngine.run_redline:
temp_files = []
try:
target_path = tempfile.NamedTemporaryFile(delete=False).name # <-- here
original_path = self._write_to_temp_file(original) if isinstance(original, bytes) else original
modified_path = self._write_to_temp_file(modified) if isinstance(modified, bytes) else modified
For contrast, line 184 in the same class handles it properly:
def _write_to_temp_file(self, data):
temp_file = tempfile.NamedTemporaryFile(delete=False)
temp_file.write(data)
temp_file.close()
return temp_file.name
Reproduction
import gc
import warnings
from python_redlines import DocxodusEngine
warnings.simplefilter("error", ResourceWarning)
engine = DocxodusEngine()
engine.run_redline("Author", original_bytes, modified_bytes)
gc.collect() # ResourceWarning: Implicitly cleaning up <_TemporaryFileWrapper ...>
The leak happens before the subprocess is launched, so it occurs even when the comparison itself fails.
Why this is worth fixing rather than filtering
Under -W error or pytest's filterwarnings = ["error"], the warning becomes an exception. Because it is raised inside __del__, it surfaces as an unraisable exception, and pytest attributes it to whichever test happened to be running when the collector ran rather than the one that called run_redline. In our suite it produced five consecutive failures in unrelated tests, none of which touched the library, with a traceback that pointed only at tempfile. It took a while to trace back here.
The open descriptor also accumulates for as long as collection is deferred, which matters for a process comparing many documents in a loop.
Suggested fix
mkstemp is the right tool when only a path is wanted, since it returns a descriptor the caller is expected to close:
fd, target_path = tempfile.mkstemp()
os.close(fd)
os is already imported in the module, and run_redline already appends target_path to temp_files for its finally cleanup, so nothing else changes.
Alternatively, keep NamedTemporaryFile and close it explicitly, matching _write_to_temp_file:
target_file = tempfile.NamedTemporaryFile(delete=False)
target_file.close()
target_path = target_file.name
Both silence the warning and close the descriptor at the point of creation. I've verified the mkstemp form leaves no warning under warnings.simplefilter("error", ResourceWarning) and that the path still exists for the subprocess to write to.
Summary
BaseEngine.run_redlinecreates the output scratch file withThis constructs a
NamedTemporaryFile, takes its.name, and immediately drops the only reference to the file object. The object is never closed, so its finalizer runs whileclose_calledis still false and CPython reports aResourceWarning: Implicitly cleaning up <_TemporaryFileWrapper ...>.One warning is emitted per call to
run_redline. The file descriptor stays open until the garbage collector reaches the object._write_to_temp_filein the same class does this correctly — it keeps the object, writes, closes it, and then returns.name— so the fix is to make the two consistent.Environment
DocxodusEngine;XmlPowerToolsEngineshares the sameBaseEngine.run_redline)tempfile._TemporaryFileCloser.__del__only began emitting this warning in a later CPython release. On 3.14 the message comes fromtempfile.py:484.Location
python_redlines/engines.py, line 153, inBaseEngine.run_redline:For contrast, line 184 in the same class handles it properly:
Reproduction
The leak happens before the subprocess is launched, so it occurs even when the comparison itself fails.
Why this is worth fixing rather than filtering
Under
-W erroror pytest'sfilterwarnings = ["error"], the warning becomes an exception. Because it is raised inside__del__, it surfaces as an unraisable exception, and pytest attributes it to whichever test happened to be running when the collector ran rather than the one that calledrun_redline. In our suite it produced five consecutive failures in unrelated tests, none of which touched the library, with a traceback that pointed only attempfile. It took a while to trace back here.The open descriptor also accumulates for as long as collection is deferred, which matters for a process comparing many documents in a loop.
Suggested fix
mkstempis the right tool when only a path is wanted, since it returns a descriptor the caller is expected to close:osis already imported in the module, andrun_redlinealready appendstarget_pathtotemp_filesfor itsfinallycleanup, so nothing else changes.Alternatively, keep
NamedTemporaryFileand close it explicitly, matching_write_to_temp_file:Both silence the warning and close the descriptor at the point of creation. I've verified the
mkstempform leaves no warning underwarnings.simplefilter("error", ResourceWarning)and that the path still exists for the subprocess to write to.