Python 개발 환경을 구성했으니 이제 프로젝트를 시작해야 한다. 나는 주로 웹 쪽을 하므로 웹 프레임워크를 선택해야 했고 대표적으로 django와 Flask가 있는데 개인적으로 Flask 쪽에 더 맘에 가긴 했지만, 더 좋은 프레임워크도 있을 수 있으므로 Facebook에 질문을 올렸다. 많은 분이 의견을 주셨지만 홍민희님의 대답이 맘에 들어서 여기에 남긴다.
파이썬 웹 프로그래밍을 처음 시작하신다는 배경 하에, Flask의 큰 장점은 파일 하나만 보는 것으로 시작할 수 있다는 점입니다. 예를 들어 Django 등의 풀스택 웹 프레임워크는 일반적으로 킥스타터의 도움을 받아 초기 파일들을 생성해주는 방식으로 시작하게 되는데, 생성된 파일이 적지 않으므로 처음 시작할 때 부담이 될 수 있습니다. 물론 이 부분은 개인차가 있을 수 있습니다. 만들어진 파일 하나 하나가 어떤 역할을 하는지 알지 않으면 신경이 쓰이는 저 같은 경우에는 Flask처럼 파일 하나로 시작하고, 그 파일조차 제가 직접 에디터에 입력하는 것으로 출발할 수 있는 쪽이 오롯이 지금 배우기 시작하는 분야에 집중하기 좋았습니다. 반면 딱히 생성되는 파일들이 어떤 역할을 하는지 이해하지 않더라도, 지금은 쓰지 않더라도 나중에 쓸 날이 오겠지 생각하고 넘어갈 수 있다면 Flask의 이런 점은 별 장점은 아닐 수 있습니다.
회사에서는 주로 django를 쓰고 있어서 django를 배워야 하긴 하지만 일단 그 전에 파이썬에 대해서 이해할 게 더 많다고 느껴졌다. 여태 개발해오면서도 Rails나 django 식의 올인원(?) 프레임워크를 안 써봐서 그런지 왠지 모를 거부감이 있었다. 사용하다 보면 결국 내부를 자세히 이해해야 잘 쓸 수 있겠지만 위에 홍민희 님이 얘기하신 것처럼 추상화 레벨이 너무 높아서 감춰진 영역이 너무 많게 느껴졌다. 빨리 웹사이트를 만들고 어드민 기능까지 바로 되어서 편할 수도 있지만, 나한테는 알 수 없이 동작하는 블랙박스처럼 느껴졌다. 그러고 보면 그동안 내가 관심 가졌던 웹 프레임워크 사이트에 들어가면 주로 "Sinatra inspired"라고 쓰여 있긴 했었다. 정작 Sinatra는 한 번도 안 써봤지만...
그리고 너무 갖추어진 환경에서 개발하는 걸 별로 안 좋아하는 게 해당 기능이 어디서 제공하는 건지 알 수 없다는 부분도 있다. 예를 들면 A 기능을 django에서 제공한다고 했을 때 이 A 기능이 원래 Python에서도 보통 사용하는 기능인데 django가 감싸거나 확장해서 제공하는 것인지 아니면 다른 Python 프로젝트에서는 없는 기능인데 django만의 기능인지를 처음 사용하는 처지에서는 구분하기 어려워진다. 각 파일의 용도나 관례를 좀 파악한 다음에 추상화된 계층을 보는 게 더 이해하기 쉽다고 생각했다.
Flask 프로젝트 구성
그래서 Flask를 사용하기로 하고 구성하기로 했다. 일단 Flask 홈페이지에 나와 있는 대로 pip install flask
로 flask
를 설치했다.
$ pip install flask
Collecting flask
Downloading Flask-0.12.2-py2.py3-none-any.whl (83kB)
100% |████████████████████████████████| 92kB 1.1MB/s
Collecting Jinja2>=2.4 (from flask)
Downloading Jinja2-2.9.6-py2.py3-none-any.whl (340kB)
100% |████████████████████████████████| 348kB 2.7MB/s
Collecting click>=2.0 (from flask)
Downloading click-6.7-py2.py3-none-any.whl (71kB)
100% |████████████████████████████████| 71kB 3.1MB/s
Collecting itsdangerous>=0.21 (from flask)
Downloading itsdangerous-0.24.tar.gz (46kB)
100% |████████████████████████████████| 51kB 5.9MB/s
Collecting Werkzeug>=0.7 (from flask)
Downloading Werkzeug-0.12.2-py2.py3-none-any.whl (312kB)
100% |████████████████████████████████| 317kB 2.8MB/s
Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->flask)
Downloading MarkupSafe-1.0.tar.gz
Building wheels for collected packages: itsdangerous, MarkupSafe
Running setup.py bdist_wheel for itsdangerous ... done
Stored in directory: /Users/outsider/Library/Caches/pip/wheels/fc/a8/66/24d655233c757e178d45dea2de22a04c6d92766abfb741129a
Running setup.py bdist_wheel for MarkupSafe ... done
Stored in directory: /Users/outsider/Library/Caches/pip/wheels/88/a7/30/e39a54a87bcbe25308fa3ca64e8ddc75d9b3e5afa21ee32d57
Successfully built itsdangerous MarkupSafe
Installing collected packages: MarkupSafe, Jinja2, click, itsdangerous, Werkzeug, flask
Successfully installed Jinja2-2.9.6 MarkupSafe-1.0 Werkzeug-0.12.2 click-6.7 flask-0.12.2 itsdangerous-0.24
Flask를 설치하자 MarkupSafe
, Jinja2
, click
, itsdangerous
, Werkzeug
, flask
가 모두 설치되었다. 어디서 의존성이 같이 내려왔는지 궁금하지만 일단은 홈페이지에 나온 대로 진행을 해보자. 다음의 내용으로 hello.py
파일을 만들었다.
# hello.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
이제 FLASK_APP=hello.py flask run
으로 실행을 하자 Hello World
웹사이트가 잘 떴다.
$ FLASK_APP=hello.py flask run
* Serving Flask app "hello"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
여기까지만 했는데도 코드와 관련 없이 궁금한 게 많이 생겼다.
pip install flask
만 하니 관련 의존성이 다 설치되었는데 이 의존성은 어떻게 관리되는가?FLASK_APP
환경변수의 의미는 무엇일까?flask
애플리케이션을 실행했더니 현재 위치에__pycache__
폴더가 생겼는데 이 폴더의 용도는 무엇일까?- 이 프로젝트가
flask
를 사용하므로 소스코드를 다운받은 후flask
를 설치하도록 의존성 관리를 해야 하는데 이는 어떻게 하는가?
Python에 대해서 아는 게 참 없구나 싶은 기분이었다. 다른 사람도 Node.js 처음 하면 이렇게 궁금한 게 많아지는가 싶기도 하면서...
flask의 의존성 관리
flask 저장소로 가봤다. 최근에 본 파이썬 3에 뛰어들기에서 setup.py
로 패키징하는 내용이 있어서 setup.py를 열어보니 다음과 같은 부분이 있었다.
setup(
name='Flask',
version=version,
url='https://github.com/pallets/flask/',
license='BSD',
author='Armin Ronacher',
author_email='armin.ronacher@active-4.com',
description='A microframework based on Werkzeug, Jinja2 '
'and good intentions',
long_description=__doc__,
packages=['flask', 'flask.json'],
include_package_data=True,
zip_safe=False,
platforms='any',
install_requires=[
'Werkzeug>=0.9',
'Jinja2>=2.4',
'itsdangerous>=0.21',
'click>=4.0',
],
extras_require={
'dotenv': ['python-dotenv'],
'dev': [
'blinker',
'python-dotenv',
'greenlet',
'pytest>=3',
'coverage',
'tox',
'sphinx',
'sphinxcontrib-log-cabinet'
],
}
)
이 파일이 Node.js의 package.json
과 같은 역할을 하는 것으로 보였다. 프로젝트에 대한 설명이 있고 install_requires
에 있는 의존성이 앞에서 설치된 패키지와 같은 것으로 보아 이 파일을 참고해서 설치한 것으로 보인다. extras_require
는 flask
를 가져다 쓸 때는 필요 없지만 개발할 때만 필요한 개발 의존성으로 보인다.
virtualenv
를 생성하고 설치했으므로 설치된 위치를 확인해 보니 현재 프로젝트의 ./venv/lib/python3.6/site-packages/
아래에 다음과 같이 설치되어 있다. venv
가 virtualenv
의 가상환경 이름이다.
├── Flask-0.12.2.dist-info
├── Jinja2-2.9.6.dist-info
├── MarkupSafe-1.0.dist-info
├── Werkzeug-0.12.2.dist-info
├── __pycache__
├── click
├── click-6.7.dist-info
├── easy_install.py
├── flask
├── itsdangerous-0.24.dist-info
├── itsdangerous.py
├── jinja2
├── markupsafe
├── pip
├── pip-9.0.1.dist-info
├── pkg_resources
├── setuptools
├── setuptools-36.5.0.dist-info
├── werkzeug
├── wheel
└── wheel-0.30.0.dist-info
setup.py
가 Python 코드라서 용도를 잘 모르겠는 코드가 있는 것은 둘째치고라도 [파이썬 3에 뛰어들기](https://blog.outsider.ne.kr/1296)
에서는 상단에서 from distutils.core import setup
로 쓰고 있었는데 flask
의 코드를 보면 from setuptools import setup
로 되어 있다. 눈치껏 setup
은 같은 역할을 하는 것 같은데 distutils
과 setuptools
이 뭐가 다르고 왜 다르게 사용하는지 모르겠다. 궁금한 게 계속 나와서 진도를 못 나가...
Stackoverflow에서 잘 정리된 답변을 발견했다.
- Distutils: 아직 Python 패키징의 표준 도구로 Python 2부터 3까지 표준 라이브러리로 포함되어 있다.
- Setuptools: Distutils의 부족한 부분을 채우려고 개발된 도구로 표준 라이브러리에 포함되어 있지 않다. 여기서
easy_install
이 도입되었다.
패키징해서 배포를 안 해봐서 아직 감이 다 오진 않지만 PyPI에 패키지를 올릴 때 사용하는 도구로 대충 이해했다. 둘 다 Python 패키지를 설치하는 도구인 pip와 잘 동작한다고 한다. 저 글의 작성자는 Setuptools
가 virtualenv
와 pip
에서 아주 잘 동작하므로 권장하고 있었다. 나중에 쓸 때 Setuptools
를 자세히 찾아봐서 쓰면 되겠다 싶다.
관련해서 찾다 보니 몇 가지 더 궁금한 단어들이 생겼다.
- wheel: Python의 패키지 형식으로
.whl
확장자를 가지고 ZIP 형식이다.virtualenv
생성하면wheel
커맨드라인 명령어도 생기던데 아직 용도는 잘 모르겠다. - easy_install** : Setuptools에 포함된 도구라고 한다. 이게 궁금했던 건
virtualenv
를 생성하면easy_install
커맨드라인 명령어가 생겨서였는데 이전에는 무심코 넘어갔는데virtualenv
를 생성할 때Installing setuptools, pip, wheel...done.
라는 문구가 나오는 걸 깨닫게 되었다. virtualenv에서 이 설치 과정까지 포함된 것으로 보인다. pip vs easy_install를 보면 easy_install은 2004년에 setuptools의 일부로 릴리스 되었고 pip는 2008년에 easy_install을 대체하려고 나왔다. 그리고 Python 프로젝트 볼 때 나한테는 어색하게 느껴졌던 requirements.txt가 pip에서 도입되었다고 한다. - Eggs: pip vs easy_install을 보다 보니
Eggs
란 용어가 나왔다.setuptools
에서 도입된 빌드된 배포형식인데Eggs are to Pythons as Jars are to Java..
라는 설명을 보니 감이 왔다. pip vs easy_install 문서의 비교표를 보면pip
는wheel
을 쓰고easy_install
은Eggs
를 쓰는 것 같다. 여기서 built distribution이라는 용어가 나오는데 설명을 보면 설치할 시스템에 복사만 하면 되는 배포 형식이라고 하는데 이 설명을 보면wheel
은 설치 전에 빌드 과정이 필요하므로 컴파일되지 않은 Python 파일이 담겨 있고 반대로Eggs
는 컴파일된 결과만 포함하고 있는 것이 둘의 가장 큰 차이로 생각된다.
예전에 Python의 패키지 관리 도구의 험난한(?) 역사에 대한 글을 본 기억이 있는데 그 긴 역사를 거치면서 남은 잔재가 아닌가 싶다.
FLASK_APP
Flask 홈페이지에서 FLASK_APP=hello.py flask run
로 실행하도록 안내하고 있는데 Quickstart를 보면 app = Flask(__name__)
에서 애플리케이션의 모듈이나 패키지 이름으로 Flask
인스턴스를 생성하고 있다. 여기서 __name__
은 현재 모듈의 이름을 담고 있는 Python의 내장 변수이다.
cli 사용법을 보면 FLASK_APP
환경변수를 flask
커맨드라인 명령어가 애플리케이션을 찾을 수 있게 하는 것으로 보인다. 이 이름을 통해 템플릿이나 정적 파일을 어디서 찾아야 하는지 찾는다고 한다. 자세한 건 Flask 앱을 개발해봐야 더 이해할 수 있을 것 같다.
__pycache__
인터넷에서 찾아보니 Python 3.2부터 도입되었는데 컴파일된 바이트 코드가 들어간다고 한다. Python 2.x의 경험이 별로 없어서 몰랐지만 Python 2.x에서는 hello.py
가 있으면 그 옆에 hello.pyc
바이트 코드 파일이 생기지만 3.2부터는 이 파일을 __pycache__
아래에 모은다고 한다.
이 관련 내용이 PEP 3147 -- PYC Repository Directories인데 이 문서를 읽어보면 성능향상을 위해서 Python 인터프리터가 컴파일한 바이트 코드를 파일시스템에 저장(.pyc
)하고 이후에는 컴파일 단계를 건너뛰어서 더 빨리 모듈을 로딩할 수 있게 한다. CPython만 사용해 봤지만 여러 버전의 Python 인터프리터를 사용하는 경우 인터프리터 간에 .pyc
를 공유할 수 없으므로 __pycache__
를 만들어서 여러 인터프리터의 바이트 코드 파일을 같이 만들어 둘 수 있도록 하기 위함으로 보인다.
그래서 바이트 코드 파일에 매직 태그가 붙는데 CPython v3.6은 .cpython-36.pyc
가 된다. 실제로 위 hello.py
파일을 실행한 결과는 hello.cpython-36.pyc
로 __pycache__
안에 생긴 것을 확인할 수 있다. 당연히 이 폴더는 VCS에서 관리할 필요가 없으므로 .gitignore
등에서 제외해야 한다.
프로젝트의 의존성 관리
프로젝트를 개발하면서 필요한 의존성(현재까지는 flask
)을 pip
로 설치해서 사용하면 되는데 다른 사람이 보면 어떤 의존성이 필요한지 알 수 없으므로 프로젝트에 이 정보를 넣어야 한다. 앞에서 flask
를 설치할 때 자동으로 jinja2
등이 설치된 것처럼 패키지로 배포했을 때도 이 정보가 필요하고 GitHub 등에 소스를 공개해두었을 때도 다른 사람이 다운받아서 실행하려면 프로젝트 내에 의존성이 명시되어있어야 하고 어떤 약속된 명령어를 실행했을 때 자동으로 관련 의존성을 모두 설치할 수 있어야 한다.
아직 Python에서 이 의존성을 어떻게 관리하는 알지 못하지만, 처음부터 이런 관리에 대해 구성을 해놓고 사용하고 싶었다. Flask 저장소에서 예제를 제공하고 있어서 일단 여기를 참고했다. 코드를 열어보니 맨 위에 # -*- coding: utf-8 -*-
가 있어서 Python 2.x에 맞춰진 것 같아서 좀 불안했지만, 의존성 관리가 달라졌을 것 같진 않아서 그냥 참고했다.
마이크로 블로그 예제인 flaskr을 보니 소스코드는 flaskr/
디렉터리에 넣고 테스트코드는 tests/
폴더 아래 둔다는 것을 알았다. flaskr
프로젝트 아래 src
같은 폴더가 아니라 flaskr
이라는 프로젝트 이름의 폴더를 한 번 더 만드는 게 이상했지만(flaskr/flaskr
처럼 되니까...) 일단 그 전에 의존성을 먼저 해결해야 했기에 넘어갔다.
먼저 눈에 띈 파일은 setup.py와 setup.cfg였다.
setup.py
앞에서 Flask의 setup.py
를 본대로 이 파일에 필요한 의존성이 명시되어 있다. 예제로 참고하는 flaskr의 setup.py
는 다음과 같이 되어 있다.
# -*- coding: utf-8 -*-
"""
Flaskr Tests
~~~~~~~~~~~~
Tests the Flaskr application.
:copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from setuptools import setup, find_packages
setup(
name='flaskr',
packages=find_packages(),
include_package_data=True,
install_requires=[
'flask',
],
setup_requires=[
'pytest-runner',
],
tests_require=[
'pytest',
],
)
앞에서도 setup.py
로 의존성을 관리하는 부분을 살펴봤는데 좀 더 이해할 필요가 있어 보였다. 이 파일이 Distutils
부터 Setuptools
까지 같이 쓰고 있는 것 같은데 setup.py
에 대한 기본적인 내용은 https://docs.python.org/3/distutils/setupscript.html에 나와 있다.
name
은 패키지 이름이다.packages
는 여기서 지적한 모듈을Distutils
가 찾아서 처리하도록 한다고 한다. 그래서packages = ['foo']
로 지정하면foo/__init__.py
를 찾는다.
아래 키워드들은 Setuptools에서 추가하거나 확장한 키워드였다.
find_packages()
는packages
를 수동으로 지정하는 게 큰 프로젝트에서는 어려우므로 이 함수로 프로젝트 폴더에서 찾아서 목록을 만들어 준다고 한다.include_package_data
를True
로 지정하면MANIFEST.in
에서 지정한 패키지 디렉터리에서 찾은 데이터 파일을 자동으로 포함한다.install_requires
는 설치할 때 필요한 다른 패키지의 목록이다.setup_requires
는 setup script를 실행할 때 필요한 패키지 목록이다.tests_require
는 테스트에 필요한 패키지 목록이다.test
명령어를 실행할 때setuptools
가 이 패키지를 가져온다.
그리고 파이썬의 개발 "환경"(env) 도구들을 보면 다음과 같이 나와 있다.
pip
같은 게 없던 때에는 라이브러리 타르볼을 받아서 푼 다음python setup.py install
명령을 실행하는 것이 일반적인 라이브러리 설치법이었습니다. 지금도pip
는*.whl
파일이 아닌*.tar.gz
/*.zip
파일인 패키지를 설치할 때 내부적으로python setup.py install
스크립트를 실행합니다.
일단 여기에서 어감으로 볼 때 pip
가 있는 지금은 python setup.py install
명령어를 사용할 필요가 없어 보였다. Stackoverflow를 보니 다음과 같이 나와 있었다.
NO. NEVER EVER do
sudo python setup.py install
whatever. Write a~/.pydistutils.cfg
that puts your pip installation into~/.local
or something. Especially files namedez_setup.py
tend to suck down newer versions of things like setuptools and easy_install, which can potentially break other things on your operating system.
정확히 이유는 모르겠지만 버전을 깨뜨릴 수 있으니 이 내용은 정확히는 python setup.py install
을 쓰지 말라고 하므로 일단 사용 안 하면 되겠다 싶었다.python setup.py install
을 쓰면 안 된다기 보다는 sudo
로 설치하지 말라는 얘기라고 한다. 글을 볼 때 pip
와 setup.py installl
의 차이를 검색하면서 들어갔고 라이브러리나 애플리케이션 코드는 sudo
로 설치하지 않는 것은 보안상 일반적인 관례이므로 그 부분을 얘기한다는 것은 생각하지 못했다.(다시 읽어봐도 약간 애매하게 쓰인 듯...) 하면 안된다기보다는 이젠 pip install
이 있어서 할 필요가 없어진 거라고 보면 된다.
그리고 README 문서를 보면 pip install --editable .
로 설치하라고 되어 있다. pip가 Python 패키지 설치 도구인 것은 알고 있었는데 더 자세한 내용 확인을 위해서 Installing Python Modules를 참고했다.
- pip는 현재 사용하는 인스톨 프로그램이고 Python 3.4부터는 Python에 포함되어 있다.
- PyPI는 Python 패키지의 공개 저장소이다.
distutils
은 1998년에 Python 표준 라이브러리로 추가된 빌드/배포 시스템이다. 현재distutils
을 직접 사용하는 게 점점 줄어들고 있지만, 현재 패키징 및 배포 인프라의 토대로 남아있다.
pip
기본 사용법은 pip install PackageName
이다. 특정 버전을 설치하려면 pip install PackageName==1.0.4
와 같이 버전일 지정할 수 있고 범위로 지정하려면 pip install PackageName>=1.0.4
와 같이 사용할 수도 있다. 이미 패키지를 설치한 상황이라면 pip install --upgrade PackageName
로 업데이트한다.
사용법은 파악했지만, 위에서 나온 대로 pip install --editable .
를 할 때
- 현재 폴더에서 설치할 때
setup.py
와의 연관 관계를 아직 정확히 이해하지 못했다. --editable
의 용도를 이해하지 못했다.pip install -r requirement.txt
같은 명령어를 많이 본 적이 있는데 이 부분을 이해하지 못했다.setup.py
와requirement.txt
의 차이를 잘 모르겠다.
코드 한 줄 작성하기도 전에 모르는 게 너무 많아서 고생하고 있지만 하나씩 자료를 찾아봤다.
pip install .
pip install
의 사용법을 살펴보자.
$ pip install --help
Usage:
pip install [options] <requirement specifier> [package-index-options] ...
pip install [options] -r <requirements file> [package-index-options] ...
pip install [options] [-e] <vcs project url> ...
pip install [options] [-e] <local project path> ...
pip install [options] <archive url/path> ...
인자로 requirement specifier
(아직 뭔지 모르겠지만), -r
옵션으로 requirements 파일, URL,디렉터리 경로를 지정할 수 있다고 나와 있다. 문서를 보면 로컬 디렉터리를 지정할 때 반드시 setup.py
를 지정해야 한다고 되어 있다. 위에서 .
은 현재 디렉터리를 지정한 것이므로 여기서 setup.py
를 찾아서 자동으로 처리해 주는 것으로 보인다.
--editable
--editable
옵션의 문서를 보면 editable 모드로 프로젝트를 설치한다고 한다. Editable 설치 문서를 보면 setuptools develop 모드와 같다고 하고 로컬에 SomeProject.egg-info
디렉터리가 생기는데 setup.py develop
보다 좋은 점은 현재 워킹 디렉터리에 바로 egg-info
를 생성하는 부분이라고 한다. egg-info
도 모르지만 setuptools develop 모드도 몰라서 문서를 읽어도 이해가 안 되었다.
이 내용은 Setuptools의 Development Mode에 자세히 나와 있었는데 이해한 대로만 간단히 줄여 보자면...
distutils
가 기본적으로 프로젝트의 배포판을 빌드할 것이라고 가정하기 때문에 개발하면서 변경을 할 때 다시 빌드하고 설치해야 한다.- 동시에 두 가지 연관 프로젝트를 개발할 때 두 프로젝트를 한 디렉터리에 넣어서 실행해야 하는데
distutils
로는 이를 할 수 없다. Setuptools
에서는 공통 디렉터리나 스테이징 영역에 파일을 복사하지 않은 채로 배포할 수 있도록 지원하고 있다. 이를 이용하면 각 프로젝트에서 코드를 바로 수정해서 사용할 수 있다. 이때는 C 확장이나 컴파일된 파일을 수정할 때만 빌드하면 된다.setup.py develop
명령어를 사용하면setup.py install
과 비슷하게 동작하지만, 아무것도 설치하지 않는다. 대신.egg-link
를 생성하고 이를 프로젝트의 소스코드와 연결한다. 배포 디렉터리가site-packages
디렉터리라면 소스코드를 포함하기 위해easy-install.pth
를 수정해서sys.path
에서 사용할 수 있게 한다.
아직 개발을 안 해봐서 의존성 설치와 현재 프로젝트의 코드 수정이 어떻게 연결되는지까지는 정확히 이해하지 못했지만 editable
모드가 어떤 의미인지는 이해했다. 수정할 때마다 빌드하지 않게 한다는 용도로 이해했는데 이는 나중에 실제로 flask 애플리케이션을 개발하면서 겪어봐야 더 이해가 될 것 같다. --editable
옵션 없이 사용해보면 불편한 부분을 눈치채지 않을까 생각한다.
실제로 위 setup.py
파일로 pip install --editable .
을 실행하면 아래의 파일이 새로 생긴다.
├── .eggs/
│ ├── README.txt
│ └── pytest_runner-2.12.1-py3.6.egg
├── demo.egg-info/
│ ├── SOURCES.txt
│ ├── dependency_links.txt
│ ├── requires.txt
│ └── top_level.txt
├── venv/lib/python3.6/site-packages/easy-install.pth
├── venv/lib/python3.6/site-packages/demo.egg-link
└── venv/pip-selfcheck.json
이 파일 중에서 demo.egg-link
를 보면 현재 폴더의 경로인 /Users/outsider/demo
로 지정되어 있고 easy-install.pth
도 같은 경로로 지정되어 있다.(지정되어 있다기보다 txt 파일이라 내용에 저 경로가 있다.) sys.path
가 달라지진 않아서 정확한 동작 방식까지는 이해 못했지만 앞에서 이해한 내용대로 동작하기 위해서 만들어진 것 정도는 이해했다.
앞에서 Eggs 포맷에 대해서 간단히 살펴봤는데 실제로 관련 파일이 생기니까 .eggs
와 .egg-info
가 무엇인지 궁금해졌다. 이 부분은 The Internal Structure of Python Eggs에 자세히 나와 있었다.
.egg
는 프로젝트의 코드와 리소스를 담고 있는 디렉터리나 zip 파일로 프로젝트의 메타데이터를 가진 EGG-INO 서브디렉토리 옆에 있는다..egg-info
는 프로젝트의 코드와 리소스 옆에 있는 파일이나 디렉터리로 프로젝트의 메타데이터를 담고 있다.
.eggs
폴더 안에 setup_requires
로 지정된 pytest_runner
의 egg
파일이 있는 것으로 보아 설치한 내용이 여기에 담긴 것 같다. --editable
옵션이 없이 설치해보면 .eggs
와 .egg-info
가 생기지 않는 것으로 보아 Editable 모드로 실행하는 데 필요한 것으로 보인다. (아직 site-packages 안에 생긴 것과 .eggs
안에 생긴 것의 차이는 잘 모르겠다.)
이 파일들은 VCS에서 관리할 필요가 없으므로 .eggs/
, *.egg-info/
는 .gitignore
등으로 제외했다.
requirements.txt
Flask 예제 프로젝트를 보고 setup.py
에 지정된 의존성을 이용해서 의존성을 설치했지만, 그동안 Python 프로젝트를 보면서는 pip install -r requirement.txt
처럼 사용하는 것을 훨씬 더 많이 봤다. 그렇다 보니 이 둘의 차이가 궁금해졌고 의존성을 어느 쪽에서 관리하는 게 좋은지도 궁금해졌다.
pip의 Requirements Files를 보면 setup.py vs requirements.txt라는 글이 링크되어 있는데 다행히도 setup.py와 requirements.txt의 차이점과 사용 방법으로 번역이 되어 있다.
setup.py
에는 PyPI에 배포할 라이브러리를 만들 때 의존성을 지정하고requirements.txt
는 서버 등에 배포할 때 의존성을 지정하는데 사용한다.setup.py
는 추상화된 의존성을 의미하고requirements.txt
는 실제 특정 라이브러리를 지정하는데 사용한다.- PyPI가 아닌 다른 곳에 패키지를 올려놓고 사용할 때
requirements.txt
에서 받을 곳을 지정해서 사용할 수 있다. - 추상 의존성과 구체적 의존성을 나누어서 사용할 때의 좋은 점은 공개된 라이브러리를 수정해서 사용할 때
requirements.txt
에서 다른 버전을 사용하도록 지정해서 사용할 수 있다.
이 글을 읽어보니 setup.py
와 requirements.txt
를 둘 다 사용하면서 의존성을 관리하고 개발자는 pip install -r requirements.txt
로 설치해서 사용하도록 안내하는 게 좋은 방법이라고 생각되었다.(Flask 예제에서는 그렇게 하고 있지 않지만...) 일단 이렇게 관리하기 시작한 뒤에 필요한 경우 requirements.txt
에서 다른 곳에서 받아오도록 지정할 수 있다.
pip freeze > requirements.txt
명령어를 이용해서 현재 로컬에 설치된 패키지 기준으로 requirements.txt
를 만들어서 사용할 수 있지만 윗글에서 나온 대로 requirements.txt
를 다음과 같이 만들어서 사용했다. PyPI에 올라온 라이브러리를 직접 수정해서 사용할 일은 근래에는 없을 것 같으므로 추가하는 의존성은 setup.py
에서 지정해서 사용하면 될 것 같다.
$ cat requirements.txt
--index-url https://pypi.python.org/simple/
-e .
이를 이용해서 재설치를 해보자 정상적으로 잘 설치가 된다.
$ pip install -r requirements.txt
Obtaining file:///Users/outsider/demo (from -r requirements.txt (line 3))
Collecting flask (from demo==0.0.0->-r requirements.txt (line 3))
Using cached Flask-0.12.2-py2.py3-none-any.whl
Collecting Jinja2>=2.4 (from flask->demo==0.0.0->-r requirements.txt (line 3))
Using cached Jinja2-2.9.6-py2.py3-none-any.whl
Collecting Werkzeug>=0.7 (from flask->demo==0.0.0->-r requirements.txt (line 3))
Using cached Werkzeug-0.12.2-py2.py3-none-any.whl
Collecting itsdangerous>=0.21 (from flask->demo==0.0.0->-r requirements.txt (line 3))
Collecting click>=2.0 (from flask->demo==0.0.0->-r requirements.txt (line 3))
Using cached click-6.7-py2.py3-none-any.whl
Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->flask->demo==0.0.0->-r requirements.txt (line 3))
Installing collected packages: MarkupSafe, Jinja2, Werkzeug, itsdangerous, click, flask, demo
Running setup.py develop for demo
Successfully installed Jinja2-2.9.6 MarkupSafe-1.0 Werkzeug-0.12.2 click-6.7 flask-0.12.2 itsdangerous-0.24 demo
setup.cfg
예제 프로젝트인 flaskr에는 setup.cfg도 있었는데 이에 대해서는 Writing the Setup Configuration File에 나와 있었다.
[aliases]
test=pytest
setup.cfg
의 내용은 위와 같았는데 위 문서를 보면 그 형태는 다음과 같다.
[command]
option=value
이는 커맨드라인 명령어를 사용할 때 해당 명령어에 옵션을 자동으로 제공하기 위해서 사용하거나 기본값을 제공하기 위해서 사용한다고 한다. 자세한 내용에 대해서는 잘 못 찾겠기는 한데 위 내용이나 Flask의 setup.cfg의 내용을 볼 때 setup.py
에서 명령어를 실행할 때 참고정보를 여기에 넣어서 사용하는 것으로 보인다. 그래서 위에 별칭이 test=pytest
로 되어 있는 것으로 보아 test
명령어를 실행하면 pytest
를 실행하라는 정도의 의미로 보였다.
$ python setup.py test
running pytest
Searching for pytest
Best match: pytest 3.2.3
Processing pytest-3.2.3-py3.6.egg
Using /Users/outsider/demo/.eggs/pytest-3.2.3-py3.6.egg
Searching for py>=1.4.33
Best match: py 1.4.34
Processing py-1.4.34-py3.6.egg
Using /Users/outsider/demo/.eggs/py-1.4.34-py3.6.egg
running egg_info
writing demo.egg-info/PKG-INFO
writing dependency_links to demo.egg-info/dependency_links.txt
writing requirements to demo.egg-info/requires.txt
writing top-level names to demo.egg-info/top_level.txt
reading manifest file 'demo.egg-info/SOURCES.txt'
writing manifest file 'demo.egg-info/SOURCES.txt'
running build_ext
======================= test session starts =======================
platform darwin -- Python 3.6.3, pytest-3.2.3, py-1.4.34, pluggy-0.4.0
rootdir: /Users/outsider/demo, inifile:
collected 0 items
================== no tests ran in 0.00 seconds ===================
실제로 해보니 위 명령어로 pytest
가 잘 실행되었고 python setup.py pytest
로 실행해도 결과는 똑같았다.
MANIFEST.in
Flask 예제를 볼 때 용도를 몰라서 넘어갔던 파일이 MANIFEST.in이다. setup.py
의 include_package_data
를 볼 때 여기서 이 파일에 지정한 디렉터리의 데이터 파일을 자동으로 포함한다고 하는데 정확한 용도는 모르겠다. 예시의 MANIFEST.in
는 아래와 같이 되어 있다.
graft flaskr/templates
graft flaskr/static
include flaskr/schema.sql
이건 실제로 Flask 애플리케이션 개발을 해야 제대로 이해할 수 있을 것 같아서 일단은 개발환경에는 바로 넣지 않았다.
이제 Flask 애플리케이션 코드를 작성해 볼 수 있게 되었다.(한꺼번에 너무 많은 개념을 보느라 잘못된 부분도 있을 것 같지만...)
글을 쓴 이후로 SNS에서 피드백을 받은 내용이 있어서 추가로 내용을 보충합니다.(2017.10.09)
wheel
과 eggs
관련:
- wheel(
*.whl
)은 Eggs(*.egg
)를 대체하려고 나온 것이고 현시점에서 거의 대체된 것으로 보인다고 한다. 그래도 setuptools의 내부에서는 아직 Eggs를 사용하는 것으로 보인다. - C/C++ 코드를 포함하는 패키지를 위한 포맷을 "bdist"라고 부른다. 빌드된 것을 배포하므로 설치할 때 C/C++ 컴파일러가 없어도 된다.
- C/C++ 코드가 있더라도 소스 코드 형태로 압축해서 배포하는 것으로 "sdist"라고 부른다. 그래서 설치하는 쪽에서 C/C++ 컴파일러가 없으면 빌드할 수 없으므로 설치할 수 없다.
- C/C++ 코드가 전혀 없으면 bdist, sdist 어느 쪽으로 배포해도 상관없다.
- bdist 이름에는 OS, CPU 종류, libc 버전 등의 태그가 붙어서
pip
로 설치할 때 부합되는 bdist를 선택해서 설치한다. libsass의 배포 파일을 보면 64비트 인텔 프로세스 Windows에서는pip install libsass==0.13.2
을 했을 때 해당 플랫폼용 bdsit가 있으므로 이를 받아서 압축을 푸는 것으로 설치가 끝나지만 FreeBSD에서 설치한다면 bdist가 없으므로pip
가 sdist를 다운받아서 C/C++ 코드를 직접 빌드하게 된다.
requirements.txt
와 setup.py
관련:
- 둘 중 어느 쪽으로 패키징 할지
requirements.txt
를 관리할 때 직접 할지pip freeze
로 할지는 아닌 많은 논의가 있다.(현재 정해진 방법은 없다.) - 파이썬 라이브러리를 만들면
setup.py
를 쓰면 된다. - 실제로 해보면서도 라이브러리로 배포하는 게 아니라면
setup.py
가 필요한가 하는 의문이 있었는데 실제로 관련 논의가 많이 있는 것 같고 웹 애플리케이션의 경우requirements.txt
로만 관리하기도 한다고 한다. - CLI 명령어를 제공해야 할 때에는
setup.py
를 사용하는 이점이 있다. setup.py
는 일반 의존성 목록을 관리할 때 쓰고requirements.txt
는pip freeze
로 만들어서lockfile
처럼 쓰는 방법도 있다.
이건 개발해보면서 어느 쪽에 이점이 있는지 다양하게 시도해봐야 할 것 같다. 약속된 관례가 있는 것은 아니라는 것을 확인한 것도 큰 수확이다.(사실 이게 꽤 궁금했다.)
Flask 관련:
- CLI로 Flask 앱을 실행하는 방식이 예전에는 없었던 방식이라는 걸 알았다.
- Flask 문서를 보면 어디서는
flask
CLI로 실행하고 어느 문서에서는if __name__ == '__main__': app.run()
같은 코드를 하단에 두어서python hello.py
로 실행하고 있다. 대문에서 CLI로 안내하고 있어서 이 글은 그대로 적었는데 후자의 방법이 감춰진 마법이 적어서 이해하기 좋을 것이라고 한다.
Comments