Flask框架详细上下文管理机制

原创gao.xiangyang 最后发布于2018-12-12 14:20:14 阅读数 1199 收藏
展开
文章目录
Flask
flask和django的区别
一、flask配置文件
二、路由系统
自定义正则路由
三、蓝图
创建蓝图
自定义蓝图的static文件夹和trmplates文件夹
为某一个蓝图内所有URL路由访问地址加前缀
before_request--访问URL先触发
四、子域名
一般固定子域名
通配符子域
*五、 threading.local--(和flask没有关系)
* 六、请求上下文管理(源码剖析)
session
flask中 session的流程详解
request
flask中 request的流程详解(和session一样)
flask请求流程图
上下文原理
线程(协程)的值相互隔离(独立空间)。
自定义线程(协程)的Local类
Flask上下文管理源码剖析
Local类
方便操作Local的类
session和requste在Flask中的原理
定义一个自动取到上面的`obj`的函数,变量名和函数名参照`Flask`源码
偏函数
flask-session
七、其他
执行父类的方法
面向对象中特殊的方法
八、Flask数据库
数据库连接池
pymysql
Flask
flask和django的区别
Django功能大而全,Flask只包含基本的配置;

Django有模板,表单,路由,认证,基本的数据库管理等等内建功能。

与之相反,Flask只是一个内核,默认依赖于两个外部库: Jinja2 模板引擎和 Werkzeug WSGI 工具集,其他很多功能都是以扩展的形式进行嵌入使用。

Flask 比 Django 更灵活 ,

flask非常适用于开发API接口

一、flask配置文件
导入类的配置文件(导入类的路径)

方法

首先创建一个PY文件,写一个类,类中写一些配置信息的静态字段

app.config.from_object("python类或类的路径")

def from_object(self, obj):
if isinstance(obj, string_types): # 判断是否是字符串
obj = import_string(obj) # 利用import_module封装的函数拿到类
for key in dir(obj): # 遍历自己写的配置类
if key.isupper(): # 判断类的属性是不是大写
self[key] = getattr(obj, key) # 保存进配置信息

1
2
3
4
5
6
7
原理

利用importlib模块中的import_module函数:

o = importlib.import_module("aa.bb")

利用getattr('var1')获取类中或者模块中的属性

获取到类之后遍历类的属性,在利用getattr()获取所有大写的类的静态变量,写进config配置文件字典

二、路由系统
方式一

@app.route('/user/<username>')
@app.route('/post/<int:post_id>')
@app.route('/post/<float:post_id>')
@app.route('/post/<path:path>')
@app.route('/login', methods=['GET', 'POST'])

# 路由中的数据类型
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
方式二

def index():
pass
app.add_url_rule('/', 'index', index)

'''
add_url_rule(self,rule,endpoint=None,view_func=None,
provide_automatic_options=None,**options)

rule-----------------------------URL规则为字符串
endpoint-------------------------字符串,端点名字,不设置默认为函数名
view_func------------------------函数名
provide_automatic_options-------- 未设置provide_automatic_options,则进入默认的OPTIONS请求回应,否则请求endpoint匹配的函数执行,并返回内容
options -------------------------请求方式,options=['GET','POST']
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
规则

属性 说明 示例
Truestrict_slashes False不严格,True严格,默认=None @app.route('/index',strict_slashes=False)
redirect_to 重定向到指定地址 @app.route('/index/<int:nid>',redirect_to='/home/<nid>'
@app.route('/index/<int:nid>', redirect_to=func)
subdomain 子域名访问(必须配置’SERVER_NAME’) app.config['SERVER_NAME'] = 'wupeiqi.com:5000'
@app.route("/", subdomain="admin")
子域名带正则 @app.route("/dynamic", subdomain="<username>")
def username_index(username)
自定义正则路由
三、蓝图
好处:
目录结构的划分
可以单独给一个蓝图加前缀
应用特殊装饰器-before_request
创建蓝图
创建一个和项目名相同的文件夹 (” crm“ ------文件夹)

在新文件夹(” crm“)内创建__init__.py文件,并在里面实例化

from flask import Flask
def create_app():
app = Flask(__name__) # 实例化flask
return app
1
2
3
4
创建主程序文件xxx.py

from crm import create_app # 导入自己写的实例化flask文件
app = create_app()
if __name__ == '__main__':
app.run()
1
2
3
4
在新文件夹(” crm“)中创建视图文件夹 views

在视图文件夹 views下可创建多个视图python文件

例:account.py

from flask import Blueprint # 导入蓝图

ac = Blueprint('ac',__name__) # 创建蓝图对象

@ac.route('/login')
def login():
return 'login'

@ac.route('/login')
def login():
return 'login'
1
2
3
4
5
6
7
8
9
10
11
在crm文件夹下的__init__.py文件创建视图文件与主程序的关系

from flask import Flask
from .views.account import ac # 导入视图文件

def create_app():
app = Flask(__name__)
app.register_blurprint(ac) # 将蓝图注册到app
return app
1
2
3
4
5
6
7
创建静态文件夹static和模板文件夹templates

crm // 工程文件夹
|-- crm // 蓝图文件夹
| |-- static // 蓝图-静态文件夹
| |-- templates // 蓝图-模板文件夹
| |-- views // 蓝图-视图文件夹
| |-- account.py // 蓝图-视图文件
| |-- __init__.py // 初始化
|-- manage.py // 主程序
1
2
3
4
5
6
7
8
自定义蓝图的static文件夹和trmplates文件夹
在 蓝图-视图文件中创建蓝图对象时,给出静态文件夹名字

ac = Blueprint('ac', __name__, template_folder='xxxx',static_url_path='xxx')

注意:

app在寻找模板文件和静态文件时,先从最外层的templates文件找,找不到才到自定义的template_folder里面找

为某一个蓝图内所有URL路由访问地址加前缀
在__init__.py文件中

app.register_blurprint(ac, url_prefix='/xxxx')

before_request–访问URL先触发
直接在app下装饰函数(在主程序文件中)

这样任意一个URL访问进来都会触发这个被装饰的函数

@app.before_request
def f1():
print('app.before_request')
1
2
3
在蓝图对象下装饰函数(在蓝图文件中)

例:有一个ac蓝图

这样只有当前蓝图下的URL访问进来时才会触发这个函数

@ac.before_request
def f1():
print('ac.before_request')
1
2
3
四、子域名
蓝图子域名:xxx = Blueprint(‘account’, name, subdomain=‘admin’)

前提需要给配置SERVER_NAME: app.config[‘SERVER_NAME’] = ‘abc.com:5000’

访问时:admin.abc.com:5000/login.html

一般固定子域名
一般用于数量比较少的子域名,一个模块对应一个子域名。先看下面一个例子:

# modules.py
from flask import Blueprint # 蓝图
public = Blueprint('public', __name__)
@public.route('/')
def home():
return 'hello flask'
1
2
3
4
5
6
# app.py
app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com'
from modules import public
app.register_blueprint(public, subdomain='public')
1
2
3
4
5
现在可以通过public.example.com/来访问public模块了。

通配符子域
通配符子域,即通过一个模块来匹配很多个子域名。比如某些网站提供的个性化域名功能,就是这种形式。

# modules.py
from flask import Blueprint
public = Blueprint('public', __name__)
@member.route('/')
def home():
return g.subdomain
1
2
3
4
5
6
# app.py
app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com'
from modules import public
app.register_blueprint(public, subdomain='<subdomain>')
1
2
3
4
5
这里的subdomain使用了动态参数<subdomain>(路由中的URL变量也是这种方式)。我们可以用这个参数在请求回调函数之前利用的组合的url处理器来获取相关的用户。这样我们就可以通过*.example.com的形式来访问member模块。

*五、 threading.local–(和flask没有关系)
local为每个线程的数据开辟一个独立的空间,使得线程对自己空间中的数据进行操作(数据隔离)

import threading
from threading import local
import time

obj = local()

def task(i):
obj.xxx = i
time.sleep(1)
print(obj.xxx,i)

for i in range(10):
t = threading.Thread(target = task,args=(i,))
t.start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
获取线程的唯一标记

import threading
import time
def task(i):
print(threading.get_ident(),i)

for i in range(10):
t = threading.Thread(target = task,args=(i,))
t.start()
1
2
3
4
5
6
7
8
自定义类似local的函数—Local 为每个协程或者线程开辟独立空间

import threading
try:
import greenlet # 导入协程模块
get_ident = greenlet.getcurrent # 获得协程唯一标识函数
except:
get_ident = threading.get_ident # 获得线程唯一标识函数

class Local(object):
DIC = {}
def __getattr__(self,item):
ident = get_ident()
if ident in self.DIC:
return self.DIC[ident].get(item)
else:
return None

def __setattr__(self,key,balue):
ident = get_ident()
if ident in self.DIC:
self.DIC[ident][key] = value
else:
self.DIC[ident] = {key:value}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
* 六、请求上下文管理(源码剖析)
session
flask中 session的流程详解
客户端的请求进来时,会调用app.wsgi_app():

此时,会生成一个ctx,其本质是一个RequestContext对象:

在RequestContext 对象中定义了session,且初值为None。

接着继续看wsgi_app函数中,ctx.push()函数,会返回一个session对象保存在ctx中(详解下面代码)

源码:

# ctx.push()中session的相关代码

if self.session is None:
#session_interface = SecureCookieSessionInterface()
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request) # open函数在下面
if self.session is None:
self.session = session_interface.make_null_session(self.app)
1
2
3
4
5
6
7
8
def open_session(self, app, request):
# 获取session签名的算法
s = self.get_signing_serializer(app)
# 如果为空 直接返回None
if s is None:
return None
# session_cookie_name 是配置信息中的SESSION名字,获取request中的cookies
val = request.cookies.get(app.session_cookie_name)
# 如果val为空,即request.cookies为空
if not val:
# session_class = SecureCookieSession
# 看其继承关系,其实就是一个特殊的字典。到此我们知道了session就是一个特殊的字典
# 并保存在ctx中
return self.session_class()
max_age = total_seconds(app.permanent_session_lifetime)
try:
data = s.loads(val, max_age=max_age)
return self.session_class(data)
except BadSignature:
return self.session_class()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
当请求第二次到来时,与第一次的不同就在open_session()那个val判断处,此时cookies不为空, 获取cookie的有效时长,如果cookie依然有效,通过与写入时同样的签名算法将cookie中的值解密出来并写入字典并返回中,若cookie已经失效,则仍然返回’空字典’。

参考Flask-cookies
flask 源码解析:session
request
flask中 request的流程详解(和session一样)
客户端的请求进来时,会调用app.__call__中app.wsgi_app():

此时,会生成一个ctx,其本质是一个RequestContext对象(ctx中封装了session和request对象)

在RequestContext 对象中定义了request = app.request_class(environ)environ包含所有的请求数据

生成ctx后,ctx对象调用了push函数,push函数中有_request_ctx_stack.push(self),将ctx对象存进_request_ctx_stack中

_request_ctx_stack---->全局变量,是一个LocalStack()类,下面有讲到这个类的原理,这里略过就可以

_request_ctx_stack对象中有__storge__={id1:{stack:[ctx对象,]} }(看完下面就明白了)

这时候可以直接试验一下

from falsk.globals import _request_ctx_stack
# 在试图函数中可以直接调用_request_ctx_stack.top
1
2
flask请求流程图
每个请求都流一遍,多线程协程中每个‘程’中都创建新的对象和属性包括(app, ctx, LocalStack,Local)

 

上下文原理
线程(协程)的值相互隔离(独立空间)。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
local_values = threading.local() # 为每个线程开辟空间,类似一个大字典,获取每个线程的唯一标识,每个标识作为一个ID ,然后{ID1:{}, ID2:{}, ID3:{}}
def func(num):
local_values.name = num
import time
time.sleep(1)
print(local_values.name, threading.current_thread().name)
for i in range(20):
th = threading.Thread(target=func, args=(i,), name='线程%s' % i)
th.start()
1
2
3
4
5
6
7
8
9
10
11
12
自定义线程(协程)的Local类
模拟栈的操作

创建一个类

创建一个字典

获取到的每个协程(线程)的唯一标记为字典内的键,创建一个新的自定当作独立空间

{id1:{}, id2:{}, id3:{}}

# 优先按照协程,次要线程
try:
from greenlet import greenlet as get_ident # 获取协程的唯一标记
except:
from threading import get_ident # 获取线程的唯一标记
class Local(object):
def __init__(self):
# 创建一个 storage = {},使用父类创建,如果不用父类,那么会调用自己定义的__setattr__函数,会报错
object.__setattr__(self,'storage',{})
def __setattr__(self, key, value):
ident = get_ident()
try:
self.storage[ident][key] = value
except KeyError:
self.storage[ident] = {key:value}

def __getattr__(self, item):
ident = get_ident()
try:
return self.storage[ident][item]
except KeyError:
print('None')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Flask上下文管理源码剖析
Local类
#!/usr/bin/env python
# -*- coding:utf-8 -*-

# 优先按照协程,次要线程
try:
from greenlet import greenlet as get_ident # 获取协程的唯一标记
except:
from threading import get_ident # 获取线程的唯一标记
from threading import local

# 清除协程(线程)中所有的变量
def release_local(local):
local.__release_local__()

# 定义一个存储协程(线程)空间的类
class Local(object):
__slots__ = ('__storage__', '__ident_func__') # 设置类只有这两个变量
def __init__(self):
object.__setattr__(self, '__storage__', {}) # self.__storage__ = {}
object.__setattr__(self, '__ident_func__', get_ident) # self.__ident_func__ = get_ident

# 清除协程(线程)中的变量
def __release_local__(self):
# 提取当前对象的 {id1:{}, id2{}} 的 idx 。如果没有则返回None
self.__storage__.pop(self.__ident_func__(), None)

# "." 触发
def __getattr__(self, name):
try:
# 返回当前协程(线程)唯一标识的字典的[name]
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
# obj.name=value触发
def __setattr__(self, name, value):
# 获得当前的线程(协程)的唯一标识
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
# 删除
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)

 

if __name__ == '__main__':
obj = Local()
obj.stack = []

obj.stack.append('老王')
obj.stack.append('老李')
print(obj.stack) # ['老王','老李']
'''
操作起来太麻烦,看下面
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
方便操作Local的类

# 定义方便操作Local的类
class LocalStack(object):
def __init__(self):
# 实例Local()
self._local = Local()

# 清除协程(线程)中的变量
def __release_local__(self):
self._local.__release_local__()

# 模拟栈操作,推入
def push(self, obj):
# 拿到Local对象中的stack属性,如果没有则返回None
rv = getattr(self._local, 'stack', None)
# 如果没找到,说明第一次进入,则创建一个列表用来模拟栈的进出
if rv is None:
self._local.stack = rv = []
# 将obj存进列表最后
rv.append(obj)
return rv
# 模拟栈操作,取出
def pop(self):
# 堆栈中删除最上面的项,也就是删除列表最后一项,并取出。如果堆栈已经为空,则为旧值或“None”。
stack = getattr(self._local, 'stack', None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop()
# 作为属性调用,返回列表的最后一项,也就是栈顶
@property
def top(self):
"""The topmost item on the stack. If the stack is empty,
`None` is returned.
"""
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None

if __name__ == '__main__':
obj = LocalStack()
obj.push('老王')
obj.push('老李')
print(obj.top)
print(obj.pop())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
拥有了这个类,使操作Local类更快捷,更方便。

但是没有这个类,也可以操作Local类,只是比较复杂

session和requste在Flask中的原理
在上面flask中session中讲到RequestContext的原理,就是保存有session和request的一个类,

这里示例创建一个RequestContext类

class RequestContext(object):
def __init__(self):
self.session = 'xxxxxxxxxxx'
self.request = 'ooooooooooo'

ctx = RequestContext()
xxx = LocalStack()
xxx.push(ctx) # { 协程ID1:{stack:[ctx对象, ]} }
obj = xxx.top # obj = ctx
print(obj.request) # 'ooooooooooo'
print(obj.session) # 'xxxxxxxxxxx'
1
2
3
4
5
6
7
8
9
10
11
定义一个自动取到上面的obj的函数,变量名和函数名参照Flask源码
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
# 返回top对象中的name属性
return getattr(top, name)
1
2
3
4
5
6
偏函数
from functools import partial # 详情请百度
#_lookup_req_object('request')
request = partial(_lookup_req_object,'request')
#_lookup_req_object('session')
session = partial(_lookup_req_object,'session')

print(request()) # 返回request信息
print(session())
1
2
3
4
5
6
7
8
参考武沛齐的博客

flask-session
pip3 install falsk-session
from flask_session import Session老版本的:from flask.ext.session import Session
Session(app)
七、其他
执行父类的方法
class Foo(object):
def func(self):
print('Foo.func')
class Bar(object):
def func(self):
print('Bar.func')
class F1(Foo,Bar):
pass

f = F1()
f.func() # print('Foo.func')
print(F1.__mro__)
'''
(<class '__main__.F1'>,
<class '__main__.Foo'>,
<class '__main__.Bar'>,
<class 'object'>)
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
面向对象中特殊的方法
def __getattr__(self,item)#---------------> 使用 (.)点 的时候触发,item为点后面的值
def __setattr__(self,key,value)#----------> obj.xx=123 时,key=xx,value=123
1
2
八、Flask数据库
暂无!等待更新

数据库连接池
pymysql
————————————————
版权声明:本文为CSDN博主「gao.xiangyang」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gh7735555/article/details/84972226

posted on 2020-03-17 13:25  枫飞飞  阅读(233)  评论(0编辑  收藏  举报