在维护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
可执行文件移到其他位置确实可以。
我希望得到一个答案:
--user
添加到args。这个问题很笼统,但下面是MCVE:
def update(piphost):
args = ['pip', 'install',
'--index-url', piphost,
'-U', 'cli']
subprocess.check_call(args)
update('https://mypypiserver:8001')
答案 0 :(得分:0)
最初推测,这里的问题是试图删除正在运行的可执行文件。 Windows不喜欢这种废话,尝试时会抛出PermissionErrors
。不过奇怪的是,您可以肯定地重命名一个正在运行的可执行文件,实际上,几个questions from different tags使用此事实可以进行明显的更改到正在运行的可执行文件。
这也解释了为什么可执行文件似乎从%LOCALAPPDATA%\Programs\Python\Python36\Scripts
开始运行,但无法从%TEMP%
删除。在执行过程中(合法)将其重命名(移动)到%TEMP%
文件夹中,然后pip尝试删除该目录,同时也删除了该文件(非法)。
实现如下:
Path(sys.argv[0]).with_suffix('.exe')
)pip install
更新程序包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)