파이선 가상환경을 품은 패키지 관리자, pipenv

파이선은 개발 환경은 민감합니다. 파이선 버전 2 혹은 3을 선택하는 문제부터 의존성 걸린 패키지를 알맞게 선택하는 일은 쉽지 않습니다. 그렇기 때문에 virtualenvconda 같은 가상환경을 통해 문제를 한정시켜서 목적에 맞지 않는 패키지로 인한 부작용(side-effect)를 제거합니다.

의존성은 배포할 때도 문제가 됩니다. 개발 장비와 동일한 서비스 환경을 갖추는 일은 생각보다 쉽지 않습니다. 그렇기 때문에 가상환경을 정의하고 적절하게 사용하는 것은 개발은 물론 안정적인 서비스에도 큰 도움이 됩니다.

1. 개발 vs. 운영 환경 (Dev vs. Prod environment)

저의 개발환경은 맥북이고, 서비스 환경은 CentOS 6 또는 7을 사용하고 있습니다. 서비스 환경의 기본 파이선은 2.6 또는 2.7 이지만, 파이선 3.6에서 개발한 프로그램도 동작하고 있습니다.

또한, 파이선 가상환경을 위해서 개발환경에서는 conda, 서비스 환경에서는 virtualenvwrapper 를 사용하고 있습니다. 2년정도 두 환경을 환경의 정리가 필요하다 싶어서 알아보려는 찰라 pipenv를 알게되어서 개발환경을 과감하게 바꿨습니다.

pipenv 의 장점은 가상환경과 패키지 설치를 함께 관리한다는 점입니다. 콘다도 같은 역할을 하지만 conda 명령만으로 설치 안되는 패키지가 많아서 pip를 버릴 수 없고, 가끔은 같은 패키지를 두 명령 모두를 이용해서 중복 설치하기도 하는 실수를 하기도 합니다.

(선택 1) 콘다와 작별

https://docs.anaconda.com/anaconda/install/uninstall

우선 콘다를 지웠습니다. 맥에서 콘다를 지우는 절차는 두 가지만 하면 됩니다.

콘다 디렉토리를 지우고,

rm -rf ~/anaconda3  # 또는 rm -rf ~/anaconda2

자산의 쉘 설정파일 (.bashrc, .zshrc 등)에서 anaconda3가 포함된 PATH 를 삭제합니다.

export PATH="/Users/${USER}/anaconda3/bin:$PATH"  # 이 줄을 삭제

2. pipenv 설치

맥에서 pipenv 설치는 다른 프로그램과 마찬가지로 brew명령을 이용합니다.

brew install pipenv

pip 를 이용해도 쉽게 설치할 수 있습니다.

pip install pipenv

설치를 확인합니다.

$ pipenv --version                                                                                  
pipenv, version 11.10.1

3. 첫 가상환경 만들기, hellov

현재 맥북의 기본 파이선 버전을 확인해보니 2.7.10 이네요.

$(which python) --version
Python 2.7.10

저는 3.6 버전을 기본으로 사용하고 있기 때문에 원하는 버전의 가상환경을 만들어보겠습니다. 리눅스에서는 프로젝트를 디렉토리 단위로 관리하는 것이 관례이죠. pipenv도 디렉토리 단위로 가상환경을 관리합니다. 디렉토리를 만들고,

mkdir hellov
cd hellov

--three 옵션으로 가상환경을 만듭니다.

pipenv --three  # pipenv --python 3.6.0

Creating a virtualenv for this project…
Using /usr/local/bin/python3 (3.6.5) to create virtualenv…
⠋Running virtualenv with interpreter /usr/local/bin/python3
Using base prefix '/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6'
New python executable in /Users/kyryu/.local/share/virtualenvs/hellov-aXq9JxsQ/bin/python3.6
Also creating executable in /Users/kyryu/.local/share/virtualenvs/hellov-aXq9JxsQ/bin/python
Installing setuptools, pip, wheel...done.

Virtualenv location: /Users/kyryu/.local/share/virtualenvs/hellov-aXq9JxsQ
Creating a Pipfile for this project…

약간의 시간이 지나고 여러가지 메시지를 표시하며 환경이 만들어졌습니다. 중요한 정보들이지만 나중에 확인하기로 하고 넘어가도록 하겠습니다. 디렉토리를 살펴보면 Pipfile이 생긴 것을 알 수 있습니다.

$ ls
total 8
drwxr-xr-x   3 kyryu  staff    96B  5  3 12:38 .
drwxr-xr-x  22 kyryu  staff   704B  5  3 12:37 ..
-rw-r--r--   1 kyryu  staff   138B  5  3 12:38 Pipfile


$ cat Pipfile

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]

[dev-packages]

[requires]
python_version = "3.6"

Pipfile 파일은 TOML이라는 조금 생소한 포맷입니다. 현재 프로젝트가 파이선 버전 3.6을 필요로 하는 것을 알 수 있습니다. 또한 앞으로 설치하는 패키지 정보가 모두 이 파일과 잠시 후에 등장하는 Pipfile.lock 파일에 기록됩니다. 이 두 개의 파일이 requirements.txt를 대체하면서 풍부한 기능을 함께 제공합니다.

이제 원하는 패키지, 저는 numpy를 설치해보겠습니다.

$ pipenv install numpy

Installing numpy…
Collecting numpy
  Using cached https://files.pythonhosted.org/packages/8e/75/7a8b7e3c073562563473f2a61bd53e75d0a1f5e2047e576ee61d44113c22/numpy-1.14.3-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl
Installing collected packages: numpy
Successfully installed numpy-1.14.3

Adding numpy to Pipfile's [packages]…
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (4fccdf)!
Installing dependencies from Pipfile.lock (4fccdf)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00
To activate this project's virtualenv, run the following:
 $ pipenv shell

설치 후 메시지를 보면 Pipfile[packages] 영역에 numpy가 추가되었음을 말해줍니다. 확인해보면 버전을 지정하지 않았기 때문에 "*" 으로 기록된 것을 확인할 수 있습니다.

$ cat Pipfile

...

[packages]
numpy = "*"

...

여기서 pipenv의 장점 중의 하나를 확인할 수 있습니다. pipenv는 개발환경에서만 필요한 패키지를 구분해서 설치할 수 있습니다. 마치 nodejs의 npm install --save-dev package_name 와 같은 개념입니다. 테스트를 위한 pytest가 그런 패키지이겠죠? --dev 옵션을 추가해서 설치합니다.

pipenv install --dev pytest 

그러면, pytestnumpy와 분리된 [dev-packages] 영역에 설치된 것을 확인할 수 있습니다.

cat Pipfile

...
[packages]
numpy = "*"

[dev-packages]
pytest = "*"
...

이제 패키지를 사용해봅시다. 설치한 패키지를 사용하는 방법은 가상환경에 들어가는 방법과 가상환경으로 실행하는 방법이 있습니다.

3.1 가상환경에 들어가는 방법 pipenv shell

해당 디렉토리에서 pipenv shell 을 실행하면, 가상환경이 적용된 쉘에 들어갑니다. 파이선을 실행하여, 설치한 패키지들을 사용할 수 있는지 확인합니다.

$ pipenv shell
Spawning environment shell (/bin/zsh). Use 'exit' to leave.
. /Users/kyryu/.local/share/virtualenvs/hellov-aXq9JxsQ/bin/activate

(hellov-aXq9JxsQ) $ 
(hellov-aXq9JxsQ) $ python
Python 3.6.5 (default, Apr 30 2018, 23:34:13)
[GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pytest
>>> import numpy as np
>>> np.array([1,2,3,4]).mean()
2.5
>>>

쉘에서 나오기 위해서는 exitCtrl+d를 누릅니다.

3.2 가상환경으로 실행하는 방법 pipenv run

정확하게 동작하는지 확인하기 위해서 앞서 만든 가상환경에서 꼭 나온 이후에 실행합니다. 실행하고 싶은 파이선 파일을 만듭니다. 저는 아래 내용을 index.py 라고 저장했습니다.

import numpy as np
import pytest

print(np.array([1, 2, 3, 4]))

def capital_case(x):
    return x.capitalize()

def test_capital_case():
    assert capital_case('semaphore') == 'Semaphore'

pipenv run 명령으로 실행합니다.

$ pipenv run python index.py
[1 2 3 4]

$ pipenv run pytest index.py
========================================================= test session starts =========================================================
platform darwin -- Python 3.6.5, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
rootdir: /Users/kyryu/tmp/hellov, inifile:
collected 1 item

index.py .                                                                                                                      [100%]

====================================================== 1 passed in 0.10 seconds =======================================================

4. git을 통한 가상환경 관리

hellov 디렉토리를 git 저장소로 만듭니다.

git init
git add Pipfile Pipfile.lock index.py
git commit -m "Initial commit"

부모 디렉토리로 이동해서, hellov 프로젝트를 git clone 하여 hellov_prod 디렉토리를 만듭니다.

hellov $ cd ..
$ git clone ./hellov hellov_prod
$ cd hellov_prod

이제 동일한 환경을 구성하기 위해서 패키지를 설치합니다. 보통의 pip 라면 freeze 명령을 통해서 requirements.txt 파일을 만들고 git 으로 관리했지만 필요한 정보는 이미 Pipfile과 Pipfile.lock에 모두 들어있습니다.

pipenv install

프로젝트에 속한 패키지 설치가 끝났으니 이제 실행합니다. 그런데, 예상과는 다르게 실행에 실패합니다. pytest가 없다는 에러입니다.

$ pipenv run pytest index.py
Error: the command pytest could not be found within PATH or Pipfile's [scripts].

이는 pipenv install 명령이 운영 환경을 위한 설치 명령 이기 때문에 그렇습니다. 하지만, 저는 앞에서 index.py에서 개발환경에 속하는 pytest를 사용했습니다. 개발환경을 포함한 설치를 하고 싶을 때는 설치와 동일한 --dev 옵션을 추가합니다. 이제 정상적으로 동작하는 것을 확인할 수 있습니다.

$ pipenv run pytest index.py

========================================================= test session starts =========================================================
platform darwin -- Python 3.6.5, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
rootdir: /Users/kyryu/tmp/hellov_prod, inifile:
collected 1 item

index.py .                                                                                                                      [100%]

====================================================== 1 passed in 0.10 seconds =======================================================

(선택 2) 스크립트에서 실행

스크립트에서 실행하기 위해서는 해당 환경으로 들어갈 필요가 있습니다. pipenv shell의 실행 메시지를 다시 살펴보면 …/bin/activate 같은 메시지를 볼 수 있습니다. 이를통해 pipenv가 내부적으로 venv 사용한다는 것을 알 수 있는데요, 그렇다면 스크립트 만드는 방법도 동일합니다. 적절한 가상환경 디렉토리에 있는 activatesource 해주면 됩니다.

pipenv는 가상환경이 관리되고 있는 위치를 pipenv --venv 명령을 통해 표시해줍니다. 이를 이용해서 아래와 같이 스크립트를 만들면 어느 위치에서도 정확한 가상환경을 사용해서 프로그램을 실행할 수 있습니다.

#!/bin/bash

PROJ_DIR=~/hellov
cd $PROJ_DIR

LOC=$(pipenv --venv)
source ${LOC}/bin/activate

python ${PROJ_DIR}/index.py

(선택 3) 의존성 파일을 프로젝트 디렉토리에 설치하기

바로 위에서 언급한 것 처럼 pipenv에서 사용하는 python을 비롯한 실행파일과 라이브러리들의 위치는 pipenv --venv를 이용해서 알 수 있습니다. 그 위치는 ${HOME}/.local/share/virtualenvs 아래이며 폴더명 + 전체경로의 해시(hash)값 형태의 디렉토리입니다. 하지만, 콘다와는 다르게 디렉토리 중심인 pipenv의 경우는 굳이 중앙에서 가상환경을 관리할 필요는 없어보입니다. npmnode_modules 같이 프로젝트 폴더 안에서 관리하고 싶다면 어떻게 할까요?

환경변수를 하나 설정한 후에 사용하면 됩니다.

export PIPENV_VENV_IN_PROJECT=1

그러면, pipenv는 프로젝트 폴더에 .venv 디렉토리를 만들어서 가상환경을 관리합니다.

$ export PIPENV_VENV_IN_PROJECT=1
$
$ mkdir hello_solo
$ cd hello_solo
$ pipenv --three
$ 
$ pipenv --venv
${HOME}/hello_solo/.venv

이제 토이 프로젝트를 만들었다가 지우고 싶으면, 폴더만 지워도 깨끗하게 정리할 수 있습니다.