Search code examples
pythontemporary-files

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?


Solution

  • 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.