-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
bpo-14243: Optionally delete NamedTemporaryFile on content manager exit but not on file close #22431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-14243: Optionally delete NamedTemporaryFile on content manager exit but not on file close #22431
Changes from all commits
144db7f
21f7df0
2d7927b
4fcddaf
76f28f3
6be9be6
faf1a34
589d0b8
e22a3db
3be2f47
39e8b50
4d8f152
9ec55f5
d6f382e
f9dc427
e29b7d2
d003a39
e2fc6db
3ac4e20
653cf27
d5d07f4
0b32b28
5773e71
d98f270
9598e6b
5a7e177
7e7ad85
4d5677e
cf5b1b5
7fd339a
da71a96
5e22c9c
b1f5152
07076e8
a0a8ae3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,20 +75,50 @@ The module defines the following user-callable items: | |
Added *errors* parameter. | ||
|
||
|
||
.. function:: NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, delete=True, *, errors=None) | ||
|
||
This function operates exactly as :func:`TemporaryFile` does, except that | ||
the file is guaranteed to have a visible name in the file system (on | ||
Unix, the directory entry is not unlinked). That name can be retrieved | ||
from the :attr:`name` attribute of the returned | ||
file-like object. Whether the name can be | ||
used to open the file a second time, while the named temporary file is | ||
still open, varies across platforms (it can be so used on Unix; it cannot | ||
on Windows). If *delete* is true (the default), the file is | ||
deleted as soon as it is closed. | ||
.. function:: NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, delete=True, *, errors=None, delete_on_close=True) | ||
|
||
This function operates exactly as :func:`TemporaryFile` does, except the | ||
following differences: | ||
|
||
* The file is guaranteed to have a visible name in the file system (on Unix, | ||
the directory entry is not unlinked). | ||
|
||
* There is more granularity in the deletion behaviour of the file (see | ||
``delete_on_close`` below) | ||
|
||
The returned object is always a file-like object whose :attr:`!file` | ||
attribute is the underlying true file object. This file-like object can | ||
be used in a :keyword:`with` statement, just like a normal file. | ||
attribute is the underlying true file object. This file-like object can be | ||
used in a :keyword:`with` statement, just like a normal file. The name of the | ||
temporary file can be retrieved from the :attr:`name` attribute of the | ||
returned file-like object. | ||
|
||
If *delete* is true (the default) and *delete_on_close* is true (the | ||
default), the file is deleted as soon as it is closed. If *delete* is true | ||
and *delete_on_close* is false, the file is deleted on context manager exit | ||
only. If *delete* is false, the value of *delete_on_close* is ignored. | ||
|
||
While the named temporary file is open, the file can always be opened again | ||
on POSIX. On Windows, it can be opened again if *delete* is false, or if | ||
*delete_on_close* is false, or if the additional open shares delete access | ||
(e.g. by calling :func:`os.open` with the flag ``O_TEMPORARY``). On | ||
Windows, if *delete* is true and *delete_on_close* is false, additional | ||
opens that do not share delete access (e.g. via builtin :func:`open`) must | ||
be closed before exiting the context manager, else the :func:`os.unlink` | ||
call on context manager exit will fail with a :exc:`PermissionError`. | ||
|
||
To use the name of the temporary file to open the closed file second time, | ||
either make sure not to delete the file upon closure (set the *delete* | ||
parameter to be false) or, in case the temporary file is created in a | ||
:keyword:`with` statement, set the *delete_on_close* parameter to be false. | ||
The latter approach is recommended as it provides assistance in automatic | ||
cleaning of the temporary file upon the context manager exit. | ||
Comment on lines
+109
to
+114
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The previous paragraphs explain the basic operation of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think one can work out this information from the previous pragagraphs, but from the not so advanced user point of view (like myself) I would want to have this paragraph as well, as it describes arguably the most frequent use of the named temporary file: write some data, close it and then use its name to open it further. |
||
|
||
On Windows, if *delete_on_close* is false, and the file is created in a | ||
directory for which the user lacks delete access, then the :func:`os.unlink` | ||
call on exit of the context manager will fail with a :exc:`PermissionError`. | ||
This cannot happen when *delete_on_close* is true because delete access is | ||
requested by the open, which fails immediately if the requested access is not | ||
granted. | ||
|
||
On POSIX (only), a process that is terminated abruptly with SIGKILL | ||
cannot automatically delete any NamedTemporaryFiles it created. | ||
|
@@ -98,6 +128,9 @@ The module defines the following user-callable items: | |
.. versionchanged:: 3.8 | ||
Added *errors* parameter. | ||
|
||
.. versionchanged:: 3.12 | ||
Added *delete_on_close* parameter. | ||
|
||
|
||
.. class:: SpooledTemporaryFile(max_size=0, mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, *, errors=None) | ||
|
||
|
@@ -346,6 +379,22 @@ Here are some examples of typical usage of the :mod:`tempfile` module:: | |
>>> | ||
# file is now closed and removed | ||
|
||
# create a temporary file using a context manager, note the name, | ||
# close the file, use the name to open the file again | ||
>>> with tempfile.TemporaryFile(delete_on_close=False) as fp: | ||
... fp.write(b'Hello world!') | ||
... fp_name = fp.name | ||
... fp.close() | ||
# the file is closed, but not removed | ||
# open the file again by using its name | ||
... f = open(fp_name) | ||
... f.seek(0) | ||
... f.read() | ||
b'Hello world!' | ||
... f.close() | ||
>>> | ||
# file is now removed | ||
|
||
# create a temporary directory using the context manager | ||
>>> with tempfile.TemporaryDirectory() as tmpdirname: | ||
... print('created temporary directory', tmpdirname) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -421,10 +421,11 @@ class _TemporaryFileCloser: | |
file = None # Set here since __del__ checks it | ||
close_called = False | ||
|
||
def __init__(self, file, name, delete=True): | ||
def __init__(self, file, name, delete=True, delete_on_close=True): | ||
self.file = file | ||
self.name = name | ||
self.delete = delete | ||
self.delete_on_close = delete_on_close | ||
|
||
# NT provides delete-on-close as a primitive, so we don't need | ||
# the wrapper to do anything special. We still use it so that | ||
|
@@ -442,7 +443,7 @@ def close(self, unlink=_os.unlink): | |
try: | ||
self.file.close() | ||
finally: | ||
if self.delete: | ||
if self.delete and self.delete_on_close: | ||
unlink(self.name) | ||
|
||
# Need to ensure the file is deleted on __del__ | ||
|
@@ -464,11 +465,13 @@ class _TemporaryFileWrapper: | |
remove the file when it is no longer needed. | ||
""" | ||
|
||
def __init__(self, file, name, delete=True): | ||
def __init__(self, file, name, delete=True, delete_on_close=True): | ||
self.file = file | ||
self.name = name | ||
self.delete = delete | ||
self._closer = _TemporaryFileCloser(file, name, delete) | ||
self.delete_on_close = delete_on_close | ||
self._closer = _TemporaryFileCloser(file, name, delete, | ||
delete_on_close) | ||
|
||
def __getattr__(self, name): | ||
# Attribute lookups are delegated to the underlying file | ||
|
@@ -500,6 +503,15 @@ def __enter__(self): | |
def __exit__(self, exc, value, tb): | ||
result = self.file.__exit__(exc, value, tb) | ||
self.close() | ||
# If the file is to be deleted, but not on close, delete it now. | ||
if self.delete and not self.delete_on_close: | ||
try: | ||
_os.unlink(self.name) | ||
# It is okay to ignore FileNotFoundError. The file may have | ||
# been deleted already. | ||
except FileNotFoundError: | ||
pass | ||
|
||
Comment on lines
+506
to
+514
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A case hasn't been covered. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @eryksun , I am just trying to figure out, whether this is the issue introduced by my changes or this is a separate issue, which existed before? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a new case to cover. Before there was just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I think I understand now. Also the new test case needs to be created to cover this, I think. Correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is now implemented in the new PR 97015 |
||
return result | ||
|
||
def close(self): | ||
|
@@ -521,15 +533,18 @@ def __iter__(self): | |
|
||
def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, | ||
newline=None, suffix=None, prefix=None, | ||
dir=None, delete=True, *, errors=None): | ||
dir=None, delete=True, *, errors=None, | ||
delete_on_close=True): | ||
"""Create and return a temporary file. | ||
Arguments: | ||
'prefix', 'suffix', 'dir' -- as for mkstemp. | ||
'mode' -- the mode argument to io.open (default "w+b"). | ||
'buffering' -- the buffer size argument to io.open (default -1). | ||
'encoding' -- the encoding argument to io.open (default None) | ||
'newline' -- the newline argument to io.open (default None) | ||
'delete' -- whether the file is deleted on close (default True). | ||
'delete' -- whether the file is automatically deleted (default True). | ||
'delete_on_close' -- if 'delete', whether the file is deleted on close | ||
or on context exit (default True). | ||
'errors' -- the errors argument to io.open (default None) | ||
The file is created as mkstemp() would do it. | ||
|
||
|
@@ -548,7 +563,7 @@ def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, | |
|
||
# Setting O_TEMPORARY in the flags causes the OS to delete | ||
# the file when it is closed. This is only supported by Windows. | ||
if _os.name == 'nt' and delete: | ||
if _os.name == 'nt' and delete and delete_on_close: | ||
flags |= _os.O_TEMPORARY | ||
|
||
if "b" not in mode: | ||
|
@@ -559,7 +574,7 @@ def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, | |
file = _io.open(fd, mode, buffering=buffering, | ||
newline=newline, encoding=encoding, errors=errors) | ||
|
||
return _TemporaryFileWrapper(file, name, delete) | ||
return _TemporaryFileWrapper(file, name, delete, delete_on_close) | ||
except BaseException: | ||
_os.unlink(name) | ||
_os.close(fd) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
The :class:`tempfile.NamedTemporaryFile` has a new optional parameter *delete_on_close* |
Uh oh!
There was an error while loading. Please reload this page.