-
Notifications
You must be signed in to change notification settings - Fork 0
flask tutorial
Flask 를 학습하면서 정리한 내용을 기록함.
python, pip, brew 등이 이미 설치된 상황에서 시작.
pip가 패키지를 global과 local로 구분해서 설치하지 않기 때문에 프로젝트간 환경을 구분하기 위해 virtualenv가 필요.
direnv가 좀 더 편리하나 사용법이 간단하여 기록을 남겨둔다.
$ pip3 install virtualenv
먼저 프로젝트를 담을 디렉토리를 먼저 생성하고, 해당 디렉토리에서 venv 를 생성한다. 아래 명령어에서 venv 앞에 .이 붙은 건 디렉토리 이름 앞에 .을 붙여서 숨김 폴더로 하기 위함이다. 저기서 프로젝트 명으로 쓰는 사람들도 있던데 취향인 것 같다.
$ mkdir flask-demo
$ cs flask-demo
$ virtualenv .venv
$ virtualenv --version # 설치 확인
# 만약 파이썬의 버전을 고정하고 싶다면 다음과 같이 한다.
# 해당 버전이 로컬에 설치되어 있어야만 설정 가능한 듯.
$ virtualenv -p python3.7 .venv
사용법
$ source .venv/bin/activate # venv 환경 실행
$ deactivate # 종료
정말로 매번 켜줘야한다. 이를 피하고 싶다면 direnv 쓰는게 가장 편리한 듯.
direnv를 이용해 환경을 구축하면 좀 더 명시적으로 버전을 관리할 수 있고, 무엇보다 npm이나 bundle처럼 디렉터리마다 자동적으로 관리되는 개발 환경을 만들 수 있다. 즉, active, deactive 따위를 안 써도 된다!
pyenv는 파이썬의 버전 관리 용으로 사용하고 virtualenv는 프로젝트 환경을 관리하는 용도로 사용된다.
$ brew install pyenv
$ brew install direnv
pyenv는 ~/.pyenv아래에 파이썬을 버전별로 설치한다.
$ pyenv install 3.7.4 # python 설치
$ ~/.pyenv/versions/3.7.4/bin/pip install virtualenv # 해당 버전의 python 환경에 venv 설치
셸 환경에 환경변수를 입력한다.
# zsh 의 경우
$ echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
# bash 의 경우
$ echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
~/.direnvrc 에 다음 내용을 입력한다.
# See https://github.com/direnv/direnv/wiki/Python#-pyenv
use_python() {
local python_root=$HOME/.pyenv/versions/$1
load_prefix "$python_root"
layout_python "$python_root/bin/python"
}
이제 direnv를 사용할 수 있다. 프로젝트 루트로 가서 .envrc를 생성하고 다음을 입력한다.
use python 3.7.4
위의 설정을 허용해준다.
$ direnv allow
이제 .direnv라는 폴더가 생성되었을 것이다. 이는 .venv와 같은 역할을 하고, activate시키지 않아도 환경을 분리하여 사용한다.
플라스크를 이용한 REST API server 개발에 flask-restful나 flask-restful-plus라는 패키지도 있는 모양인데, 이번엔 왠만하면 플라스크의 기본기를 익히기 위해 안 써도 될 거 같은 라이브러리는 최대한 안 쓰고 해보려고 한다.
$ pip3 install flask
전통적인 Hello World 프로젝트를 통해 Flask가 잘 설치되었는지 확인해본다.
vi app.py
from flask import Flask
app = Flask(__name__)
@app.route('/') # decorator 를 통해 라우팅 경로를 지정
def hello_world():
return 'Hello, World!'
@app.route('/user/<user_id>') # 가변되는 URI 는 <>를 이용해 표현한다.
def user(user_id):
return 'Hello, %s' % user_id
if __name__ == "__main__":
app.run()실행
$ python3 app.pyhttp://127.0.0.1:5000/ 에서 Hello World!가 출력되면 잘 된 것이다.
npm 처럼 설치된 패키지 들의 버전 관리를 관리 해보자. requirements.txt로 패키지를 관리하는 것이 거의 표준이라고 한다.
$ pip3 freeze > requirements.txt해당 패키지 들을 설치하려면
pip3 install -r requirements.txt위와 같은 명령어를 통해 협업 시 패키지가 서로 안 맞는 경우를 방지할 수 있다. 귀찮으니 shell script를 하나 만들어서 관리하자.
vi install-packages.sh#!/bin/bash
pip3 install -r requirements.txt
pip3 freeze > requirements.txt이제 source ./install-packages.sh 로 패키지 설치 및 목차 업데이트를 할 수 있다.
프로젝트의 구조를 만들어 보자. 누가 이렇게 하길래 일단 따라해 봄.
app/
├ main/
│ ├ controllers/
│ │ └ __init__.py
│ ├ models/
│ │ └ __init__.py
│ └ __init__.py
└ test/
environments/
├ requirements
├ install-packages.sh
└ init.py
run.py
run.py
# Start script
from app.main import create_app
app = create_app()
if __name__ == '__main__':
app.run()app/main/__init__.py
from flask import Flask
def create_app():
app = Flask(__name__)
@app.route('/') # decorator 를 통해 라우팅 경로를 지정
def hello_world():
return 'Hello, World!'
return appconfig값들을 app이 받아 올 수 있도록 처리하자. 안전하게 환경 변수를 관리하기 위해 dotenv도 쓰자.
# https://pypi.org/project/python-dotenv
$ pip3 install -U python-dotenv # 설치.env를 생성한다. 이때 실제 환경 변수가 들어간 파일은 커밋 하지 않고, 동일한 포멧에 페이크값만 들어간 .env-example같은 파일을 커밋 하여 상황에 맞게 환경 변수를 수정할 수 있도록 한다.
ENV=development # development or production
HOST=0.0.0.0
PORT=5001
DEBUG=True
TESTING=False
MYSQL_DATABASE=homestead
MYSQL_USER=homestead
MYSQL_ROOT_PASSWORD=secret
MYSQL_PASSWORD=secret
MYSQL_PORT=3306
MYSQL_HOSTNAME=localhost
TEST_HOST=0.0.0.0
TEST_PORT=5000
TEST_DEBUG=False
TEST_TESTING=True
TEST_MYSQL_DATABASE=homestead
TEST_MYSQL_USER=homestead
TEST_MYSQL_PASSWORD=secret
TEST_MYSQL_ROOT_PASSWORD=secret
TEST_MYSQL_PORT=3306
TEST_MYSQL_HOSTNAME=localhost
PRO_HOST=0.0.0.0
PRO_PORT=5000
PRO_DEBUG=False
PRO_TESTING=False
PRO_MYSQL_DATABASE=homestead
PRO_MYSQL_USER=homestead
PRO_MYSQL_PASSWORD=secret
PRO_MYSQL_ROOT_PASSWORD=secret
PRO_MYSQL_PORT=3306
PRO_MYSQL_HOSTNAME=localhostconfig.py 를 생성한다.
import os
from os.path import join, dirname
from dotenv import load_dotenv
def get_env(key):
dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)
return os.getenv(key)
def config(key):
if get_env('ENV') == 'development':
return get_env(key)
elif get_env('ENV') == 'test':
return get_env('TEST' + key)
elif get_env('ENV') == 'production':
return get_env('PRO' + key)
# Server Configuration
ENV = get_env('ENV')
DEBUG = config('DEBUG')
TESTING = config('TESTING')
HOST = config('HOST')
PORT = config('PORT')
# Database Configuration
MYSQL_DATABASE = config('MYSQL_DATABASE')
MYSQL_USER = config('MYSQL_USER')
MYSQL_ROOT_PASSWORD = config('MYSQL_ROOT_PASSWORD')
MYSQL_PASSWORD = config('MYSQL_PASSWORD')
MYSQL_PORT = config('MYSQL_PORT')
MYSQL_HOSTNAME = config('MYSQL_HOSTNAME')app/main/__init__.py와 run.py를 수정한다.
app/main/__init__.py
from flask import Flask
def create_app(override=None):
app = Flask(__name__)
# Initialize config (config.py)
app.config.from_object('config')
if override:
app.config.update(override)
@app.route('/') # decorator 를 통해 라우팅 경로를 지정
def hello_world():
return 'Hello, World!'
return apprun.py
# Start script
from app.main import create_app
app = create_app()
if __name__ == '__main__':
app.run(
host=app.config['HOST'],
port=app.config['PORT'],
debug=app.config['DEBUG']
)데이터베이스 관련 내용을 ORM으로 처리 해 보자. 듣자 하니 migration은 Alembic 을 많이 쓰고 모델 관리는 SQLAlchemy를 많이 쓴다. 이때, 테이블 생성 등의 기능은 SQLAlchemy에게 있고, migration script들을 관리해주는 부분이 Alembic이다.
일단 mysql 기준으로. migration 부터.
# https://alembic.sqlalchemy.org/en/latest/
$ pip3 install alembic # 설치
$ alembic init database # 초기화. 이 때 init 뒤의 database 는 임의로 정한 폴더 명이다.
$ pip3 install SQLAlchemy-Utils # migration 등에서 파일 타입을 디테일하게 정할 때 씀Alembic를 설치하면 SQLAlchemy가 같이 설치된다. (pip3 freeze로 확인)
초기화를 진행하면 다음과 같은 구조가 프로젝트에 추가된다.
database/
├ versions/
├ env.py
├ README
└ script.py.mako
alembic.ini
설치를 했으니, DB와 연결해 보자.
원래는 alembic.ini의 sqlalchemy.url값을 수정해서 접속 하는게 기본이다.
sqlalchemy.url = driver://user:pass@localhost/dbname가 들어있는데, 이를 아래와 같이 써야 한다고 한다.
sqlalchemy.url = mysql+pymysql://username:password@localhost/dbname하지만 .env로 환경 변수를 통합 관리하는 입장에서 마음에 안 들고 보안상으로 좋지니 않으니 env.py를 수정 하기로 한다.
database/connection.py를 하나 만든다.
from sqlalchemy import create_engine
from config import MYSQL_USER, MYSQL_PASSWORD, MYSQL_HOSTNAME, MYSQL_DATABASE
def database_url():
return "mysql+pymysql://%s:%s@%s/%s" % (
MYSQL_USER,
MYSQL_PASSWORD,
MYSQL_HOSTNAME,
MYSQL_DATABASE,
)
engine = create_engine(database_url())그리고 env.py 를 아래와 같이 수정한다. 중요한 건 run_migrations_online()함수 내용이 변경 되었다는 점이다.
from logging.config import fileConfig
from sqlalchemy import create_engine
from alembic import context
from os.path import abspath, dirname
from sys import path
path.append(dirname(dirname(abspath(__file__)))) # 이게 없으니 database 폴더를 못 찾더라.
... 중략 ...
def run_migrations_online():
from database.connection import engine
connectable = engine
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()이제 DB연결 설정도 완료되었다.
Alembic의 사용법은 php의 Eloquent 나 node.js의 Sequelize 등 다른 언어에서 쓰는 방식과 유사하다.
$ alembic revision -m "메모 내용을 작성합시다" # 마이그레이션 스크립트 생성
$ alembic upgrade head # 마이그레이션 스크립트 적용마이그레이션 스크립트를 생성하면 database/versions에 차곡차곡 쌓인다.
테이블 하나 생성 하는 예제를 하나 들면: 아래 명령어로 migration script를 하나 생성한다.
$ alembic revision -m "create users table"마이그레이션 스크립트에 원하는 테이블 스키마를 작성한다.
1f9b6bfff97e_create_users_table.py
"""create users table
Revision ID: 1f9b6bfff97e
Revises: 36d29f362525
Create Date: 2019-10-15 16:15:58.827570
"""
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils as su
# revision identifiers, used by Alembic.
revision = '1f9b6bfff97e'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
'Users',
sa.Column('id', su.types.uuid.UUIDType, nullable=False),
sa.Column('username', sa.VARCHAR(length=100), nullable=False),
sa.Column('password', sa.CHAR(length=41), nullable=False),
sa.Column('name', sa.VARCHAR(length=30), nullable=False),
sa.PrimaryKeyConstraint('id'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci',
mysql_engine='InnoDB',
mysql_row_format='DYNAMIC'
)
pass
def downgrade():
op.drop_table('Users')
pass이제 위 스크립트를 터미널에서 실행한다.
$ alembic upgrade head
$ alembic downgrade -1 # 한 단계 다운그레이드본인의 DB에 Users테이블이 생성된 것이 확인된다면 성공 한 것이다.
...
(작성중)
2019-10-24