Как вызвать pip из скрипта Python и установить его локально в этот скрипт?

У нас есть скрипт Python в нашем репозитории исходного кода, который я поддерживаю. Давайте представим, что это на месте

scripts/python/make_salad/make_salad.py

Он регистрируется в репозитории как есть. Пользователи скрипта хотят просто щелкнуть скрипт в проводнике Windows и все. Они отказываются использовать командную строку. Однако сценарий также зависит от многих внешних пакетов, которые мне нужно установить. Я использовал следующий прием для установки любых пакетов, необходимых при первом запуске пользователя. сценарий. Это похоже на

def install(package):
    # This is an evil little function
    # that installs packages via pip.
    # This means the script can install
    # it's own dependencies.
    try:
        __import__(package)
    except:
        import subprocess
        subprocess.call([sys.executable, "-m", "pip", "install", package])

install("colorama")
install("pathlib")
install("iterfzf")
install("prompt_toolkit")
install("munch")
install("appdirs")
install("art")
install("fire")

import os
import tkFileDialog
import getpass
import json
import shutil
import subprocess
import sys
import pprint
import art

# <snip> out all my business logic
print("Making Salad")

Однако мне это не нравится, потому что он устанавливает пакеты в глобальный репозиторий пакетов. Я бы хотел, чтобы все пакеты были установлены примерно так

scripts/python/make_salad/make_salad.py
                         /__packages__
                                      /colorama
                                      /pathlib
                                      /iterfzf
                                      ...
                                      /fire

и тогда этот каталог будет первым в пути поиска при вызове импорта. Можно ли взломать приведенный выше скрипт, чтобы это стало возможным?

Обратите внимание на требования

  1. В репозитории хранится только один скрипт
  2. Пользователи должны щелкнуть скрипт в проводнике Windows.
  3. Требовать, чтобы pip устанавливал внешние пакеты из скрипта
  4. Пакеты не должны загрязнять глобальные пакеты

person bradgonesurfing    schedule 21.08.2019    source источник
comment
Итак, вы хотели бы, чтобы вещи были установлены в своего рода виртуальной среде?   -  person 9769953    schedule 21.08.2019
comment
Да, но я не могу позволить пользователям возиться с командной строкой, иначе они сходят с ума. И я не могу проверить какие-либо виртуальные среды в системе контроля версий или выполнить предварительную сборку.   -  person bradgonesurfing    schedule 21.08.2019
comment
Поможет ли в этом случае какой-нибудь из exe-сборщиков (в зависимости от пакетов); в том, что он создает один исполняемый файл из Python и всех соответствующих пакетов.   -  person 9769953    schedule 21.08.2019
comment
Нет. Я не могу регистрировать большие исполняемые файлы в исходном коде.   -  person bradgonesurfing    schedule 21.08.2019
comment
может быть, создать venv из скрипта? docs.python.org/3/library/venv.html   -  person vinzBad    schedule 21.08.2019
comment
Пфф, смешной набор требований, если можно так выразиться; и даже с Python 2. Рад, что я не в вашем положении.   -  person 9769953    schedule 21.08.2019
comment
@vinzBad Требование (одно из них) — Python 2.7; venv был встроен только с Python 3.3, согласно вашей ссылке.   -  person 9769953    schedule 21.08.2019
comment
Я могу запустить virtualenv с python2.7 через pip. Это работает довольно хорошо, чтобы написать его, и теперь у меня есть рабочее решение. stackoverflow.com/a/57604352/158285   -  person bradgonesurfing    schedule 22.08.2019


Ответы (2)


Оказывается, не так уж сложно использовать virtualenv прямо из скрипта для решения этой проблемы. Я написал небольшой вспомогательный класс.

import sys
import subprocess

class App:
    def __init__(self, virtual_dir):
        self.virtual_dir = virtual_dir
        self.virtual_python = os.path.join(self.virtual_dir, "Scripts", "python.exe")

    def install_virtual_env(self):
        self.pip_install("virtualenv")
        if not os.path.exists(self.virtual_python):
            import subprocess
            subprocess.call([sys.executable, "-m", "virtualenv", self.virtual_dir])
        else:
            print("found virtual python: " + self.virtual_python)

    def is_venv(self):
        return sys.prefix==self.virtual_dir

    def restart_under_venv(self):
        print("Restarting under virtual environment " + self.virtual_dir)
        subprocess.call([self.virtual_python, __file__] + sys.argv[1:])
        exit(0)

    def pip_install(self, package):
        try:
            __import__(package)
        except:
            subprocess.call([sys.executable, "-m", "pip", "install", package, "--upgrade"])

    def run(self):
        if not self.is_venv():
            self.install_virtual_env()
            self.restart_under_venv()
        else:
            print("Running under virtual environment")

И может использовать его из верхней части основного скрипта make_salad.py вот так

pathToScriptDir = os.path.dirname(os.path.realpath(__file__))

app = App(os.path.join(pathToScriptDir, "make_salad_virtual_env"))

app.run()

app.install("colorama")
app.install("pathlib")
app.install("iterfzf")
app.install("prompt_toolkit")
app.install("munch")
app.install("appdirs")
app.install("art")
app.install("fire")
app.install("appdirs")

print "making salad"

При первом запуске он установит виртуальную среду и все необходимые пакеты. Второй раз он просто запустит скрипт в виртуальной среде.

found virtual python: C:\workspace\make_salad_virtualenv\Scripts\python.exe
Restarting under virtual environment C:\workspace\make_salad_virtualenv\Scripts
Running under virtual environment
Making Salad
person bradgonesurfing    schedule 22.08.2019

Возможно, вы могли бы использовать параметр --prefix с pip, изменить sys.path, чтобы включить этот префикс (+ lib/python2.7/site-packages/). Я думаю, что --prefix будет работать даже с относительным путем:

def install(package):
    try:
        __import__(package)
    except:
        import subprocess
        subprocess.call([sys.executable, "-m", "pip", "install", package, "--prefix="packages"])
.
.
.
sys.path.append("packages/lib/python2.7/site-packages/")
.
.
.
import art
.
.
.

(не проверено)

person 9769953    schedule 21.08.2019