Flask之配置文件,蓝图,数据库连接池,上下文原理
一、Flask配置文件
1. 配置文件原理
配置文件本质上是使用的地址,实现方式只有两种方法
- setitem
class Foo(): def __setitem__(self,key,value): print(key,value) obj = Foo() obj['xxx'] = 123
- 继承字典
class Foo(dict): def __init__(self,val): dict.__init__(self,val) obj = Foo({'xxx':123}) print(obj)
2. 设置方式
- 方式一,使用字典方式配置
app.config['SESSION_COOKE_NAME'] = 'session_liling'
- 方式二,引入文件,设置
# s1.py from flask import Flask app = Flask(__name__) app.config.from_pyfile('settings.py') # 引用settings.py中的AAAA print(app.config['AAAA']) # 123 # settings.py AAAA = 123
- 方法三,使用环境变量设置,推荐使用
from flask import Flask app = Flask(__name__) import os os.environ['FLASK-SETTINGS'] = 'settings.py' app.config.from_envvar('FLASK-SETTINGS')
- 方式四,通过对象方式导入使用,可根据不同环境选择不同的配置,推荐使用
# s1.py from flask import Flask app = Flask(__name__) app.config.from_object('settings.BaseConfig') print(app.config['NNNN']) # 123 # settings.py class BaseConfig(object): # 公用配置 NNNN = 123 class TestConfig(object): DB = '127.0.0.1' class DevConfig(object): DB = '192.168.1.1' class ProConfig(object): DB = '47.18.1.1'
3. 不同的文件引用配置
from flask import Flask,current_app app = Flask(__name__) app.secret_key = 'adfadsfhjkhakljsdfh' app.config.from_object('settings.BaseConfig') @app.route('/index') def index(): print(current_app.config['NNNN']) return 'xxx' if __name__ == '__main__': app.run()
4. instance_path参数和instance_relative_config参数
from flask import Flask,current_app app = Flask(__name__,instance_path=None,instance_relative_config=False) # 默认instance_relative_config = False,instance_relative_config和instance_path都不会生效 # instance_relative_config=True,instance_path才会生效,app.config.from_pyfile('settings.py')将会失效 # 配置文件找的路径,按instance_path的值作为配置文件路径 # 默认instance_path=None,None会按照当前路径下的instance文件夹为配置文件的路径 # 如果设置路径,按照设置的路径查找配置文件。 app.config.from_pyfile('settings.py') @app.route('/index') def index(): print(current_app.config['NNNN']) return 'xxx' if __name__ == '__main__': app.run()
二、蓝图
对应用程序的目录结构进行分配,一般适用于小中型企业
- 目录结构
- 内容
# crm/__init__.py # 创建flask项目,用蓝图注册不同的模块 from flask import Flask from .views import account from .views import order app = Flask(__name__) app.register_blueprint(account.account) app.register_blueprint(order.order) ------------------------------------------------------------------------ # manage.py # 启动文件 import crm if __name__ == '__main__': crm.app.run() ------------------------------------------------------------------------ # crm/views/account.py # 视图函数模块,Blueprint,将函数引入app from flask import Blueprint account = Blueprint('account',__name__,url_prefix='/xxx') @account.route('/login') def login(): return 'Login'
三、Flask之数据库连接池
1. 为什么时候数据库连接池
- 多连接
如果不使用数据库连接池,会造成每次请求反复常见数据库连接,数据库会耗费过多资源,数量过大的话,数 据库会过载。
每次连接数据库会有延迟,也会造成程序运行缓慢。
- 单链接
在程序中全局中创建连接,不管关闭连接,程序会一直使用一个连接,避免了反复连接造成的问题。
但是pymysql模块只支持一个线程的连接,在无法实现多线程。
2. 基于DBUtils实现数据库连接池
数据库连接池避免每次操作都要连接数据库
一直使用一个连接,多线程也会出现问题,可加锁,但变为串行
import pymysql import threading from threading import RLock LOCK = RLock() CONN = pymysql.connect(host='127.0.0.1', port = 3306, user = 'root', password = '123', database = 'ok1', charset = 'utf8' ) def task(arg): with LOCK: cursor = CONN.cursor() cursor.execute('select * from book') result = cursor.fetchall() cursor.close() print(result) for i in range(10): t = threading.Thread(target=task,args=(i,)) t.start()
- 本地线程
本地线程可实现,线程之间的数据隔离
import threading import time # 本地线程对象 local_values = threading.local() def func(num): """ # 第一个线程进来,本地线程对象会为他创建一个 # 第二个线程进来,本地线程对象会为他创建一个 { 线程1的唯一标识:{name:1}, 线程2的唯一标识:{name:2}, } :param num: :return: """ local_values.name = num # 4 # 线程停下来了 time.sleep(2) # 第二个线程: local_values.name,去local_values中根据自己的唯一标识作为key,获取value中name对应的值 print(local_values.name, threading.current_thread().name) for i in range(5): th = threading.Thread(target=func, args=(i,), name='线程%s' % i) th.start()
- 模式一,每个线程创建一个连接
基于threading.local实现创建每个连接。
每个线程会创建一个连接,该线程没有真正关闭。
再次调用该线程时,还是使用原有的连接。
线程真正终止的时候,连接才会关闭。
from DBUtils.PersistentDB import PersistentDB import pymysql POOL = PersistentDB( creator=pymysql, # 使用链接数据库的模块 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always closeable=False, # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接) threadlocal=None, # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置 host='127.0.0.1', port=3306, user='root', password='123', database='pooldb', charset='utf8' ) def func(): # conn = SteadyDBConnection() conn = POOL.connection() cursor = conn.cursor() cursor.execute('select * from tb1') result = cursor.fetchall() cursor.close() conn.close() # 不是真的关闭,而是假的关闭。 conn = pymysql.connect() conn.close() conn = POOL.connection() cursor = conn.cursor() cursor.execute('select * from tb1') result = cursor.fetchall() cursor.close() conn.close() import threading for i in range(10): t = threading.Thread(target=func) t.start()
- 模式二,线程复用连接池(推荐使用)
创建一个连接池,为所有线程提供连接,线程使用连接时获取连接,使用完毕放回连接池。
线程不断地重用连接池里的连接。
import time import pymysql import threading from DBUtils.PooledDB import PooledDB, SharedDBConnection 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=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='123456', database='flask_test', charset='utf8' ) def func(): # 检测当前正在运行连接数的是否小于最大链接数,如果不小于则:等待或报raise TooManyConnections异常 # 否则 # 则优先去初始化时创建的链接中获取链接 SteadyDBConnection。 # 然后将SteadyDBConnection对象封装到PooledDedicatedDBConnection中并返回。 # 如果最开始创建的链接没有链接,则去创建一个SteadyDBConnection对象,再封装到PooledDedicatedDBConnection中并返回。 # 一旦关闭链接后,连接就返回到连接池让后续线程继续使用。 # PooledDedicatedDBConnection conn = POOL.connection() # print(th, '链接被拿走了', conn1._con) # print(th, '池子里目前有', pool._idle_cache, '\r\n') cursor = conn.cursor() cursor.execute('select * from userinfo') result = cursor.fetchall() print(result) conn.close() conn = POOL.connection() # print(th, '链接被拿走了', conn1._con) # print(th, '池子里目前有', pool._idle_cache, '\r\n') cursor = conn.cursor() cursor.execute('select * from userinfo') result = cursor.fetchall() conn.close() # func()
四、Flask上下文管理
所谓上下文,像考试题目根据上下文,回答一下问题。
程序中,泛指的外部环境,像wsgi来的网络请求,而且通常只有上文。
flask中的上下文,被使用在current_app,session,request上。
以request为例,请求进来首先调用flask的__call__方法,返回wsgi_app,
并将请求加入wsgi_app,wsgi_app将请求加入实例化对象请求上下文中(ctx),
之后调用ctx.push方法,其实是在求情上下文栈中(_reqeust_ctx_stack.push)压入了请求,
_reqeust_ctx_stack其实为LocakStack()类内部构建方法中self._local = Local()了本地线程
在Local中请求压入了类似字典格式的对象中。通过每个线程的唯一标识的value的key(stack)的value保存请求信息:{ 111:{'stack':[] },222:{'stack':[] } }。
每个请求一个线程,push保存请求,通过LocalProxy中的Local的top方法获取请求,当线程结束的时候pop请求。
1. flask本地线程
from flask import session try: from greenlet import getcurrent as get_ident # grenlet协程模块 except ImportError: try: from thread import get_ident except ImportError: from _thread import get_ident # get_ident(),获取线程的唯一标识 class Local(object): # 引用session中的LocalStack下的Local __slots__ = ('__storage__', '__ident_func__') # __slots__该类在外面调用时,只能调用定义的字段,其他的不能调用 def __init__(self): # object.__setattr__为self设置值,等价于self.__storage__ = {} # 为父类object中包含的__steattr__方法中的self.__storage__ = {} # 由于类内包含__steattr__,self.xxx(对象.xxx)时会自动会触发__steattr__, # 当前__steattr__中storage = self.__storage__又会像self.xxx要值,故会造成递归 # 所以在父类中__steattr__方法赋值,避免self.xxx调用__setattr__造成的递归 object.__setattr__(self, '__storage__', {}) object.__setattr__(self, '__ident_func__', get_ident) # 赋值为协程 def __iter__(self): return iter(self.__storage__.items()) def __release_local__(self): self.__storage__.pop(self.__ident_func__(), None) def __getattr__(self, name): try: return self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): ident = self.__ident_func__() # 获取单钱线程(协程)的唯一标识 storage = self.__storage__ # {} try: storage[ident][name] = value # { 111 : {'stack':[] },222 : {'stack':[] } } except KeyError: storage[ident] = {name: value} def __delattr__(self, name): try: del self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) _local = Local() # flask的本地线程功能,类似于本地线程,如果有人创建Local对象并,设置值,每个线程里一份 _local.stack = [] # _local.stack会调用__setattr__的self.__ident_func__()取唯一标识等
2. 特殊栈
from flask import session try: from greenlet import getcurrent as get_ident except ImportError: try: from thread import get_ident except ImportError: from _thread import get_ident # 获取线程的唯一标识 get_ident() class Local(object): __slots__ = ('__storage__', '__ident_func__') def __init__(self): # self.__storage__ = {} # self.__ident_func__ = get_ident object.__setattr__(self, '__storage__', {}) object.__setattr__(self, '__ident_func__', get_ident) def __iter__(self): return iter(self.__storage__.items()) def __release_local__(self): self.__storage__.pop(self.__ident_func__(), None) def __getattr__(self, name): try: return self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): ident = self.__ident_func__() # 获取当前线程(协程)的唯一标识 storage = self.__storage__ # {} try: storage[ident][name] = value # { 111:{'stack':[] },222:{'stack':[] } } except KeyError: storage[ident] = {name: value} def __delattr__(self, name): try: del self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) _local = Local() _local.stack = []
3. 利用flask源码中的stack和local
from functools import partial from flask.globals import LocalStack, LocalProxy _request_ctx_stack = LocalStack() class RequestContext(object): def __init__(self, environ): self.request = environ def _lookup_req_object(name): top = _request_ctx_stack.top if top is None: raise RuntimeError(_request_ctx_stack) return getattr(top, name) # 实例化了LocalProxy对象,_lookup_req_object参数传递 session = LocalProxy(partial(_lookup_req_object, 'session')) """ local = { “标识”: {'stack': [RequestContext(),]} } """ _request_ctx_stack.push(RequestContext('c1')) # 当请求进来时,放入 print(session) # 获取 RequestContext('c1'), top方法 print(session) # 获取 RequestContext('c1'), top方法 _request_ctx_stack.pop() # 请求结束pop