Passing temp.name to a library does not pass the open Python file object. The library may open that path again, possibly in another process. If a temporary-file workflow succeeds on one machine and fails on another, inspect the writer handle, deletion policy and consumer lifetime before changing the filename.
A practical default for a synchronous consumer is to finish writing, close the writer, invoke the consumer, wait until the consumer has closed its handles, and then remove the owned temporary file. The examples below exercise that ordering with a small reader. They do not establish compatibility with an arbitrary third-party program or a background task that returns before reading finishes.
Separate visibility from handle lifetime
There are two independent questions: have buffered bytes been made available, and can the consumer open the file under the platform's sharing rules? flush() addresses Python buffering; it does not close the writer handle or make a deletion-sharing conflict disappear. Closing the writer completes its buffered output and ends that handle's lifetime.
Default NamedTemporaryFile cleanup can remove the name when the writer closes. Therefore “close it first” needs a matching deletion policy. Otherwise the consumer receives a path that no longer exists. Keep the lifetime of the name long enough for the real consumer, then clean up in code that owns that file.
A Python 3.11-compatible closed-writer pattern
Save this as closed-writer.py and run python3 closed-writer.py. It uses delete=False, closes the writer before opening a reader, and removes its own generated path in finally. The reader deliberately fails once so the example also verifies cleanup on an ordinary exception.
from pathlib import Path
import tempfile
def consume(path, fail=False):
with path.open("rb") as reader:
data = reader.read()
if fail:
raise ValueError("consumer failed after closing its handle")
return data
for fail in (False, True):
path = None
try:
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as writer:
path = Path(writer.name)
writer.write(b"reader input\n")
assert writer.closed
assert path.exists()
result = consume(path, fail=fail)
assert result == b"reader input\n"
except ValueError as error:
assert fail and str(error).startswith("consumer failed")
finally:
if path is not None:
path.unlink()
assert not path.exists()
print("failure" if fail else "success", "owned file removed")The finally block is intentional. delete=False transfers responsibility for removal to the application; it does not arrange later automatic deletion. Do not extend this cleanup to paths supplied by a user or discovered by a broad filename search. The code deletes only the file it just created.
If unlinking fails, the failure remains visible. Production code may need to record both a consumer error and a cleanup error without losing either. Quietly suppressing every removal failure hides leftover data and makes diagnosis harder. The temporary content in these examples is disposable, not confidential application input.
Python 3.12 and later can defer automatic deletion to context exit
delete_on_close=False was added in Python 3.12. With the default delete=True, it lets you close the writer inside the context, use the remaining path, then request automatic removal when the context exits. Run the following example on Python 3.12 or later; Python 3.11 does not accept this parameter.
from pathlib import Path
import tempfile
with tempfile.NamedTemporaryFile(
mode="wb", delete_on_close=False
) as writer:
path = Path(writer.name)
writer.write(b"close before reader\n")
writer.close()
assert path.exists()
with path.open("rb") as reader:
assert reader.read() == b"close before reader\n"
assert path.exists()
assert not path.exists()
print("writer closed, reader closed, context removed file")The nesting is part of the solution: the reader closes before the outer context attempts cleanup. If the consumer retains a handle, schedules asynchronous work, or starts a child that continues after the call returns, keep the outer lifetime active until that work has actually finished. A function returning a job ID is not proof that it has finished reading.
The official documentation describes additional Windows reopen and delete-sharing conditions. For example, an additional open that does not share delete access must close before context cleanup, and directory delete permissions can affect removal. Treat these as platform contracts to check in your environment, not as Windows test results from this article. The local examples were executed on macOS; no Windows runtime was used.
A temporary directory can make the handoff clearer
When a reader expects a suffix or produces companion files, a temporary directory containing an ordinary named file often makes ownership easier to understand. Keep all reader work inside the directory context and wait for it to finish before leaving.
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "input.txt"
path.write_bytes(b"ordinary file inside owned directory\n")
with path.open("rb") as reader:
assert reader.read() == b"ordinary file inside owned directory\n"
assert path.exists()
assert not Path(folder).exists()
print("directory removed after reader finished")This removes the need to keep a NamedTemporaryFile writer object around, but it does not eliminate handle or permissions problems. A reader that stays open beyond the context can still prevent cleanup on some systems. If a consumer accepts an existing file-like object, passing that object may avoid reopening altogether; confirm its API and ownership rules before assuming that option exists.
Match the test to actual reader behavior
The closed-writer and directory examples were tested with Python 3.11.6, 3.13.2 and 3.14.3 on macOS. The deferred-delete example was tested on the two installed newer versions. Those tests verify byte handoff and normal cleanup, including the explicit consumer failure in the first example. They do not certify every parser, subprocess or platform.
For the actual integration, check whether the consumer reads immediately or lazily, whether it needs the path after return, and who closes every opened handle. Exercise both a successful read and a reader failure. Then inspect whether cleanup left the owned path behind. Do not use a sleep as a substitute for a documented completion signal.
Finally, context managers and finally blocks are not crash recovery. Forced termination can prevent cleanup; the documentation specifically notes POSIX SIGKILL cannot trigger automatic removal of named temporary files. For sensitive or long-running jobs, decide separately how to limit retained data and recover files owned by abandoned jobs. Never promise that a context manager guarantees erasure after every kind of process failure.