PIP安装升级无法删除临时文件

时间:2019-04-23 18:54:13

标签: python permissions pip

在维护CLI实用程序的过程中,我想添加一个update操作,该操作将从PyPI中获取该软件包的最新版本并升级现有安装。

$ cli -V
1.0.23

$ cli update
// many lines of pip spam

$ cli -V
1.0.24  // or etc

这在所有在系统范围内安装Python(在C:\Python36或类似版本中)的计算机上都可以正常工作,但是在安装了Python的用户(在C:\users\username\AppData\Local\Programs\Python\Python36中)的计算机上收到此错误。旧版本已卸载:

Could not install packages due to an EnvironmentError: [WinError 5] Access is denied: 'C:\\Users\\username\\AppData\\Local\\Temp\\pip-uninstall-f5a7rk2y\\cli.exe'
Consider using the `--user` option or check the permissions.

我以为这是由于以下事实:当pip尝试删除错误文本中的cli.exe时,它正在运行,但是此处的路径不是到%LOCALAPPDATA%\Programs\Python\Python36\Scripts exe存在,但改为%TEMP%

如何允许将文件移到那里,但是一旦移走就不能删除? 错误消息所建议的

在安装参数中包含--user (与对此问题的早期编辑有所不同)解决了问题,但将cli可执行文件移到其他位置确实可以。

我希望得到一个答案:

  1. 解释了无法从TEMP目录中删除可执行文件的根本问题,以及...
  2. 为该问题提供解决方案,可以绕过权限错误,或者查询以用户身份安装此软件包,以便代码可以将--user添加到a​​rgs。

这个问题很笼统,但下面是MCVE:

def update(piphost):
    args = ['pip', 'install',
        '--index-url', piphost,
        '-U', 'cli']
    subprocess.check_call(args)

update('https://mypypiserver:8001')

1 个答案:

答案 0 :(得分:0)

最初推测,这里的问题是试图删除正在运行的可执行文件。 Windows不喜欢这种废话,尝试时会抛出PermissionErrors。不过奇怪的是,您可以肯定地重命名一个正在运行的可执行文件,实际上,几个questions from different tags使用此事实可以进行明显的更改到正在运行的可执行文件。

这也解释了为什么可执行文件似乎从%LOCALAPPDATA%\Programs\Python\Python36\Scripts开始运行,但无法从%TEMP%删除。在执行过程中(合法)将其重命名(移动)到%TEMP%文件夹中,然后pip尝试删除该目录,同时也删除了该文件(非法)。

实现如下:

  1. 重命名当前可执行文件(Path(sys.argv[0]).with_suffix('.exe')
  2. pip install更新程序包
  3. 在入口点添加逻辑,以删除重命名的可执行文件(如果存在)。
import click  # I'm using click for my CLI, but YMMV
from pathlib import Path
from sys import argv

def entrypoint():
    # setup.py's console_scripts points cli.exe to here

    tmp_exe_path = Path(argv[0]).with_suffix('.tmp')
    try:
        tmp_exe_path.unlink()
    except FileNotFoundError:
        pass
    return cli_root

@click.group()
def cli_root():
    pass

def update(pip_host):

    exe_path = Path(argv[0])
    tmp_exe_path = exe_path.with_suffix('.tmp')
    handle_renames = False
    if exe_path.with_suffix('.exe').exists():
        # we're running on Windows, so we have to deal with this tomfoolery.
        handle_renames = True
        exe_path.rename(tmp_exe_path)
    args = ['pip', 'install',
        '--index-url', piphost,
        '-U', 'cli']
    try:
        subprocess.check_call(args)
    except Exception:  # in real code you should probably break these out to handle stuff
        if handle_renames:
            tmp_exe_path.rename(exe_path)  # undo the rename if we haven't updated

@cli_root.command('update')
@click.option("--host", default='https://mypypiserver:8001')
def cli_update(host):
    update(host)