Flask之配置文件,蓝图,数据库连接池,上下文原理

一、Flask配置文件

1. 配置文件原理

配置文件本质上是使用的地址,实现方式只有两种方法

- setitem

class Foo():
    def __setitem__(self,key,value):
        print(key,value)

obj = Foo()
obj['xxx'] = 123
View Code

- 继承字典

class Foo(dict):
    def __init__(self,val):
        dict.__init__(self,val)

obj = Foo({'xxx':123})
print(obj)
View Code

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()
View Code

二、蓝图

对应用程序的目录结构进行分配,一般适用于小中型企业

- 目录结构

- 内容

# 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'
View Code

 三、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()
View Code

- 本地线程

  本地线程可实现,线程之间的数据隔离

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()
View Code

- 模式一,每个线程创建一个连接

  基于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()
View Code

- 模式二,线程复用连接池(推荐使用)

  创建一个连接池,为所有线程提供连接,线程使用连接时获取连接,使用完毕放回连接池。

  线程不断地重用连接池里的连接。

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, '
')

    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, '
')

    cursor = conn.cursor()
    cursor.execute('select * from userinfo')
    result = cursor.fetchall()
    conn.close()
    #

func()
View Code

 四、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__()取唯一标识等
View Code

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 = []
View Code

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
View Code
原文地址:https://www.cnblogs.com/yunweixiaoxuesheng/p/8418135.html