Sphinx로 TIL 문서 페이지 만들기

Today I Learned 을 언제부터인가 쓰고 있었는데 Markdown으로 작성하고 Git으로 관리하니 매우 편했다.

하지만 다시 보고 싶을 때 검색이 용이하지 않아서 뭔가 검색할 수 있는 페이지를 만들면 좋겠다 싶어서 이것 저것 찾아보았다.

Markdown을 정적 웹 페이지로 만들어주는 툴은 많았지만(심지어 이 블로그도 Markdown 기반…) 뭔가 안써본걸 써보고 싶었다.

그렇게 알아보던 중 Python 문서화 툴인 Sphinx를 사용할 수 있지 않을까라는 막연한 생각에 찾아봤는데 역시나 가능했다.

Sphinx???

SphinxPython Documentation Generator 이다.
Python Code의 docstring을 가져와 자동으로 문서화해주는 라이브러리인데 설정도 아주 손쉽게 할 수 있어서 널리 사용되고 있다.

또한 reStructuredText 문서도 테마에 맞는 HTML로 만들어주고 검색페이지도 만들어 줘서 장점이 한둘이 아니다.

그리고 Read The Docs를 사용하면 무료로 호스팅도 가능하다.

자, 그럼 셋팅을 해보자

일단 TIL 디렉토리는 아래처럼 되어있다.

1
2
3
4
5
6
7
8
.
├── README.md
├── mysql
│   └── mysql.md
├── python
│   └── python.md
└── vim
└── vim.md

그리고 Sphinx를 설치하고 sphinx-quickstart를 사용해 기본 셋팅을 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ sphinx-quickstart
> Root path for the documentation [.]:
> Separate source and build directories (y/n) [n]:
> Name prefix for templates and static dir [_]:
> Project name: TIL
> Author name(s): Yunseop Song
> Project version []: 1.0
> Project release [1.0]:
> Project language [en]:
> Source file suffix [.rst]:
> Name of your master document (without suffix) [index]:
> Do you want to use the epub builder (y/n) [n]:
> autodoc: automatically insert docstrings from modules (y/n) [n]:
> doctest: automatically test code snippets in doctest blocks (y/n) [n]:
> intersphinx: link between Sphinx documentation of different projects (y/n) [n]:
> todo: write "todo" entries that can be shown or hidden on build (y/n) [n]:
> coverage: checks for documentation coverage (y/n) [n]:
> imgmath: include math, rendered as PNG or SVG images (y/n) [n]:
> mathjax: include math, rendered in the browser by MathJax (y/n) [n]:
> ifconfig: conditional inclusion of content based on config values (y/n) [n]:
> viewcode: include links to the source code of documented Python objects (y/n) [n]:
> githubpages: create .nojekyll file to publish the document on GitHub pages (y/n) [n]:
> Create Makefile? (y/n) [y]:
> Create Windows command file? (y/n) [y]: n

Creating file ./conf.py.
Creating file ./index.rst.
Creating file ./Makefile.

Finished: An initial directory structure has been created.

You should now populate your master file ./index.rst and create other documentation
source files. Use the Makefile to build the docs, like so:
make builder
where "builder" is one of the supported builders, e.g. html, latex or linkcheck.

그럼 여러 파일이 생겨서 다음과 같이 바뀐다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├── Makefile
├── README.md
├── _build
├── _static
├── _templates
├── conf.py
├── index.rst
├── mysql
│   └── mysql.md
├── python
│   └── python.md
└── vim
└── vim.md

이 상태에서 make html을 사용하면 _build 디렉토리에 문서 페이지가 생성된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ make html
Running Sphinx v1.6.5
making output directory...
loading pickled environment... not yet created
building [mo]: targets for 0 po files that are out of date
building [html]: targets for 1 source files that are out of date
updating environment: 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
writing output... [100%] index
generating indices... genindex
writing additional pages... search
copying static files... done
copying extra files... done
dumping search index in English (code: en) ... done
dumping object inventory... done
build succeeded.

Build finished. The HTML pages are in _build/html.

그리고 _build/html/index.html을 열면 다음처럼 깔끔한 페이지를 볼 수 있다.

하지만 이 상태라면 마크다운 문서는 보여지지 않는다.
Sphinx에서 마크다운을 사용하려면 문서에 나온대로 recommonmark라는 패키지를 사용해야한다.

일단 recommonmark를 설치하자.

1
$ pip install recommonmark

그리고 conf.py 파일을 수정하자.

1
2
3
4
5
6
7
from recommonmark.parser import CommonMarkParser

source_parsers = {
'.md': CommonMarkParser,
}

source_suffix = ['.rst', '.md']

그리고 Sphinx에서 바라보는 master_docindex.rsttoctree를 추가해야한다.
index.rsttoctree를 추가하는 이유는 Markdown에서 지원을 하지 않기 때문에 약간의 편법…(사실 어떻게 하는지 잘 모르겠다.)

1
2
3
4
5
6
7
8
.. toctree::
:caption: TOC:
:glob:
:titlesonly:

mysql/*
python/*
vim/*

이렇게 추가를 하고 다시 make html을 하면 TOC가 제대로 추가된 것을 확인할 수 있다.

배포를 해봅시다.

Sphinx를 사용해 TIL 페이지는 완성되었다.

이제 배포만 남았는데 어디로 어떻게 배포할 지를 결정해야 했는데 구글링을 해보던 중 아주아주 좋은 것을 발견했다.

travis-sphinx라는 것인데 내 고민을 한방에 해결해주었다.

travis-sphinx를 사용하면 나는 문서를 커밋해서 Push하면 Travis에서 Sphinx로 빌드해서 gh-pages 브랜치로 배포를 해줘서 Github 호스팅을 사용할 수 있다.

travis-sphinx 설치

우선 pip를 사용해 travis-sphinx를 설치합시다.

1
$ pip install travis-sphinx

.travis.yml

.travis.yml을 추가한다.

1
2
3
4
5
6
7
8
9
10
language: python - "2.7"

install:
- sudo pip install -U -r requirements.txt

script:
- travis-sphinx build -s . --nowarn

after_success:
- travis-sphinx deploy

Github Access Token 추가

Access Token 페이지로 가서 Generate new token을 눌러 Travis에서 사용할 토큰을 생성하자.
권한은 public_repo만 줬다.

생성된 토큰을 복사해 두고 Travis에 설정을 합시다.

Travis Environment Variables 추가

Travis에 설정 페이지로 가서 Environment VariablesGH_TOKEN 이름으로 토큰 값을 추가한다.

배포!

이제 master로 푸시하면 Travis에서 Sphinx로 빌드한 html 파일들을 gh-pages 브랜치로 푸시하게 된다.

이렇게 빌드 내역도 볼 수 있드아

그리고 테마는 Readthedocs를 사용해서 다음과 같이 TIL 페이지를 만들었다.

그렇지만

Sphinx에서 Markdown을 사용할 때 Github Flavor Markdown에서는 지원하는 Table을 지원하지 않는다.

eval_rst라는 기능을 사용해서 rst 문법으로 Table을 사용할 수 있겠지만 아직 그냥 냅뒀다. 아직 답을 못찾은 걸 수도 있지않을까 해서…

그래도

이 페이지를 만들고 더 자주 TIL을 쓰게 되는 것 같아 잘 해둔 것 같다.

서버리스 마이크로서비스(Serverless Microservice) with Zappa -2-

이전의 포스팅Zappa와 함께하는 Serverless Microservice - 희망편이었다면

이번의 포스팅은 Zappa와 함께하는 Serverless Microservice - 파멸편이라고 할 수 있다.

Zappa를 도입하면서 삽질했던 것들을 적어보려한다.

삽질 1. Package

Lambda에서는 순수 Python으로 작성된 모듈만 사용할 수 있다. 별도의 컴파일 과정이 불가능하기 때문이다.

그래서 MySQL-Python 모듈도 쓰지못하는 문제가 있었다.

이럴 때는 Lambda가 돌아가는 환경과 제일 비슷한 Amazon Linux에서 패키지를 빌드해서 사용하는 방법과

같은 기능을 하는 다른 모듈로 대체하는 방법(MySQL-Python -> pymysql)이 있다.

하지만 Zappa에서는 미리 컴파일된 패키지를 제공해서 이를 가능하게 해준다. 모든 패키지는 아니고 사람들이 자주 쓰는 일부 지원하는 패키지가 있다.

찾아보니 docker-lambda라는 프로젝트도 있어서 활용할 수 있을 것 같다. Amazon Linux의 도커 이미지로 도커 컨테이너 안에서 Lambda에 사용할 패키지를 구성해서 테스트해볼 수 있다고 한다.

삽질 2. VPC 설정

배포는 성공했다. 하지만 여전히 500 Server Error 만 뱉어대고 있다.

로그를 살펴보니 DB에 접근을 하지 못하고 있었다.

지금 사용하고 있는 DB는 AWS RDS를 VPC안에서 사용하고 있기 때문에 Lambda에서 접근하지 못했던 것이다.

따라서 Lambda에서도 VPC를 사용하면 문제해결이었다. DB에서 사용하고 있는 VPC의 Subnet과 적절한 Securety Group을 zappa_settings.json 파일에 다음과 같이 설정을 추가하자.

1
2
3
4
5
6
{
"vpc_config": {
"SubnetIds": [ "subnet-12345678" ],
"SecurityGroupIds": [ "sg-12345678" ]
}
}

이제 DB에도 잘 연결되고 만사형통인 줄 알았지만 그게 아니었다.

삽질 3. Internet Gateway 설정

보통의 기능은 정상적으로 동작하지만 외부 망으로 요청을 보내면 연결을 하지 못해서 에러가 났다.

구글링을 해보니 VPC를 사용하는 Lambda는 Public Internet에 접속하지 못한다고 한다. 하지만 역시 방법이 있었다.

해결과정

1. VPC 설정

VPC를 하나 생성하자. 이미 있다면 그것을 사용하면 된다.
CIDR Block은 충분히 크게 잡는 것이 좋다.(ex. 192.168.0.0/16)

2. Subnet 설정

2개의 Subnet이 필요하다. CIDR Block이 겹치지 않게 설정하자.
하나는 Public Subnet 으로, 다른 하나는 Private Subnet 으로 사용할 것이다.

3. Internet Geteway 설정

1번에서 생성(사용)한 VPC를 attach한 Internet Gateway를 생성한다.

4. NAT Gateway 설정

2번에서 생성한 Public subnet 을 선택하고 생성함(Elastic IP 필요)

5. Route Table 설정

1번에서 생성(사용)한 VPC를 사용하는 2개의 Route Table이 필요하다.
이 역시 Public Route Table, Private Route Table 이다.

  • Public Route Table 의 Routes 탭에서 Destination: 0.0.0.0/0, Target을 3번에서 생성한 Internet Gateway로 설정하고, Public VPC를 연결한다.
  • Private Route Table 의 Routes 탭에서 Destination: 0.0.0.0/0, Target을 4번에서 생성한 NAT Gateway로 설정하고, Private VPC를 연결한다.

여기까지하면 AWS에서 설정은 완료된다.

6. zappa_settings.json 파일을 수정한다. SubnetIdsPrivate SubnetSubnet Id를 넣는다.
1
2
3
4
5
6
{
"vpc_config": {
"SubnetIds": [ "subnet-privateSubnetId" ],
"SecurityGroupIds": [ "sg-12345678" ]
}
}

설정이 다 되었다면 zappa update로 다시 배포를 하면 된다.

삽질 4. 잔잔한 버그들!

아주 간헐적으로 배포를 하면 "TypeError: 'NoneType' object is not callable" 이런 에러를 뱉으면서 에러를 내뿜었다.

zappa tail로 로그를 확인해보니 ModuleNotFoundError: No module named 'numpy' Import 에러가!!

로컬에서 잘 동작했고 virtualenv에 패키지가 설치되어 있는지 확인했는데도 뭐가 문제인지 잘 몰랐다.

그래서 혹시 나와 같은 이슈가 있을까 싶어서 찾아봤더니 이런 이슈가 등록되어 있었다.

나와 같은 고민의 냄새가 난다...

평소같았으면 그저 누군가가 해결해주기를 기다렸겠지만 뭔가 파보고 싶었다.

조금 파보니 Zappa가 패키징을 할 때 로컬에서 .whl 파일을 캐싱해서 쓰는데, 나중에 다시 쓰려할 때 파일이 손상되어 있다면 exception이 발생하는 버그를 발견했다.

그래서 버그를 잡고 구글 번역기의 힘을 빌려 아래처럼 코멘트를 달았다.

Thanks to Google Translate! 👍

그리고 조금후에 PR을 달라는 답변이와서 그렇게 난 Zappa의 contributor가 될 수 있었다.(막 엄청 대단한 일은 아니지만 처음이라…)

그렇게 내가 기여한 부분이 포함되어 새 버젼이 릴리즈 되어서 사용해보는데 이번엔 다른 버그를 발견했다.

그래서 바로 다시 PR을 보냈다.(뭐든 처음이 어렵지 두번째는 아니다.)

그러다가 Zappa 슬랙을 눈팅하던 중 나처럼 버그를 발견한 사람이 나왔다.

버그 발견!

이 떄다 싶어 바로 리액션을 줬다.

내가 고쳐줄게!

그렇게 머지가 되서 핫픽스로 릴리즈 되었다. Change Log에 내 이름이!

이 맛에 오픈소스하는건가...

삽질기라고 시작은 했지만

사실 꽤나 재미있었던 과정이었다.

이번 삽질로 AWS 기반의 네트워크 환경에 대해서 좀 더 알게되었고, 그동안 오픈소스에 관심은 많았지만 어떻게 시작을 해야할지 몰랐었는데 이렇게 좋은 오픈소스 프로젝트에 기여도 해볼 수 있어서

뭔가 삽질기보단 성장기라고 해야 더 적절할 것 같다.

앞으로 더 성장(삽질)해야겠다.

서버리스 마이크로서비스(Serverless Microservice) with Zappa -1-

내가 입사할 때 까지만 해도 회사의 백엔드는 Monolithic Architecture로 하나의 거대한 백엔드 시스템에서 여러가지 기능을 담당하고 있었다.

그리고 몇가지 이유로 이제 Monolithic Architecture를 고집할 수 없겠다 싶어 점진적으로 Microservice Architecture화 하기로 결정했다.

1차적으로 백엔드의 특정 기능을 담당하는 API 서버를 분리하기로 결정했는데 이번 기회에 요즘 핫한 Serverless Architecture를 시도해보기로 했다.

AWS의 Lambda, Azure의 Functions 등 여러가지 서버리스 아키텍쳐를 위한 제품들이 나오면서 서버리스 아키텍쳐(Serverless Architecture)가 뜨고 있다.

아니, 이미 꽤나 인기있는 아키텍쳐로 자리잡은 것 같다.

언젠가 서버리스 아키텍쳐를 한번 사용해보고 싶었는데 마침 기회가 왔다.

Microservice와 Serverless

개인적으로 이 둘의 궁합은 매우 잘 맞는다고 생각한다.

마이크로서비스(Microservice)는 하나의 거대한 서비스를 백엔드 시스템으로 사용하는 것이 아닌 여러 개의 작은 모듈처럼 구축한 서비스를 백엔드 시스템으로 연결하여 사용하는 것이다.

각 서비스는 프로그래밍 언어, 프레임 워크 등을 독립적으로 선택할 수 있고, 다른 서비스들과 독립적으로 배포가 될 수 있어서 하나의 서비스로 사용할 수 있어야 한다.

이는 서비스들이 해당 서비스에 특화된 프로그래밍 언어, 프레임워크, 배포환경 등을 선택할 수 있다 는 장점을 가져온다.

또한 기존 다른 서비스들에 의존성이 없기 떄문에 서비스를 배포할 때 고려해야할 것들을 줄일 수 있다.

서버리스(Serverless)는 말 그대로 서버가 없는 환경을 의미한다.

클라우드 서비스가 발전하면서 물리적인 하드웨어 서버가 클라우드 환경의 가상머신으로 대체 된 것처럼, 여러 API들을 수행하는 백엔드 시스템이 각 API들을 수행하는 하나의 코드만을 따로 배포할 수 있게 되었다.

개발자는 그저 비즈니스 로직에 맞는 코드만 작성하면 되고 이를 배포하면 된다. 다른 인프라나 데이터베이스 등의 부가적인 환경에 대해서 신경쓰지 않아도 된다는 것을 의미한다.

Microservice와 Serverless가 공통적으로 추구하는 방향은 비즈니스 로직에 집중하는 것이다.

Microservice를 사용해 서비스를 구축하고 이를 Serverless 환경으로 배포한다면 개발자는 다른 것에는 신경쓰지 않고 오로지 비즈니스 로직을 충실히 수행하는 서비스를 구축하고 배포하면 된다.

자, 그럼 서버리스의 세계로 가봅시다.

회사에서 AWS를 사용하고 있기 때문에 AWS Lambda의 선택은 당연했지만, Lambda를 어떻게 쓰는가에 대한 선택지는 매우 다양했다.

Lambda에서 실행할 코드를 작성해 AWS 콘솔에서 업로드하고 API Gateway도 직접 설정하는 방법도 있지만,

요즘에는 서버리스 프레임 워크도 많이 나와서 매우 쉽게 서버리스 아키텍쳐를 구축할 수 있다.

또한 서버리스를 위한 프레임 워크도 매우 다양한데(Serverless, Apex, Chalice, Zappa 등) CTO님의 제안으로 Zappa라는 프레임 워크를 사용하기로 했다.

Zappa는

AWS Lambda & API Gateway에 Python WSGI Application을 배포해 서버리스 아키텍쳐를 구성해주는 프레임 워크다.

지금까지 사용하며 겪은 바로 Zappa의 (매우 주관적인)장점은 다음과 같다.

  • Python WSGI Application을 배포하기 떄문에 코드의 변경없이 Django, Flask와 같이 WSGI Application을 그대로 배포할 수 있다.(내 생각에 이게 가장 큰 장점이 아닐까 싶다.)
  • 서버리스로 가기 위한 최소한의 셋팅을 다 알아서 해준다. 프로젝트를 압축하여 Lambda에 배포하고 API Gateway에서 Lambda를 사용할 수 있게 알아서 설정해준다. 또한 몇가지 설정만 추가한다면 다른 AWS의 서비스도 사용가능하다.
  • API Gateway의 Stage를 구성하기가 매우 쉽다. 그냥 설정파일에 stage용 설정을 추가하기만 하면 끝이다. 도메인까지 설정해서 쓰면 요긴하기 쓸 수 있다.

하지만 뭐든 그렇듯 물론 장점만 있지는 않다. 내가 생각하는 Zappa의 단점은

  • 모니터링이 힘들다. New Relic을 붙여서 사용하려고 했더니 뭔가 로그가 불규칙적으로 들어온다. 문제가 뭔지 잘 모르겠다.(해결방법을 아시는 분 있다면 공유좀…)
  • 그래서 AWS X-Ray를 쓰려했더니 Node.js, Java, .Net만 지원하다고 한다. 그래서 찾아보니 xrayvision이라는 프로젝트가 있어서 사용 중이다. 하지만 X-Ray가 모니터링 하기에 뭔가 부족한 느낌의 서비스인 것 같다.
  • 아직 성숙한 프로젝트가 아니다. 그렇다고 막 버그 투성이인 프로젝트는 아니다. 그럼에도 단점에 적은 이유는 Zappa로 배포하고 테스트를 진행하던 중 뭔가 안되는 부분이 있었는데, 알고보니 아직 지원을 안하는 것이었다. 하지만 다행히(?) 내가 삽질하던 그즈음에 누군가 PR를 보냈고 머지 되어 지금은 잘 사용하고 있다. 이런것을 보면 오히려 좋은 오픈소스 프로젝트에 기여할 기회가 많다 고 생각할 수도 있다.

자, 그럼 Zappa를 써봅시다.

Zappa Github Page에 가서 보면 알겠지만 매우 간단하다.

3줄로 요약하면

1
2
3
(env) $ pip install zappa
(env) $ zappa init
(env) $ zappa deploy

3줄의 명령어로 Python WSGI Application을 AWS Lambda & API Gateway에 배포할 수 있다.

시작하기 전에

Zappavirtualenv 안에 설치 되어야한다. Zappa가 배포할 때 virtualenv안에 있는 패키지들을 가져와서 압축하기 때문이다.

Zappapip를 사용해서 설치하면 된다.

1
(env) $ pip install zappa

Zappa를 사용하기 위해 설정파일을 만들어야하는데 zappa init 이라는 명령으로 사용하면 된다.

그럼 다음과 같이 몇가지 입력을 받고 설정파일을 만들어주는데 입력은 그냥 디폴트 값으로 넣어도 무관하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
(env) $ zappa init

███████╗ █████╗ ██████╗ ██████╗ █████╗
╚══███╔╝██╔══██╗██╔══██╗██╔══██╗██╔══██╗
███╔╝ ███████║██████╔╝██████╔╝███████║
███╔╝ ██╔══██║██╔═══╝ ██╔═══╝ ██╔══██║
███████╗██║ ██║██║ ██║ ██║ ██║
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝

Welcome to Zappa!

Zappa is a system for running server-less Python web applications on AWS Lambda and AWS API Gateway.
This `init` command will help you create and configure your new Zappa deployment.
Let's get started!

Your Zappa configuration can support multiple production stages, like 'dev', 'staging', and 'production'.
What do you want to call this environment (default 'dev'):

AWS Lambda and API Gateway are only available in certain regions. Let's check to make sure you have a profile set up in one that will work.
Okay, using profile default!

Your Zappa deployments will need to be uploaded to a private S3 bucket.
If you don't have a bucket yet, we'll create one for you too.
What do you want call your bucket? (default 'zappa-8wjmc0weu'):

What's the modular path to your app's function?
This will likely be something like 'your_module.app'.
Where is your app's function?: app.__init__.app

You can optionally deploy to all available regions in order to provide fast global service.
If you are using Zappa for the first time, you probably don't want to do this!
Would you like to deploy this application globally? (default 'n') [y/n/(p)rimary]:

Okay, here's your zappa_settings.json:

{
"dev": {
"app_function": "app.__init__.app",
"aws_region": "ap-northeast-1",
"profile_name": "default",
"s3_bucket": "zappa-8wjmc0weu"
}
}

Does this look okay? (default 'y') [y/n]:

Done! Now you can deploy your Zappa application by executing:

$ zappa deploy dev

After that, you can update your application code with:

$ zappa update dev

To learn more, check out our project page on GitHub here: https://github.com/Miserlou/Zappa
and stop by our Slack channel here: https://slack.zappa.io

Enjoy!,
~ Team Zappa!

위에 보이는 것처럼 배포는 zappa deploy <stage> 를 사용하면 된다.

최초 배포 후에는 zappa update <stage>를 사용하여 업데이트를 하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(env) $ zappa deploy dev
Calling deploy for stage dev..
Creating zappa-dashboard-dev-ZappaLambdaExecutionRole IAM Role..
Creating zappa-permissions policy on zappa-dashboard-dev-ZappaLambdaExecutionRole IAM Role.
Downloading and installing dependencies..
Packaging project as zip.
Uploading zappa-dashboard-dev-1505403223.zip (10.5MiB)..
100%|█████████████████████████████████████████████████| 11.0M/11.0M [00:04<00:00, 2.34MB/s]
Scheduling..
Scheduled zappa-dashboard-dev-zappa-keep-warm-handler.keep_warm_callback with expression rate(4 minutes)!
Uploading zappa-dashboard-dev-template-1505403250.json (1.6KiB)..
100%|█████████████████████████████████████████████████| 1.66K/1.66K [00:00<00:00, 3.32KB/s]
Waiting for stack zappa-dashboard-dev to create (this can take a bit)..
100%|█████████████████████████████████████████████████| 4/4 [00:14<00:00, 5.26s/res]
Deploying API Gateway..
Deployment complete!: https://pqj13rwzi5.execute-api.ap-northeast-1.amazonaws.com/dev

하지만

이렇게 모든일이 일사천리로 끝난다면 매우매우 좋았겠지만 꼭 예상하지 못한 이슈가 발생하기 마련이다.

Zappa를 도입하기로 했을 때 여러가지 이슈로 삽질을 하게 되었는데,(물론 내가 부족해서 그랬던것이 대부분이지만)

그런 이슈들을 기록으로 남겨두면 좋을 것 같아 다음 포스팅으로 써야겠다.

Python에서 is 와 == 의 차이

2줄 요약

is 는 레퍼런스 체크

== 는 값 체크

내용

Python에서 무언가를 비교할 때 is를 많이 쓰고 했다.

is== 모두 값을 비교한다는 것만 알고 이 둘을 별로 구분없이 사용했다.(하지만 아니었다.)

1
2
3
4
5
>>> a = 1
>>> a is 1
True
>>> a == 1
True

여기까지는 당연하다

1
2
3
4
5
>>> a = 257
>>> a is 257
False
>>> a == 257
True

a is 257False를 반환하는 반면 a == 257True를 반환한다.

이것은 is는 레퍼런스를 비교하고 ==는 값을 비교하기 때문이다.

1
2
3
4
>>> id(a)
140316191538784
>>> id(257)
140316191538808

이와 같이 서로 다른 메모리 주소 값을 가지고 있기 때문에 위와 같은 결과가 나오게 된다.

1
2
3
4
5
>>> a = 1
>>> id(a)
140316194656296
>>> id(1)
140316194656296

a = 1을 하게 되면 메모리 주소가 같기 때문에 True를 반환한 것!

하지만 이는 Python Interpreter에서 [-5, 256] 범위의 Integer를 미리 캐싱(?)하고 있기 때문에 발생하는 일이다.

1
2
3
4
5
6
>>> def test():
... a = 257
... print a is 257
...
>>> test()
True

저렇게 컴파일 된 함수(?)안에서는 똑같은 메모리를 바라보게 되어 비교해보면 True를 반환한다

새롭고 신기한 Python의 세계

Vim 으로 개발한다는 것

회사에서 Vim을 일하면서 사용한지 2달 쯤 되가는 것 같다.

아직도 기존에 쓰던 에디터인 Atom을 왔다갔다 하지만 점점 Vim을 사용하는 시간이 더 늘어나 것 같다.

이제는 거의 Vim : Atom = 8 : 2 정도?

아직 허접이지만 그동안 Vim을 사용한 후기 정도를 적어보려한다.

Tmux & Vim 의 조합이란!

Tmuxterminal multiplexer 이다.

터미널에서 session 별로 window를 만들게 해주고 터미널을 종료해도 session 이 유지되기 때문에 작업하기에 많은 도움을 준다.

또한 터미널을 pane으로 분할하여 여러 화면을 쓰게 해주는데 Vim과 서버를 띄워둔 터미널을 같은 화면에 띄워 놓고 작업한다면 생산성이 좋아진다.

테스트 코드를 작성하고 그 자리에서 테스트 코드를 실행할 수 있는 플러그인을 사용해서 쓰는데 정말 좋다.(vimux-nose-test 짱!)

Vim 으로 어디까지 될까…

사실 Vim을 이렇게 까지 써보기 전까지는 .vimrcset=number라던가 syntax=on 정도만 사용해서 Vim은 내게 매우 불편한 에디터 였다.(사실 에디터라고 생각하지도 않았다. 그냥 매우 엄청 되게 많이 불편한 무언가라고만 생각했었다.)

하지만 여러가지 플러그인들을 설치하고 Vim의 단축키들을 손에 익히니 정말 어디까지 되는 것인가 궁금할 정도였다.

지금은 거의 10개 정도의 플러그인을 설치해서 Auto Complete나 snippets 등 여러가지 기능들을 사용하고 있어서 여타 에디터 부럽지 않다.

내 vim이 이렇게 변하다니...

아직 셋팅하고 싶은 것들이 많기도 하고 과연 어떤 기능까지 될까 궁금해서 한때는 vim 플러그인들을 모아둔 사이트인 Vim Awesome을 틈만나면 들어가곤 했다.

얼마전에는 우아한형제들 기술 블로그에서 Vim 플러그인으로 벽돌깨기 게임을 만드신 분을 봤는데 정말 충격적이었다.

속으로 아마 저분은 Vim으로 우주도 창조할 수 있을 것 같다고 생각했다.

작업 환경을 통일하는 매직!

이렇게 터미널 기반으로 작업하다보니 여러가지 설정들만 가져가면 같은 환경에서 작업할 수 있는 이점이 생겼다.

.vimrc.tmux.conf를 관리하는 dotfiles라는 Repo를 만들어서 관리하고 있다.

이렇게 관리하니 다른 PC에서 이 저장소만 불러와서 적용하면 어디서든 내가 사용하던 환경으로 작업할 수 있다.

새로운 PC로 이동한다거나, AWS, Azure 서버에서 단 2 줄 이면 끝!

1
2
$ git clone https://github.com/songyunseop/dotfiles.git .dotfiles
$ cd .dotfiles && ./setting.sh

아직 멀었다.

뭐 이렇게 Vim을 사용하고 있지만 아직까지도 단축키가 궁금해서 구글링을 하고 급하게 작업해야하는 일이라면 Atom을 쓰곤한다.

아직 Vim의 숨겨진(사실 숨겨져 있진 않지만) 기능들을 전부 사용해보려면 멀었고, 그 기능들을 숙련되게 사용할 수 있을 때 생산성에 얼마나 영향을 줄지 상상도 되지 않는다.

하지만 지금 사용하는 만큼만 vim을 사용해도 손이 키보드에만 머무를 수 있게 되고 터미널 밖으로 나가지 않아도 되니 집중이 더 잘 되는 느낌이다.

언젠가는 Vim 없이 개발을 하지 못하는 날이 올지도…(아닐거다)

여담이지만…

내가 사용하고 있는 무선 마우스는 얼마동안 사용하지 않으면 절전모드로 돌아가는데 터미널에서 작업을 하다가 가끔 마우스를 잡았을 때 절전모드로 되어 있을 때는 은근 기분이 좋다.

Flake8로 Python Code를 Lint 해보자

lint는 컴퓨터 프로그래밍에서 의심스럽거나, 에러를 발생하기 쉬운 코드에 표시(flag)를 달아 놓는 것을 말한다. 원래는 C 언어에서 사용하던 용어였으나 지금은 다른 언어에서도 일반적으로 사용된다. - 위키백과, 우리 모두의 백과사전.

협업을 하다보면 코딩 컨벤션의 중요성을 느끼게 된다.

코딩 컨벤션을 통일해 다수의 개발자가 코드를 수정하더라도 일관성있는 코드를 생산하는 것은 매우 중요하다.

코드 리뷰를 통해 코딩 컨벤션 체크를 할 수 있지만, 모든 커밋의 모든 코드를 매번 체크해서 컨벤션을 유지하는 것은 매우 어려운 일이다.

그래서 각 언어 별로 이를 위한 도구가 나오게 되는데 이것을 보통 Lint한다고 하는 것 같다.

Flake8

Python 에도 당연히 Lint를 위한 도구가 존재하고 대표적으로 flake8 이 있다.

기본적으로 PEP8을 기반으로 코드 컨벤션을 검사한다.

기본적인 설치와 사용법은 다음과 같다.

1
2
$ pip install flake8
$ flake8 [option] <file_name|dir_name>

다음과 같이 Python 코드를 작성하자.

1
2
3
4
def hello():
print 'hello'
import os
hello()

그리고 Lint를 실행하면 다음과 같이 메세지를 띄워준다.

1
2
3
4
5
$ flake8 test.py
test.py:3:1: E305 expected 2 blank lines after class or function definition, found 0
test.py:3:1: E402 module level import not at top of file
test.py:3:1: F401 'os' imported but unused
test.py:5:1: W391 blank line at end of file

Flake는 다양한 옵션과 함께 쓸수 있는데(특정 오류만 체크한다던지 등) cli에서 --와 옵션을 써줘서 사용하거나 Configuration File을 만들어서 사용할 수 있다.

Configuration File 은 전역으로 사용하기 위해 User 별로 설정할 수 있다.

  • Linux, OS X: ~/.config/flake8
  • Windows : ~\.flake8

또한 각 프로젝트 별로 사용하기 위해 프로젝트의 상위 디렉토리의 setup.cfg, tox.ini, .flake8 와 같은 파일을 사용할 수 있다.

둘 중의 하나의 방법으로 아래의 내용으로 설정 파일을 만들자.

1
2
3
4
5
6
7
[flake8]

ignore = E501, E402, E261

exclude = .git, __pycache__

count = True

이제 다시 flake8을 실행하면 메세지의 변화가 생긴다.

1
2
3
4
5
$ flake8 test.py
test.py:3:1: E305 expected 2 blank lines after class or function definition, found 0
test.py:3:1: F401 'os' imported but unused
test.py:5:1: W391 blank line at end of file
3

다른 설정은 문서를 참고해서 작성하면 된다.

Git Hook

Git 에는 어떠한 이벤트에 특정 스크립트를 실행하는 Hook이라는 기능이 있다.

이 중 커밋하기 전에 실행되는 훅인 pre-commit에서 Lint를 하는 작업을 하게 할 수 있다.

Flake8 은 고맙게도 Git Hook을 사용해 Lint를 할 수 있는 기능을 미리 만들어뒀다.

이 기능은 Git과 Mercurial 을 지원한다.(아래는 Git에 관한 예다.)

문서를 보면 알겠지만 매우 간단하다.

1
$ flake8 --install-hook git

이 명령어를 입력하면 .git/hooks/pre-commit 파일이 생성된다.

strict, lazy 두 옵션을 설정해서 사용할 수 있다.

  • strict

기본적으로 false인데 이 때는 Lint의 결과에 상관없이 커밋이 진행된다.

1
$ git config --bool flake8.strict true

위 처럼 true로 설정을 해두면 Lint가 실패하면(에러메세지가 하나라도 있다면) 커밋이 취소되고 메세지로 Lint 결과를 보여준다.

  • lazy

기본적으로 false인데 이 때는 index에 올라간 코드만 검사를 한다.

1
$ git config --bool flake8.lazy true

lazy를 true로 바꾼다면 변경이 있는 파일 전체를 검사해서 Lint가 실패하면 커밋이 취소되고 메세지로 Lint 결과를 보여준다.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×