FLASK的使用和组件
一、WSGI
所有web框架都是基于socket实现接收网络请求,wsgi协议实现了socket的功能,django使用wsgiref模块实现
默认情况下flask使用werkzeug模块实现,还有第三方的uwsgi模块flask上可以实现wsgi协议的功能,uwsgi模块性能较高一般在生产上使用。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from werkzeug.wrappers import Request, Response from werkzeug.serving import run_simple @Request.application def hello(request): return Response('Hello World!') if __name__ == '__main__': run_simple('localhost',4000,hello)
二、简单示例登录
1. 目录结构
2. 示例代码
- pro_flask.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,render_template,redirect,request,session app = Flask(__name__,template_folder="templates",static_url_path='/sss') # template_folder="templates,指定模板路径 # static_url_path='/sss',指定静态文件前缀 app.secret_key = 'afasdfasdfjjd' # session默认存储在签名的cookie中,放在浏览器上,设置secret_key做签名加密 @app.route('/index') # 默认求情为GET方法 def index(): user_info = session.get('user_info') # session如果用所以没有的话会报错,所以有get方法 if not user_info: return redirect('/login') return render_template('index.html') @app.route('/login',methods=['GET','POST']) # methods=['GET','POST'],指定接收的请求方法 def login(): if request.method == "GET": return render_template('login.html') else: user = request.form.get('user') # request.from.get接收表单请求 pwd = request.form.get('pwd') if user == 'alex' and pwd == '123': session['user_info'] = user return redirect('/index') return render_template('login.html',**{'msg':'用户名或密码错误'}) if __name__ == '__main__': app.run()
- login.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录页面</h1> <form method="POST" novalidate> <input type="text" name="user"> <input type="text" name="pwd"> <input type="submit" value="提交">{{msg}} </form> </body> </html>
- index.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>欢迎进入系统</h1> <img src="/sss/111.png" alt=""> </body> </html>
三、 登录session验证装饰器
方式一:
使用session做验证,可以加验证装饰器,写一次给需要验证的视图函数加上装饰器即可
验证装饰器需要加在视图函数与路由装饰器之间,使路由装饰器与auth和视图函数整体产生路由关系,在路由过程中验证session,反之加在路由函数之上,路由过程中没有验证session。
在flask中如果加上装饰器,路由系统会在找视图函数的时候,所有加上装饰器的视图函数都是装饰器里的inner函数,无法分辨该去找哪个视图函数,所以需要在路由装饰器上加入endpoint做为url和视图函数的中间层,指向endpoint即可找到对应的视图函数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,render_template,redirect,request,session app = Flask(__name__) app.secret_key = 'afasdfasdfjjd' def auth(func): def inner(*args,**kwargs): user_info = session.get('user_info') if not user_info: return redirect('/login') return func(*args,**kwargs) return inner @app.route('/index',endpoint='n1') @auth def index(): return 'index' @app.route('/order',endpoint='n2') @auth def order(): return 'order' @app.route('/login',methods=['GET','POST']) def login(): if request.method == "GET": return render_template('login.html') else: user = request.form.get('user') pwd = request.form.get('pwd') if user == 'alex' and pwd == '123': session['user_info'] = user return redirect('/index') return render_template('login.html',**{'msg':'用户名或密码错误'}) if __name__ == '__main__': app.run()
方式二:
在验证装饰器里的内嵌函数加上functools.wraper可以更方便的解决方式一种的路由混淆问题。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,render_template,redirect,request,session import functools app = Flask(__name__) app.secret_key = 'afasdfasdfjjd' def auth(func): @functools.wraps(func) def inner(*args,**kwargs): user_info = session.get('user_info') if not user_info: return redirect('/login') return func(*args,**kwargs) return inner @app.route('/index') @auth def index(): return 'index' @app.route('/order') @auth def order(): return 'order' @app.route('/login',methods=['GET','POST']) def login(): if request.method == "GET": return render_template('login.html') else: user = request.form.get('user') pwd = request.form.get('pwd') if user == 'alex' and pwd == '123': session['user_info'] = user return redirect('/index') return render_template('login.html',**{'msg':'用户名或密码错误'}) @app.route('/logout') def logout(): del session['user_info'] return redirect('/login') if __name__ == '__main__': app.run()
方式三:
还有更简便的方式使用be_fore_request扩张,想中间件一样在还没有进入到项目中之前就做验证,并且可以用黑白名单控制限制访问的页面。下面会讲到。
四、flask配置文件
- flask默认配置项
flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{ 'DEBUG': get_debug_flag(default=False), 是否开启Debug模式 'TESTING': False, 是否开启测试模式 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'LOGGER_HANDLER_POLICY': 'always', 'SERVER_NAME': None, 'APPLICATION_ROOT': None, 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, }
- 配置方式一
程序文件内配置
由于config对象本质上是字典,所以还可以使用app.config.update(...)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
app.config['DEBUG'] = True app.config['TESTING'] = False app.config['SESSION_REFRESH_EACH_REQUEST'] = True
- 配置方式二
在程序文件导入配置文件,单独写入配置文件,类的方式存储配置,并且可以指定不同环境的配置类
配置文件内可设基类写入常用配置,其他环境配置类里继承基类,除该环境需要的配置,其他配置继承基类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# pro_flask.py app.config.from_object('setttings.TestingConfig') # settings.py class BaseConfig(object): DEBUG = False class ProConfig(BaseConfig): pass class BaseConfig(BaseConfig): DEBUG = True class BaseConfig(BaseConfig): DEBUG = True
五、flask路由系统
flask路由系统使用了app.route装饰器处理,本质上是调用了self.add_url_rule(rule, endpoint, f, **options)做处理
添加路由的方式有两种一种使用装饰器,一种使用self.add_url_rule(rule, endpoint, f, **options)
- 源码route装饰器
f:传入的作为参数的视图函数
rule:关联视图函数的url
endpoint:反向生成url,默认为空
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def route(self, rule, **options): def decorator(f): endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator
- self.add_url_rule(rule, endpoint, f, **options)
add_url_rule方法中首先判断endpoint是否为空,如果为空得到view_func的函数名,如果有值则使用该值,之后加入options['endpoint'] ,之后options中的methods方法赋值给methods。
之后将rule,methods,options打包封装加入到self.url_rule_class并赋值给rule。
之后self.url_map.add(rule)加入到url_map,url_map本质上是Map独享之前封装着一个列表。
最后每一个函数的rule封装着路由关系的信息加入到了url_map中列表中,url_map中存贮着所有的路由关系
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options): if endpoint is None: endpoint = _endpoint_from_view_func(view_func) """ def _endpoint_from_view_func(view_func): return view_func.__name__ """ options['endpoint'] = endpoint methods = options.pop('methods', None) rule = self.url_rule_class(rule, methods=methods, **options) """ url_rule_class = Rule """ self.url_map.add(rule) """ self.url_map = Map() class Map(object): def __init__: self._rules = [] def add(self, rulefactory): self._rules.append(rule) """ """ url_map = Map() = [ Rule(rule=/index/ endpoint=None view_func=函数名), Rule(rule=/order/ endpoint=None view_func=函数名), Rule(rule=/login/ endpoint=None view_func=函数名) ] """
- 每一个请求会获取url信息和url_map中的rule中的url做匹配执行相应的视图函数
六、flask之CBV,FBV
在flask框架中使用视图函数或者类的方式
FBV不用多提及,更多的时候用的是FBV
CBV用到了类装饰器,原理和FBV大致相同
简单的业务逻辑使用FBV会更简单
如果写接口CBV更合适,get或者post方法可以直接定义到不同的方法流,省略if,else判断
- CBV方式一:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,views app = Flask(__name__) class TestView(views.View): methods = ['GET'] def dispatch_request(self): return 'test!' app.add_url_rule('/test',view_func=TestView.as_view(name='test')) # name = endpoint """ @classmethod def as_view(cls, name, *class_args, **class_kwargs): def view(*args, **kwargs): self = view.view_class(*class_args, **class_kwargs) return self.dispatch_request(*args, **kwargs) return view """ if __name__ == '__main__': app.run()
- CBV方式二:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,views app = Flask(__name__) def auth(): pass class X1View(views.MethodView): methods = ['GET', 'POST'] decorators = [auth, ] # 定义装饰器直接会被调用 def get(self): return 'x1.GET' def post(self): return 'x1.POST' app.add_url_rule('/x1', view_func=X1View.as_view(name='x1')) # name=endpoint if __name__ == '__main__': app.run()
七、before_request 和 after_request(扩展)
扩展在视图函数前后,可执行操作。
before_request没有返回值,会继续执行视图函数,有返回值会直接执行before_request。
after_request需要包含response参数,且有返回值。
可在before_request中利用session做登录验证。
before_request执行顺序正序,after_request执行顺序倒叙,after_request在源码中将after_request的列表reverse反转,故倒叙执行。
如果遇到before_request有值返回,会从最后一个after_request开始,执行所有的after_request。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,redirect,request,session app = Flask(__name__) app.secret_key = 'asdfj90asd09f098sadf' @app.before_request def bf(): if request.path == '/login': return None user_info = session.get('user_info') if not user_info: return redirect('/login') @app.after_request def af(response): return response @app.route('/') def index(): return "hello world" @app.route('/login') def login(): return 'login' if __name__ == '__main__': app.run(port=9999)
八、请求和相应相关信息
1. 请求
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
request.method request.args request.form request.values request.cookies request.headers request.path request.full_path request.script_root request.url request.base_url request.url_root request.host_url request.host request.files obj = request.files['the_file_name'] obj.save('/var/www/uploads/' + secure_filename(f.filename))
2.相应
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
return "字符串" return render_template('html模板路径',**{}) return redirect('/index.html') 响应头 + Cookie: from flask import make_response response = make_response("字符串") response = make_response(render_template('index.html')) response = make_response(redirect('/index.html')) response.delete_cookie('key') response.set_cookie('key', 'value') response.headers['X-Something'] = 'A value' return response
九、模板
模板引擎依赖jinjia2组件
- s1.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,redirect,render_template,Markup app = Flask(__name__) app.secret_key = 'asdfj90asd09f098sadf' def f1(value): return "<input type='text' value='%s' />" %(value,) def f2(value): return Markup("<input type='text' value='%s' />" %(value,)) @app.route('/') def index(): context = { 'k1':'v1', 'k2':[11,22,33,44], 'k3':{ 'name':'oldboy', 'age':56, }, 'k4':f1, 'k4':f2, } return render_template('index.html',**context) # return render_template('index.html',k1 = 'v1') @app.route('/order') def order(): return render_template('order.html') if __name__ == '__main__': app.run()
- templates/index.html
index引用了layout作为模板
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% extends "layout.html" %} {% block content %} <h4>欢迎进入系统</h4> <p>取值</p> <p>{{k1}}</p> <p>取列表中的值</p> <p>{{k2.0}} {{k2[1]}}</p> <p>循环列表中的值</p> <ul> {% for i in k2%} <li>{{i}}</li> {% endfor %} </ul> <p>取字典中的值</p> <p>{{k3.name}} {{k3['age']}} {{k3.get('name')}}</p> <p>循环字典中的值</p> <p>for i k3.keys()</p> <p>for i k3.values()</p> <ul> {% for k,v in k3.items() %} {% if v == 'oldboy' %} <li>老:{{k}} {{v}}</li> {% else %} <li>new:{{k}} {{v}}</li> {% endif %} {% endfor %} </ul> <h1>函数: {{k4('123')|safe}}</h1> <h1>函数: {{k4('123')}}</h1> {% endblock %}
- templates/layout.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="height: 128px;">头</div> <div> {% block content %} {% endblock %} </div> <div style="height: 128px;">底</div> </body> </html>
- templates/order.html
order引用了xxx中的组件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% extends "layout.html" %}
{% block content %}
{% include 'xxx.html' %}
{% include 'xxx.html' %}
{% include 'xxx.html' %}
{% endblock %}
- templates/xxx.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<form> <input type="text"> <input type="text"> <input type="text"> <input type="text"> </form>
- 全局函数
在所有模板文件中都可以使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# xxx.py @app.template_global() def sb(a1,a2): return a1 + a2 @app.template_filter() def db(a1,a2,a3): return a1 + a2 + a3 # templates/index.html <h1>全局函数:{{sb(1,2)}}</h1> <h1>全局函数:{{1|db(2,3)}}</h1>
十、蓝图
1. 示例讲解
- 目录结构
- manage.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 导入app文件,导入过程中执行pro_flask/__init__.py文件 from pro_flask import app if __name__ == '__main__': app.run()
- pro_flask/__init__.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 导入视图函数 # 实例化话flask对象 # 导入包含视图函数的蓝图对象注册到蓝图 # 可使用扩展中间件做控制操作 from flask import Flask from .views.account import ac from .views.user import us app = Flask(__name__) app.register_blueprint(ac) app.register_blueprint(us) @app.before_request def check_login(): print('...')
- pro_flask/views/user.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
复制代码 # 使用蓝图实例化视图函数成为蓝图对象,蓝图对象将代理找到对应的视图函数 # 也可以在文件模块级别使用before_request from flask import Blueprint us = Blueprint('us',__name__,url_prefix='/xxx') @us.before_request def check_login(): print('...') @us.route('/index') def login(): return 'index'
2. 总结蓝图的三大功能
- 目录结构划分
将原有单一的py文件模块,转化为各个功能的目录,便于管理
- before_request扩展中间件
使用扩展中间件,灵活的运用在不同位置的函数上,可以整体的控制,或者局部的控制请求
- 指定url前缀
ac = Blueprint('ac',__name__,url_prefix='/xxx')
url_prefix='xxx'可以指定访问url的前缀
十一、闪现
1. 作用
显现基于session实现,使用到了session.pop(),可用于设置一次,获取后,立即删除的操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
十二、中间件?
该中间件不同于,before_request,他的原理是在源码级别的wsgi_app处理刚进来网络请求(也就是还没有到before_request处理的范围)执行一些代码
重新定义源码中的wsgi,原有wsgi代码在实例化过程中会出发__call__方法,同理在自定义的类中,将wsgi传入为参数,在类中的__call__方法内执行wsgi方法
在wsgi之前或之后执行自定义的代码,比如ping书库链接能不能成功,情况缓存等等定制脚本的操作(不要做flask请求相关的操作)。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask app = Flask(__name__) @app.route('/index') def index(): return 'abc' class Middleware(object): def __init__(self,old_wsgi_app): self.old_wsgi_app = old_wsgi_app def __call__(self,*args,**kwargs): response = self.old_wsgi_app(*args,**kwargs) return response if __name__ == '__main__': app.wsgi_app = Middleware(app.wsgi_app) app.run()
十三、flask-session存储
flask-session默认情况下保存在cookie中,保存在客户端的浏览器上,可以自定义存储数据库中。
1. 安装
pip install flask-session
pip install redis
2. 示例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,session app = Flask(__name__) app.secret_key = 'aadfasdfkl' from flask.ext.session import Session from redis import Redis app.config['SESSION_TYPE'] = 'redis' app.config['SESSION_REDIS'] = Redis(host='192.168.1.1',port='6379',password='xxx') Session(app) @app.route('/login') def login(): session['user_info'] = 'xxx' return 'login' @app.route('/index') def index(): print(session['user_info']) return 'index' if __name__ == '__main__': app.run()
十四、上下文管理
1. threading.local(本地线程)
- 多线程程序中执行中,如遇IO阻塞等问题,新的线程会覆盖之前线程的数据,导致所有线程都只取到最后一个线程中的数据,致使数据混乱,解决方式可以使用本地线程。
- 本地线程实现了线程之间的隔离,每个线程开辟独立空间,存放数据,防止线程之间数据混乱
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading import time def task(arg): # global obj obj = arg time.sleep(2) print(obj) for i in range(10): t = threading.Thread(target=task,args=(i,)) t.start() -------------------------------------------------------------------------------- import threading import time obj = threading.local() def task(arg): global obj obj.value = arg time.sleep(2) print(obj.value) for i in range(10): t = threading.Thread(target=task,args=(i,)) t.start()
- 本地线程原理是使用字典,每个线程的唯一标识(ident),数据的key,value保存在唯一标识下。
- 除线程级别之外还可以使用携程,实现更细的粒度
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading import time try: from greenlet import getcurrent as get_ident except ImportError: from threading import get_ident class Local(object): def __init__(self): self.storage = {} def __setitem__(self, key, value): ident = get_ident() if ident in self.storage: self.storage[ident][key] = value else: self.storage[ident] = {key:value} def __getitem__(self, key): ident = get_ident() return self.storage[ident][key] obj = Local() def task(arg): global obj obj['value'] = arg time.sleep(2) print(obj['value']) for i in range(10): t = threading.Thread(target=task,args=(i,)) t.start()
- 对象点某属性赋值(obj.statck = [])可以调用__setattr__,如果__setattr__中引用了构建方法中的属性,会出现__setattr__执行先于属性赋值的报错,通过继承父类object中的使用父类中的__setattr__线性赋值,之后在执行当前类中的__setattr__的方式可以解决报错
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import get_ident class Local(object): def __init__(self): #self.storage = {} object.__setattr__(self,'__storage__',{}) def __setattr__(self, key, value): ident = get_ident() try: self.__storage__[ident][key] = value except KeyError as e: self.__storage__[ident] = {key:value} def __getattr__(self, key): ident = get_ident() return self.__storage__[ident][key] obj = Local() obj.statck = []
2. 源码剖析上下文管理流程
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
app = Flask(__name__)触发__call__(self,env)方法,__call__返回wsgi_app(env)。 wsgi_app中主要执行了四个语句: ctx = self.request_context(environ) ctx.push() response = self.full_dispatch_request() ctx.auto_pop(error) - 其中ctx = self.request_context(environ),执行request_context返回RequestContext,在RequestContext的构建方法中初始化了最初的空session等信息 - ctx.push执行的是RequestContext中的push执行了_request_ctx_stack.push(self),由于_request_ctx_stack = LocalStack(),所以实际执行了LocalStack()的push方法。 该方法中rv = getattr(self._local, 'stack', None)用于检测当前线程Local()中是否有stack,如果没有创建同时赋值_local.stack和rv为空列表(self._local=Local()),使二者保持一致,同时将obj加入了Local中的__storage__字典保存当前线程中的请求相关的信息。格式如下。 __storage__ = { IDENT:{stack:[obj(session,xxx),]}, IDENT:{stack:[obj(session,xxx),]}, } - response = self.full_dispatch_request()首先执行rv = self.preprocess_request(),处理蓝图中的before_request。 之后执行rv = self.dispatch_request(),处理匹配视图函数匹配,返回self.view_functions[rule.endpoint](**req.view_args) - 最后执行ctx.auto_pop(error),还是调用Localstack在pop调__storage__ 当前线程的stack,删除内容中当前线程的信息
3. session赋值源码中的过程
session简单的说是从客户请求头中的cookie中获取相关的信息,将所有信息放置在一个继承字典的特殊自定义字典中
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
session['v'] = 123 session = LocalProxy(partial(_lookup_req_object, 'session')),session['v'] = 123,触发了LocalProxy中的__setitem__方法。 __setitem__内容部执行了self._get_current_object()[key] = value,self._get_current_object中会执行self._local(),也就是实例化LocalProxy过程中,同时也会执行partial(_lookup_req_object, 'session') partial(_lookup_req_object, 'session')是一个偏函数,_lookup_req_object函数中会自动传参name,当前该参数使‘session’。 在_lookup_req_object中执行了top = _request_ctx_stack.top,_request_ctx_stack = LocalStack,所以执行了Localstack的top方法,top方法返回了Local.stack中当前线程下stack下的列表的最后一个值,也就是obj对象,obj对象也就是ctx对象,也就是top对象本身, 通过getattr(top,name),找到top对象中封装的session,回到self._get_current_object()[key] = value的动作,self._get_current_object()获取了session对象,session对象同时也是一个特殊的字典,S = self._get_current_object()的话,就相当于S[key] = value 此时session赋值的动作,成功将key和value加入到__storage__当前线程的stack下的列表中的obj对象的session字典中(赋值session由于在__storage__的列表中,内存地址一致,所以修改后__storage__字典也会改变)。
4. 自定义LocalStack
定义几个LocalStack就有几个,Local对象
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import get_ident class Local(object): def __init__(self): #self.storage = {} object.__setattr__(self,'__storage__',{}) def __setattr__(self, key, value): ident = get_ident() try: self.__storage__[ident][key] = value except KeyError as e: self.__storage__[ident] = {key:value} def __getattr__(self, item): ident = get_ident() try: return self.__storage__[ident][item] except KeyError as e: return None class LocalStack(object): def __init__(self): self._local = Local() def push(self,data): stack = self._local.stack if not stack: self._local.stack = [] self._local.stack.append(data) stack1 = LocalStack() stack2 = LocalStack() stack1.push('x1') stack2.push('x2') """ Local1 = { "asdfasfffdf": { stack:['x1']} } Local2 = { "dadafasdfed": { stack:['x2']} } """
5. Local和LocalStack对象联合使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import get_ident class Local(object): def __init__(self): #self.storage = {} object.__setattr__(self,'__storage__',{}) def __setattr__(self, key, value): ident = get_ident() try: self.__storage__[ident][key] = value except KeyError as e: self.__storage__[ident] = {key:value} def __getattr__(self, item): ident = get_ident() try: return self.__storage__[ident][item] except KeyError as e: return None class LocalStack(object): def __init__(self): self._local = Local() def push(self,data): stack = self._local.stack if not stack: self._local.stack = [] self._local.stack.append(data) stack1 = LocalStack() stack2 = LocalStack() class RequestContext(object): def __init__(self): self.request = '666' self.session = '999' ctx = RequestContext() class Flask(object): pass app = Flask() stack1.push(ctx) stack2.push(app) ident = get_ident() print(stack1._local.__storage__[ident]) # {11256: {'stack': [<__main__.RequestContext object at 0x000000000221EBE0>]}} print(stack1._local.__storage__[ident]['stack'][0].request) # 666 print(stack1._local.__storage__[ident]['stack'][0].session) # 999 print(stack2._local.__storage__[ident]['stack']) # {11256: {'stack': [<__main__.RequestContext object at 0x000000000221EBE0>]}} """ Local1 = { "asdfasfffdf": { stack:['x1']} } local2 = { 'adfasdfasdf': { stack: ["app" ] } } """
6. 上下文管理图
7. “全局变量g”
g可以在单个请求周期内帮助传值,无需在周期结尾操作删除,自动在Local字典中自动删除。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,g app = Flask(__name__) @app.before_request def func(): permission_code_list = ['add','update','get'] g.permission_code_list = permission_code_list @app.route('/') def index(): print(g.permission_code_list) # ['add', 'update', 'get'] return 'hello world!' if __name__ == '__main__': app.run()
十五、基于轮询和长轮询实现数据实时推送
1. 轮询
依赖于js中周期性的轮询取数据,实现页面的数据更新
app.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,render_template app = Flask(__name__) UUUU = { '1':{'name':'兴普','count':1}, '2':{'name':'天乐','count':1}, '3':{'name':'峰峰','count':1}, } @app.route('/index') def index(): return render_template('index.html',user_list = UUUU) if __name__ == '__main__': app.run()
templates/index.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>沙河最帅的男人</title> </head> <body> <ul> {% for k,v in user_list.items() %} <li>{{k}}: {{v.name}} {{v.count}} </li> {% endfor %} </ul> <script> function reload() { window.location.reload() } setInterval(reload,2000) </script> </body> </html>
2. 长轮询
app.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,render_template,request,session,redirect,jsonify from uuid import uuid4 from queue import Queue,Empty import json app = Flask(__name__) app.secret_key = "asdfasdfasdf" UUUU = { '1':{'name':'兴普','count':1}, '2':{'name':'天乐','count':1}, '3':{'name':'峰峰','count':1}, } # 为每个登录用户保存 # dfasdfadsfasdfadf: Queue() USER_QUEUE_DICT = { } @app.before_request def check_login(): if request.path == '/login': return None user_info = session.get('user_info') if not user_info: return redirect('/login') @app.route('/login',methods=['GET','POST']) def login(): if request.method == "GET": return render_template('login.html') else: user = request.form.get('user') nid = str(uuid4()) USER_QUEUE_DICT[nid] = Queue() session['user_info'] = {'nid':nid, 'user':user } return redirect('/index') @app.route('/index') def index(): return render_template('index.html',user_list = UUUU) @app.route('/query') def query(): """每个用户查询最新投票信息""" ret = {'status':True,'data':None} current_user_nid = session['user_info']['nid'] queue = USER_QUEUE_DICT[current_user_nid] try: # {'uid':1, 'count':6} ret['data'] = queue.get(timeout=10) except Empty as e: ret['status'] = False # return jsonify(ret) return json.dumps(ret) @app.route('/vote') def vote(): """ 用户投票 :return: """ uid = request.args.get('uid') old = UUUU[uid]['count'] new = old + 1 UUUU[uid]['count'] = new for q in USER_QUEUE_DICT.values(): q.put({'uid':uid, 'count':new}) return "投票成功" if __name__ == '__main__': app.run(host='0.0.0.0',threaded=True)
tamplates/login.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post"> <input type="text" name="user"> <input type="submit" value="提交"> </form> </body> </html>
tamplates/index.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>沙河最帅的男人</title> </head> <body> <ul> {% for k,v in user_list.items() %} <li style="cursor: pointer;" ondblclick="doVote('{{k}}')" id="user_{{k}}">{{k}}: {{v.name}} <span>{{v.count}}</span> </li> {% endfor %} </ul> <!--<script src="/static/jquery-1.12.4.js"></script>--> <script src="{{ url_for('static',filename='jquery-1.12.4.js') }}"></script> <script> $(function () { get_data(); }) /* 查询最新信息 */ function get_data() { $.ajax({ url: '/query', type:'GET', dataType:'json', success:function (arg) { if(arg.status){ var liId = "#user_" + arg.data.uid; $(liId).find('span').text(arg.data.count); } get_data(); } }) } /* 投票 */ function doVote(uid) { $.ajax({ url:'/vote', // /vote?uid=1 type:'GET', data:{ uid:uid }, success:function (arg) { } }) } </script> </body> </html>
十六、flask-应用websocket
1. HTTP协议和Websocket协议的对比
- Http协议:
- 数据格式:“GET / http1.1\r\nhost:www.baidu.com\r\n”
- 请求获取相应之后断开连接
- Websocket协议:
- 数据格式:“host:www.baidu.com\r\n”
- 连接:创建连接之后不断开
- 校验:
1. 客户端发起连接请求,服务端回复连接成功。
2. 之后客户端发送一些非数据的关于客户端的相关信息(sec-websocket-key),服务端收到信息,校验信息是否符合websocket协议。之后服务端将客户端发送的信息按照某种规则加密,之后发送给客户端。
3. 客户端收到信息包含自己之前发送给服务端的信息,客户端浏览器将该信息也加密和收到的服务端加密信息对比,如果一致,则加密规则一致,判断服务端支持websocket协议,此过程可以理解为握手,之后客户端可以开始传递数据。
- 加密:开始传输数据,传输过程中客户端和服务端发送数据要进行加密,收到数据要解密。加密规则公开,所以传输过程并不安全。
2. websoket原理剖析
- http与websocket请求字符串对比
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sock.bind(('127.0.0.1',8002)) sock.listen(5) conn,address = sock.accept() # http = conn.recv(8096) # print(http) # b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8002\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\nAccept-Encoding: gzip, # deflate\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\n\r\n' websocket = conn.recv(8096) print(websocket) # b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8002\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\nAccept-Encoding: gzip, # deflate\r\nSec-WebSocket-Version: 13\r\n # Origin: http://localhost:63342\r\n # Sec-WebSocket-Extensions: permessage-deflate\r\n # Sec-WebSocket-Key: YFzzHBxHfx+nFpKQ0HAcGw==\r\n # 客户端发送的字符串,服务端将该字符串加密,返回给客户端,客户端将发送的字符串也加密和返回的服务端发送的加密字符串做对比,一致表示都遵循了websocket加密的规范,之后才可以收发数据 # Connection: keep-alive, Upgrade\r\n # Pragma: no-cache\r\n # Cache-Control: no-cache\r\n # Upgrade: websocket\r\n\r\n'
- 代码剖析websocket原理
s1.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import hashlib import base64 def get_headers(data): """ 将请求头格式化成字典 :param data: :return: """ header_dict = {} data = str(data, encoding='utf-8') header, body = data.split('\r\n\r\n', 1) header_list = header.split('\r\n') for i in range(0, len(header_list)): if i == 0: if len(header_list[i].split(' ')) == 3: header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ') else: k, v = header_list[i].split(':', 1) header_dict[k] = v.strip() return header_dict def send_msg(conn, msg_bytes): """ WebSocket服务端向客户端发送消息 :param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept() :param msg_bytes: 向客户端发送的字节 :return: """ import struct token = b"\x81" length = len(msg_bytes) if length < 126: token += struct.pack("B", length) elif length <= 0xFFFF: token += struct.pack("!BH", 126, length) else: token += struct.pack("!BQ", 127, length) msg = token + msg_bytes conn.send(msg) return True sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', 8002)) sock.listen(5) # 等待用户连接 conn, address = sock.accept() # WebSocket发来的连接 # 1. 获取握手数据 data = conn.recv(1024) headers = get_headers(data) # 获取到了握手过程中要加密的字符串 # print(headers['Sec-WebSocket-Key']) # mDilfZV7lAfmna5w2ZhueQ== # 2. 对握手信息进行加密: magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' # 魔法字符串,固定的字符串 value = headers['Sec-WebSocket-Key'] + magic_string # 拼接字符串和魔法字符串 # value.encode('utf-8')变成字节,之后同过hashlib.sha1加密,之后通过base64加密 ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest()) # print(ac) # b'sKJ3kS7Y6P2g/X3v+tVJZwli6cc=' # 3. 返回握手信息 response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade:websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \ "WebSocket-Location: ws://127.0.0.1:8002\r\n\r\n" response_str = response_tpl % (ac.decode('utf-8'),) conn.sendall(bytes(response_str, encoding='utf-8')) # 之后,才能进行首发数据。 while True: # 对数据进行解密 info = conn.recv(8096) # websocket的客户端和服务端收发数据过程需要封包和解包,JS类库已经实现了该过程,服务端的socke需要手动实现 # 原理为在数据包中第二个字节(先不考虑第一个字节)可以用索引一拿到,该字节的值可以判断真正数据在数据包中从哪里开始 # 有三种情况 # 第二个字节小于等于125,第二个字节之后(还有4字节mask_key,下面说)的位置就为数据 # 第二个字节等于126,第二个字节之后(同上)再加2个字节的位置开始为数据 # 第三个字节等于127,第二个字节之后(同上)再加8个字节的位置开始为数据 # mask key在判断数据包第二个字节的值判断的加的字节之后的位置,mask key和真正的数据做位运算,封解真正的数据。 # 封包过程数据字节中的每一位和mask key做位运算(异或),解包也许如此。 payload_len = info[1] & 127 # 和127做2进制与运算 得出125,126,127等的值做判断,二进制与运算中0得出0,1得出还是原来的值,127做运算第一位是0,所以后7位得出的值小于等于与127 if payload_len == 126: extend_payload_len = info[2:4] mask = info[4:8] decoded = info[8:] elif payload_len == 127: extend_payload_len = info[2:10] mask = info[10:14] decoded = info[14:] else: extend_payload_len = None mask = info[2:6] decoded = info[6:] bytes_list = bytearray() for i in range(len(decoded)): chunk = decoded[i] ^ mask[i % 4] bytes_list.append(chunk) msg = str(bytes_list, encoding='utf-8') print(msg) rep = msg + 'sb' send_msg(conn,bytes(rep,encoding='utf-8')) # 直接发送数据 # send_msg(conn, bytes('alex', encoding='utf-8')) # send_msg(conn, bytes('SB', encoding='utf-8')) # info = conn.recv(8096) # print(info)
h1.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>websocket协议</h1> <script type="text/javascript"> // 向 127.0.0.1:8002 发送一个WebSocket请求 var socket = new WebSocket("ws://127.0.0.1:8002"); socket.onmessage = function (event) { /* 服务器端向客户端发送数据时,自动执行 */ var response = event.data; console.log(response); }; </script> </body> </html>
3. 在flask中使用websocket-实时消息推送
- flask默认使用wsgi支持http协议,如需使用websocket需要安装gevent-websocket模块,http,websocket协议都可以支持
Django应用:channel
Tornado应用:自己有
pip3 install gevent-websocket
- 投票,websocket示例
app.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,request,render_template,redirect,session import uuid from geventwebsocket.handler import WebSocketHandler from gevent.pywsgi import WSGIServer import json app = Flask(__name__) app.secret_key = ';lkjnfdidiclsjek' GENTIEMAN = { '1':{'name':'钢弹','count':0}, '2':{'name':'铁锤','count':0}, '3':{'name':'闫帅','count':0}, } WEBSOCKET_DICT = {} @app.before_request def before_reuqest(): if request.path == '/login': return None user_info = session.get('user_info') if user_info: return None return redirect('login') @app.route('/login',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: uid = str(uuid.uuid4()) session['user_info'] = {'id':uid,'name':request.form.get('user')} return redirect('/index') @app.route('/index') def index(): return render_template('index.html',users=GENTIEMAN) @app.route('/message') def message(): # 1. 判断是否为Websocket请求,http不包含wsgi.websocket ws = request.environ.get('wsgi.websocket') if not ws: return 'use websocket' # 此处连接成功 current_user_id = session['user_info']['id'] WEBSOCKET_DICT[current_user_id] = ws while True: # 2. 等待用户发送消息,并接受 message = ws.receive() # 投票对应的ID # 关闭 mesaage = None if not message: del WEBSOCKET_DICT[current_user_id] break # 3. 获取用户要投票的ID,并+1 old = GENTIEMAN[message]['count'] new = old + 1 GENTIEMAN[message]['count'] = new data = {'user_id':message,'count':new,'type':'vote'} # 给所有客户端推送消息 for conn in WEBSOCKET_DICT.values(): conn.send(json.dumps(data)) return '完毕' @app.route('/notify') def notify(): data = {'data':'订单生成','type':'alert'} for conn in WEBSOCKET_DICT.values(): conn.send(json.dumps(data)) return '完毕' if __name__ == '__main__': # 如果是http请求走app使用原有的wsgi处理,如果是websocket请求走WebSocketHandler处理 http_server = WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler) http_server.serve_forever()
index.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<body> <h1>投票系统:参与投票的人</h1> <ul> {% for k,v in users.items() %} <li id="user_{{k}}" ondblclick="vote('{{k}}')">{{v.name}} <span>{{v.count}}</span> </li> {% endfor %} </ul> <script src="{{ url_for('static',filename='jquery-3.3.1.min.js')}}"></script> <script> var socket = new WebSocket("ws://127.0.0.1:5000/message"); socket.onmessage = function (event) { /* 服务器端向客户端发送数据时,自动执行 */ var response = JSON.parse(event.data); // {'user':1,'count':new} if(response.type == 'vote'){ var nid = '#user_' + response.user_id; $(nid).find('span').text(response.count) }else{ alert(response.data); } }; /* 我要给某人投票 */ function vote(id) { socket.send(id); } </script> </body>
login.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<body> <form method="post"> <input type="text" name="user"> <input type="submit" value="提交"> </form> </body>
4. websocket针对前端的响应
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
socket = new WebSocket("ws://127.0.0.1:5000/message");
socket.onopen = function () {
/* 与服务器端连接成功后,自动执行 */
};
socket.onmessage = function (event) {
/* 服务器端向客户端发送数据时,自动执行 */
var response = JSON.parse(event.data); // {'user':1,'count':new}
var nid = '#user_' + response.user;
$(nid).find('span').text(response.count)
};
socket.onclose = function (event) {
/* 服务器端主动断开连接时,自动执行 */
};
socket.send('向后台发送数据')
socket.close() # 前端关闭,后端接收空
十七、flask-数据库操作
1. 使用模块
pymysql 支持 python2/3
MySqlDB 支持 python2
2. pymysql的使用
详细使用见下方数据库连接池
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import pymysql # 创建连接 conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='day03db') # 创建游标 # cursor = conn.cursor() # 取到的数据是元祖格式 cursor = conn.cursor(pymysql.cursors.DictCursor) # 取到的数据是字典格式 # 执行SQL,并返回收影响行数 effect_row = cursor.execute("select * from user") result = cursor.fetchall() # 关闭游标 cursor.close() # 关闭连接 conn.close() print(result)
十八、flask-数据库连接池
1. 安装
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
pip3 install DBUtils
2. 数据库连接池的2种模式
- 每个线程创建一个连接,适用于线程少的场景
基于threading.locl,为每个线程创建一个连接,线程即使调用了close方法,也不会关闭,只是把连接重新放到连接池,供自己线程再次使用。当线程终止时,连接自动关闭。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 POOL = PersistentDB( 2 creator=pymysql, # 使用链接数据库的模块 3 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 4 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] 5 ping=0, 6 # 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 7 closeable=False, 8 # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接) 9 threadlocal=None, # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置 10 host='127.0.0.1', 11 port=3306, 12 user='root', 13 password='123', 14 database='pooldb', 15 charset='utf8' 16 ) 17 18 def func(): 19 conn = POOL.connection(shareable=False) 20 cursor = conn.cursor() 21 cursor.execute('select * from tb1') 22 result = cursor.fetchall() 23 cursor.close() 24 conn.close() 25 26 func()
- 创建一定个数的连接,所有的线程都来这个连接池来获取连接。(推荐)
PS:由于pymysql、MySQLdb等threadsafety值为1,所以该模式连接池中的线程会被所有线程共享。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import pymysql import threading from DBUtils.PooledDB import PooledDB, SharedDBConnection POOL = PooledDB( creator=pymysql, # 使用链接数据库的模块 maxconnections=20, # 连接池允许的最大连接数,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='192.168.11.38', port=3306, user='root', passwd='apNXgF6RDitFtDQx', db='m2day03db', charset='utf8' ) def connect(): # 创建连接 # conn = pymysql.connect(host='192.168.11.38', port=3306, user='root', passwd='apNXgF6RDitFtDQx', db='m2day03db') conn = POOL.connection() # 创建游标 cursor = conn.cursor(pymysql.cursors.DictCursor) return conn,cursor def close(conn,cursor): # 关闭游标 cursor.close() # 关闭连接 conn.close() def fetch_one(sql,args): conn,cursor = connect() # 执行SQL,并返回收影响行数 effect_row = cursor.execute(sql,args) result = cursor.fetchone() close(conn,cursor) return result def fetch_all(sql,args): conn, cursor = connect() # 执行SQL,并返回收影响行数 cursor.execute(sql,args) result = cursor.fetchall() close(conn, cursor) return result def insert(sql,args): """ 创建数据 :param sql: 含有占位符的SQL :return: """ conn, cursor = connect() # 执行SQL,并返回收影响行数 effect_row = cursor.execute(sql,args) conn.commit() close(conn, cursor) def delete(sql,args): """ 创建数据 :param sql: 含有占位符的SQL :return: """ conn, cursor = connect() # 执行SQL,并返回收影响行数 effect_row = cursor.execute(sql,args) conn.commit() close(conn, cursor) return effect_row def update(sql,args): conn, cursor = connect() # 执行SQL,并返回收影响行数 effect_row = cursor.execute(sql, args) conn.commit() close(conn, cursor) return effect_row
十九、flask-SQLAlchemy
1. SQLAlchemy介绍
SQLAlchemy是一个第三方的orm模块,通过对象和类对数据库操作
SQLAlchemy自己不能连接和操作数据库,可以借助pymysql,mysqldb等的工作连接数据库
SQLAlchemy自带数据库连接池,可以利用自带的连接池功能,也可以用其他的组件作为连接池功能支持,可自定义
2. SQLAlchemy连接方式
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from sqlalchemy import create_engine import threading # 基于Session执行原生SQL (*******) engine = create_engine( "mysql+pymysql://root:123@127.0.0.1:3306/day03db", # 定义连接数据库类型,连接的模块 max_overflow=0, # 超过连接池大小做多创建的连接 pool_size=5, # 连接池大小 pool_timeout=30, # 池中没有线程最多等待的时间,否则报错 pool_recycle=1 # 多久之后对线程池中的线程进行一次连接的回收(重置) ) def task(arg): conn = engine.raw_connection() cursor = conn.cursor() # cursor.execute('select * from user',[]) cursor.execute('select * from user') result = cursor.fetchall() cursor.close() conn.close() print(result) for i in range(20): t = threading.Thread(target=task,args=(i,)) t.start()
3. SQLAlchemyORM操作
- 表操作
- 创建单表
SQLAlchemy只能创建表或者删除表不能修改表,如需修改表需要安装第三方组件,或者操作表在修改orm中的对应类即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import datetime from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index Base = declarative_base() class Users(Base): __tablename__ = 'users' # index 加索引 # nullable 是否允许为空 # unique 唯一索引 id = Column(Integer, primary_key=True) name = Column(String(32), index=True, nullable=False) # email = Column(String(32),unique=True) # ctime = Column(DateTime,default=datetime.datetime.now) # ps:datetime.datetime.now不加括号,如果加括号在第一次运行的时候就等于一个值了,之后就一直是这个值 if __name__ == '__main__': engine = create_engine( "mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8", max_overflow=0, # 超过连接池大小外最多创建的连接 pool_size=5, # 连接池大小 pool_timeout=30, # 池中没有线程最多等待的时间,否则报错 pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置) ) Base.metadata.create_all(engine) # 创建表 # Base.metadata.drop_all(engine) # 删除表
- 增删改查
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from model import Users from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker engine = create_engine( "mysql+pymysql://root:123@127.0.0.1:3306/day03db", # 定义连接数据库类型,连接的模块 max_overflow=0, # 超过连接池大小做多创建的连接 pool_size=5, # 连接池大小 pool_timeout=30, # 池中没有线程最多等待的时间,否则报错 pool_recycle=1 # 多久之后对线程池中的线程进行一次连接的回收(重置) ) # 操作数据库时,需要创建一个session类 SessionClass = sessionmaker(bind=engine) session = SessionClass() # ORM操作 # 添加操作 # obj = Users(name='alex') # session.add(obj) # 查询操作 # result = session.query(Users).all() # result = session.query(Users).filter(Users.id > 1) # print(result) # [<model.Users object at 0x0000000003E78C88>, <model.Users object at 0x0000000003E787B8>] # for row in result: # print(row.id,row.name) # 1 alex # 2 eric # 删除操作 # session.query(Users).filter(Users.id > 1).delete() # 修改操作 # session.query(Users).filter(Users.id > 0).update({'name':'xingpu'}) # 提交事务 session.commit() # 关闭session session.close()
- 一对多创建
s3.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import datetime from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index from sqlalchemy.orm import relationship Base = declarative_base() # ##################### 单表示例 ######################### class Users(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String(32), index=True) age = Column(Integer, default=18) email = Column(String(32), unique=True) ctime = Column(DateTime, default=datetime.datetime.now) extra = Column(Text, nullable=True) __table_args__ = ( # 联合唯一索引 # UniqueConstraint('name', 'email', name='uix_id_name'), # 联合索引 # Index('ix_id_name', 'name', 'email'), ) class Hosts(Base): __tablename__ = 'hosts' id = Column(Integer, primary_key=True) name = Column(String(32), index=True) ctime = Column(DateTime, default=datetime.datetime.now) # ##################### 一对多示例 ######################### class PersonType(Base): __tablename__ = 'persontype' id = Column(Integer, primary_key=True) caption = Column(String(50), default='普通用户') class Person(Base): __tablename__ = 'person' nid = Column(Integer, primary_key=True) name = Column(String(32), index=True, nullable=True) type_id = Column(Integer, ForeignKey("persontype.id")) # 与生成表结构无关,仅用于查询方便,数据库不会增加字段,实现连表功能 ptype = relationship("PersonType", backref='pers') def init_db(): """ 根据类创建数据库表 :return: """ engine = create_engine( "mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8", max_overflow=0, # 超过连接池大小外最多创建的连接 pool_size=5, # 连接池大小 pool_timeout=30, # 池中没有线程最多等待的时间,否则报错 pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置) ) Base.metadata.create_all(engine) def drop_db(): """ 根据类删除数据库表 :return: """ engine = create_engine( "mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8", max_overflow=0, # 超过连接池大小外最多创建的连接 pool_size=5, # 连接池大小 pool_timeout=30, # 池中没有线程最多等待的时间,否则报错 pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置) ) Base.metadata.drop_all(engine) if __name__ == '__main__': drop_db() init_db()
s4.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from s3 import Person,PersonType engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8", max_overflow=0, pool_size=5) SessionClass = sessionmaker(bind=engine) # 每次执行数据库操作时,都需要创建一个session session = SessionClass() # ############# 批量添加 ############# # session.add_all([ # PersonType(caption="普通用户"), # PersonType(caption="超级用户"), # PersonType(caption="SVIP用户"), # ]) # session.add_all([ # Person(name="李杰",type_id=1), # Person(name="征集文",type_id=1), # Person(name="兴普",type_id=2), # ]) # 使用对象操作,默认不能使用需要在相应类中加入ptype = relationship("PersonType", backref='pers') # obj = session.query(PersonType).filter(PersonType.id==2).first() # 取到对象 # session.add_all([ # Person(name="李杰1",ptype=obj), # Person(name="征集文1",ptype=obj), # Person(name="兴普1",ptype=obj), # ]) # 首先创建一个PersonType,之后创建Person.name = '宏伟',同时两张表都插入数据 # obj = Person(name='宏伟',ptype=PersonType(caption="VVIP")) # session.add(obj) session.commit() # 关闭session session.close()
- 一对多常见操作语句
s3.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/usr/bin/env python # -*- coding:utf-8 -*- import datetime from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index from sqlalchemy.orm import relationship Base = declarative_base() # ##################### 单表示例 ######################### class Users(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String(32), index=True) age = Column(Integer, default=18) email = Column(String(32), unique=True) ctime = Column(DateTime, default=datetime.datetime.now) extra = Column(Text, nullable=True) __table_args__ = ( # 联合唯一索引 # UniqueConstraint('name', 'email', name='uix_id_name'), # 联合索引 # Index('ix_id_name', 'name', 'email'), ) class Hosts(Base): __tablename__ = 'hosts' id = Column(Integer, primary_key=True) name = Column(String(32), index=True) ctime = Column(DateTime, default=datetime.datetime.now) # ##################### 一对多示例 ######################### class PersonType(Base): __tablename__ = 'persontype' id = Column(Integer, primary_key=True) caption = Column(String(50), default='普通用户') class Person(Base): __tablename__ = 'person' nid = Column(Integer, primary_key=True) name = Column(String(32), index=True, nullable=True) type_id = Column(Integer, ForeignKey("persontype.id")) # 与生成表结构无关,仅用于查询方便(反向),数据库不会增加字段,实现连表功能 ptype = relationship("PersonType", backref='pers') def init_db(): """ 根据类创建数据库表 :return: """ engine = create_engine( "mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8", max_overflow=0, # 超过连接池大小外最多创建的连接 pool_size=5, # 连接池大小 pool_timeout=30, # 池中没有线程最多等待的时间,否则报错 pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置) ) Base.metadata.create_all(engine) def drop_db(): """ 根据类删除数据库表 :return: """ engine = create_engine( "mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8", max_overflow=0, # 超过连接池大小外最多创建的连接 pool_size=5, # 连接池大小 pool_timeout=30, # 池中没有线程最多等待的时间,否则报错 pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置) ) Base.metadata.drop_all(engine) if __name__ == '__main__': drop_db() init_db()
s4.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from s3 import Person,PersonType engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8", max_overflow=0, pool_size=5) SessionClass = sessionmaker(bind=engine) # 每次执行数据库操作时,都需要创建一个session session = SessionClass() # ############# 批量添加 ############# # session.add_all([ # PersonType(caption="普通用户"), # PersonType(caption="超级用户"), # PersonType(caption="SVIP用户"), # ]) # session.add_all([ # Person(name="李杰",type_id=1), # Person(name="征集文",type_id=1), # Person(name="兴普",type_id=2), # ]) # 使用对象操作,默认不能使用需要在相应类中加入ptype = relationship("PersonType", backref='pers') # obj = session.query(PersonType).filter(PersonType.id==2).first() # 取到对象 # session.add_all([ # Person(name="李杰1",ptype=obj), # Person(name="征集文1",ptype=obj), # Person(name="兴普1",ptype=obj), # ]) # 首先创建一个PersonType,之后创建Person.name = '宏伟',同时两张表都插入数据 # obj = Person(name='宏伟',ptype=PersonType(caption="VVIP")) # session.add(obj) # session.commit() # ############# 查询 ############# # 查询所有用户,并打印姓名 # obj_list = session.query(Person).all() # for obj in obj_list: # print(obj.name) # 查询所有用户,并打印姓名+用户类型名称,需要跨表查询,也需要使用relationship # obj_list = session.query(Person).all() # for obj in obj_list: # print(obj.name,obj.type_id,obj.ptype.caption) # 查询所有用户,并打印姓名+用户类型名称 # label('nnn') 列加别名 # 默认关联ForeignKey关,也可以指定Person.type_id==PersonType.id # join默认为inner join,如需left join 使用isouter=True,没有right join,只能调换表位置 # result = session.query(Person.nid,Person.name.label('nnn'),PersonType.caption).join(PersonType,Person.type_id==PersonType.id,isouter=True).all() # for row in result: # print(row.nnn,row.caption) # 找到属于某个用户类型的所有人。 # pers来源于类中的ptype = relationship("PersonType", backref='pers') 反向关联字段 # obj = session.query(PersonType).filter(PersonType.id == 2).first() # print(obj.id) # print(obj.caption) # # print(obj.pers[0].name) # for row in obj.pers: # print(row.name) # 不加.all(),.first就会像是为sql语句 # result = session.query(Person).filter(Person.type_id == 2) # result1 = session.query(Person).filter(Person.type_id == 2).all() # print(result) # print(result1) # 关闭session session.close()
- 多对多表创建
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/usr/bin/env python # -*- coding:utf-8 -*- import datetime from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index from sqlalchemy.orm import relationship Base = declarative_base() # ##################### 多对多示例 ######################### class Server2Depart(Base): __tablename__ = 'server2depart' id = Column(Integer, primary_key=True, autoincrement=True) server_id = Column(Integer, ForeignKey('server.id')) depart_id = Column(Integer, ForeignKey('depart.id')) __table_args__ = ( # 联合唯一索引 UniqueConstraint('server_id', 'depart_id', name='uix_depart_server'), ) class Depart(Base): __tablename__ = 'depart' id = Column(Integer, primary_key=True) name = Column(String(64), unique=True, nullable=False) # 与生成表结构无关,仅用于查询方便 servers = relationship('Server', secondary='server2depart', backref='departs') class Server(Base): __tablename__ = 'server' id = Column(Integer, primary_key=True, autoincrement=True) hostname = Column(String(64), unique=True, nullable=False) if __name__ == '__main__': engine = create_engine( "mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8", max_overflow=0, # 超过连接池大小外最多创建的连接 pool_size=5, # 连接池大小 pool_timeout=30, # 池中没有线程最多等待的时间,否则报错 pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置) ) Base.metadata.create_all(engine)
- 多对多常见操作语句
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from s6 import Depart, Server, Server2Depart engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8", max_overflow=0, pool_size=5) SessionClass = sessionmaker(bind=engine) # 每次执行数据库操作时,都需要创建一个session session = SessionClass() # ############# 添加 ############# # session.add_all([ # Depart(name='IT'), # Depart(name='测试'), # Depart(name='运维'), # Server(hostname='c1.com'), # Server(hostname='c2.com'), # Server(hostname='c3.com'), # Server(hostname='c4.com'), # Server(hostname='c5.com'), # # ]) # session.add_all([ # Server2Depart(server_id=1,depart_id=1), # Server2Depart(server_id=1,depart_id=2), # Server2Depart(server_id=1,depart_id=3), # Server2Depart(server_id=2,depart_id=1), # Server2Depart(server_id=2,depart_id=2), # ]) # 问题:创建一个部门,为补充新增3个主机 # d1 = Depart(name='运营') # d1.servers = [Server(hostname='c6.com'),Server(hostname='c7.com'),Server(hostname='c8.com'),] # session.add(d1) # # # session.commit() # ############# 查询 ############# # 运营部都有哪些机器? # result = session.query(Server2Depart.id, Depart.name, Server.hostname).join(Depart, Server2Depart.depart_id == Depart.id, # isouter=True).join(Server, # Server2Depart.server_id == Server.id, # isouter=True).filter(Depart.name=='运营').all() # for item in result: # print(item) # obj = session.query(Depart).filter(Depart.name=='运营').first() # for s in obj.servers: # print(obj.name,s.id,s.hostname) # c1.com 都给哪些部门使用了? obj = session.query(Server).filter(Server.hostname=='c1.com').first() for d in obj.departs: print(d.id,d.name) # 关闭session session.close()
- 常见语句补充
补充一
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# ################ 修改 ################ """ session.query(Users).filter(Users.id > 0).update({"name" : "099"}) session.query(Users).filter(Users.id > 0).update({Users.name: Users.name + "099"}, synchronize_session=False) session.query(Users).filter(Users.id > 0).update({"age": Users.age + 1}, synchronize_session="evaluate") session.commit() """ # ################ 查询 ################ """ r1 = session.query(Users).all() r2 = session.query(Users.name.label('xx'), Users.age).all() r3 = session.query(Users).filter(Users.name == "alex").all() r4 = session.query(Users).filter_by(name='alex').all() r5 = session.query(Users).filter_by(name='alex').first() r6 = session.query(Users).filter(text("id<:value and name=:name")).params(value=224, name='fred').order_by(Users.id).all() r7 = session.query(Users).from_statement(text("SELECT * FROM users where name=:name")).params(name='ed').all()
补充二
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 条件 ret = session.query(Users).filter_by(name='alex').all() ret = session.query(Users).filter(Users.id > 1, Users.name == 'eric').all() ret = session.query(Users).filter(Users.id.between(1, 3), Users.name == 'eric').all() ret = session.query(Users).filter(Users.id.in_([1,3,4])).all() ret = session.query(Users).filter(~Users.id.in_([1,3,4])).all() ret = session.query(Users).filter(Users.id.in_(session.query(Users.id).filter_by(name='eric'))).all() from sqlalchemy import and_, or_ ret = session.query(Users).filter(and_(Users.id > 3, Users.name == 'eric')).all() ret = session.query(Users).filter(or_(Users.id < 2, Users.name == 'eric')).all() ret = session.query(Users).filter( or_( Users.id < 2, and_(Users.name == 'eric', Users.id > 3), Users.extra != "" )).all() # 通配符 ret = session.query(Users).filter(Users.name.like('e%')).all() ret = session.query(Users).filter(~Users.name.like('e%')).all() # 限制 ret = session.query(Users)[1:2] # 排序 ret = session.query(Users).order_by(Users.name.desc()).all() ret = session.query(Users).order_by(Users.name.desc(), Users.id.asc()).all() # 分组 from sqlalchemy.sql import func ret = session.query(Users).group_by(Users.extra).all() ret = session.query( func.max(Users.id), func.sum(Users.id), func.min(Users.id)).group_by(Users.name).all() ret = session.query( func.max(Users.id), func.sum(Users.id), func.min(Users.id)).group_by(Users.name).having(func.min(Users.id) >2).all() # 连表 ret = session.query(Users, Favor).filter(Users.id == Favor.nid).all() ret = session.query(Person).join(Favor).all() ret = session.query(Person).join(Favor, isouter=True).all() # 组合 q1 = session.query(Users.name).filter(Users.id > 2) q2 = session.query(Favor.caption).filter(Favor.nid < 2) ret = q1.union(q2).all() q1 = session.query(Users.name).filter(Users.id > 2) q2 = session.query(Favor.caption).filter(Favor.nid < 2) ret = q1.union_all(q2).all()
- 原生SQL
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/usr/bin/env python # -*- coding:utf-8 -*- import time import threading from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine from sqlalchemy.sql import text from sqlalchemy.engine.result import ResultProxy from db import Users, Hosts engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/s6", max_overflow=0, pool_size=5) Session = sessionmaker(bind=engine) session = Session() # 查询 # cursor = session.execute('select * from users') # result = cursor.fetchall() # 添加 cursor = session.execute('insert into users(name) values(:value)',params={"value":'wupeiqi'}) session.commit() print(cursor.lastrowid) session.close()
4. scoped_session
可以帮助session自动支持多线程,给每个线程来放进一个有标识的位置做区分和隔离。
使用scoped_session就可以及支持单线程也支持多线程
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session import threading from s6 import Depart, Server, Server2Depart engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8", max_overflow=0, pool_size=5) SessionClass = sessionmaker(bind=engine) session = scoped_session(SessionClass) def task(): cursor = session.execute('select sleep(3)') result = cursor.fetchall() session.remove() # 线程执行结束不用close,remove()就可以,连接归还连接池 print(result) for i in range(20): t = threading.Thread(target=task) t.start()
5. 手动创建ession
每一个线程单独创建一个session,如果公用一个session,第一个线程来结束的时候就会把session close,影响其他线程。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:apNXgF6RDitFtDQx@192.168.11.38:3306/m2day03db?charset=utf8", max_overflow=0, pool_size=5) SessionClass = sessionmaker(bind=engine) # 每次执行数据库操作时,都需要创建一个session def task(): # 从数据库连接池获取一个连接。 session = SessionClass() # 查询 cursor = session.execute('select sleep(3)') result = cursor.fetchall() session.close() print(result) for i in range(20): t = threading.Thread(target=task) t.start()
6. flask-sqlalchemy应用
把flask中操作sqlalchemy的行为,封装到flask-sqlalchemy中
1. 目录结构
2. 示例内容
- CMDB-flask-sqlalchemy\manage.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from CMDB import create_app app = create_app() if __name__ == '__main__': app.run()
- CMDB-flask-sqlalchemy\settings.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Base(object): SECRET_KEY = 'LKADFSLJK;ADFSLJK' SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8" SQLALCHEMY_POOL_SIZE = 5 # 最终对象的修改并发送信号,做数据库操作的一个信号 SQLALCHEMY_TRACK_MODIFICATIONS = False class Dev(Base): pass
- CMDB-flask-sqlalchemy\.py
创建表,或删除表
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from CMDB import db from manage import app # with app.app_context():执行app_context中的__enter__方法,内部将db封装到上下文中,一遍之后方便使用 with app.app_context(): #省略了Base.metadata.create_all(engine),创建表的过程 db.create_all() # db.drop_all()
- CMDB-flask-sqlalchemy\CMDB\__init__.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask from flask_sqlalchemy import SQLAlchemy # 1. 实例化flask_sqlalchemy对象 db = SQLAlchemy() from .models import * from .views import account from .views import home def create_app(): app = Flask(__name__) app.config.from_object('settings.Dev') app.register_blueprint(account.ac) app.register_blueprint(home.hm) # 2. 初始化,自动化的读取配置文件中的,db地址,端口,密码等信息 db.init_app(app) return app
- CMDB-flask-sqlalchemy\CMDB\models.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import datetime from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index from CMDB import db # flask-sqlalchemy中表的类直接继承db.Model即可,declarative_base已经都放入db对象中 # Base = declarative_base() class UserInfo(db.Model): __tablename__ = 'userinfo' id = Column(Integer, primary_key=True) name = Column(String(32), index=True, nullable=False)
- CMDB-flask-sqlalchemy\CMDB\views\account.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Blueprint from CMDB import db from CMDB import models ac = Blueprint('ac',__name__) @ac.route('/login') def login(): # 帮助创建一个scoped_session result = db.session.query(models.UserInfo).all() for row in result: print(row.id,row.name) db.session.remove() return 'login' @ac.route('/logout') def logout(): return 'logout'
- CMDB-flask-sqlalchemy\CMDB\views\home.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Blueprint hm = Blueprint('hm',__name__) @hm.route('/index') def index(): return 'index'
- CMDB-flask-sqlalchemy\settings.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Base(object): SECRET_KEY = 'LKADFSLJK;ADFSLJK' SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:123@127.0.0.1:3306/day03db?charset=utf8" SQLALCHEMY_POOL_SIZE = 5 # 最终对象的修改并发送信号,做数据库操作的一个信号 SQLALCHEMY_TRACK_MODIFICATIONS = False class Dev(Base): pass