Temporary file handling¶
AUTHORS:
- Volker Braun, Jeroen Demeyer (2012-10-18): move these functions here from sage/misc/misc.py and make them secure, see trac ticket #13579.
- Jeroen Demeyer (2013-03-17): add
atomic_write
, see trac ticket #14292.
-
class
sage.misc.temporary_file.
atomic_write
(target_filename, append=False, mode=438)¶ Write to a given file using a temporary file and then rename it to the target file. This renaming should be atomic on modern operating systems. Therefore, this class can be used to avoid race conditions when a file might be read while it is being written. It also avoids having partially written files due to exceptions or crashes.
This is to be used in a
with
statement, where a temporary file is created when entering thewith
and is moved in place of the target file when exiting thewith
(if no exceptions occured).INPUT:
target_filename
– the name of the file to be written. Normally, the contents of this file will be overwritten.append
– (boolean, default: False) if True andtarget_filename
is an existing file, then copy the current contents oftarget_filename
to the temporary file when entering thewith
statement. Otherwise, the temporary file is initially empty.mode
– (default:0o666
) mode bits for the file. The temporary file is created with modemode & ~umask
and the resulting file will also have these permissions (unless the mode bits of the file were changed manually).
EXAMPLES:
sage: from sage.misc.temporary_file import atomic_write sage: target_file = tmp_filename() sage: open(target_file, "w").write("Old contents") sage: with atomic_write(target_file) as f: ....: f.write("New contents") ....: f.flush() ....: open(target_file, "r").read() 'Old contents' sage: open(target_file, "r").read() 'New contents'
The name of the temporary file can be accessed using
f.name
. It is not a problem to close and re-open the temporary file:sage: from sage.misc.temporary_file import atomic_write sage: target_file = tmp_filename() sage: open(target_file, "w").write("Old contents") sage: with atomic_write(target_file) as f: ....: f.close() ....: open(f.name, "w").write("Newer contents") sage: open(target_file, "r").read() 'Newer contents'
If an exception occurs while writing the file, the target file is not touched:
sage: with atomic_write(target_file) as f: ....: f.write("Newest contents") ....: raise RuntimeError Traceback (most recent call last): ... RuntimeError sage: open(target_file, "r").read() 'Newer contents'
Some examples of using the
append
option. Note that the file is never opened in “append” mode, it is possible to overwrite existing data:sage: target_file = tmp_filename() sage: with atomic_write(target_file, append=True) as f: ....: f.write("Hello") sage: with atomic_write(target_file, append=True) as f: ....: f.write(" World") sage: open(target_file, "r").read() 'Hello World' sage: with atomic_write(target_file, append=True) as f: ....: f.seek(0) ....: f.write("HELLO") sage: open(target_file, "r").read() 'HELLO World'
If the target file is a symbolic link, the link is kept and the target of the link is written to:
sage: link_to_target = os.path.join(tmp_dir(), "templink") sage: os.symlink(target_file, link_to_target) sage: with atomic_write(link_to_target) as f: ....: f.write("Newest contents") sage: open(target_file, "r").read() 'Newest contents'
We check the permission bits of the new file. Note that the old permissions do not matter:
sage: os.chmod(target_file, 0o600) sage: _ = os.umask(0o022) sage: with atomic_write(target_file) as f: ....: pass sage: oct(os.stat(target_file).st_mode & 0o777) '644' sage: _ = os.umask(0o077) sage: with atomic_write(target_file, mode=0o777) as f: ....: pass sage: oct(os.stat(target_file).st_mode & 0o777) '700'
Test writing twice to the same target file. The outermost
with
“wins”:sage: open(target_file, "w").write(">>> ") sage: with atomic_write(target_file, append=True) as f, ....: atomic_write(target_file, append=True) as g: ....: f.write("AAA"); f.close() ....: g.write("BBB"); g.close() sage: open(target_file, "r").read() '>>> AAA'
-
sage.misc.temporary_file.
delete_tmpfiles
()¶ Remove the directory
SAGE_TMP
.TESTS:
This is automatically run when Sage exits, test this by running a separate session of Sage:
sage: from sage.tests.cmdline import test_executable sage: child_SAGE_TMP, err, ret = test_executable(["sage", "-c", "print(SAGE_TMP)"]) sage: err, ret ('', 0) sage: os.path.exists(child_SAGE_TMP) # indirect doctest False
The parent directory should exist:
sage: parent_SAGE_TMP = os.path.normpath(child_SAGE_TMP + '/..') sage: os.path.isdir(parent_SAGE_TMP) True
-
sage.misc.temporary_file.
graphics_filename
(ext='.png')¶ Deprecated SageNB graphics filename
You should just use
tmp_filename()
.When run from the Sage notebook, return the next available canonical filename for a plot/graphics file in the current working directory. Otherwise, return a temporary file inside
SAGE_TMP
.INPUT:
ext
– (default:".png"
) A file extension (including the dot) for the filename.
OUTPUT:
The path of the temporary file created. In the notebook, this is a filename without path in the current directory. Otherwise, this an absolute path.
EXAMPLES:
sage: from sage.misc.temporary_file import graphics_filename sage: print(graphics_filename()) # random, typical filename for sagenb sage0.png
TESTS:
When doctesting, this returns instead a random temporary file. We check that it’s a file inside
SAGE_TMP
and that the extension is correct:sage: fn = graphics_filename(ext=".jpeg") sage: fn.startswith(str(SAGE_TMP)) True sage: fn.endswith('.jpeg') True
-
sage.misc.temporary_file.
tmp_dir
(name='dir_', ext='')¶ Create and return a temporary directory in
$HOME/.sage/temp/hostname/pid/
The temporary directory is deleted automatically when Sage exits.
INPUT:
name
– (default:"dir_"
) A prefix for the directory name.ext
– (default:""
) A suffix for the directory name.
OUTPUT:
The absolute path of the temporary directory created, with a trailing slash (or whatever the path separator is on your OS).
EXAMPLES:
sage: d = tmp_dir('dir_testing_', '.extension') sage: d # random output '/home/username/.sage/temp/hostname/7961/dir_testing_XgRu4p.extension/' sage: os.chdir(d) sage: _ = open('file_inside_d', 'w')
Temporary directories are unaccessible by other users:
sage: os.stat(d).st_mode & 0o077 0
-
sage.misc.temporary_file.
tmp_filename
(name='tmp_', ext='')¶ Create and return a temporary file in
$HOME/.sage/temp/hostname/pid/
The temporary file is deleted automatically when Sage exits.
Warning
If you need a particular file extension always use
tmp_filename(ext=".foo")
, this will ensure that the file does not yet exist. If you were to usetmp_filename()+".foo"
, then you might overwrite an existing file!INPUT:
name
– (default:"tmp_"
) A prefix for the file name.ext
– (default:""
) A suffix for the file name. If you want a filename extension in the usual sense, this should start with a dot.
OUTPUT:
The absolute path of the temporary file created.
EXAMPLES:
sage: fn = tmp_filename('just_for_testing_', '.extension') sage: fn # random '/home/username/.sage/temp/hostname/8044/just_for_testing_tVVHsn.extension' sage: _ = open(fn, 'w')
Temporary files are unaccessible by other users:
sage: os.stat(fn).st_mode & 0o077 0