06. flask上下文源码、flask_session、数据库连接池、flask_script

文章目录

  • 一、flask上下文源码分析
  • 二、flask-session的使用
      • 第一种使用方式(不常用)
      • 第二种使用方式(通过配置,本质和方式一是一样的)
  • 三、数据库连接池
      • 1 传统方案存在的问题
      • 2 使用数据库连接池
      • 3 使用步骤
          • 第一步:新建sql_pool.py
          • 第二步:使用(导入连接池是单例模式)
  • 四、flask-script模块
      • 作用:
      • 安装:
      • 使用:
      • 自定制命令(简单示例)

一、flask上下文源码分析

请求上下文执行流程(ctx):
		-0 flask项目一启动,有6个全局变量
			-_request_ctx_stack:LocalStack对象
			-_app_ctx_stack :LocalStack对象
			-request : LocalProxy对象
			-session : LocalProxy对象
             -g : LocalProxy对象(全局变量)
             -current_app : LocalProxy对象
		-1 请求来了 app.__call__()---->内部执行:self.wsgi_app(environ, start_response)
		-2 wsgi_app()
			-2.1 执行:ctx = self.request_context(environ):返回一个RequestContext对象,并且封装了request(当次请求的request对象),session等
			-2.2 执行: ctx.push():RequestContext对象的push方法
				-2.2.1 push方法中中间位置有:_request_ctx_stack.push(self),self是ctx对象
				-2.2.2 去_request_ctx_stack对象的类中找push方法(即LocalStack类中找push方法)
				-2.2.3 push方法源码:
				    def push(self, obj):
						#通过反射找self._local,在init实例化的时候生成的:self._local = Local()
						#Local()flask封装的支持线程和协程的local对象
						# 一开始取不到stack,返回None
						rv = getattr(self._local, "stack", None)
						if rv is None:
							#走到这个链式赋值,self._local.stack=[],rv=self._local.stack
							self._local.stack = rv = []
						# 把ctx放到了以线程/协程号为key的字典中的列表中
						#self._local={'线程id1':{'stack':[ctx,]},'线程id2':{'stack':[ctx,]},'线程id3':{'stack':[ctx,]}}
						rv.append(obj)  # 操作rv就等于操作self._local.stack
						return rv
		-3 如果在视图函数中使用request对象,比如:print(request)
			-3.1 会调用request对象的__str__方法,request是LocalProxy类的实例化对象
			-3.2 LocalProxy中的__str__方法(用匿名函数改写的):
            		lambda x: str(x._get_current_object())
				-3.2.1 内部执行self._get_current_object()
				-3.2.2 _get_current_object()方法的源码如下:
				    def _get_current_object(self):
						if not hasattr(self.__local, "__release_local__"):
							#self.__local()  在init的时候,实例化的,在init中:object.__setattr__(self, "_LocalProxy__local", local)
							# 用了隐藏属性
							#self.__local 实例化该类的时候传入的local(偏函数的内存地址:partial(_lookup_req_object, "request"))
							#加括号返回,就会执行偏函数,也就是执行_lookup_req_object,不需要传参数了
							#这个地方的返回值就是request对象(当次请求的request,没有乱套)
							return self.__local()
						try:
							return getattr(self.__local, self.__name__)
						except AttributeError:
							raise RuntimeError("no object bound to %s" % self.__name__)
				-3.2.3 _lookup_req_object函数源码如下:
					def _lookup_req_object(name):
						#name是'request'字符串
						#top方法是把第二步中放入的ctx取出来,因为都在一个线程内,当前取到的就是当次请求的ctx对象
						top = _request_ctx_stack.top
						if top is None:
							raise RuntimeError(_request_ctx_err_msg)
						#通过反射,去ctx中把request对象返回
						return getattr(top, name)
				-3.2.4 所以:print(request) 实质上是在执行当次请求的request对象的__str__方法
		-4 如果在视图函数中使用request对象,比如:print(request.method):实质上是取到当次请求的reuquest对象的method属性
		
		-5 最终,请求结束执行: ctx.auto_pop(error),把ctx移除掉
		
	# 其他的注意点:
		-session:
			-请求来了open_session
				-ctx.push()---->也就是RequestContext类的push方法的最后的地方:
					if self.session is None:
						#self是ctx,ctx中有个app就是flask对象,   
                          #self.app.session_interface也就是它:SecureCookieSessionInterface()
						session_interface = self.app.session_interface
						self.session = session_interface.open_session(self.app, self.request)
						if self.session is None:
							#经过上面还是None的话,就生成一个空session
							self.session = session_interface.make_null_session(self.app)
			-请求走了save_session
				-response = self.full_dispatch_request() 方法内部:执行了before_first_request,before_request,视图函数,after_request,save_session
				-self.full_dispatch_request()---->执行:self.finalize_request(rv)-----》self.process_response(response)----》最后:self.session_interface.save_session(self, ctx.session, response)
		-请求扩展相关
			before_first_request,before_request,after_request依次执行
		-flask有一个请求上下文,一个应用上下文
			-ctx:
				-是:RequestContext对象:封装了request和session
				-调用了:_request_ctx_stack.push(self)就是把:ctx放到了那个位置
			-app_ctx:
				-是:AppContext(self) 对象:封装了当前的app和g
				-调用 _app_ctx_stack.push(self) 就是把:app_ctx放到了那个位置
	# g是什么?
		- 专门用来存储用户信息的g对象,g的全称的为global- g对象是全局的,在一次请求中的所有的代码的地方,都是可以使用的,只能在当次请求中使用
         - 请求结束后app_ctx被移除了,也就销毁了
            
	# g对象与session的区别?
    	- g对象只能针对当次请求有效
        - session可以跨请求,该用户的多次请求中都是可以使用的
        
	# 代理模式
		-request和session就是代理对象,用的就是代理模式

二、flask-session的使用

1 由于原生的flask中session是加密后放到了cookie中
2 我们如果想保存到文件中,数据库中,redis中(常见)。。。
3 借助于第三方模块:flask-session

第一种使用方式(不常用)

from flask import Flask, session
from flask_session import RedisSessionInterface
import redis

app = Flask(__name__)
# 不需要指定app.secret_key
conn = redis.Redis(host='127.0.0.1', port=6379)
app.session_interface = RedisSessionInterface(conn, key_prefix='cc')
# conn不传会默认使用本地的
# key_prefix是前缀,没有默认值,必须传

@app.route('/set_session')
def set_session():
    session['name'] = 'cc'
    return 'session有内容了,写了name=cc'

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

第二种使用方式(通过配置,本质和方式一是一样的)

from flask import Flask, session
from flask_session import Session
from datetime import timedelta
import redis

app = Flask(__name__)
# 配置方式一:后期如果需要更改session储存位置,只需要修改配置即可
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.Redis(host='127.0.0.1', port=6379)
app.config['SESSION_KEY_PREFIX'] = 'cc'
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)  # 设置session的过期时间为7天

# 配置方式二:通过类
# app.config.from_object('settings.Pro')

Session(app)  # 使用第三方插件,是一个通用方式

@app.route('/set_session')
def set_session():
    session['name'] = 'cc'
    return 'session有内容了,写了name=cc'

@app.route('/get_session')
def get_session():
    # s = session['name']
    s = session.get('name', '取不到')
    return 'session是%s' % s

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

三、数据库连接池

1 传统方案存在的问题

# 第一种方案:全局使用沟通一个curser会存在效率问题,安全性问题
from flask import Flask
import pymysql

app = Flask(__name__)
conn = pymysql.Connect(host='127.0.0.1', user='root', password="111",
                       database='luffy', port=3306)
cursor = conn.cursor()

@app.route('/get_data')
def get_data():
    cursor.execute('select * from luffy_order where id < 2')
    res = cursor.fetchall()
    print(res)
    return '数据查到了'

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


# 第二种方案:不限制数据库的连接数,(并发量小就没问题)并发量大了会导致连接数暴增,存在效率问题
from flask import Flask
import pymysql

app = Flask(__name__)

@app.route('/get_data')
def get_data():
    conn = pymysql.Connect(host='127.0.0.1', user='root', password="111",
                       database='luffy', port=3306)
	cursor = conn.cursor()
    cursor.execute('select * from luffy_order where id < 2')
    res = cursor.fetchall()
    print(res)
    return '数据查到了'

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

2 使用数据库连接池

参考:https://www.cnblogs.com/liuqingzheng/articles/9006055.html
-pip3 install DButils
-两种模式:
    第一种模式不建议使用(为每个线程创建一个连接,线程即使调用了close方法,也不会关闭,只是把连接重新放到连接池,只能供自己线程下次使用。当线程终止时,连接自动关闭)
    第二种:创建一批连接到连接池,供所有线程共享使用

3 使用步骤

第一步:新建sql_pool.py
import pymysql
from dbutils.pooled_db import PooledDB

POOL = PooledDB(
    creator=pymysql,  # 使用链接数据库的模块
    maxconnections=6,  # 连接池允许的最大连接数,0和None表示不限制连接数
    mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
    maxcached=5,  # 链接池中最多闲置的链接,0和None不限制
    maxshared=3,  # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
    blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
    maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
    setsession=[],  # 开始会话前执行的命令列表。
    ping=0,
    # ping MySQL服务端,检查是否服务可用。
    host='127.0.0.1',
    port=3306,
    user='root',
    password='111',
    database='luffy',
    charset='utf8'
)    
第二步:使用(导入连接池是单例模式)

s1.py

from sql_pool import POOL

conn = POOL.connection()  # 从连接池中取一个连接(如果没有可用的,就阻塞在这)
cursor = conn.cursor()
cursor.execute('select * from luffy_order where id < 2')
res = cursor.fetchall()
print(res)

四、flask-script模块

作用:

- 自带一个runserver命令
- 可以用于实现类似于django中 python3 manage.py runserver  等类似的命令

安装:

pip3 install flask-script

使用:

s1.py

from flask import Flask
from flask_script import Manager

app = Flask(__name__)
manage = Manager(app)
      
@app.route('/')
def index():
    return '首页'

if __name__ == '__main__':
    manage.run()
'''
直接使用python s1.py runserver 运行文件
'''

自定制命令(简单示例)

from flask import Flask
from flask_script import Manager

app = Flask(__name__)

manage = Manager(app)

@manage.command
def custom(arg):
    """
    自定义命令
    python manage.py custom 123
    :param arg:
    :return:
    """
    print(arg)


@manage.option('-n', '--name', dest='name')
@manage.option('-u', '--url', dest='url')
def cmd(name, url):
    """
    自定义命令(-n也可以写成--name)
    执行: python manage.py  cmd -n lqz -u http://www.baidu.com
    执行: python manage.py  cmd --name lqz --url http://www.baidu.com
    :param name:
    :param url:
    :return:
    """
    print(name, url)


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

"""
作用:比如可以定制一个命令去执行,将excel的数据导入数据库
"""

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