tempfile.TemporaryDirectory contextmanager 不使用 /tmp 文件夹
tempfile.TemporaryDirectory contextmanager does not use /tmp folder
我使用 tempfile.TemporaryDirectory
class 作为上下文管理器。它应该默认使用 /tmp
文件夹。我尝试过使用默认值,也尝试过强制使用 /tmp
文件夹。它会在调用者脚本所在的位置创建临时文件夹。
设置:
- 红帽 7 Linux
- Python 3.6.6
- 脚本是从 Jenkins 调用的
代码:
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)
输出:
>>> python3 /home/my_home/test.py
home/my_home
home/my_home/my_test_m1vljq2h
我的问题:
有人知道我该如何解决吗?
怎么可能?我检查了 TemporaryDirectory
class 的实现,但我看不出任何原因。如果有人能够解释它的原因,我将非常高兴。
我已经阅读了相关的官方文档以及tempfile
模块的实现,但我没有找到任何相关代码部分可以导致此类问题。
注意: 如果可能的话,我不想继承和更改此模块的许多元素,但我愿意接受想法。
编辑:
完整回溯(来自 Jenkins 运行):
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'
编辑2:
我无法在本地复制它。此问题仅在 Jenkins 中经常发生!
编辑3:
添加行:
logging.info(tempfile._sanitize_params("my_test_", None, None))
Jenkins 中的输出:
2019-11-08 15:09:26 [Thread-1] [INFO] ('my_test_', '', '/tmp', <class 'str'>)
更改行:
logging.info(tempfile._sanitize_params("my_test_", None, "/tmp"))
在詹金斯中添加:
2019-11-08 15:13:46 [Thread-1] [INFO] ('my_test_', '', '/tmp', <class 'str'>)
如果您不给它一个 dir
参数,tempfile.TemporaryDirectory()
对象将使用 tempfile.mkdtemp()
to create the temporary directory from the arguments passed in. This, in turn, will use tempfile.gettempdir()
。
如果你传入一个dir='/tmp'
并且仍然看不到/tmp
中创建的目录,那么有两种可能:
- 您的
prefix
值不是您认为的那样,而是以 /
开头
- 您系统 上的
tempfile
模块 已被更改,其行为方式不再与 standard library version distributed with Python 3.6.0 相同。这些更改可以在磁盘上进行,也可以通过其他 Python 动态更改行为的代码进行。
正常行为是 mkdtemp()
函数调用一个名为 _sanitize_params()
的内部函数,returns dir
如果已设置则不变,并且值gettempdir()
否则:
>>> 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()
然后使用该调用的结果(返回更新的 prefix
、suffix
、dir
以及 bytes
或 str
type) 和随机字符串,为您创建一个新目录。
这导致您可能没有正确排除 prefix
值确实是您认为的那样。 mkdtemp()
函数使用:
os.path.join(dir, prefix + name + suffix)
将前缀、候选 name
(随机值)和后缀(在您的情况下为空字符串)串联起来加入 dir
路径。但请注意,os.path.join()
function 将 丢弃以 /
斜杠 :
开头的参数之前的所有路径元素
>>> import os.path
>>> os.path.join("/foo", "bar")
'/foo/bar'
>>> os.path.join("/foo", "/bar")
'/bar'
因此,您看到的行为也可以通过以斜线开头的前缀来解释,因此:
TMP_DIR_PREFIX = "/home/my_home/my_test_"
会立即产生相同的结果:
>>> 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'
之前已将此报告给 Python 项目 issue #35278。
您可以在 Jenkins 作业中简单地包含两个测试来排除这些选项。确保记录 TMP_DIR_PREFIX
值,以及 tempfile._sanitize_params(TMP_DIR_PREFIX, None, '/tmp')
returns.
如果其中任何一个都没有在您的系统上产生预期的输出,那么您就知道您需要专注于寻找; tempfile
模块行为已更改,或者您假设 TMP_DIR_PREFIX
具有它所具有的值是不正确的。
您可以使用以下 shell 命令检查本地副本是否与发布版本不同:
$ 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
或者您可以计算文件的校验和:
import hashlib
with open(tempfile.__file__, 'rb') as file_to_hash:
tempfile_checksum = hashlib.sha1(file_to_hash.read()).hexdigest()
并将该校验和值与已发布文件的校验和值进行比较:
$ curl -s https://raw.githubusercontent.com/python/cpython/v3.6.0/Lib/tempfile.py | \
> sha1sum
38ad01ccc5972e193e1b96a1de8b7ba1bd8d289d -
如果没有出现任何问题,您将使用调试器单步执行调用或查看所涉及函数的 __module__
属性。例如。例如,如果 _sanitize_params()
被动态更改(monkey patched),那么 tempfile._sanitize_params.__module__
将不会设置为 'tempfile'
。但是,请注意,您的回溯已经显示 TemporaryDirectory.__init__
和 mkdtemp
均取自正确的文件,并且可见的两行的行号与已发布源中的行号相匹配。
我使用 tempfile.TemporaryDirectory
class 作为上下文管理器。它应该默认使用 /tmp
文件夹。我尝试过使用默认值,也尝试过强制使用 /tmp
文件夹。它会在调用者脚本所在的位置创建临时文件夹。
设置:
- 红帽 7 Linux
- Python 3.6.6
- 脚本是从 Jenkins 调用的
代码:
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)
输出:
>>> python3 /home/my_home/test.py
home/my_home
home/my_home/my_test_m1vljq2h
我的问题:
有人知道我该如何解决吗?
怎么可能?我检查了
TemporaryDirectory
class 的实现,但我看不出任何原因。如果有人能够解释它的原因,我将非常高兴。
我已经阅读了相关的官方文档以及tempfile
模块的实现,但我没有找到任何相关代码部分可以导致此类问题。
注意: 如果可能的话,我不想继承和更改此模块的许多元素,但我愿意接受想法。
编辑:
完整回溯(来自 Jenkins 运行):
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'
编辑2:
我无法在本地复制它。此问题仅在 Jenkins 中经常发生!
编辑3:
添加行:
logging.info(tempfile._sanitize_params("my_test_", None, None))
Jenkins 中的输出:
2019-11-08 15:09:26 [Thread-1] [INFO] ('my_test_', '', '/tmp', <class 'str'>)
更改行:
logging.info(tempfile._sanitize_params("my_test_", None, "/tmp"))
在詹金斯中添加:
2019-11-08 15:13:46 [Thread-1] [INFO] ('my_test_', '', '/tmp', <class 'str'>)
如果您不给它一个 dir
参数,tempfile.TemporaryDirectory()
对象将使用 tempfile.mkdtemp()
to create the temporary directory from the arguments passed in. This, in turn, will use tempfile.gettempdir()
。
如果你传入一个dir='/tmp'
并且仍然看不到/tmp
中创建的目录,那么有两种可能:
- 您的
prefix
值不是您认为的那样,而是以/
开头
- 您系统 上的
tempfile
模块 已被更改,其行为方式不再与 standard library version distributed with Python 3.6.0 相同。这些更改可以在磁盘上进行,也可以通过其他 Python 动态更改行为的代码进行。
正常行为是 mkdtemp()
函数调用一个名为 _sanitize_params()
的内部函数,returns dir
如果已设置则不变,并且值gettempdir()
否则:
>>> 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()
然后使用该调用的结果(返回更新的 prefix
、suffix
、dir
以及 bytes
或 str
type) 和随机字符串,为您创建一个新目录。
这导致您可能没有正确排除 prefix
值确实是您认为的那样。 mkdtemp()
函数使用:
os.path.join(dir, prefix + name + suffix)
将前缀、候选 name
(随机值)和后缀(在您的情况下为空字符串)串联起来加入 dir
路径。但请注意,os.path.join()
function 将 丢弃以 /
斜杠 :
>>> import os.path
>>> os.path.join("/foo", "bar")
'/foo/bar'
>>> os.path.join("/foo", "/bar")
'/bar'
因此,您看到的行为也可以通过以斜线开头的前缀来解释,因此:
TMP_DIR_PREFIX = "/home/my_home/my_test_"
会立即产生相同的结果:
>>> 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'
之前已将此报告给 Python 项目 issue #35278。
您可以在 Jenkins 作业中简单地包含两个测试来排除这些选项。确保记录 TMP_DIR_PREFIX
值,以及 tempfile._sanitize_params(TMP_DIR_PREFIX, None, '/tmp')
returns.
如果其中任何一个都没有在您的系统上产生预期的输出,那么您就知道您需要专注于寻找; tempfile
模块行为已更改,或者您假设 TMP_DIR_PREFIX
具有它所具有的值是不正确的。
您可以使用以下 shell 命令检查本地副本是否与发布版本不同:
$ 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
或者您可以计算文件的校验和:
import hashlib
with open(tempfile.__file__, 'rb') as file_to_hash:
tempfile_checksum = hashlib.sha1(file_to_hash.read()).hexdigest()
并将该校验和值与已发布文件的校验和值进行比较:
$ curl -s https://raw.githubusercontent.com/python/cpython/v3.6.0/Lib/tempfile.py | \
> sha1sum
38ad01ccc5972e193e1b96a1de8b7ba1bd8d289d -
如果没有出现任何问题,您将使用调试器单步执行调用或查看所涉及函数的 __module__
属性。例如。例如,如果 _sanitize_params()
被动态更改(monkey patched),那么 tempfile._sanitize_params.__module__
将不会设置为 'tempfile'
。但是,请注意,您的回溯已经显示 TemporaryDirectory.__init__
和 mkdtemp
均取自正确的文件,并且可见的两行的行号与已发布源中的行号相匹配。