5
\$\begingroup\$

For my package dealing with pptx-files I need to create a temporary *.pptx file, to open in PowerPoint (e.g. to export as PDF). After some research I found that tempfile.NamedTemporaryFile is broken on Windows (you could have a temporary file, or you could get a filename - but you cannot have both). So I tried to build a function, that returns a filename of a temporary file, and takes care of creating and deleting the file internally. After going in circles many times I came up with the following code monster:

import os
from typing import Generator

import tempfile
import pptx


class TempFileGenerator:
    generator = None
    @classmethod
    def get_new_generator(cls, prs):
        TempFileGenerator.generator = cls.temp_generator(prs)
        return cls.generator

    @staticmethod
    def temp_generator(prs: pptx.presentation.Presentation) -> Generator[str, None, None]:
        temp_pptx = None
        try:
            with tempfile.NamedTemporaryFile(suffix='.pptx', delete=False) as temp_pptx:
                temp_pptx.close()  # needed on windows systems to access file
                prs.save(temp_pptx.name)
                yield temp_pptx.name
        finally:
            if temp_pptx is not None:
                try:
                    os.remove(temp_pptx.name)
                except PermissionError as e:  # file still in use somewhere
                    pass
            else:
                print("How did I get here?")


def get_temporary_pptx_filename(prs: pptx.presentation.Presentation) -> str:
    """
    Generates a temporary pptx file. Returns the filename of the temporary file.
    """
    my_generator = TempFileGenerator.get_new_generator(prs)
    for filename in my_generator:
        return filename

Calling get_temporary_pptx_filename() does what I want. Using a class to save the generator ensures, that the clean up in finally does not happen as soon as I return the filename in get_temporary_pptx_filename. But looking at this code monster, I hope there is a better way to do it? If not I guess it would be easier to not use the tempfile module at all.

\$\endgroup\$
  • \$\begingroup\$ You forgot adding import os; from typing import Generator to the code which makes it off-topic \$\endgroup\$ – Grajdeanu Alex. Jan 18 at 19:45
  • \$\begingroup\$ thanks @Grajdeanu, I made a copy&paste error. \$\endgroup\$ – natter1 Jan 18 at 19:55
  • 2
    \$\begingroup\$ @GrajdeanuAlex. No it doesn't. \$\endgroup\$ – Peilonrayz Jan 19 at 7:58
1
\$\begingroup\$
  • After some research I found that tempfile.NamedTemporaryFile is broken on Windows (you could have a temporary file, or you could get a filename - but you cannot have both).

    This is just flat out wrong. Maybe you're misunderstanding something and conflating two things. But this is just factually incorrect. And can be seen in your code:

    temp_pptx.close()  # needed on windows systems to access file
    prs.save(temp_pptx.name)
    

    The first line is closing the temporary file, and the second is getting it's filename.

  • needed on windows systems to access file

    This is due to a security feature.

    The file is created securely, using the same rules as mkstemp().

    Creates a temporary file in the most secure manner possible. There are no race conditions in the file’s creation, assuming that the platform properly implements the os.O_EXCL flag for os.open(). The file is readable and writable only by the creating user ID. If the platform uses permission bits to indicate whether a file is executable, the file is executable by no one. The file descriptor is not inherited by child processes.

    Clearly you don't need this, so tempfile is pretty much useless to you and you will be fighting it.

  • Your code looks very much like a mix of an exploit of how generators and garbage collection work, and some dodgy monkey patch to get around tempfile. Both are on a level of wtf/m that I'd suggest just writing your own temporary file class.

import os


class TemporaryFile:
    __slots__ = ('_file',)

    def __init__(self, file):
        self._file = file

    @classmethod
    def open(cls, *args, **kwargs):
        return cls(open(*args, **kwargs))

    def __enter__(self):
        return self._file.__enter__()

    def __exit__(self, exc_type, exc_value, traceback):
        ret = self._file.__exit__(exc_type, exc_value, traceback)
        os.remove(self._file.name)
        return ret


if __name__ == '__main__':
    with TemporaryFile.open('foo.txt', 'w+') as f:
        pass
    with TemporaryFile(open('foo.txt', 'w+')) as f:
        pass

I can write to the file using Atom using the above. Even if Excel has more security operations, then you can easily work around them without monkey patches.

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ I now that I can use the file (as I did with closing) and use also the name. But I can not use it as an temporary. Because I have to set delete=False. So I have a file and a name - but no temporary file. The Named... Part only makes sense to me, if I want to use the file via its filesystem path. \$\endgroup\$ – natter1 Jan 19 at 11:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.