Flask 快速指南

下文為 Flask 的〈Quickstart〉文件的摘要。
安裝
在 Linux,並且 Python 已經安裝好且也把 Pipenv 也裝好的情況下,建一個專案資料夾 flask-app,進入專案資料夾。
安裝 Flask 套件:
> pipenv install Flask
Hello, World!
照慣例來個 Hello, World!。拿編輯器在專案資料夾內開個 hello.py 檔,內容如下:
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'
注意大小寫別打錯了!
切換到終端機,先加個環境變數讓 flask run 的時候知道要去跑這支腳本:
(flask-app) > export FLASK_APP=hello.py
(flask-app) > flask run
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
終端機提示符號前面多了那串 (flask-app) 表示該 shell 是位於 Python 虛擬環境內。
可以看到 Flask 有提示網址以及 CTRL+C 可以退出的字樣。瀏覽器打開 127.0.0.1:5000 應該就可以看到那親切的 Hello World。
除錯模式
在開發環境開啟除錯模式會觸發下列行為:
  • 啟動 debugger
  • 啟動自動重載
  • 啟動 Flask 的除錯模式
  • 如果沒有自動重載,每次改完程式還要手動重載,很不方便。
    一樣透過設定環境變數來開啟除錯模式,再跑一波 Flask:
    (flask-app) > export FLASK_ENV=development
    (flask-app) > flask run
    除錯模式很方便,但也透露了很多環境訊息,在正式環境記得不要打開。
    路由
    Flask 接收到某網址的請求後,會尋找該網址是否有對應的函式來做回應,這樣的動作或設計就叫路由。當代的 web app framework 都是採用路由的設計,而非真的有那個路徑的檔名存在,路由有可能是固定寫死的,也有可能寫成動態對應的,端看 app 的功能需求而定。
    拿上面的 Hello World 改一下加上另一個路由:
    @app.route('/')
    def index():
        return 'Index Page'
    
    @app.route('/hello')
    def hello():
        return 'Hello, World!'
    就只是很單純的靜態路由,應該很好理解。
    開始來點動態路由,動態路由的範例:
    @app.route('/user/<username>')
    def show_user_profile(username):
        # show the user profile for that user
        return f'User {username}'
    
    @app.route('/post/<int:post_id>')
    def show_post(post_id):
        # show the post with the given id, the id is an integer
        return f'Post {post_id}'
    
    @app.route('/path/<path:subpath>')
    def show_subpath(subpath):
        # show the subpath after /path/
        return f'Subpath {subpath}'
    傳入 app.route() 內的字串如果用角括號包住(像這樣: <variable_name>)會被識別成變數,且不僅 app.route() 可以識別這樣的變數,後續的函式也一樣可以識別這樣的變數。
    角括號內還可以轉型,參照上例,<int: post_id> 就是把客戶端請求的字串轉換成整數型態;<path: subpath> 就是把客戶端請求的字串轉換成路徑型態,完整的轉型清單如下:
  • string
  • int
  • float
  • path
  • uuid
  • 路由字串的最後,斜線或沒斜線在行為上是有所差異的:
    @app.route('/projects/')
    def projects():
        return 'The project page'
    
    @app.route('/about')
    def about():
        return 'The about page'
    如果客戶端請求 projects,Flask 會自動導引到 projects/。反之如果客戶端請求 about,Flask 並不會自動導引到 about/,如果依照上面的定義,客戶端請求 about/,只會接收到 404 錯誤。如果你又很畫蛇添足的另外定義了 about/ 的路由,Flask 會報錯,並提示 URL 被重複定義的訊息。
    這樣的機制確保了 URL 的唯一性,projects = projects/,about 就是 about,沒有 about/。
    預設情況下,Flask 只會回應 HTTP GET 請求,如果希望 Flask 回應其它 HTTP 方法,route() 加上 methods 參數即可(注意引數名稱是複數的 methods,別拼錯了):
    from flask import request
    
    app = Flask(__name__)
    
    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if request.method == 'POST':
            return do_the_login()
        else:
            return show_the_login_form()
    靜態檔案
    依照 Flask 的慣例,在專案資料夾內建立一個子目錄 static,並把常用的靜態資源像是樣式表、圖像、JavaScript 這些檔案放進去,再配合 url_for() 就可以在 Python 腳本內自動產生這些資源檔的 URL:
    url_for('static', filename='style.css')
    上面的例子會對應到 static/style.css。
    模板
    在此之前, 路由、controller、view 都摻在一起寫,從這裡開始要引進模板的概念,Flask 使用 Jinja2 做為模板引擎,有了模板引擎,就可以在 view 內寫 view 自己的邏輯,並且幫你把變數內的 HTML 標籤過濾掉,還有就是可以達成組版的效果。
    使用 render_template() 方法來使用模板:
    from flask import render_template
    
    @app.route('/hello/')
    @app.route('/hello/<name>')
    def hello(name=None):
        return render_template('hello.html', name=name)
    依照 Flask 的慣例,它會在 templates 資料夾內去找模板檔,而 templates 資料夾的位置會根據這支 web app 是模組或是套件而有所不同。
    如果是模組:
    /application.py
    /templates
        /hello.html
    如果是套件:
    /application
        /__init__.py
        /templates
            /hello.html
    以上面的 hello.html 為例的模板內容:
    <!doctype html>
    <title>Hello from Flask</title>
    {% if name %}
      <h1>Hello {{ name }}!</h1>
    {% else %}
      <h1>Hello, World!</h1>
    {% endif %}
    模板內還可以存取到 requestsessiong 物件與 get_flashed_messages() 方法。
    存取 request 資料
    客戶端傳來的資料都放在 request 這個全域變數內,web app 利用 request 提供的資訊與客戶端互動。
    客戶端的 HTTP 方法存放在 requestmethod 屬性內,而 form 就存放在 requestform 屬性內。示例:
    from flask import request
    from flask import render_template
    
    @app.route('/login', methods=['GET', 'POST'])
    def login():
        error = None
        if request.method == 'POST':
            if valid_login(request.form['username'], request.form['password']):
                return log_the_user_in(request.form['username'])
            else:
                error = 'Invalid username / password'
        # the code below is executed if the request method was GET or the credentials were invalid
        return render_template('login.html', error=error)
    如果 form 屬性不存在,伺服器端會引發 KeyError 錯誤,客戶端則會收到 HTTP 400 錯誤。
    如果是要取用 URL 參數,則使用 args 屬性內的 get() 方法:
    searchword = request.args.get('key', '')
    如果是要取用客戶端上傳的檔案,先確定在前端 HTML 表單的設定正確的屬性 enctype="multipart/form-data",瀏覽器才會正確的把檔案上傳。調用 requestfiles 屬性就可以調用到檔案:
    from flask import request
    
    @app.route('/upload', methods=['GET', 'POST'])
    def upload_file():
        if request.method == 'POST':
            f = request.files['the_file']
            f.save('/var/www/uploads/uploaded_file.txt')
    files 本身是個 dictionary,所以必須呼叫它裡面的 key 才會接到真正的檔案,這個 file 物件的行為就像標準的 Python file 物件,但它有個 save() 方法讓我們把檔案存到自己想要的路徑。
    如果想要沿用客戶端上傳的檔名,則調用 filename 屬性,但由於檔名的不可預知,可能會有安全風險,最好用 Werkzeug 的 secure_filename() 方法過濾掉:
    from flask import request
    from werkzeug.utils import secure_filename
    
    @app.route('/upload', methods=['GET', 'POST'])
    def upload_file():
        if request.method == 'POST':
            f = request.files['the_file']
            f.save('/var/www/uploads/' + secure_filename(f.filename))
    如果是要讀取 cookie 就調用 request 物件的 cookies 屬性,如果是要設置 cookie 就用 response 物件的 set_cookie() 方法。
    requestcookies 也是一個 dictionary,裡面放了所有可以調用的 cookie。
    但是如果想使用 session 的話,Flask 有提供更完善的 session 機制可以利用,不要手工用 cookie 來管理 session。
    讀取 cookie 的範例:
    from flask import request
    
    @app.route('/')
    def index():
        username = request.cookies.get('username')
        # use cookies.get(key) instead of cookies[key] to not get a KeyError if the cookie is missing.
    存入 cookie 的範例:
    @app.route('/')
    def index():
        resp = make_response(render_template(...))
        resp.set_cookie('username', 'the username')
        return resp
    重導頁面與錯誤頁面
    要重導使用 redirect() 方法,要中斷並報錯用 abort() 方法:
    from flask import abort, redirect, url_for
    
    @app.route('/')
    def index():
        return redirect(url_for('login'))
    
    @app.route('/login')
    def login():
        abort(401)
        this_is_never_executed()
    如果不想使用 Flask 預設的陽春錯誤頁,則利用 errorhandler() 修飾子來做客製:
    from flask import render_template
    
    @app.errorhandler(404)
    def page_not_found(error):
        return render_template('page_not_found.html'), 404
    注意到 return 那行最後面的 404,雖然上面的修飾子已經是 404,但 return 後面還是要加 404 Flask 才認得這是 404 錯誤頁。
    Response
    View 的回傳值都會被自動轉換成 response 物件,如果原本是回傳字串,則字串內容會被包成 response body,再加上 200 的 HTTP 狀態碼,與 text/htmlmimetype
    具體的轉換邏輯如下:
  • 如果原本就是回傳 response 物件,則就原樣回傳,不經過轉換加工。
  • 如果是字串,會被轉換成 response 物件,加上預設參數。
  • 如果是 tuple,則依照特定格式轉換成 response 物件:(response, status, headers)(response, headers) 裡面的 statusheaders 值會變成 response 物件的屬性,其中 header 是一個 list 或 dictionary,裡面的欄位就會是 HTTP header 的資訊。
  • 如果以上皆非,Flask 會假設該回傳值是 WSGI 並轉換成 response 物件。
  • 如果想要在 view 裡面先取得 response 物件,可以使用 make_response() 方法。
    假設有個 view 如下:
    @app.errorhandler(404)
    def not_found(error):
        return render_template('error.html'), 404
    make_response() 來幫它加工加上一組 header 資訊:
    @app.errorhandler(404)
    def not_found(error):
        resp = make_response(render_template('error.html'), 404)
        resp.headers['X-Something'] = 'A value'
        return resp
    Session
    Session 用來紀錄、辨識用戶的活動,實現方式是加密過的 cookie。
    得先設置密鑰才能使用 session:
    from flask import Flask, session, redirect, url_for, escape, request
    from werkzeug.utils import secure_filename
    
    app = Flask(__name__)
    
    # Set the secret key to some random bytes. Keep this really secret!
    app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
    
    @app.route('/')
    def index():
        if 'username' in session:
            return f"Logged in as {escape(session['username'])}"
        return 'You are not logged in'
    
    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if request.method == 'POST':
            session['username'] = request.form['username']
            return redirect(url_for('index'))
        return '''
            <form method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
            </form>
        '''
    
    @app.route('/logout')
    def logout():
        # remove the username from the session if it's there
        session.pop('username', None)
        return redirect(url_for('index'))
    前面模板的章節有說過,模板引擎會幫我們把表單的 HTML 過濾掉,而在這裡沒有使用模板引擎,所以手動調用了 escape() 方法來濾掉 HTML 碼。
    最後附註一點,瀏覽器可能會限制單一 cookie 容量,如果發現某個值應該要有卻調用不出來的話,想想看是不是超過 cookie 的容量上限了。
    快閃訊息
    快閃訊息用於發送通知給用戶,例如對用戶某個行動的反應。在前一個 request 使用 flash() 方法設定訊息,在下一個 request 就可以用 get_flashed_messages() 方法來讀出訊息。
    Log 紀錄
    Flask app 物件有使用 Python 內建的 logger 模組,可以簡單調用:
    app.logger.debug('A value for debugging')
    app.logger.warning('A warning occurred (%d apples)', 42)
    app.logger.error('An error occurred')

    28

    This website collects cookies to deliver better user experience

    Flask 快速指南