[ Python ] Install all dependency packages

2020. 1. 25. 18:17분석 Python/구현 및 자료

한 번만 실수로 광고를 눌러주세요! 블로그 운영에 큰 힘이 됩니다. :)

상상한 구조

인터넷이 안 되는 환경에 패키지를 설치해야 할 일이 있다.
이런 일이 있을 때, 만약 도커 같은 것을 굉장히 편리하게 할 수 있지만, 아직 안 쓰는 곳도 있을 때는 의존성을 다 확인해서 가져가야 한다.

R에서는 이러한 것을 고려해서 한번 설치하면 모든 dependency를 설치해주는 miniCRAN이라는 방법이 있지만, 
아쉽게도 파이썬에서는 모든 dependency까지는 고려해주는 것을 아직까진 본 적이 없다.

pip download 를 사용하면, 해당 패키지에 대한 Dependency까지는 고려해주지만, 그 Dependency의 Dependency 패키지는 설치를 안 해주는 것 같다. 

https://pip.pypa.io/en/stable/reference/pip_download/

 

pip download — pip 20.0.2 documentation

pip also supports downloading from “requirements files”, which provide an easy way to specify a whole environment to be downloaded. pip download does the same resolution and downloading as pip install, but instead of installing the dependencies, it collect

pip.pypa.io

그래서 이번에는 완벽하지는 않지만, 일단 필요한 것은 전부 긁어오는 것을 만들어봤다.
물론 이 작업을 하고 나서 같은 패키지의 2개의 버전이 생길 수 있다.
예를 들어 아래의 그림과 같은 일이 생긴다.

이러한 문제가 발생하는 이유는 pip download를 통해 한 패키지의 dependency를 설치하는데, 그때는 가장 update된 패키지를 가져오는 일이 발생하기 때문인 것 같다.
그래서 내가 원하는 것은 joblib-0.13.2인데, 다른 패키지 dependency에서 joblib이 있어서 0.14.1를 다운로드하게 되는 일이 발생하는 것 같다.

그래서 이번 글에 목표는 필요한 패키지의 모든 dependency 패키지를 긁어오는 것이다.

import os , re
import numpy as np

일단 내가 원하는 패키지는 다음과 같다.
이제 이것의 모든 dependency를 긁어와보자.

pkgs = ["scikit-learn==0.21.3","scipy==1.2.1",
        "tensorflow-gpu==1.14.0","gputil==1.4.0","tqdm==4.35.0",
        "mysql-connector-python==8.0.17"]
        

패키지를 설치하면서 log를 찍으면 다음과 같은 말이 나온다.

Collecting matplotlib==3.1.1
  Using cached https://files.pythonhosted.org/packages/57/4f/dd381ecf6c6ab9bcdaa8ea912e866dedc6e696756156d8ecc087e20817e2/matplotlib-3.1.1-cp36-cp36m-manylinux1_x86_64.whl
  Saved ./pkg/matplotlib-3.1.1-cp36-cp36m-manylinux1_x86_64.whl
Collecting pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 (from matplotlib==3.1.1)
  Using cached https://files.pythonhosted.org/packages/5d/bc/1e58593167fade7b544bfe9502a26dc860940a79ab306e651e7f13be68c2/pyparsing-2.4.6-py2.py3-none-any.whl
  Saved ./pkg/pyparsing-2.4.6-py2.py3-none-any.whl
Collecting numpy>=1.11 (from matplotlib==3.1.1)
  Using cached https://files.pythonhosted.org/packages/62/20/4d43e141b5bc426ba38274933ef8e76e85c7adea2c321ecf9ebf7421cedf/numpy-1.18.1-cp36-cp36m-manylinux1_x86_64.whl
  Saved ./pkg/numpy-1.18.1-cp36-cp36m-manylinux1_x86_64.whl
Collecting cycler>=0.10 (from matplotlib==3.1.1)
  Using cached https://files.pythonhosted.org/packages/f7/d2/e07d3ebb2bd7af696440ce7e754c59dd546ffe1bbe732c8ab68b9c834e61/cycler-0.10.0-py2.py3-none-any.whl
  Saved ./pkg/cycler-0.10.0-py2.py3-none-any.whl
Collecting kiwisolver>=1.0.1 (from matplotlib==3.1.1)
  Using cached https://files.pythonhosted.org/packages/f8/a1/5742b56282449b1c0968197f63eae486eca2c35dcd334bab75ad524e0de1/kiwisolver-1.1.0-cp36-cp36m-manylinux1_x86_64.whl
  Saved ./pkg/kiwisolver-1.1.0-cp36-cp36m-manylinux1_x86_64.whl
Collecting python-dateutil>=2.1 (from matplotlib==3.1.1)
  Using cached https://files.pythonhosted.org/packages/d4/70/d60450c3dd48ef87586924207ae8907090de0b306af2bce5d134d78615cb/python_dateutil-2.8.1-py2.py3-none-any.whl
  Saved ./pkg/python_dateutil-2.8.1-py2.py3-none-any.whl
Collecting six (from cycler>=0.10->matplotlib==3.1.1)
  Using cached https://files.pythonhosted.org/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl
  Saved ./pkg/six-1.14.0-py2.py3-none-any.whl
Collecting setuptools (from kiwisolver>=1.0.1->matplotlib==3.1.1)
  Using cached https://files.pythonhosted.org/packages/a7/c5/6c1acea1b4ea88b86b03280f3fde1efa04fefecd4e7d2af13e602661cde4/setuptools-45.1.0-py3-none-any.whl
  Saved ./pkg/setuptools-45.1.0-py3-none-any.whl
Successfully downloaded matplotlib pyparsing numpy cycler kiwisolver python-dateutil six setuptools

저 예제에서는 cloudpickle이 하나밖에 없지만 dependecy가 있다면 아래 또 다른 패키지들이 설치될 것이다.
그러면 이제는 아래 만든 함수를 통해서 긁어온다.
그러면 a는 다음과 같이 나오게 된다.
이제 저기서 다시 저것의 dependency를 다운로드하기 위해서 setuptools45.1.0만 가져와서
setutools==45.1.0로 만들어야 한다..

a = [re.split("./pkg/", line)[-1].split("\n")[0] for line in lines if re.search("Saved", line)]

## a=[setuptools-45.1.0-py3-none-any.whl]

### => change [setuptools==45.1.0]

이렇게 만드는 함수는 append 함수로 만들었습니다.

def append(a) :
    store = []
    for i in a :
        lists = i.split("-")
        if re.search("\d", lists[1] ) is not None :
            pkg_name = f'{lists[0]}=={lists[1]}'
        else :
            pkg_name = f'{lists[0]}-{lists[1]}=={lists[2]}'
        pkg_name = pkg_name.split(".tar.gz")[0]
        print(pkg_name)
        store.append(pkg_name)
    return store        
    
## append(a)
### -> [setuptools==45.1.0]

그래서 저렇게 만든 것을 새로 설치할 패키지 list에 추가한다.

store = append(a)
pkgs = pkgs + store
pkgs = list(dict.fromkeys(pkgs))
pkgs.remove(pkg)

위의 코드의 의미는 일단 패키지 list에 붙이고, 중복 제거하고 그리고 설치했으니 해당 패키지를 list에서 지우는 코드이다.

그래서 전체 코드는 다음과 같다.

import os , re
import numpy as np
pkgs = ["scikit-learn==0.21.3","scipy==1.2.1",
        "tensorflow-gpu==1.14.0","gputil==1.4.0","tqdm==4.35.0",
        "mysql-connector-python==8.0.17"]
def append(a) :
    store = []
    for i in a :
        lists = i.split("-")
        if re.search("\d", lists[1] ) is not None :
            pkg_name = f'{lists[0]}=={lists[1]}'
        else :
            pkg_name = f'{lists[0]}-{lists[1]}=={lists[2]}'
        pkg_name = pkg_name.split(".tar.gz")[0]
        print(pkg_name)
        store.append(pkg_name)
    return store        
    
total_store = []
while True :
    i = 0 
    before = len(os.listdir("./pkg/"))
    pkg = pkgs[i]
    ck = os.system(f"pip download {pkg} --only-binary=:all: -d ./pkg --python-version 36 --abi cp36m > installed_{pkg}.txt")
    print(f"Install {pkg} Check : {ck}")
    if ck != 0 :
        ck = os.system(f"pip download {pkg} -d ./pkg > installed_{pkg}.txt")
    f = open(f"installed_{pkg}.txt", 'r')
    lines = f.readlines()
    a = [re.split("./pkg/", line)[-1].split("\n")[0] for line in lines if re.search("Saved", line)]
    store = append(a)
    print("="*40)
    print(store)
    print("="*40)
    total_store.extend(store)
    try :
        store.remove(pkg)
    except :
        pass
    total_store = list(dict.fromkeys(total_store))
    pkgs = pkgs + store
    pkgs = list(dict.fromkeys(pkgs))
    pkgs.remove(pkg)
    print(pkgs)
    after = len(os.listdir("./pkg/"))
    print(before , after)
    i += 1
    if len(pkgs) == 0 :
#     if before == after :
        print("탈출")
        break

이제 돌고 나서 중복을 체크하면 다음과 같은 결과가 나온다.

result = append(os.listdir("./pkg/")[1:])
import collections
dup_check = [store.split("==")[0] for store in result ]
print(collections.Counter(dup_check))

패키지를 모두 가져왔다. 그렇지만 한 패키지에 여러 버전이 같이 다운로드가 된 것을 알 수 있다.
음... 여기서부터는 더 아이디어가 생기면 해결해보겠다. 
일단은 여기까지 하면 모든 의존성 패키지를 가져온 것이라고 할 수 있다.

 

일단 끝~

728x90