一、請(qǐng)求全局鉤子【hook】

此處的全局鉤子,其實(shí)就是類似django里面的中間件。 也就是只要調(diào)用或者注冊(cè)了,在http請(qǐng)求響應(yīng)中是必然執(zhí)行的。

在客戶端和服務(wù)器交互的過程中,有些準(zhǔn)備工作或掃尾工作需要處理,比如:

  • 在項(xiàng)目運(yùn)行開始時(shí),建立數(shù)據(jù)庫連接,或創(chuàng)建連接池;

  • 在客戶端請(qǐng)求開始時(shí),根據(jù)需求進(jìn)行身份識(shí)別,權(quán)限校驗(yàn);

  • 在請(qǐng)求結(jié)束視圖返回?cái)?shù)據(jù)時(shí),指定轉(zhuǎn)換數(shù)據(jù)的格式,或者記錄操作日志;

為了讓每個(gè)視圖函數(shù)避免編寫重復(fù)功能的代碼,F(xiàn)lask提供了通用設(shè)置的功能,即請(qǐng)求鉤子。

請(qǐng)求鉤子是通過裝飾器的形式實(shí)現(xiàn),F(xiàn)lask支持如下四種請(qǐng)求鉤子(注意:鉤子的裝飾器名字是固定):

  • before_first_request

    • 在處理第一個(gè)請(qǐng)求前執(zhí)行[項(xiàng)目剛運(yùn)行第一次被客戶端請(qǐng)求時(shí)執(zhí)行的鉤子]

  • before_request

    • 在每一次請(qǐng)求前執(zhí)行[項(xiàng)目運(yùn)行后,每一次接收到客戶端的request請(qǐng)求都會(huì)執(zhí)行一次]

    • 如果在某修飾的函數(shù)中返回了一個(gè)響應(yīng),視圖函數(shù)將不再被調(diào)用

  • after_request

    • 如果沒有拋出錯(cuò)誤,在每次請(qǐng)求后執(zhí)行

    • 接受一個(gè)參數(shù):視圖函數(shù)作出的響應(yīng)

    • 在此函數(shù)中可以對(duì)響應(yīng)值在返回之前做最后一步修改處理

    • 需要將參數(shù)中的響應(yīng)在此參數(shù)中進(jìn)行返回

  • teardown_request:

    • 在每一次請(qǐng)求后執(zhí)行

    • 接受一個(gè)參數(shù):錯(cuò)誤信息,如果有相關(guān)錯(cuò)誤拋出

    • 需要設(shè)置flask的配置DEBUG=False,teardown_request才會(huì)接受到異常對(duì)象。

代碼

 1 from flask import Flask, session
 2 
 3 # 應(yīng)用實(shí)例對(duì)象
 4 app = Flask(__name__)
 5 
 6 """給app單獨(dú)設(shè)置配置項(xiàng)"""
 7 # 設(shè)置秘鑰
 8 app.config["SECRET_KEY"] = "my SECRET KEY"
 9 
10 @app.before_first_request
11 def before_first_request():
12     """
13     這個(gè)鉤子會(huì)在項(xiàng)目啟動(dòng)后第一次被用戶訪問時(shí)執(zhí)行
14     可以編寫一些初始化項(xiàng)目的代碼,例如,數(shù)據(jù)庫初始化,加載一些可以延后引入的全局配置
15     """
16     print("----before_first_request----")
17     print("系統(tǒng)初始化的時(shí)候,執(zhí)行這個(gè)鉤子方法")
18     print("會(huì)在接收到第一個(gè)客戶端請(qǐng)求時(shí),執(zhí)行這里的代碼")
19 
20 
21 @app.before_request
22 def before_request():
23     """
24     這個(gè)鉤子會(huì)在每次客戶端訪問視圖的時(shí)候執(zhí)行
25     # 可以在請(qǐng)求之前進(jìn)行用戶的身份識(shí)別,以及對(duì)于本次訪問的用戶權(quán)限等進(jìn)行判斷。..
26     """
27     print("----before_request----")
28     print("每一次接收到客戶端請(qǐng)求時(shí),執(zhí)行這個(gè)鉤子方法")
29     print("一般可以用來判斷權(quán)限,或者轉(zhuǎn)換路由參數(shù)或者預(yù)處理客戶端請(qǐng)求的數(shù)據(jù)")
30 
31 
32 @app.after_request
33 def after_request(response):
34     print("----after_request----")
35     print("在處理請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法")
36     print("一般可以用于記錄會(huì)員/管理員的操作歷史,瀏覽歷史,清理收尾的工作")
37 
38     response.headers["Content-Type"] = "application/json"
39     response.headers["Company"] = "python.Edu..."
40 
41     # 必須返回response參數(shù)
42     return response
43 
44 
45 @app.teardown_request
46 def teardown_request(exc):
47     print("----teardown_request----")
48     print("在每一次請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法")
49     print("如果有異常錯(cuò)誤,則會(huì)傳遞錯(cuò)誤異常對(duì)象到當(dāng)前方法的參數(shù)中")
50     # 在項(xiàng)目關(guān)閉了DEBUG模式以后,則異常信息就會(huì)被傳遞到exc中,我們可以記錄異常信息到日志文件中
51     print(f"錯(cuò)誤提示:{exc}")  # 異常提示
52 
53 
54 @app.route("/")
55 def index():
56     print("-----------視圖函數(shù)執(zhí)行了---------------")
57     return "ok"
58 
59 if __name__ == '__main__':
60     # 啟動(dòng)項(xiàng)目的web應(yīng)用程序
61     app.run(host="0.0.0.0", port=5000, debug=False)

在第1次請(qǐng)求時(shí)的打印(關(guān)閉DEBUG模式,視圖代碼正確執(zhí)行,沒有異常的情況):

----before_first_request----
系統(tǒng)初始化的時(shí)候,執(zhí)行這個(gè)鉤子方法
會(huì)在接收到第一個(gè)客戶端請(qǐng)求時(shí),執(zhí)行這里的代碼
----before_request----
每一次接收到客戶端請(qǐng)求時(shí),執(zhí)行這個(gè)鉤子方法
一般可以用來判斷權(quán)限,或者轉(zhuǎn)換路由參數(shù)或者預(yù)處理客戶端請(qǐng)求的數(shù)據(jù)
-----------視圖函數(shù)執(zhí)行了---------------
----after_request----
在處理請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法
一般可以用于記錄會(huì)員/管理員的操作歷史,瀏覽歷史,清理收尾的工作
----teardown_request----
在每一次請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法
如果有異常錯(cuò)誤,則會(huì)傳遞錯(cuò)誤異常對(duì)象到當(dāng)前方法的參數(shù)中
錯(cuò)誤提示:None

在第2次請(qǐng)求時(shí)的打印(關(guān)閉DEBUG模式,視圖代碼正確執(zhí)行,沒有異常的情況):

----before_request----
每一次接收到客戶端請(qǐng)求時(shí),執(zhí)行這個(gè)鉤子方法
一般可以用來判斷權(quán)限,或者轉(zhuǎn)換路由參數(shù)或者預(yù)處理客戶端請(qǐng)求的數(shù)據(jù)
-----------視圖函數(shù)執(zhí)行了---------------
----after_request----
在處理請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法
一般可以用于記錄會(huì)員/管理員的操作歷史,瀏覽歷史,清理收尾的工作
----teardown_request----
在每一次請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法
如果有異常錯(cuò)誤,則會(huì)傳遞錯(cuò)誤異常對(duì)象到當(dāng)前方法的參數(shù)中
錯(cuò)誤提示:None

在第1次請(qǐng)求時(shí)的打印(關(guān)閉DEBUG模式,視圖執(zhí)行錯(cuò)誤,有異常的情況):

----before_first_request----
系統(tǒng)初始化的時(shí)候,執(zhí)行這個(gè)鉤子方法
會(huì)在接收到第一個(gè)客戶端請(qǐng)求時(shí),執(zhí)行這里的代碼
----before_request----
每一次接收到客戶端請(qǐng)求時(shí),執(zhí)行這個(gè)鉤子方法
一般可以用來判斷權(quán)限,或者轉(zhuǎn)換路由參數(shù)或者預(yù)處理客戶端請(qǐng)求的數(shù)據(jù)
-----------視圖函數(shù)執(zhí)行了---------------
----after_request----
在處理請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法
一般可以用于記錄會(huì)員/管理員的操作歷史,瀏覽歷史,清理收尾的工作
----teardown_request----
在每一次請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法
如果有異常錯(cuò)誤,則會(huì)傳遞錯(cuò)誤異常對(duì)象到當(dāng)前方法的參數(shù)中
錯(cuò)誤提示:division by zero

在第2次請(qǐng)求時(shí)的打印(關(guān)閉DEBUG模式,視圖執(zhí)行有異常的情況):

----before_request----
每一次接收到客戶端請(qǐng)求時(shí),執(zhí)行這個(gè)鉤子方法
一般可以用來判斷權(quán)限,或者轉(zhuǎn)換路由參數(shù)或者預(yù)處理客戶端請(qǐng)求的數(shù)據(jù)
-----------視圖函數(shù)執(zhí)行了---------------
----after_request----
在處理請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法
一般可以用于記錄會(huì)員/管理員的操作歷史,瀏覽歷史,清理收尾的工作
----teardown_request----
在每一次請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法
如果有異常錯(cuò)誤,則會(huì)傳遞錯(cuò)誤異常對(duì)象到當(dāng)前方法的參數(shù)中
錯(cuò)誤提示:division by zero

鉤子裝飾器裝飾了多個(gè)函數(shù)的執(zhí)行順序如下:

from flask import Flask, session

# 應(yīng)用實(shí)例對(duì)象
app = Flask(__name__)

"""給app單獨(dú)設(shè)置配置項(xiàng)"""
# 設(shè)置秘鑰
app.config["SECRET_KEY"] = "my SECRET KEY"

@app.before_first_request
def before_first_request():
    """
    這個(gè)鉤子會(huì)在項(xiàng)目啟動(dòng)后第一次被用戶訪問時(shí)執(zhí)行
    可以編寫一些初始化項(xiàng)目的代碼,例如,數(shù)據(jù)庫初始化,加載一些可以延后引入的全局配置
    """
    print("----before_first_request----")
    print("系統(tǒng)初始化的時(shí)候,執(zhí)行這個(gè)鉤子方法")
    print("會(huì)在接收到第一個(gè)客戶端請(qǐng)求時(shí),執(zhí)行這里的代碼")


@app.before_request
def before_request():
    """
    這個(gè)鉤子會(huì)在每次客戶端訪問視圖的時(shí)候執(zhí)行
    # 可以在請(qǐng)求之前進(jìn)行用戶的身份識(shí)別,以及對(duì)于本次訪問的用戶權(quán)限等進(jìn)行判斷。..
    """
    print("----before_request----")
    print("每一次接收到客戶端請(qǐng)求時(shí),執(zhí)行這個(gè)鉤子方法")
    print("一般可以用來判斷權(quán)限,或者轉(zhuǎn)換路由參數(shù)或者預(yù)處理客戶端請(qǐng)求的數(shù)據(jù)")


@app.after_request
def after_request1(response):
    print("----after_request1----")
    print("在處理請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法")
    print("一般可以用于記錄會(huì)員/管理員的操作歷史,瀏覽歷史,清理收尾的工作")

    response.headers["Content-Type"] = "application/json"
    response.headers["Company"] = "python oldboy..."

    # 必須返回response參數(shù)
    return response


@app.after_request
def after_request2(response):
    print("----after_request2----")

    # 必須返回response參數(shù)
    return response

@app.after_request
def after_request3(response):
    print("----after_request3----")

    # 必須返回response參數(shù)
    return response

@app.teardown_request
def teardown_request(exc):
    print("----teardown_request----")
    print("在每一次請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法")
    print("如果有異常錯(cuò)誤,則會(huì)傳遞錯(cuò)誤異常對(duì)象到當(dāng)前方法的參數(shù)中")
    # 在項(xiàng)目關(guān)閉了DEBUG模式以后,則異常信息就會(huì)被傳遞到exc中,我們可以記錄異常信息到日志文件中
    print(f"錯(cuò)誤提示:{exc}")  # 異常提示


@app.route("/")
def index():
    print("-----------視圖函數(shù)執(zhí)行了---------------")
    return "ok"

if __name__ == '__main__':
    # 啟動(dòng)項(xiàng)目的web應(yīng)用程序
    app.run(host="0.0.0.0", port=5000, debug=False)

執(zhí)行效果:

----before_first_request----
系統(tǒng)初始化的時(shí)候,執(zhí)行這個(gè)鉤子方法
會(huì)在接收到第一個(gè)客戶端請(qǐng)求時(shí),執(zhí)行這里的代碼
----before_request----
每一次接收到客戶端請(qǐng)求時(shí),執(zhí)行這個(gè)鉤子方法
一般可以用來判斷權(quán)限,或者轉(zhuǎn)換路由參數(shù)或者預(yù)處理客戶端請(qǐng)求的數(shù)據(jù)
-----------視圖函數(shù)執(zhí)行了---------------
----after_request3----
----after_request2----
----after_request1----
在處理請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法
一般可以用于記錄會(huì)員/管理員的操作歷史,瀏覽歷史,清理收尾的工作
----teardown_request----
在每一次請(qǐng)求以后,執(zhí)行這個(gè)鉤子方法
如果有異常錯(cuò)誤,則會(huì)傳遞錯(cuò)誤異常對(duì)象到當(dāng)前方法的參數(shù)中
錯(cuò)誤提示:None

結(jié)論:以視圖執(zhí)行為中心點(diǎn),before_request之前(請(qǐng)求過程)的先裝飾先執(zhí)行,after_request之后(響應(yīng)過程),后裝飾的先執(zhí)行。

二、異常拋出和捕獲異常

主動(dòng)拋出http異常

  • abort 方法

    • 拋出一個(gè)給定狀態(tài)代碼的 HTTPException 或者 指定響應(yīng),例如想要用一個(gè)頁面未找到異常來終止請(qǐng)求,你可以調(diào)用 abort(404)

  • 參數(shù):

    • code – HTTP的錯(cuò)誤狀態(tài)碼

from flask import Flask,abort,request
app = Flask(import_name=__name__)


# 配置類
class Config(object):
    DEBUG = True     # 開啟調(diào)試模式

# 加載配置
app.config.from_object(Config)


@app.route("/")
def index():
    # try:
    #     1/0
    # except:
    #     abort(500)

    username = request.args.get("username")
    if username is None:
        abort(400)

    return "ok"

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

abort,只能拋出 HTTP 協(xié)議的錯(cuò)誤狀態(tài)碼,一般用于權(quán)限等頁面上錯(cuò)誤的展示提示.

abort 在有些前后端分離的項(xiàng)目里面不會(huì)被使用,往往在業(yè)務(wù)錯(cuò)誤的時(shí)候使用raise進(jìn)行拋出錯(cuò)誤類型,而不是拋出http異常。

捕獲錯(cuò)誤

  • app.errorhandler 裝飾器

    • 注冊(cè)一個(gè)錯(cuò)誤處理程序,當(dāng)程序拋出指定錯(cuò)誤狀態(tài)碼的時(shí)候,就會(huì)調(diào)用該裝飾器所裝飾的方法

  • 參數(shù):

    • code_or_exception – HTTP的錯(cuò)誤狀態(tài)碼或指定異常

  • 例如統(tǒng)一處理狀態(tài)碼為500的錯(cuò)誤給用戶友好的提示:

@app.errorhandler(500)
def internal_server_error(e):
    return '服務(wù)器搬家了'

捕獲指定異常類型

@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
    return '除數(shù)不能為0'

 

代碼:

 1 from flask import Flask, request, abort
 2 
 3 # 應(yīng)用實(shí)例對(duì)象
 4 app = Flask(__name__)
 5 
 6 class APIError(Exception):
 7     pass
 8 
 9 """異常拋出"""
10 @app.route("/")
11 def index():
12     username = request.args.get("username")
13     if username is None:
14         abort(400)
15     if username != "xiaoming":
16         raise APIError
17     return "ok"
18 
19 
20 @app.errorhandler(400)
21 def internal_server_error(e):
22     return {
23         "errno": 400,
24         "errmsg": "參數(shù)有誤!",
25     }
26 
27 
28 @app.errorhandler(APIError)
29 def api_error(e):
30     return {
31         "errno": 500,
32         "errmsg": "接口訪問有誤!",
33     }
34 
35 
36 if __name__ == '__main__':
37     # 啟動(dòng)項(xiàng)目的web應(yīng)用程序
38     app.run(host="0.0.0.0", port=5000, debug=True)

三、context

執(zhí)行上下文:即語境,語意,在程序中可以理解為在代碼執(zhí)行到某一行時(shí),根據(jù)之前代碼所做的操作以及下文即將要執(zhí)行的邏輯,可以決定在當(dāng)前時(shí)刻下可以使用到的變量,或者可以完成的事情。

Flask中提供的執(zhí)行上下文對(duì)象:相當(dāng)于一個(gè)容器,保存了 Flask 程序運(yùn)行過程中的一些信息[變量、函數(shù)、類與對(duì)象等信息]。

Flask中有兩種上下文,請(qǐng)求上下文(request context)和應(yīng)用上下文(application context)。

  1. application 指的就是當(dāng)你調(diào)用app = Flask(__name__)創(chuàng)建的這個(gè)對(duì)象app

  2. request 指的是每次http請(qǐng)求發(fā)生時(shí),WSGI server(比如gunicorn/uwsgi)調(diào)用Flask.__call__()之后,在Flask對(duì)象內(nèi)部創(chuàng)建的Request對(duì)象;

  3. application 表示用于響應(yīng)WSGI請(qǐng)求的應(yīng)用本身,request 表示服務(wù)端每次響應(yīng)客戶單的http請(qǐng)求;

  4. application的生命周期大于request,一個(gè)application存活期間,可能發(fā)生多次http請(qǐng)求,所以也就會(huì)有多個(gè)request

請(qǐng)求上下文(request context)

思考:在視圖函數(shù)中,如何取到當(dāng)前請(qǐng)求的相關(guān)數(shù)據(jù)?比如:請(qǐng)求地址,請(qǐng)求方式,cookie等等

在 flask 中,可以直接在視圖函數(shù)中使用 request 這個(gè)對(duì)象進(jìn)行獲取相關(guān)數(shù)據(jù),而 request 就是請(qǐng)求上下文提供的對(duì)象,保存了當(dāng)前本次請(qǐng)求的相關(guān)數(shù)據(jù),請(qǐng)求上下文提供的對(duì)象有:request、session

所以每次客戶端不同的請(qǐng)求,得到的request和session的對(duì)象都是同一個(gè),但是內(nèi)部的數(shù)據(jù)都是不一樣的。

  • request

    • 封裝了HTTP請(qǐng)求的內(nèi)容,針對(duì)的是http請(qǐng)求。舉例:user = request.args.get('user'),獲取的是get請(qǐng)求的參數(shù)。

  • session

    • 用來記錄請(qǐng)求會(huì)話中的信息,針對(duì)的是用戶信息。舉例:session['name'] = user.id,可以記錄用戶的狀態(tài)信息。還可以通過session.get('name')獲取用戶的狀態(tài)信息。

請(qǐng)求上下文提供的變量/屬性/方法/函數(shù)/類與對(duì)象,只能在視圖中或者被視圖調(diào)用的地方使用

  代碼:

 1 from flask import Flask, request, session
 2 
 3 
 4 app = Flask(__name__)
 5 
 6 app.config.update({
 7     "DEBUG": True,
 8     "SECRET_KEY": "sklaasle3k2334"
 9 })
10 
11 
12 @app.route("/")
13 def index():
14     print(request, id(request), request.args)
15     return "ok!"
16 
17 
18 if __name__ == '__main__':
19     # request寫在這里,就表示超出了用戶請(qǐng)求和響應(yīng)流程之外的地方了.會(huì)報(bào)錯(cuò).
20     # print(request)  # RuntimeError: Working outside of request context. requset不能在情趣上傳文以外的地址被調(diào)用
21     # print(session)
22     app.run()

應(yīng)用上下文(application context)

它的字面意思是 應(yīng)用上下文,但它不是一直存在的,它只是request context 中操作當(dāng)前falsk應(yīng)用對(duì)象 app 的代理對(duì)象,就是所謂本地代理(local proxy)。它的作用主要是幫助 request 獲取當(dāng)前的flask應(yīng)用相關(guān)的信息,它是伴 request 而生,隨 request 而滅的。

應(yīng)用上下文提供的對(duì)象有:current_app,g

current_app

應(yīng)用程序上下文,用于存儲(chǔ)flask應(yīng)用實(shí)例對(duì)象中的變量,可以通過current_app.name打印當(dāng)前app的名稱,也可以在current_app中存儲(chǔ)一些變量,例如:

  • 應(yīng)用的啟動(dòng)腳本是哪個(gè)文件,啟動(dòng)時(shí)指定了哪些參數(shù)

  • 加載了哪些配置文件,導(dǎo)入了哪些配置

  • 連接了哪個(gè)數(shù)據(jù)庫

  • 有哪些可以調(diào)用的工具類、常量

  • 當(dāng)前flask應(yīng)用在哪個(gè)機(jī)器上,哪個(gè)IP上運(yùn)行,內(nèi)存多大

代碼:

 1 from flask import Flask,request,session,current_app,g
 2 
 3 # 初始化
 4 app = Flask(import_name=__name__)
 5 
 6 # 聲明和加載配置
 7 class Config():
 8     DEBUG = True
 9 app.config.from_object(Config)
10 
11 # 編寫路由視圖
12 @app.route(rule='/')
13 def index():
14     # 應(yīng)用上下文提供給我們使用的變量,也是只能在視圖或者被視圖調(diào)用的地方進(jìn)行使用,
15     # 但是應(yīng)用上下文的所有數(shù)據(jù)來源于于app,每個(gè)視圖中的應(yīng)用上下文基本一樣
16     print(current_app.config)   # 獲取當(dāng)前項(xiàng)目的所有配置信息
17     print(current_app.url_map)  # 獲取當(dāng)前項(xiàng)目的所有路由信息
18 
19     return "<h1>hello world!</h1>"
20 
21 if __name__ == '__main__':
22     # 運(yùn)行flask
23     app.run(host="0.0.0.0")

g變量

g 作為 flask 程序全局的一個(gè)臨時(shí)變量,充當(dāng)者中間媒介的作用,我們可以通過它傳遞一些數(shù)據(jù),g 保存的是當(dāng)前請(qǐng)求的全局變量,不同的請(qǐng)求會(huì)有不同的全局變量,通過不同的thread id區(qū)別

g.name='abc' # name是舉例,實(shí)際要保存什么數(shù)據(jù)到g變量中,可以根據(jù)業(yè)務(wù)而定,你可以任意的數(shù)據(jù)進(jìn)去

代碼:

 1 from flask import Flask,request,session,current_app,g
 2 
 3 # 初始化
 4 app = Flask(import_name=__name__)
 5 
 6 # 聲明和加載配置
 7 class Config():
 8     DEBUG = True
 9 app.config.from_object(Config)
10 
11 @app.before_request
12 def before_request():
13     g.name = "root"
14 
15 # 編寫路由視圖
16 @app.route(rule='/')
17 def index():
18     # 請(qǐng)求上下文提供的變量/屬性/方法/函數(shù)/類與對(duì)象,只能在視圖中或者被視圖調(diào)用的地方使用
19     # 請(qǐng)求上下文里面信息來源于每次客戶端的請(qǐng)求,所以每個(gè)視圖中請(qǐng)求上下文的信息都不一樣
20     # print(session)
21 
22     # 應(yīng)用上下文提供給我們使用的變量,也是只能在視圖或者被視圖調(diào)用的地方進(jìn)行使用,
23     # 但是應(yīng)用上下文的所有數(shù)據(jù)來源于于app,每個(gè)視圖中的應(yīng)用上下文基本一樣
24     print(current_app.config)   # 獲取當(dāng)前項(xiàng)目的所有配置信息
25     print(current_app.url_map)  # 獲取當(dāng)前項(xiàng)目的所有路由信息
26     index2()
27     index3()
28     return "ok!"
29 
30 
31 def index2():
32     print(g.name)
33 
34 def index3():
35     print(g.name)
36 
37 
38 if __name__ == '__main__':
39     # 默認(rèn)情況下,應(yīng)用上下文提供的對(duì)象,也只能在客戶端的請(qǐng)求與響應(yīng)階段進(jìn)行調(diào)用。
40     # 但是工作中往往,需要在請(qǐng)求響應(yīng)之外,調(diào)用服務(wù)端信息,此時(shí),就必須要在請(qǐng)求響應(yīng)以外的地方調(diào)用current_app
41     # 例如:回頭使用celery實(shí)現(xiàn)異步任務(wù)或是定時(shí)任務(wù),那么如果任務(wù)里面需要操作數(shù)據(jù),則必須調(diào)用項(xiàng)目配置,那么就一定要使用current_app
42     with app2.app_context():
43         print(current_app)
44         print(g)
45     app2.run()

兩者的區(qū)別:

  • 請(qǐng)求上下文:保存了客戶端和服務(wù)器交互的數(shù)據(jù),一般來自于客戶端。

  • 應(yīng)用上下文:flask 應(yīng)用程序運(yùn)行過程中,保存的一些配置信息,比如路由列表,程序名、數(shù)據(jù)庫連接、應(yīng)用信息等

    應(yīng)用上下文提供的對(duì)象,可以直接在請(qǐng)求上下文中使用,但是如果在請(qǐng)求上下文之外調(diào)用,則需要使用

    with app.app_context()創(chuàng)建一個(gè)應(yīng)用上下文環(huán)境才能調(diào)用。

四、終端腳本命令

在flask1.1版本之前版本中都是采用flask-script模塊來執(zhí)行終端腳本命令,flask1.1版本以后不再使用這個(gè)模塊了,因?yàn)榇嬖诩嫒菪詥栴}。

flask0.11.0版本以后,flask內(nèi)置了一個(gè)Click模塊,這個(gè)模塊是終端命令模塊,可以讓我們直接通過Click的裝飾器,編寫和運(yùn)行一些終端命令。在flask2.0版本已經(jīng)不能兼容flask-script模塊了,所以需要改成使用Click模塊來運(yùn)行和自定義管理終端命令了。

安裝了flask2.0以后,當(dāng)前項(xiàng)目所在的python環(huán)境就提供了一個(gè)全局的flask命令,這個(gè)flask命令是Click提供的。

# 要使用Click提供的終端命令flask,必須先在環(huán)境變量中聲明當(dāng)前flask項(xiàng)目的實(shí)例對(duì)象所在的程序啟動(dòng)文件。
# 例如:manage.py中使用了 app = Flask(__name__),則manage.py就是程序啟動(dòng)文件


# 使用flask終端命令之前,可以配置2個(gè)環(huán)境變量。
# 指定入口文件,開發(fā)中入口文件名一般:app.py/run.py/main.py/index.py/manage.py/start.py
export FLASK_APP=manage.py
# 指定項(xiàng)目所在環(huán)境
export FLASK_ENV=development   # 開發(fā)環(huán)境,默認(rèn)開啟DEBUG模式
# export FLASK_ENV=production   # 生成環(huán)境,默認(rèn)關(guān)閉DEBUG模式

默認(rèn)情況下,flask命令提供的子命令。

flask routes  # 顯示當(dāng)前項(xiàng)目中所有路由信息
flask run     # 把flask項(xiàng)目運(yùn)行在內(nèi)置的測(cè)試服務(wù)器下
# flask run --host=0.0.0.0 --port=5055
flask shell   # 基于項(xiàng)目的應(yīng)用上下文提供終端交互界面,可以進(jìn)行代碼測(cè)試。

Click自定義終端命令

import click
from flask import Flask, views

app = Flask(__name__)
# 配置
app.config.update({
    "DEBUG": False,
})


# 自定義終端命令
@app.cli.command("faker")                                                # 假設(shè)這個(gè)用于生成測(cè)試數(shù)據(jù)
@click.argument("data", default="user")                                  # data表示生成數(shù)據(jù)的類型[參數(shù)argument是命令調(diào)用時(shí)的必填參數(shù)]
@click.option('-n', 'number', type=int, default=1, help='生成的數(shù)據(jù)量.')   # num表示測(cè)試數(shù)據(jù)的生成數(shù)量[選項(xiàng)option是命令調(diào)用時(shí)的可選參數(shù)]
def faker_command(data, number):
    """添加測(cè)試信息"""
    print("添加測(cè)試信息")
    print(f"數(shù)據(jù)類型:data={data}")
    print(f"生成數(shù)量:number={number}")


@app.route("/")
def index():
    return "ok"


if __name__ == '__main__':
    app.run()
    
"""
flask faker --help
flask faker -n10 user
flask faker user
"""

終端下的運(yùn)行效果:

(flask) moluo@ubuntu:~/Desktop/flaskdemo$ flask faker -n10 user
添加測(cè)試信息
數(shù)據(jù)類型:data=user
生成數(shù)量:number=10
(flask) moluo@ubuntu:~/Desktop/flaskdemo$ flask faker user
添加測(cè)試信息
數(shù)據(jù)類型:data=user
生成數(shù)量:number=1
(flask) moluo@ubuntu:~/Desktop/flaskdemo$ flask faker goods
添加測(cè)試信息
數(shù)據(jù)類型:data=goods
生成數(shù)量:number=1

練習(xí):

1. flask2.0的終端下,輸入 python manage.py startapp home 則可以在當(dāng)前目錄下創(chuàng)建以下目錄和文件
項(xiàng)目目錄/
 └── home
     ├── views.py
     ├── models.py
     ├── urls.py
     └── tests.py

 代碼:

 

import click, os
from flask import Flask


app = Flask(__name__)
# 配置
app.config.update({
    "DEBUG": False
})


@app.cli.command("startapp")
@click.argument("name")
# @click.option('-n', 'name', help='app name')
def startapp(name):
    """生成子模塊或子應(yīng)用"""
    if os.path.isdir(name):
        print(f"當(dāng)前{name}目錄已存在!請(qǐng)先處理完成以后再創(chuàng)建。")
        return

    os.mkdir(name)
    open(f"{name}/views.py", "w")
    open(f"{name}/models.py", "w")
    open(f"{name}/documents.py", "w")
    open(f"{name}/ws.py", "w")
    open(f"{name}/services.py", "w")
    open(f"{name}/urls.py", "w")
    open(f"{name}/test.py", "w")
    print(f"{name}子應(yīng)用創(chuàng)建完成....")


@app.route("/")
def index():
    return "ok"


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

終端調(diào)用:

flask startapp home
flask startapp users

  六、jinja2模板引擎

Flask內(nèi)置的模板語言Jinja2,它的設(shè)計(jì)思想來源于 Django 的模板引擎DTP(DjangoTemplates),并擴(kuò)展了其語法和一系列強(qiáng)大的功能。

  • Flask提供的 render_template 函數(shù)封裝了該模板引擎Jinja2

  • render_template 函數(shù)的第一個(gè)參數(shù)是模板的文件名,后面的參數(shù)都是鍵值對(duì),表示模板中變量對(duì)應(yīng)的數(shù)據(jù)值。

模板基本使用

1、在flask應(yīng)用對(duì)象創(chuàng)建的時(shí)候,設(shè)置或者保留template_folder參數(shù),創(chuàng)建模板目錄

app = Flask(__name__,template_folder='templates')

2、在項(xiàng)目下手動(dòng)創(chuàng)建 templates 文件夾,用于存放所有的模板文件,并在目錄下創(chuàng)建一個(gè)模板html文件 index.html

 

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ content }}</h1>
</body>
</html>

3、在視圖函數(shù)設(shè)置渲染模板并設(shè)置模板數(shù)據(jù)

from flask import Flask, render_template

# 默認(rèn)情況下,flask默認(rèn)直接支持視圖加載模板的。
# Flask類在實(shí)例化的時(shí)候,默認(rèn)當(dāng)前根目錄下的templates目錄作為模板目錄
app = Flask(__name__, template_folder="templates")
# 配置
app.config.update({
    "DEBUG": True
})

@app.route("/")
def index():
    data = {
        "title": "我的模板標(biāo)題",
        "content": "我的模板內(nèi)容"
    }
    return render_template("index.html", **data)

@app.route("/user/<id>")
def user(id):
    title = "User Center"
    content = "我的個(gè)人中心"
    print(locals()) # {'id': '100', 'title': 'User Center', 'content': '我的個(gè)人中心'}
    return render_template("user.html", **locals())

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