Flask高级处理

  1. 请求钩子
    请求钩子可以对请求的各阶段进行监听, 方便开发者 针对请求完成一些统一的处理, 以便减少重复代码, 作用类比Django中的中间件

    开发中中主要会用到以下四种请求钩子:
      before_request: 每次执行视图函数之前; 对请求进行一些准备处理; 如果在函数中返回了一个响应,视图函数将不再被调用
      after_request: 没有异常,每次执行视图函数之后(已经包装为响应对象)调用; 在此函数中可以对象应值再返回之前做最后一步修改处理
      before_first_request(不常用): 只在第一次访问该应用时执行,可以进行web应用初始化处理
      teardown_request: 每次执行视图函数之后调用;无论是否出现异常都会执行, 一般用于请求收尾;接受一个参数:错误信息,如果有相关错误抛出
  2. 蓝图(重点)
    蓝图的作用: 实现Flask项目 模块化
    项目模块化主要是 将业务以功能模块进行划分, 每个功能模块对应一个包, 用于存放和其有关的视图/工具/模型文件等, 如homeuser
    对于大型项目, 一般 每个功能模块对应创建一个蓝图, 由多个蓝图代替应用来分别管理各模块的视图
    大型项目目录结构示意图:
    --------- project # 工程目录
      |------ main.py # 启动文件
      |------ user  # 用户模块
      |  |--- __init__.py  # 包的初始化文件, 此处创建管理用户模块的蓝图对象
      |  |--- views.py  # 视图文件
      |  |--- ...
      |
      |------ home # 首页模块
      |  |--- __init__.py  # 包的初始化文件, 此处创建管理首页模块的蓝图对象
      |  |--- views.py  # 视图文件
      |  |--- ...
      |...

    蓝图的基本使用:

    1).创建蓝图对象 (创建home模块包,在里面的__init__.py文件里添加代码)

    from flask import Blueprint
    home_blue = Blueprint("home_b", __name__, url_prefix="/home")
    # 4. 让视图文件和程序建立关联(一旦遇到导包错误,解决办法:查看并调整代码顺序)
    from . import views


    2). 使用蓝图对象来定义路由 (home下视图文件views.py里)

    from home import home_blue
    @home_blue.route("/demo")
    def demo():
    return "demo"

    3).注册蓝图对象(在工程目录下的主成序启动文件的main.py里)

    from flask import Flask

    app = Flask(__name__)

    # 3. 注册蓝图对象
    from project.home import home_blue
    app.register_blueprint(home_blue)

    if __name__ == '__main__':
    # print(app.url_map)
    print(app.view_functions)
    app.run(debug=True)


    4).让视图文件和程序建立关联(这一步可能遇到导包错误,解决办法: 查看并调整代码的执行顺序)

      这一步是在home包下的__init__.py文件下操作

  3. 上下文(context)
    上下文:是一个 数据容器,用于保存Flask的各相关数据
    上下文分类: 请求上下文 和 应用上下文
    共同特点: 上下文中数据有使用范围 [请求开始:请求结束]

    请求上下文: 记录一些和请求有关的数据 request, session 等
    应用上下文: 记录一些和应用有关的数据 g current_app

    g: flask给开发者预留的一个容器,用于存储一些自定义数据 特点: 每次请求,g记录的数据会重置
    g的应用场景: 1> 在视图函数 和 钩子函数之间传递数据 2> 函数嵌套调用是, 可以使用g代替参数传递数据

    current_app: 会自定引用创建的flask应用,想要在项目的其他文件中使用app时,应该使用current_app,
    这样可以减少循环导入的错误

     

     

      

  4. 综合认证: 统一处理 ; 访问限制
    1.统一处理:
    需求: 获取用户身份
    分析: 除了静态资源, 基本所有视图都需要获取用户身份, 每个视图单独获取出现大量的代码冗余
    解决办法: 设置 请求钩子, 并通过 g变量 将数据传递给视图函数
    代码:
    ===================================================================================================================
    from flask import Flask, session, g
    
    app = Flask(__name__)
    app.secret_key = 'test'
    
    # 需求1: 所有视图都需要获取用户身份
    # 解决办法: 用钩子函数进行封装  减少代码冗余
    @app.before_request
    def prepare():
        # 必须使用g变量来传递数据, 使用全局变量不能记录并发的多个请求数据
        g.name = session.get('username')
    
    
    @app.route('/')
    def index():
        if g.name:
            return "欢迎回来, %s" % g.name
        else:
            return '首页'
    
    
    @app.route('/demo1/')
    def demo1():
        print(g.name)
        return 'demo1'
    
    
    @app.route('/login')
    def login():
        """登录"""
        session['username'] = 'zs'
        return '登录成功'
    
    
    
    if __name__ == '__main__':
        app.run(debug=True)


    2.访问限制
    需求: 对指定的路由进行访问限制
    分析:部分视图需要身份校验,这不是图每个单独校验仍会出现大量的代码冗余
    解决办法: 封装 装饰器 完成身份校验逻辑, 对指定视图函数设置装饰器
    # 访问限制代码实现及优化
    from flask import Flask, session, g, abort
    from functools import wraps
    
    app = Flask(__name__)
    app.secret_key = 'test'
    
    
    @app.before_request
    def prepare():
        g.name = session.get('username')
    
    
    @app.route('/')
    def index():
        if g.name:
            return "欢迎回来, %s" % g.name
    
        else:
            return '首页'
    
    
    @app.route('/login')
    def login():
        """登录"""
        session['username'] = 'zs'
        return '登录成功'
    
    
    # 使用装饰器封装访问限制 
    def login_required(f):  # f = user
    
        @wraps(f)  # 会将被装饰的函数(wrapper)的函数信息替换为指定函数(f)的函数信息(__name__ 函数名, __doc__ 函数注释)
        # 设置该装饰器后, 可以让闭包函数使用原函数名, 避免函数标记出现冲突(函数标记是根据函数名来生成的)
        def wrapper(*args, **kwargs):
    
            if g.name: 
                return f(*args, **kwargs)  
    
            else: 
                abort(401)  
    
        return wrapper
    
    
    @app.route('/user')
    @login_required  
    def user():
        """个人中心"""
        return '访问 %s 的个人中心' % g.name
    
    
    @app.route('/demo1')
    @login_required
    def demo1():
        return 'demo1'
    
    
    
    if __name__ == '__main__':
        print(app.url_map)
        app.run(debug=True)
  5. 应用配置
    主文件 main.py 从对象中加载封装的配置 app.config.from_object()
    加载隐私配置: app.config.from_envvar('ENV_CONFIG',silent=True)
    加载配置时, 设置参数 silent=True, 则配置加载失败也不会报错

    config.py文件里代码示例:

    # config.py 
    
    from datetime import timedelta
    
    
    class BaseConfig:
        """配置基类  可以将相同的配置抽取到基类中, 减少重复代码"""
    
        # 定义和配置同名的类属性
        PERMANENT_SESSION_LIFETIME = timedelta(days=7)
    
    
    class DevelopmentConfig(BaseConfig):
        """开发环境"""
        SQL_URL = '127.0.0.1:3306/test1'  # 数据库地址
    
    
    class ProductionConfig(BaseConfig):
        """生产环境"""
        SQL_URL = '222.10.15:3306/users'  # 数据库地址



    # 定义工厂函数需要的配置(方便主文件提取配置类)
    # 定义字典来记录 配置类型 和 配置子类 之间的映射关系 config_dict = { 'dev': DevelopmentConfig, 'pro': ProductionConfig }
     

    1). main.py加载配置示例:

    # main.py 
    
    from datetime import timedelta
    from flask import Flask
    
    app = Flask(__name__)
    
    # 从对象中加载配置
    # 优点: 面向对象的设计有利于 减少重复代码 以及 代码解耦合
    from config import DevelopmentConfig
    app.config.from_object(DevelopmentConfig)
    
    
    @app.route('/')
    def index():
        print(app.config.get('PERMANENT_SESSION_LIFETIME'))
        return "index"
    
    
    if __name__ == '__main__':
        app.run(debug=True)

    2). 切换配置方案: 定义工厂函数, 封装应用的创建过程; 利用环境变量,调用工厂函数, 指定配置并动态创建应用

 定义工厂函数main.py代码示例:

from flask import Flask, current_app, Config
from config import config_dict


# 工厂函数: 根据参数需求, 内部封装对象的创建过程
def create_app(config_type):
    """封装应用的创建过程"""

    # 创建应用
    flask_app = Flask(__name__)

    # 根据配置类型取出对应的配置子类
    config_class = config_dict[config_type]

    # 加载普通配置
    flask_app.config.from_object(config_class)

    return flask_app


# 创建应用对象
app = create_app('dev')


@app.route("/")
def index():
    print(app.config.get('SQL_URL'))
    return "index"


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

  3). 加载隐私配置示例

在项目目录外 创建隐私配置文件secret_config.py, 并以全局变量形式设置隐私配置

# secret_config.py

SECRET_KEY = 'heima123'   # 隐私配置

主文件main.py 通过环境变量方式来加载隐私配置

# main.py

from datetime import timedelta
from flask import Flask

app = Flask(__name__)

# 从环境变量中加载配置
# 优点: 可以保护隐私配置   export ENV_CONFIG="隐私配置的文件路径"
app.config.from_envvar('ENV_CONFIG')


@app.route('/')
def index():
    print(app.config.get('SECRET_KEY'))
    return "index"


# if __name__ == '__main__':
#     app.run(debug=True)

环境变量&终端命令 启动程序

$ export FLASK_APP="main"  # 设置内置环境变量
$ export ENV_CONFIG="/xx/secret_config.py"  # 设置隐私配置对应的环境变量
$ flask run  # 启动web程序

实际开发中的方案: 

  • 开发阶段, 只加载普通配置
  • 生产阶段, 先加载普通配置, 再通过环境变量的方式加载项目以外的隐私配置并覆盖原有配置

代码示例:

def create_app(config_type):
    """封装应用的创建过程"""

    # 创建应用
    flask_app = Flask(__name__)
    # 根据配置类型取出对应的配置子类
    config_class = config_dict[config_type]

    # 先加载普通配置
    flask_app.config.from_object(config_class)
    # 再加载隐私配置  silent=True, 配置加载失败也不报错
    flask_app.config.from_envvar('ENV_CONFIG', silent=True)

    return flask_app

加载配置时, 设置参数 silent=True, 则配置加载失败也不会报错

原文地址:https://www.cnblogs.com/yqyn-study/p/13511433.html