I use the tempfile.TemporaryDirectory
class as a contextmanager. It should use the /tmp
folder in default. I have tried to use the default value and also tried to force to use the /tmp
folder. It creates the temp folder to where the caller script is.
Set-up:
Code:
import tempfile
import os
print(os.path.dirname(__file__))
TMP_DIR_PREFIX = "my_test_"
with tempfile.TemporaryDirectory(prefix=TMP_DIR_PREFIX, dir="/tmp") as tmp_dir:
print(tmp_dir)
Output:
>>> python3 /home/my_home/test.py
home/my_home
home/my_home/my_test_m1vljq2h
My questions:
Does anybody have any idea how I can solve it?
How is it possible? I have checked the implementation of the TemporaryDirectory
class but I couldn't see any reasons. I would be very happy if somebody would be able to explain the reason of it.
I have already read the related official documentation as well as the implementation of tempfile
module but I didn't find any related code part which can cause this type of issue.
NOTE: If it is possible I don't want to inherit and change many elements of this module but I am open for ideas.
EDIT:
Complete traceback (From Jenkins running):
File "/my_path/python/3.6.0/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/my_path/python/3.6.0/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "/home/my_home/copy_to_location.py", line 143, in upload_files
with tempfile.TemporaryDirectory(prefix=TMP_DIR_PREFIX, dir="/tmp") as tmp_dir:
File "/my_path/python/3.6.0/lib/python3.6/tempfile.py", line 790, in __init__
self.name = mkdtemp(suffix, prefix, dir)
File "/my_path/python/3.6.0/lib/python3.6/tempfile.py", line 368, in mkdtemp
_os.mkdir(file, 0o700)
PermissionError: [Errno 13] Permission denied: '/home/my_home/my_test_q1pldmf2'
EDIT2:
I am not able to reproduce it in local. This issue happens only in Jenkins constantly!
EDIT3:
Added line:
logging.info(tempfile._sanitize_params("my_test_", None, None))
Output in Jenkins:
2019-11-08 15:09:26 [Thread-1] [INFO] ('my_test_', '', '/tmp', <class 'str'>)
Changed line:
logging.info(tempfile._sanitize_params("my_test_", None, "/tmp"))
Added in Jenkins:
2019-11-08 15:13:46 [Thread-1] [INFO] ('my_test_', '', '/tmp', <class 'str'>)
The tempfile.TemporaryDirectory()
object uses tempfile.mkdtemp()
to create the temporary directory from the arguments passed in. This, in turn, will use tempfile.gettempdir()
if you don't give it a dir
argument.
If you are passing in a dir='/tmp'
and still don't see the directory created in /tmp
, then there are two possibilities:
prefix
value is not what you think it is, but rather starts with a /
tempfile
module on your system has been altered and is no longer behaving the same way as the standard library version distributed with Python 3.6.0. The changes could have been made on disk, or by other Python code that changed the behaviour dynamically.The normal behaviour is for the mkdtemp()
function to call an internal function named _sanitize_params()
, which returns dir
unchanged if it is set, and the value of gettempdir()
otherwise:
>>> import tempfile
>>> tempfile._sanitize_params('my_test_', None, '/tmp')
('my_test_', '', '/tmp', <class 'str'>)
>>> tempfile._sanitize_params('my_test_', None, None)
('my_test_', '', '/tmp', <class 'str'>)
>>> tempfile.gettempdir()
'/tmp'
mkdtemp()
then uses the results of that call (returning updated prefix
, suffix
, dir
and either the bytes
or str
type) together with random strings, to create a new directory for you.
This leads to the possibility is that you haven't ruled out, properly, that the prefix
value is indeed what you think it is. The mkdtemp()
function uses:
os.path.join(dir, prefix + name + suffix)
to join the dir
path with the concatenation of your prefix, the candidate name
(random value) and the suffix (empty string in your case). But note that the os.path.join()
function will discard any path elements that come before an argument that starts with a /
slash:
>>> import os.path
>>> os.path.join("/foo", "bar")
'/foo/bar'
>>> os.path.join("/foo", "/bar")
'/bar'
So the behaviour you see could also be explained by your prefix starting with a slash, so:
TMP_DIR_PREFIX = "/home/my_home/my_test_"
would immediately produce the same result:
>>> TMP_DIR_PREFIX = "/home/my_home/my_test_"
>>> tempfile.mkdtemp(prefix=TMP_DIR_PREFIX, dir="/tmp")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/.../lib/python3.6/tempfile.py", line 368, in mkdtemp
_os.mkdir(file, 0o700)
PermissionError: [Errno 13] Permission denied: '/home/my_home/my_test_v4cqpamm'
This was reported previously to the Python project as issue #35278.
You can trivially include two tests in your Jenkins job to rule out these options. Make sure you log the TMP_DIR_PREFIX
value, as well as what tempfile._sanitize_params(TMP_DIR_PREFIX, None, '/tmp')
returns.
If either doesn't produce the expected output on your system, then you know you need to focus on looking for; either the tempfile
module behaviour has changed, or your assumption that TMP_DIR_PREFIX
has the value that it does is incorrect.
You can check if the local copy differs from the publish version with the following shell command:
$ diff -u \
> <(curl -s https://raw.githubusercontent.com/python/cpython/v3.6.0/Lib/tempfile.py) \
> /my_path/python/3.6.0/lib/python3.6/tempfile.py
or you could calculate a checksum for the file:
import hashlib
with open(tempfile.__file__, 'rb') as file_to_hash:
tempfile_checksum = hashlib.sha1(file_to_hash.read()).hexdigest()
and compare that checksum value with the one for the published file:
$ curl -s https://raw.githubusercontent.com/python/cpython/v3.6.0/Lib/tempfile.py | \
> sha1sum
38ad01ccc5972e193e1b96a1de8b7ba1bd8d289d -
If that doesn't turn up anything, you'll either have step through the calls with a debugger or look at the __module__
attributes of the functions involved. E.g. if _sanitize_params()
was dynamically altered (monkey patched) then tempfile._sanitize_params.__module__
will not be set to 'tempfile'
, for example. However, note that your traceback already shows that both TemporaryDirectory.__init__
and mkdtemp
are taken from the correct file and that the line numbers for the two lines visible match those in the published source.