petersohn
petersohn

Reputation: 11730

Renaming temporary files in Python

I want to do the following. Create a temporary file and work on it. If successful, rename it to a real file. If it fails, then delete the file.

I tried the following:

with tempfile.NamedTemporaryFile(dir=os.getcwd()) as f:
    f.write(b'foobar')
    f.delete = False
    f.flush()
    os.rename(f.name, 'foobar')

However, I still get an exception that the file does not exist when it tries to delete it. I might use a try-catch to ignore this error, but that would be ugly and might ignore other errors too. Or I could use mkstemp() and manage deletion myself, but that has a problem that it returns a file descriptor instead of a file object, and I couldn't find a way to create a file object from the file descriptor.

Is there any proper solution to the problem?

Upvotes: 0

Views: 1890

Answers (3)

Benjy Wiener
Benjy Wiener

Reputation: 1265

To avoid the FileNotFoundError without disabling automatic deletion, you can create a hard link instead of moving the file:

import os
import tempfile

with tempfile.NamedTemporaryFile('w') as temp:
    # write some data to temp
    if success:
        os.link(temp.name, permanent_path)

If the link is created, the file will persist after the temporary file is automatically unlinked. If not, it will be deleted automatically.

Upvotes: 0

Divyesh Peshavaria
Divyesh Peshavaria

Reputation: 599

You need to pass delete=False during initialization, setting f.delete = False later is not working. I was able to rename it after writing with this code:

with tempfile.NamedTemporaryFile(dir=os.getcwd(), delete=False) as f:
    f.write(b'foobar')
    f.flush()
    os.rename(f.name, 'foobar')

I went through the code of Lib/tempfile.py to confirm this behavior.

When you call NamedTemporartyFile, it initializes and returns an instance of _TemporaryFileWrapper class defined in the same file. Look at the __init__() of this class:

def __init__(self, file, name, delete=True):
        self.file = file
        self.name = name
        self.delete = delete
        self._closer = _TemporaryFileCloser(file, name, delete)

And here is the __init__() of _TemporaryFileCloser class:

def __init__(self, file, name, delete=True):
        self.file = file
        self.name = name
        self.delete = delete

When you exit from the with statement, during closing of the file, the self.delete attribute of _TemporaryFileCloser is checked. While when you do f.delete = False, you change the self.delete attribute of the _TemporaryFileWrapper class instead of the _TemporaryFileCloser class. Therefore, the closing behavior is not altered even if it looks like that on developer side. So you must pass delete=False during the initialization in the with statement like in my code example.

If you still want to modify the behavior, you can access self._closer attribute, it will be a little ugly however.

with tempfile.NamedTemporaryFile(dir=os.getcwd()) as f:
    f.write(b'foobar')
    f.flush()
    f._closer.delete = False
    os.rename(f.name, 'foobar')

Btw, I performed this on Ubuntu WSL on Windows 10. I'm not sure which OS you are using, that might affect the behavior.

Upvotes: 3

vidstige
vidstige

Reputation: 13085

This can be achieved with the NamedTemporaryFile class. Once closed, you can move it to the desired location/filename. You need to close the file before moving it to avoid the deletion error.

from tempfile import NamedTemporaryFile
import shutil

with NamedTemporaryFile(delete=False) as tmp:
    tmp.write(b'hi')

# move file to target location/name
shutil.move(tmp.name, 'target')

Upvotes: 2

Related Questions