flask tutorial - RLidea/dev.docs GitHub Wiki
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.py
http://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 app
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__.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 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']
)
๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ด๋ จ ๋ด์ฉ์ 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