Skip to content

flask tutorial

Centell edited this page Apr 22, 2020 · 1 revision

Flask 시작하기

Flask 를 학습하면서 정리한 내용을 기록함.

Initialize

python, pip, brew 등이 이미 설치된 상황에서 시작.

1. 환경 설정

1-1. venv

pip가 패키지를 globallocal로 구분해서 설치하지 않기 때문에 프로젝트간 환경을 구분하기 위해 virtualenv가 필요.
direnv가 좀 더 편리하나 사용법이 간단하여 기록을 남겨둔다.

설치

$ pip3 install virtualenv

venv 생성

먼저 프로젝트를 담을 디렉토리를 먼저 생성하고, 해당 디렉토리에서 venv 를 생성한다. 아래 명령어에서 venv 앞에 .이 붙은 건 디렉토리 이름 앞에 .을 붙여서 숨김 폴더로 하기 위함이다. 저기서 프로젝트 명으로 쓰는 사람들도 있던데 취향인 것 같다.

$ mkdir flask-demo
$ cs flask-demo
$ virtualenv .venv
$ virtualenv --version  # 설치 확인

# 만약 파이썬의 버전을 고정하고 싶다면 다음과 같이 한다. 
# 해당 버전이 로컬에 설치되어 있어야만 설정 가능한 듯.
$ virtualenv -p python3.7 .venv

사용법

$ source .venv/bin/activate  # venv 환경 실행
$ deactivate  # 종료

정말로 매번 켜줘야한다. 이를 피하고 싶다면 direnv 쓰는게 가장 편리한 듯.

1-2. 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시키지 않아도 환경을 분리하여 사용한다.

2. Install Flask

플라스크를 이용한 REST API server 개발에 flask-restfulflask-restful-plus라는 패키지도 있는 모양인데, 이번엔 왠만하면 플라스크의 기본기를 익히기 위해 안 써도 될 거 같은 라이브러리는 최대한 안 쓰고 해보려고 한다.

Install

$ 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.py

http://127.0.0.1:5000/ 에서 Hello World!가 출력되면 잘 된 것이다.

3. Manage Enviroments

Manage Packages

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 로 패키지 설치 및 목차 업데이트를 할 수 있다.

4. Project Structure

프로젝트의 구조를 만들어 보자. 누가 이렇게 하길래 일단 따라해 봄.

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 app

5. Config & .env

config값들을 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=localhost

config.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__.pyrun.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 app

run.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']
    )

6. ORM

데이터베이스 관련 내용을 ORM으로 처리 해 보자. 듣자 하니 migrationAlembic 을 많이 쓰고 모델 관리는 SQLAlchemy를 많이 쓴다. 이때, 테이블 생성 등의 기능은 SQLAlchemy에게 있고, migration script들을 관리해주는 부분이 Alembic이다.
일단 mysql 기준으로. migration 부터.

Migration with Alembic

# 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.inisqlalchemy.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테이블이 생성된 것이 확인된다면 성공 한 것이다.

Model Control with SQLAlchemy

...

(작성중)

2019-10-24

Clone this wiki locally