tempfile.TemporaryDirectory().name should return pathlib.Path instead of str

if not that then maybe another property instead? Something like

>>> tempfile.TemporaryDirectory().name
'C:\\Users\\monarch\\AppData\\Local\\Temp\\tmp57z91nmz'
>>> tempfile.TemporaryDirectory().path
WindowsPath('C:/Users/monarch/AppData/Local/Temp/tmp57z91nmz')

Would make things a bit smoother when working with Temporary Directories where you might have to do further path operations

1 Like

I’m curious how you’re using it to actually end up with a TemporaryDirectory object. I always use:

with TemporaryDirectory() as tmpdir:
    ...

The TemporaryDirectory object never gets named, I just get the string tmpdir. If I need a Path, I use Path(tmpdir).

I suppose if I wrote x = TemporaryDirectory(), then Path(x.name) is not significantly less convenient than x.path. If we are going to modify the interface, I would prefer it to implement the PathLike protocol so I could use Path(x), as I just like the explicitness.

2 Likes

FYI you can’t change the return type as it would break too much code.

5 Likes

That’s understandable, perhaps a different property can be considered then?

I’m working with something along the lines of:

if user_specified_dir:
        work_dir = user_specified_dir
else:
    tempdir = tempfile.TemporaryDirectory()
    work_dir = Path(tempdir.name)


function_that_accepts_Path_does_path_operations(work_dir )
function_that_also_accepts_Path_does_different_path_operations(work_dir )

Indeed, it’s not a big problem. I was a bit surprised when I noticed tempdir returns a string instead of a path because I sort of expected it to return a Path. I do feel it would be a tad bit smoother and convenient to have another path property over doing Path(tempdir.name)

Would the following work?

if user_specified_dir:
    work_dir = user_specified_dir
else:
    tempdir = tempfile.TemporaryDirectory()
    work_dir = Path(tempdir)

This would require defining TemporaryDirectory.__fspath__(), which seems like a good idea to me anyway. In any event, you wouldn’t be able to use this until your minimum Python supported .path or .__fspath__, so FWIW I would also generally guard functions that expect Paths to accept PathLikes and strings:

def function_that_accepts_Path_does_path_operations(work_dir):
    work_dir = Path(work_dir)

Then for now you could write work_dir = tempdir.name, and eventually shift to work_dir = tempdir or work_dir = Path(tempdir).

For now, the easiest thing for me to do is just Path(tempdir.name) which is what I’m doing. It’s fine and works. I’m also aware I won’t be able to use this if it did get implemented for a long while because of having to support older versions. But regardless I wanted to get the idea out for a bit of extra QoL/convenience if it ever gets implemented.

Or a parameter as that can still type appropriately via an overload.

I wonder if a viable alternative would be to take advantage of @barneygale’s great work on extending pathlib, and fit TemporaryDirectory into that class hierarchy such that it retains its current behavior but can be used as a Path as well. Maybe that doesn’t make sense, but it sounds like a satisfying solution :sweat_smile:

1 Like

First impression: I agree that TemporaryDirectory should be os.PathLike, and that Path(my_temp_dir) would obviate the need for a path attribute.

I’d love to make TemporaryDirectory a subclass of Path, but the difference in the meaning of the name attribute makes it incompatible, and these are heavily-used, longstanding APIs.

3 Likes

I’m sorry that I don’t have time to find out why, but in basically every code base I maintain I always have a wrapper around that context manager that resolves the path before yielding: hatch/src/hatch/utils/fs.py at 35f8ffdacc937bdcf3b250e0be1bbdf5cde30c4c · pypa/hatch · GitHub

I know this fixes issues for macOS but I can’t find the two issues that I have in mind. Something about the temporary folder actually being a symbolic link.

1 Like

I have also encountered that: cache propagation issue on OSX · Issue #108 · nipype/pydra · GitHub

/var -> /private/var, which can lead to inconsistencies when one function resolves and another doesn’t, so it’s simplest to just resolve once.

I’m not quite sure how that plays into this discussion. Are you suggesting that __fspath__ should return a pre-normalized path?

Oh sorry about that. My point was that I must not be the only one and that many people are already calling Path over the return directory so maybe this wouldn’t be too beneficial.

1 Like

Got it. Agreed, the benefit is marginal, but it’s also quite a small patch:

Patch
diff --git a/Lib/tempfile.py b/Lib/tempfile.py
index cbfc172a78..c07c6f620c 100644
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -945,6 +945,9 @@ def __exit__(self, exc, value, tb):
         if self._delete:
             self.cleanup()
 
+    def __fspath__(self):
+        return self.name
+
     def cleanup(self):
         if self._finalizer.detach() or _os.path.exists(self.name):
             self._rmtree(self.name, ignore_errors=self._ignore_cleanup_errors)
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
index b64b6a4f2b..3108ce1ff6 100644
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -1990,5 +1990,10 @@ def test_delete_false(self):
         self.assertTrue(os.path.exists(working_dir))
         shutil.rmtree(working_dir)
 
+    def test_pathlike_protocol(self):
+        td = tempfile.TemporaryDirectory()
+
+        self.assertEqual(Path(td), Path(td.name))
+
 if __name__ == "__main__":
     unittest.main()

To me, the main question is whether it would encourage uses like Path(TemporaryDirectory()), which creates a directory that immediately gets deleted when the argument is garbage collected, and result in user confusion that the context manager avoids.

3 Likes

I actually had no idea there’s a case like this with MacOS and this to me sounds another good reason why TemporaryDirectory should have an alternate return where it returns a resolved path to both satisfy a path return type and fix the MacOS issue.

TemporaryDirectory claims it works on all supported platforms, so I just expect it to work without needing to do .resolve() for a specific platform.

It does work. Issues crop up when apps don’t handle symlinks uniformly and then expect to compare, hash or find relative paths, so it is reasonable for an app or library to choose to resolve them, but I disagree that Python’s stdlib should be doing that.

1 Like

Given that pitfall and the backwards-compatibility issues with changing the return type, I wonder if there’s value in adding a separate tempfile.TemporaryPath that would provide this convenience.

4 Likes