Flask

简介

FLask框架本身只实现了最基本的功能,所以FLask被称为 microFramework(微框架),
但是从工程上来看,这种所谓的“小”,反而带来了更多的便利性。
和Django这种完善完整高集成框架比起来,它很多东西都没有。
比如数据库database,template等等。需要自己安装相关的包,
不过优势就是,它只有一个基础的框架,想添加什么东西都是按照自己的意愿,
而且flask默认的模板渲染引擎是jinja2,这个可比Django(虽然也可以换成jinja2)自带的好用多了

安装

pip install flask

基架搭建

  • run.py 启动文件
from flask import Flask

def create_app():
    # 树根
    app = Flask(__name__)

    @app.route("/test", methods=['POST', 'GET'])
    def test():
        return """

See you!

"""
return app app = create_app() if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5001)

项目配置

有多种方式可以实现项目配置,这里介绍最常用的方式: python类路径方式导入

  • ./config/configs.py 配置文件

# 公共配置
class Config(object):
    PUBLIC_FIELD = '1234'
    BUCKET_NAME = 'dameinv'

# 开发环境
class DevelopmentConfig(Config):
    DEBUG = True
    HOST = localhost
    PORT = 8888
    # 配置日志
    # LOG_LEVEL = "DEBUG"
    LOG_LEVEL = "INFO"

    REDIS_HOST = 'server-ip'
    REDIS_PORT = 6380
    REDIS_DB = 4
    REDIS_PASSWORD = '×××××××××××'

    MYSQL_INFO = "mysql://××××××××××@server-ip:3306/blog01?charset=utf8"
    

# 测试环境
class TestingConfig(Config):
    TESTING = True  


# 生成环境
class ProductionConfig(Config):
    DEBUG = False

    REDIS_HOST = 'server-ip'
    REDIS_PORT = 6380
    REDIS_DB = 4
    REDIS_PASSWORD = '×××××××××××'

    MYSQL_INFO = "mysql://××××××××××@server-ip:3306/blog01?charset=utf8"


# 指定项目使用哪个环境下的配置
Conf = DevelopmentConfig
  • run.py 中 引入、激活、使用 配置文件
from flask import Flask
from config.configs import Conf

def create_app():
    app = Flask(__name__)

    # 将配置信息注册到app上
    app.config.from_object(Conf) 

    return app

app = create_app()

if __name__ == '__main__':
    app.run(debug=app.config['DEBUG'], host=app.config['HOST'], port=app.config['PORT'])
  • 蓝图文件中 引入、使用
from config.configs import Conf

@api.route('/')
def hello_world():
    print(Conf.USER_NAME)
    return 'Hello World!'

flask 的默认配置参数

{
    'DEBUG':                                get_debug_flag(default=False),  是否开启Debug模式
    'TESTING':                              False,                          是否开启测试模式
    'PROPAGATE_EXCEPTIONS':                 None,                         
    'PRESERVE_CONTEXT_ON_EXCEPTION':        None,
    'SECRET_KEY':                           None,
    'PERMANENT_SESSION_LIFETIME':           timedelta(days=31),
    'USE_X_SENDFILE':                       False,
    'LOGGER_NAME':                          None,
    'LOGGER_HANDLER_POLICY':               'always',
    'SERVER_NAME':                          None,
    'APPLICATION_ROOT':                     None,
    'SESSION_COOKIE_NAME':                  'session',
    'SESSION_COOKIE_DOMAIN':                None,
    'SESSION_COOKIE_PATH':                  None,
    'SESSION_COOKIE_HTTPONLY':              True,
    'SESSION_COOKIE_SECURE':                False,
    'SESSION_REFRESH_EACH_REQUEST':         True,
    'MAX_CONTENT_LENGTH':                   None,
    'SEND_FILE_MAX_AGE_DEFAULT':            timedelta(hours=12),
    'TRAP_BAD_REQUEST_ERRORS':              False,
    'TRAP_HTTP_EXCEPTIONS':                 False,
    'EXPLAIN_TEMPLATE_LOADING':             False,
    'PREFERRED_URL_SCHEME':                 'http',
    'JSON_AS_ASCII':                        True,
    'JSON_SORT_KEYS':                       True,
    'JSONIFY_PRETTYPRINT_REGULAR':          True,
    'JSONIFY_MIMETYPE':                     'application/json',
    'TEMPLATES_AUTO_RELOAD':                None,
}

路由

flask 的 路由 以 路由处理函数 的 装饰器 的形式管控着整个请求的导流,
这样处理的好处是简化了 路由注册的繁琐步骤。

from flask import Flask

def create_app():
    app = Flask(__name__)

    # 路由1
    @app.route("/aaa", methods=['GET]) 
    def aaa():
        return """

See you!

"""
# 路由2 @app.route("/bbb", methods=['POST']) def bbb(): return """bbb""" # 路由3 @app.route("/ccc", methods=['POST', 'GET]) def ccc(): return """ccc""" return app app = create_app()

蓝图

把一个应用分解为一套蓝图。
这是针对大型应用的理想方案:一个项目可以实例化 一个应用,初始化多个扩展,并注册许多蓝图。
在一个应用的 URL 前缀和(或)子域上注册一个蓝图。
URL 前缀和(或)子域的 参数成为蓝图中所有视图的通用视图参数(缺省情况下)。

  • 该处的蓝图目录结构
├── app_1_0 # 蓝图1号
│   ├── handfunc111.py # 路由处理模块一
│   ├── handfunc222.py # 路由处理模块一
│   └── __init__.py
│
├── app_2_0 # 蓝图1号
│   ├── __init__.py
│   └── handfunc111.py # 路由处理模块一
│
├── config
│   └── configs.py
│
└── run.py # 蓝图注册地点、
  • run.py
from flask import Flask

def create_app():
    app = Flask(__name__)

    # 注册 蓝图1号
    from app_1_0 import api as api_v1 
    app.register_blueprint(api_v1, url_prefix='/api/v1') # /api/v1 是蓝图1号的 路由前缀

    # 注册 蓝图2号
    from app_2_0 import api as api_v2 
    app.register_blueprint(api_v2, url_prefix='/api/v2')
    
    return app

app = create_app()

if __name__ == '__main__':
    app.run(debug=app.config['DEBUG'], host=app.config['HOST'], port=app.config['PORT'])
  • 蓝图1号: init.py
# coding:utf-8
from flask import Blueprint

api = Blueprint('api', __name__)

from . import handfunc111,handfunc222 # 这条语句,放在最后!!!
  • 蓝图1号: handfunc111.py
# coding:utf-8
from flask import Flask, request, jsonify
from . import api

# 访问路由: /api/v1/aaa
@api.route('/aaa', methods=['GET]) 
def aaa():
    return 'aaa!'

@api.route('/ccc', methods=['POST])
def wtt():
    return 'ccc!'
  • 蓝图2号: init.py
from flask import Blueprint

api = Blueprint('api2_0', __name__)

from . import handfunc111 # 这条语句,放在最后!!!

静态路由

from flask import Flask

def create_app():
    # 默认静态目录 为工程目录下的 static目录, 
    # 可以 如下 手动制定:
    app = Flask(__name__, static_folder='./wtt')
    
    return app

app = create_app()

if __name__ == '__main__':
    app.run(debug=app.config['DEBUG'], host=app.config['HOST'], port=app.config['PORT'])

重定向

from flask import Flask, redirect

@app.route("/who")
def hello():
    return redirect('/wtt', 302) # 重定向到 /wtt

@app.route("/wtt")
def hello123():
    return """

See you!

"""

中间件(钩子)

非同名钩子的 介绍顺序 就是 执行顺序
同名钩子的执行顺序 是从上到下
绑定到 app上的 钩子是 全局钩子, 绑定到 蓝图上的钩子是 局部钩子。

before_first_request

第一个请求被触发是 被调用,
一般用于初始化工作 和 调用 处理一些 定时任务 的函数

before_request

每次 试图函数执行前 被调用
一般用于实现请求准备工作, 如参数校验,黑名单过滤,数据统计

after_request

每次 试图函数执行后 被调用
一般用于实现响应的 加工工作, 如 设置统一的请求头,格式转换

teardown_request

每次请求销毁(after_request 执行)后 被调用, 用来记录服务器的异常信息后
无论是否出现一场都会触发,
一般用于 请求的收尾工作, 如资源回收, 错误统计

局部 钩子 和 全局 钩子的 执行顺序(和位置顺序无关)

  1. 全局 before_request
  2. 局部 before_request
  3. 局部 after_request
  4. 全局 after_request
  5. 局部 teardown_request
  6. 全局 teardown_request
app = Flask(__name__)
CORS(app,supports_credentials=True) # 跨域处理
app.config.from_object(ConfigData)  # 将配置信息注册到app上

# 局部钩子 没有 before_first_request
#  'Blueprint' object has no attribute 'before_first_request'
# @v2.before_first_request
# def asdfzxcvsdf():
#     print('222 before_first_request')
    
@app.before_first_request
def asdfzxcvsdf():
    print('before_first_request')

@app.before_request
def onnect123444():
    print('before_request')

@app.after_request
def gguest(resp):
    print("after_request")
    return resp

@app.teardown_request
def dfgosdfgdfe(exc):
    print('teardown_request')
  1. 中间件 中断 路由处理函数的执行:
@app.before_request
def aaa():
    print('''before_request''')
    
    # # abort 可以 禁止 路由处理函数的执行, 接着让 after_request执行
    # res = make_response("""

See You!

""")
# abort(res) # # # 用 return 也可以 禁止 路由处理函数的执行, 接着让 after_request执行 # res = make_response("""

See You!

""")
# return res
  1. 跨域解决方案
    使用 flask-cors库可以很容易的解决
    两种方法,一个是全局/批量的,一个是单一独立的:
    安全起见,一般来说使用独立的方式会常用一些。
下载flask_cors 包
pip3 install flask-cors
使用方式
1.单路由 使用

通过给路由添加@cross_origin标识即可

from flask_cors import cross_origin

@app.route('/index', methods=['POST', 'OPTIONS'])
@cross_origin()
def index():
    result_text = {"result": "True"}
    return jsonify(result_text)
2.批量 使用

根据路由正则来批量控制等方式,更加灵活,可以查阅官方文档。

from flask import Flask
from flask_cors import CORS


app = Flask(__name__)
CORS(app, resources={r"/api/*": {"origins": "*"}},supports_credentials=True)


if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)
3.全局使用
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app,supports_credentials=True)


if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

局部钩子

蓝图1号: init.py

# coding:utf-8
from flask import Blueprint

api = Blueprint('api', __name__)

from . import handfunc111,handfunc222 # 这条语句,放在最后!!!

# coding:utf-8
from flask import Blueprint
from db.mysql import database
# from flask import Flask, request as r

# 蓝图1号: 分支一
v1 = Blueprint('v1', __name__)

@v1.before_request
def _db_connect():
    database.connect()

@v1.teardown_request
def _db_close(exc):
    if not database.is_closed():
        database.close()

from . import handfunc111,handfunc222 # 这条语句,放在最后!!!

前后交互

请求报文

请求报文封装在了flask 的 reques模块, 使用前先导入

请求参数

  • GET
from flask import Flask, request

# 访问 aaa?name=tom&age=10
@app.route("/aaa")
def hello():
    # way1
    name = request.args['name'] # 如果url中没有name参数,则会报错!!!
    age = request.args['age']

    #way2
    name = request.args.get('name') # 如果url中没有name参数,则 name = None
    age = request.args.get('age')
    return "ok"
  • POST
### form-data
@app.route("/aaa")
def hello():
    # way1
    name = request.form['name'] # 如果请求体中没有name参数,则会报错!!!
    age = request.form['age']

    #way2
    name = request.form.get('name') # 如果请求体中没有name参数,则 name = None
    age = request.form.get('age')
    return "ok"


### JSON 等原始数据
import json

@app.route("/aaa")
def hello():
    # way1
    indata = json.loads(request.data)
    return "ok"


### 文件数据
import uuid

@app.route("/aaa")
def hello():
    upload_file = request.files["file"]
    filePath = str(uuid.uuid4()) + upload_file.filename
    upload_file.save(filePath)
    return res_success(filePath)

其他信息

  • 获取cookie:request.cookies
  • 获取header信息:request.headers
  • 获取请求方法:request.method
  • 获取请求路径:request.path
  • 获取请求IP:request.remote_addr

响应报文

  • 数据
### 返回json数据
return {
    "code":0,
    "data": "Ok",
}

### 返回普通字符串
return "123"

### 返回HTML片段
return """

See you!

"""
### 返回文件流 filePath = "/hero/static/" fileName = "好音乐.mp3" return send_from_directory(directory=filePath,filename=fileName,path= filePath+fileName, as_attachment=True)
  • 响应头
from flask import Flask, request, make_response
# make_response  相应报文 制造者

@app.route("/")
def hello():
    resp = make_response({
        "name": "tom",
        "age": 11
    }) 
    resp.headers ['name'] ='tom'
    resp.set_cookie("key", "value")
    return resp

部署上线

直接使用 python3 run.py 运行服务的方式只适合本地开发。
线上运行时要保证更高的性能和稳定性,我们需要使用 uwsgi 进行部署。

uwsgi部署

  • way1: 命令启动
uwsgi --socket 0.0.0.0:5000 --protocol=http -p 3 -w run:app
--socket 0.0.0.0:5000:指定暴露端口号为5000。

--protocol=http:说明使用 http 协议,即端口5000可以直接使用HTTP请求进行访问。

-p 3表示启动的服务占用3个进程。

-w run:app:-w 指明了要启动的模块,run 就是项目启动文件 run.py 去掉扩展名,app 是 run.py 文件中的变量 app,即 Flask 实例。

  • way2: 配置文件启动, 在 uwsgi目录中
- 安装
pip3 install uwsgi

cd 到 uwsgi.ini 所在目录下

- 启动:
uwsgi --ini uwsgi.ini

- 重启:
uwsgi --reload uwsgi.pid

- 停止:
uwsgi --stop uwsgi.pid

uwsgi.ini

[uwsgi]
# 四元组配置:
# 和 nginx 配合使用时 使用 socket 配置
#socket=0.0.0.0:8000 
# 直接作为web服务器使用时 使用 http 配置,就不必启用 socket配置了
http=0.0.0.0:8010

# 配置工程目录(入口py文件 的 pwd)
chdir = /home/hero/Desktop/testFlask

# module相当于之前命令行中的-w参数,指明了在 工程目录下 要启动的模块,
# run 就是项目启动文件 run.py 去掉扩展名,
# app 是 run.py 文件中的变量 app,即 Flask 实例
module = run:app


#配置进程,线程信息
master=True
processes=1
threads=8 #最好和核心数保持一致,可以减少 线程切换的资源损耗
buffer-size = 32768


# 守护进程 方式的启动, 并且 记录 守护进程 方式的启动 的 日志记录
daemonize=uwsgi.log
# 记录守护进程 的 id
pidfile=uwsgi.pid

# 非 守护进程 方式的启动 的 日志记录
# logto = /home/hero/Desktop/testFlask/myproject.log

说明:

# 如果要采用uwsgi 启动, 
app.run(debug=app.config['DEBUG'], host=app.config["HOST"], port=app.config["PORT"])

# 则需将上行代码改为下面:
app.run()

orm: peewee

Peewee是一种简单而小的ORM。它有一种小的、有表现力的形式, 思想上很切合flask的主张。

安装

# 安装驱动
pip install pymysql 
# 安装orm
pip install peewee

现有数据表 反射 为 类模型

python3 -m pwiz -e mysql -u root -H localhost --password music_level > Model.py

from db import database, Test,db

# 推荐: 添加数据, 使用字典, 且不能有 主键
p = {'name':"tantan", 'age' : "100", 'aaa': "123"}
p_id = Test.insert(p).execute()
print(p_id) # 被 主键值 回显


# 数据以参数 的形式添加,且不能有 主键, 返回主键值
p = Test.create(name = "123", uid = 60)
print(p)


# 根据主键,有则更新,无则添加
p = Test(uid =44, name= "tantan", age = "100", aaa= "123")
res = p.save() # p 被 主键值 回显, res 是行数
print(p,res)


# 使用事务, 插入 多条 数据
data = [{'name': 'tom', 'age': i} for i in range(10)]
with database.atomic():
    for i in range(10):
        Test.insert_many(data[i]).execute()

# 删除数据 
res = Test.delete().where(Test.uid >= 45).execute()
print(res) # 删除行数

from playhouse.shortcuts import model_to_dict

# 查询 一条 数据 的 主键值, 找不到 则 报错: NotExist
p = Test.get(Test.age == 100)
print(p) # 主键id
print(p.name) # 查询记录的 name字段值

# 查询的结果都是该 Model 的 object,注意不是 dict。
# 如果想让结果为 dict,需要 playhouse.shortcuts 模块的  model_to_dict工具方法进行转化
resMap = model_to_dict(p)
print(resMap) # {'uid': 44, 'aaa': 101, 'age': '100', 'name': 'haha+++'}


# 查询 一条 数据, 没有数据返回None
p = Test.get_or_none(Test.age == 100)
print(p==None)
print(p) # 默认展示的是 主键值


# 查询 多条 数据
ps = Test.select(Test.uid,Test.aaa).where(Test.name == "tom")
print(ps)  # 默认展示的是 sql语句
results = [model_to_dict(item) for item in ps]
for p in results:
    print(p)

# 更新数据
p = Test.update({
    Test.aaa:101,
    Test.name: "haha+++"
}).where(Test.name == "tantan").execute()

print(p) # 返回更新记录数目


# 推荐:字段值的 ++ --
updates = {
    "uid": Bbb.uid - 10,
    "name": "tom",
}
Bbb.update().where(Bbb.id == 1).execute()

其他

# 获取数量
total = Test.select().where(Test.name=='tom').count()
print(total)

# 排序
ps = Test.select(Test.uid,Test.aaa).where(Test.name == "tom").order_by(Test.uid.desc())

# 分页
ps = Test.select(Test.uid,Test.aaa).where(Test.name == "tom").order_by(Test.uid.desc()).limit(3).offset(2)

# like
sql = f"(Test.name % '%{indata['name']}%')"
ps = Test.select().where(eval(sql))

# in
sql = f"(Test.name << ['tom', 'cat'])"

# 且
sql = f"(Test.name == 'tom') & (Test.age == 10)"

# 或
sql = f"(Test.name == 'tom') | (Test.age == 10)"

JOIN

datas = AAA.select(AAA.uid, BBB.name).join(BBB, on=(AAA.exam_id==BBB.id)).where()
for item in datas.objects():
    print(item.uid)
    print(item.name)

'''
如果不加 .objects(),只能获取到 表AAA 的数据,
而我们拼表的目的往往就是为了获取 表BBB 相关信息,这从思维上将应该是peewee的bug。

注意:
在使用[model_to_dict(item) for item in datas.objects()] 即使加上了objects()也只是显示 AAA的数据表结构的数据。
'''

事务

# 当事务执行成功之后,它会自动commit(),不需要我们手动调。
# 当事务的代码块中抛出异常时,它会自动调用rollback(), 
# 但是 错误只抛出一次,如果我们 用 try...expect 捕捉了,他就捕捉不到异常, 
# 就无法自动调用rollback(),需要我们手动调用自动调用rollback()
with database.atomic() as tx:
    try:
        Test.create(name = "123")
        Test.create(name = "123")
        Test.create(name = "123", uid = 50)

    except Exception as e:
        print(e)
        print(123123)
        tx.rollback()

执行原生SQL: database.execute_sql()

        offset = (indata['curpage']-1)*10
        limt = 10

        keys = ('stat','total_fee','uid', 'id')
        select = ''
        for key in keys:
            if select == '':
                select = select + "a."+key + ' '
            else:
                select = select + ', a.' + key + ' '

        sql = f"from `order` a join `user` b on a.uid = b.uid where b.exam_area='{indata['examarea']}' "

        if 'stat' in indata:
            sql += f"and a.stat = {indata['stat']} "


        sql_find = f"select {select} "
        sql_count = "select count(*) "
        limt = f"limit {offset}, {limt}"

        ret = database.execute_sql(sql_find+sql+limt)
        res = ret.fetchall()

        outdata = []
        for item in res:
            m = {}
            i = 0
            for key in keys:
                m[key] = item[i]
                i += 1
            outdata.append(m)

        ret = database.execute_sql(sql_count+sql)
        total = ret.fetchone()[0]
        page = page_handle(total, indata['curpage'])

        return res_success({
            'page': page,
            'list': outdata,
        })

你可能感兴趣的:(flask,python,nginx)