จะเรียก pip จากสคริปต์ python และทำให้มันติดตั้งในสคริปต์นั้นได้อย่างไร [ทำซ้ำ]

เรามีสคริปต์ Python ในพื้นที่เก็บข้อมูลซอร์สโค้ดซึ่งฉันดูแลรักษาไว้ ลองจินตนาการว่ามันอยู่ในสถานที่

scripts/python/make_salad/make_salad.py

มันถูกตรวจสอบในที่เก็บตามที่เป็นอยู่ ผู้ใช้สคริปต์ต้องการเพียงแค่คลิกที่สคริปต์ใน windows explorer แล้วพวกเขาก็ไป พวกเขาปฏิเสธที่จะใช้บรรทัดคำสั่ง อย่างไรก็ตาม สคริปต์ยังขึ้นอยู่กับแพ็คเกจภายนอกจำนวนมากที่ฉันต้องติดตั้งด้วย ฉันใช้เคล็ดลับต่อไปนี้เพื่อติดตั้งแพ็คเกจใด ๆ ที่จำเป็นในครั้งแรกที่ผู้ใช้รัน บท. มันเป็นไปตามนั้น

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 explorer
  3. จำเป็นต้องติดตั้งแพ็คเกจภายนอกจากภายในสคริปต์
  4. แพ็คเกจไม่ควรทำให้แพ็คเกจทั่วโลกเสียหาย

person bradgonesurfing    schedule 21.08.2019    source แหล่งที่มา
comment
คุณอยากจะติดตั้งสิ่งต่าง ๆ ในสภาพแวดล้อมเสมือนจริงบ้างไหม?   -  person 9769953    schedule 21.08.2019
comment
ใช่ แต่ฉันไม่สามารถให้ผู้ใช้ยุ่งกับบรรทัดคำสั่งได้ ไม่เช่นนั้นพวกเขาจะตกใจกลัว และฉันไม่สามารถตรวจสอบสภาพแวดล้อมเสมือนใดๆ ในการควบคุมแหล่งที่มาหรือสร้างสิ่งใดไว้ล่วงหน้าได้   -  person bradgonesurfing    schedule 21.08.2019
comment
exe-builders คนใดจะช่วยในกรณีนี้ (ขึ้นอยู่กับแพ็คเกจ) โดยจะสร้างไฟล์ปฏิบัติการเดียวจาก 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
Pff นั่นเป็นข้อกำหนดที่ไร้สาระถ้าฉันจะพูดอย่างนั้น และแม้กระทั่งกับ 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"

ครั้งแรกที่รันระบบจะติดตั้ง virtual env และแพ็คเกจที่จำเป็นทั้งหมด ครั้งที่สองมันจะเรียกใช้สคริปต์ภายใต้สภาพแวดล้อมเสมือน

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