가상 환경 친화적 앱

Rule 0 - 응용 프로그램은 가상 환경에서 실행된다

좀 오래 묵은 글이지만 파이썬의 가상 환경의 장점에 대해서는 이미 소개한바있다.

그래서 일단 가상 환경을 쓰기로했다고 가정해보자. 그러면 거꾸로 응용 프로그램을 만들 때, 이 가상 환경에 좀 더 친화적이고, 가상 환경이 주는 혜택을 좀 더 끌어낼 수 있도록 만드는 방법이 있을까?

이 질문에 답하는 것이 이 글의 목적이다.

주석

이 글은 파이썬 3.7.1 이 나온 시점에 쓰고있고, 파이썬 3.4 이상의 버전을 대상으로한다.

Rule 1 - 응용 프로그램은 setuptools 가 설치하는 스크립트로 구동한다

이제 파이썬에는 가상 환경이 기본 배포판에 포함되어있고, 표준 라이브러리에도 venv 라는 이름으로 들어있다. 이를 사용해서 가상 환경을 만드는 표준적인 방법은 이렇다:

$ python3 -m venv <my-venv-dir>

파이썬 3.4 부터는 이렇게 만들어진 가상 환경에 setuptools 와 pip 도 함께 설치된다. 그렇다면 setuptools 가 제공하는 기능 하나를 활용할 수 있다. 바로 console_scripts 다.

setuptools 는 설치하는 패키지에 포함된 함수를 실행해주는 스크립트를 만들어서, 가상 환경의 바이너리 디렉터리에 설치할 수 있다. 이 때 스크립트는 가상 환경의 인터프리터를 사용하도록 설정되기 때문에, 가상 환경을 활성화하지 않고도 직접 실행할 수 있다. 아예 scripts 라는 인자를 통해 설치할 스크립트 파일을 지정할 수도 있고, 이 때도 가상 환경의 인터프리터를 사용하도록 변환되어 설치된다. 하지만 직접 만든 스크립트 파일을 설치하는 것보다는 console_scripts 를 사용하는 것을 권한다.

주석

파이썬은 구동 스크립트를 특별히 __main__ 이라는 이름의 모듈로 취급한다. 때문에 이 모듈을 코드내에서 임포트하려고 하면 다른 모듈로 인식해서 다시 임포트하게 된다. 같은 모듈이 두개의 서로 다른 이름으로 임포트되는 것이다. 이게 문제가 될 수도 있고 그렇지 않을 수도 있다. 하지만 왜 이런 가능성을 열어두는가? 구동 스크립트는 임포트할 수 없는 대상으로 취급하자.

console_scripts 를 사용하려면 당연히 setup.py 부터 만들어야 한다. https://github.com/flowdas/flowdas/blob/master/setup.py 를 템플릿 삼아서 필요한 부분을 고치면된다.

이 파일에서 주목할 부분은:

entry_points={
    'console_scripts': [
        'flowdas=flowdas.cli:Flowdas.main',
    ],
},

이다.

flowdas 라는 스크립트를 설치하는데, flowdas.cli 라는 모듈에 정의된 Flowdas.main 이라는 함수를 실행하도록 구성하라는 뜻이다.

Flowdas.main 은 사실 클래스 메서드다. 잘 알려지지 않았지만, console_scripts 는 스태틱 메서드와 클래스 메서드도 지원한다. 여기에서 클래스 메서드를 쓴 이유는 따로 설명할 기회가 있을 것이다.

이제 응용 프로그램을 설치하려면 이렇게 하면된다:

$ <my-venv-dir>/bin/pip install .

응용 프로그램을 PyPI 에 등록했다면:

$ <my-venv-dir>/bin/pip install flowdas

개발 환경이라면:

$ <my-venv-dir>/bin/pip install -e .

주석

윈도우에서는, <my-venv-dir>\Scripts\pip 를 사용해야한다.

어떤 방법으로 설치했건, 이제 실행될 때 가상 환경의 인터프리터가 자동 선택되는 스크립트 <my-venv-dir>/bin/flowdas 가 설치된다. 이 스크립트를 셸 명령 줄에서 실행하건, 크론 잡으로 실행하건, 윈도우 탐색기에서 더블 클릭으로 실행하건 가상 환경의 인터프리터로 실행된다.

Rule 2 - 구성 파일은 sys.prefix 에 넣는다

가상 환경의 또 다른 장점은 여러 환경이 서로 충돌하지 않고 동시에 운영될 수 있다는 것이다. 우리가 만들 응용 프로그램도 그러지 못할 이유가 뭔가? 응용 프로그램의 여러 버전이 동시에 실행되면서도 서로 충돌하지 않기 위해서는, 공유 자원간의 충돌이 없어야한다. 가령 같은 경로의 파일이나 같은 소켓 포트를 을 갖고 싸우지 말아야한다. 이런 교통 정리에는 구성(configuration) 파일이나 환경 변수등이 사용되는데, 가상 환경에서의 최적의 방법은 가상 환경 디렉터리에 상대적인 경로를 갖는 구성 파일이다.

가상 환경의 디렉터리는 sys.prefix 다.

가상 환경 바깥은 어떻게 될지 알 수 없으니 가장 좋은 장소는 가상 환경 디렉터리 밑이다. 필자는 가상 환경 디렉터리에 직접 config.json 파일을 둔다. 이 전략은 가상 환경 디렉터리를 응용 프로그램의 홈 디렉터리로 사용한다고 볼 수도 있다. 필자는 응용 프로그램 마다 하나의 가상 환경을 할당하는 단독 주택 모델을 쓰는데, 여러 응용 프로그램이 하나의 가상 환경을 공유하는 아파트 모델을 쓰려면 sys.prefix 밑에 디렉터리를 만들고 들어가면 된다.

일단 구성 파일을 갖고 싸우지 않도록 막았다면, 나머지 자원들은 구성 파일에 잘 기록하는 것으로 해결된다.

한가지 문제가 남는다. 사용자가 가상 환경에 응용 프로그램을 설치하지 않았다면 어쩔것인가?

이 때를 위해 환경 변수는 필요하다. 가령 FLOWDAS_HOME 이라는 환경 변수가 있다면 그 경로를 홈 디렉터리로 사용하고, 그렇지 않다면 sys.prefix 를 사용하는 것이다. 이 환경 변수는 테스트를 실행할 때도 유용하다.

대체로 환경 변수를 지정하지 않고, 가상 환경에 설치하지도 않았다면 시스템 디렉터리를 홈 디렉터리로 사용하게 된다. 구성 파일 없이도 기본값으로 설행되도록 만들었다면, 큰 문제 없이 실행될 수도 있지만, 홈 디렉터리에 로그를 남기거나 출력 파일을 만드는 응용 프로그램이라면 권한 문제를 일으키거나 더 나쁘면 의도하기 않게 뭔가를 망가뜨릴 수 있다. 이를 아예 막고 싶다면 가상 환경인지를 감지할 필요가 있다. 표준적인 방법은 sys.prefixsys.base_prefix 가 다른지 확인하는 것이다. 가상 환경이라면 두 값이 달라야한다.

주석

가상 환경을 만드는 방법은 venv 만 있는 것이 아니다. venv 가 표준 라이브러리에 있는 만큼, 모든 제삼자 가상 환경이 이와 100% 호환된다면 좋겠지만, 현실은 그렇지 못하다. 가령 흔히 사용되는 conda 가 그렇다.

condavenv 와 pip 를 합쳐놓은 것 같은 기능 집합을 제공한다. 이를 사용하는 이유는 C 컴파일러를 필요로하는 확장 모듈의 설치가 다양한 플랫폼에서 좀 더 쉽기 때문이다. 특히 데이터 분석 관련 패키지들이 그렇다.

venv 가상 환경에 pip 로 conda 를 설치할 수는 있으나(pip install conda), 설치 후에 실제로 동작하지는 않는다. (이건 뭐하는 물건인고?)

거꾸로 conda 로 만든 가상 환경에 pip 를 설치해야하는데, 파이썬 3에서는 보통 pip 가 함께 설치된다. 가령:

$ conda create --prefix=<my-venv-dir> python=3
$ <my-venv-dir>/bin/pip install flowdas

엉뚱한 곳에 가상 환경을 만들지 않도록 --prefix 를 지정한다.

이상태에서 sys.prefix 가 가상 환경의 절대 경로인 것은 venv 와 같다. 하지만 sys.base_prefix 가 문제인데, venv 와 달리 sys.prefix 와 같은 값이 들어간다.

따라서 conda 에서 가상 환경임을 확인하려면 sys.prefix 밑에 conda-meta 디렉터리가 있는지 확인하는 절차가 더 필요하다.

필자는 conda 를 프로덕션 환경에 쓰지 않는다. 때로 Intel Distribution for Python 때문에 프로덕션 환경에서도 사용하기도 했지만, 이 역시 이제는 conda 없이 pip 로 설치할 수 있다. 하지만 개발 환경이라면 사용할 수도 있기 때문에 어느정도의 지원은 필요하다.

Rule 3 - 라이브러리는 패키지 리소스를 사용한다

응용 프로그램은 각종 라이브러리로 모듈화하는 것이 좋다. 그런데 이 라이브러리들은 당연히 같은 가상 환경을 공유하게 된다. 라이브러리들은 어떻게 구성 파일을 갖고 싸우지 않을 것인가? 이에 대한 답은, 라이브러리는 sys.prefix 밑의 구성 파일을 직접 사용하지 말라는 것이다. 대신 패키지 리소스를 사용한다.

가상 환경내에서 각 라이브러리들을 위한 전용 공간이 있는데, 그 곳은 바로 라이브러리가 설치된 site-packages 디렉터리다. 파이썬 3.7 에 추가된 importlib.resources 모듈은 패키지 디렉터리에 저장된 자원 파일에 접근하는 표준적인 방법을 제공한다. 이전 버전을 위한 백포트 패키지 importlib_resources 도 제공된다.

Rule 4 - Application Singleton 을 사용한다

물론 이 전략은 읽기 전용 리소스에만 적용된다. 뭔가 쓰려면 sys.prefix 에 접근할 수 밖에 없는데, 누군가 교통 정리를 해야한다. 또 라이브러리들간의 직접적인 커플링을 줄이려면 일종의 의존성 역전 을 위한 매개체가 필요하다. 이 역할을 Application Singleton 이 맡을 수 있다.

앞에서 console_scripts 의 예로 들었던 Flowdas.mainFlowdas 가 바로 Application Singleton 이다. 이에 대해서는 다른 글에서 설명한다.

주석

싱글톤으로 이어집니다.