Background

研究了Flask 一段時間, 所以把自己的一些開發習慣&別的在網路上的教學做了一些整合. 發展了一個比較適合我自己的開發環境. 所以就把它記錄 & 放在github中.

Github: https://github.com/TaikerLiang/flask-basic

建議大家搭配原始碼看, 因為有些東西用打的很容易miss掉. 尤其是config那邊XD.

那其實也歡迎大家多參考 這東西沒有誰對誰錯, 大家可以多互相交流. 幾個我認為在開發中比較重要的部分:

  1. documents
  2. unit tests
  3. db migrations
  4. CI
  5. Revision control (這我就不講了, 太重要&太基本了)

這幾個是我覺得現在再開發中(後端 Backend)很重要的幾個部分.

我的出發點有兩點:

  1. 系統上線的品質
  2. 如果有人要跟你交接. 他能多快地上手(同時並能確保接手的Quality)

以上這兩點可能是我最近這幾年在學程式相關的新東西會去考慮的主要兩點. 那以下的文章會依照上面提到的 1~4 點來講我利用什麼工具跟Flask整合來達到我的目的.

Start

首先講一下自己的環境設定. Python3.6. 那沒記錯的話 Python3.3 之後好像就內建支援 virtual environment.

$ mkdir myapp
$ cd myapp
$ pyvenv .venv  # 算是我個人習慣. 你可以換個名字之類的
$ source .venv/bin/activate
$ pip3 install flask
$ touch run.py README.md requirements.txt

run.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello World!"

@app.route('/<name>')
def hello_name(name):
    return "Hello {}!".format(name)

if __name__ == '__main__':
    app.run()

接著python run.py 會看到類似下面這種東西. 打開瀏覽器看一下有沒有問題.

> python run.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

接下來要想一下你整個application的架構.

├── README.md
├── run.py
├── config
├── docs
├── requirements.txt
└── tests
  • config: 基本上你之後一定會有deploy相關的issue. 所以你需要config來幫助你.
  • docs: documents
  • tests: unit tests

Config

接著下來編輯config相關的檔案

$ cd config
$ touch __init__.py app.py

config/app.py

from config import database as db
from config import domain as dm

class Config(object):
    DEBUG = False
    TESTING = False
    CSRF_ENABLED = True
    SECRET_KEY = 'this-really-needs-to-be-changed'
    DB_HOST = db.DB_HOST
    DB_PORT = db.DB_PORT
    DB_USER = db.DB_USERNAME
    DB_NAME = db.DB_DATABASE
    DB_PW   = db.DB_PASSWORD
    SQLALCHEMY_DATABASE_URI = 'postgresql://%s:%s@%s/%s' % (DB_USER, DB_PW, DB_HOST, DB_NAME)

class ProductionConfig(Config):
    DOMAINNAME = dm.DOMAIN['prd']
    DEBUG = False

class StagingConfig(Config):
    DOMAINNAME = dm.DOMAIN['stg']
    DEVELOPMENT = True
    DEBUG = True

class DevelopmentConfig(Config):
    DOMAINNAME = dm.DOMAIN['dev']
    DEVELOPMENT = True
    DEBUG = True

class TestingConfig(Config):
    TESTING = True

簡單講一下這個設定檔的內容, 正常deploy的流程中可能包含了 Development, Testing and Production等不同的環境. 所以通常我們會在config中來指定目前的環境是哪一種, 因此會對應到不同的設定.

回到run.py中, 設定怎麼讀到你新設定的config檔.

run.py

import os
from flask import Flask

app = Flask(__name__)
app.config.from_object('config.app.DevelopmentConfig')

@app.route('/')
def hello():
    return "Hello World!"


@app.route('/<name>')
def hello_name(name):
    return "Hello {}!".format(name)

if __name__ == '__main__':
    app.run()

接的你就可以利用 app.config["NAME"] 來讀取你在 config/app.py 中class的內容.

Database

我自己是比較常用PostgreSQL, 之後會以PosgreSQL來操作, 所以需要先安裝相關的package:

  • PostgreSQL (9.5)
  • Psycopg2 (2.6.1) – a Python adapter for Postgres

Install

關於安裝PostgreSQL我這邊就不細講, 網路上應該一堆相關資源.

$ pip3 install Psycopg2 
# createdb in postgreSQL, if failed, maybe u should switch your user.
$ createdb basic

Migrations

要在Flask中達到 DB Migrations的目的需要安裝 Flask-Migrate.

$ pip3 install Flask-Migrate

那Flask-Migrate 中有兩種方式來管理你的 DB Migration:

  1. Including Flask-Migrate directly into your application
  2. Creating a separate script for handling database migrations

那我這邊主要介紹第二種, 我自己比較習慣用這種方式來管理.

manage.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand

import sys
sys.path.append("..")

app = Flask(__name__)
app.config.from_object('config.app.DevelopmentConfig')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
print(app.config['SQLALCHEMY_DATABASE_URI'])

db = SQLAlchemy(app)
migrate = Migrate(app, db)

manager = Manager(app)
manager.add_command('db', MigrateCommand)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))

if __name__ == '__main__':
    manager.run()

接著我們之後所有的 DB Migration的操作都會透過 manage.py 這的檔案.

# check which commands u can use
$ python manage.py db --help

接著就要先 init migrations 檔案

$ python manage.py db init

你會看到出現一個 migrations 的資料夾, 裡面會記錄你migrate的動作. 確認 manage.py 中Class相關的code沒問題後(會決定你的migration動作), 執行下面的指令.

$ python manage.py db migrate

執行完之後你可以到 migrations/versions 中看會發現多了一個程式. 長的類似這樣:

"""empty message

Revision ID: d237c3da9ca4
Revises:
Create Date: 2017-06-22 10:56:39.363314

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'd237c3da9ca4'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('user',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=128), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('user')
    # ### end Alembic commands ###

那我們發現裡面有兩個 functions: upgrade & downgrade. 所以之後可以利用這兩個functions來操作 migrate or restore.

$ python manage.py db upgrade
$ python manage.py db downgrade

所以整個的流程大致如下:

  1. Make an update to your database schema (in your development environment) and test it out
  2. python manage.py db migrate -m "message"
  3. python manage.py db edit, to check the migration script
  4. python manage.py db upgrade
  5. Commit to the changes to your git repository

Testing

Install

$ pip3 install pytest

那測試其實佔開發非常重要的一塊. 那我自己的習慣在web service中只測APIS而已. 所以這搭配著Documents. Documents中有幾個APIS那相對應的就會有幾個API的unit test. (感覺這樣比較直覺啦)

NOTICE: 使用pytest所以, unit test的檔名必須以test開頭或結尾

main_test.py

簡單的來說就是利用 requests 來模擬實際發送API的請求的狀況.

import sys
import requests
import json

sys.path.append("..")

from config import domain as dm

def test_index():
    url = dm.DOMAIN[dm.type] + '/'
    print (url)
    r = requests.get(url)
    res = json.loads(r.text)
    assert res['msg'] == "Hello World!"

def test_domain():
    url = dm.DOMAIN[dm.type] + '/domain'
    print (url)
    r = requests.get(url)
    res = json.loads(r.text)
    assert res['msg'] == "http://127.0.0.1:5000"

if __name__ == "__main__":
    test_index()
    test_domain()

Usage

$ python -m pytest
============================== test session starts ==============================
platform darwin -- Python 3.6.1, pytest-3.1.2, py-1.4.34, pluggy-0.4.0
rootdir: /Users/Taiker/Dropbox/dev/flask-basic, inifile:
collected 2 items

tests/main_test.py ..

============================== 2 passed in 0.57 seconds ==============================

CircleCI

那CI的部分是利用 CircleCI, 因為它提供可以讓private的專案可以免費的run他的服務(1500mins/per month). 這對我來說還不錯. 畢竟有些不可告人的專案是不能open的, 所以就決定用他們家的服務.

以下是circle CI的設定檔, 但是不包含deploy的部分. 因為我自己也沒用過, 所以之後打算再找時間來研究一下. 以下只包含code push上 github 的時候 CI 會自動幫你跑所有的unit tests.

circle.yml

machine:
  python:
    version: 3.5.2
  hosts:
    localhost: 127.0.0.1

dependencies:
  pre:
    - pip3 install --upgrade pip
    - pip3 install -r requirements.txt

test:
  pre:
    - python run.py:
        background: true
    - sleep 4

  override:
    - python -m pytest

Documents

Documents我則是用了APIDOC來幫助我, 因為我主要focus的部分是API不是所有的function. 相關的介紹可以參考我之前寫的文章

Conclusion

其實這篇文章只是基本的Flask的設定再加上我自己個人喜好的工具. 這樣如果我之後想要快速的開發一些好玩的東西我可以很快的就開始. 而不用在設定一堆東西. 但其實還有很多可以改進的空間, 期中一部分就是router的管理, 似乎沒有向 Laravel 一樣可以透過一個檔案來管理所有的API routers. 這我之後有空會再慢慢研究.

在做這個專案的過程中, 自己也常在想怎不使用Django就好. 可能我跟Django不熟吧, 這一類的Framework感覺黑盒子很多, 也沒有Flask輕便&彈性, 這可能也是我會建立這個project的初衷吧.

Reference