flask
flask简介
Python 目前主要流行的web框架:flask、Django、Tornado
1.falsk框架是一款基于WSGI的轻量级的Web框架,flask犹如耳详的"麻雀虽小,五脏俱全",因此flask具有简单可扩展性的特点.
2.Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
3.“微”(micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。
默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用
flask框架的优势
- 基于WSGI应用程序,必须使用显式实例化
- 使用Werkzeug路由系统进行自动排序路由
- 使用Jinja2模板引擎,快速方便使用模板
- 使用线程局部变量,实现快速访问weby应用程序
- 支持异步等待和ASCI(async-first)
- 衔接单元测试,开发人员快速进行测试检查
- 自带开发服务器,无需借助其他第三方网络服务
flask快速入门
安装
pip3 install falsk
简单入门
from flask import Flask,request
# 实例化产生一个Flask对象
app = Flask(__name__)
# 装饰器加括号与不加括号有区别
# 装饰器 路由匹配
@app.route('/')
def index():
# 打印请求路径,request为全局
print(request.path)
return 'hello hello'
if __name__ == '__main__':
app.run() # 最终调用了run_simple(),并传端口,self
# 请求一来,执行app(),就是执行flask类的__call__方法
flask三板斧
HttpResponse、render、redirect
# author:xionghuan
from flask import Flask,render_template,redirect
app = Flask(__name__)
@app.route('/')
def index():
return redirect('http://www.baidu.com')
'''
return 'hello hello' ====> HttpResponse
return render_template('s1.html') ====> render()
return redirect('http://www.baidu.com') ====> redirect
注意:新建templates文件夹要和py文件在同级,负责会报错 “jinja2.exceptions.TemplateNotFound”
'''
if __name__ == '__main__':
app.run()
登录小案例
main.py
Copyfrom flask import Flask,render_template,request,redirect,session,url_for
app = Flask(__name__)
app.debug = True
app.secret_key = 'sdfsdfsdfsdf'
USERS = {
1:{'name':'张三','age':18,'gender':'男','text':"道路千万条"},
2:{'name':'李四','age':28,'gender':'男','text':"安全第一条"},
3:{'name':'王五','age':18,'gender':'女','text':"行车不规范"},
}
@app.route('/detail/<int:nid>',methods=['GET'])
def detail(nid):
user = session.get('user_info')
if not user:
return redirect('/login')
info = USERS.get(nid)
return render_template('detail.html',info=info)
@app.route('/index',methods=['GET'])
def index():
user = session.get('user_info')
if not user:
# return redirect('/login')
url = url_for('l1')
return redirect(url)
return render_template('index.html',user_dict=USERS)
@app.route('/login',methods=['GET','POST'],endpoint='l1')
def login():
if request.method == "GET":
return render_template('login.html')
else:
# request.query_string
user = request.form.get('user')
pwd = request.form.get('pwd')
if user == 'cxw' and pwd == '123':
session['user_info'] = user
return redirect('http://www.baidu.com')
return render_template('login.html',error='用户名或密码错误')
if __name__ == '__main__':
app.run()
detail.html
Copy<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>详细信息 {{info.name}}</h1>
<div>
{{info.text}}
</div>
</body>
</html>
index.html
Copy<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户列表</h1>
<table>
{% for k,v in user_dict.items() %}
<tr>
<td>{{k}}</td>
<td>{{v.name}}</td>
<td>{{v['name']}}</td>
<td>{{v.get('name')}}</td>
<td><a href="/detail/{{k}}">查看详细</a></td>
</tr>
{% endfor %}
</table>
</body>
</html>
login.html
Copy<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户登录</h1>
<form method="post">
<input type="text" name="user">
<input type="text" name="pwd">
<input type="submit" value="登录">{{error}}
</form>
</body>
</html>
总结:
1 三板斧:
-return 字符串
-return render_template('index.html')
-return redirect('/login')
2 路由写法(路径,支持的请求方式,别名)
@app.route('/login',methods=['GET','POST'],endpoint='l1')
3 模板语言渲染
-同dtl,但是比dtl强大,支持加括号执行,字典支持中括号取值和get取值
4 分组(django中的有名分组)
@app.route('/detail/<int:nid>',methods=['GET'])
def detail(nid):
5 反向解析
url_for('别名')
6 获取前端传递过来的数据
# get 请求
request.query_string
# post请求
user = request.form.get('user')
pwd = request.form.get('pwd')
配置文件
# 方式一:直接通过app对象设置,只能设置这两个,其他不支持
app.secret_key = 'sdfsdfsdfsdf' # 秘钥,django配置文件中的秘钥
pp.debug = False # debug模式,开启了,就会热更新debug模式
# debug模式介绍:
1.flask默认是没有开启debug模式的,开启debug模式有很多好处:第一,可以帮助我们查找代码里面的错误,比如:
# 方式二:直接通过app对象的config(字典)属性设置
app.config['DEBUG']=True # debug模式
print(app.config)
# 方式三:直接使用py文件(指定settings.py文件内写[配置信息])
app.config.from_pyfile("settings.py")
app.config.from_object('settings.TestingConfig')
settings.py
class Config(object):
DEBUG = False
TESTING = False
DATABASE_URI = 'sqlite://:memory:'
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
后期重点是使用第三种方式来配置
# 写法格式:
# app.config.from_object("python类或类的路径")
# 可以直接指定配置文件类路径
# 优点:
1.开发上线测试直接写多个类配置即可
2.方便切换,上线与未上线时的配置文件配置
3.不需要像django一样要重新创建一个配置文件
# 使用
app.config.from_object('settings.DevelopmentConfig')
print(app.config['DATABASE_URI'])
if __name__ == '__main__':
app.run()
内置配置文件
{
'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,
}
路由
典型写法
@app.route('/index/<name>',methods=['GET'],view_func='index',defaults={'name':'lqz'},strict_slashes=True,redirect_to='http://www.baidu.com')
# 参数:
methods : 允许的请求方式
defaults : 视图函数名称
strict_slashes : 严格模式
redirect_to : 访问路由永久重定向
默认转换器
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
常用路由写法
from flask import Flask,request,render_template,redirect,session,url_for
app = Flask(__name__)
app.debug = True # debug模式,开启了,就会热更新
app.secret_key = 'sdfsdfsdfsdf' # 秘钥,django配置文件中的秘钥
@app.route('/index/<string:name>/<int:pk>',methods=['GET'],endpoint='index')
def index(name,pk):
print(name)
return 'hello'
if __name__ == '__main__':
app.run()
正则表达式
可以通过继承werkzeug.routing 的BaseConverter
类从而自己定义一个动态路由过滤器的规则
1 写类,继承BaseConverter
2 注册:app.url_map.converters['regex'] = RegexConverter
3 使用:@app.route('/index/<regex("\d+"):nid>') 正则表达式会当作第二个参数传递到类中
from flask import Flask, views, url_for
from werkzeug.routing import BaseConverter
app = Flask(import_name=__name__)
class RegexConverter(BaseConverter):
"""
自定义URL匹配正则表达式
"""
def __init__(self, map, regex):
super(RegexConverter, self).__init__(map)
self.regex = regex
def to_python(self, value):
"""
路由匹配时,匹配成功后传递给视图函数中参数的值
"""
return int(value)
def to_url(self, value):
"""
使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
"""
val = super(RegexConverter, self).to_url(value)
return val
# 添加到flask中
app.url_map.converters['regex'] = RegexConverter
@app.route('/index/<regex("\d+"):nid>')
def index(nid):
print(url_for('index', nid='888'))
return 'Index'
if __name__ == '__main__':
app.run()
路由的本质
当执行router的时候,router调用了decorate被饰器的视图函数,触发endpoint,调用了add_url_rule
路由系统的本质,就是 app.add_url_rule(路径, 别名, 函数内存地址, **options)
endpoint:如果不填,默认就是函数名(加装饰器时要注意)与django路由类似django与flask路由:flask路由基于装饰器,本质是基于:add_url_rule endpoint不能重名
add_url_rule 源码中,endpoint如果为空,endpoint = _endpoint_from_view_func(view_func),最终取view_func.__name__(函数名)
add_url_rule的参数
rule, URL规则
view_func, 视图函数名称
defaults = 默认为None, 默认值, 定义{'k':'v'}数据,那么视图函数也需要定义参数k接收当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'} 为函数提供参数
endpoint = None, 名称,用于反向生成URL,即: url_for('名称')
methods = None, 允许的请求方式,如:["GET", "POST"]
严格模式
# 对URL最后的 / 符号是否严格要求
strict_slashes = None
# 设置True代表严格模式,访问必须带/,设置flase不需要带/自定匹配
@app.route('/index', strict_slashes=False)
重定向
# 重定向到指定地址
redirect_to = None, # 默认None
redirect_to = 'http://www.baidu.com' # 方法该路由永远重定向该指定地址
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')
CBV
from flask.views import View
使用
from flask import Flask,request,render_template,redirect,session,url_for
from flask.views import View,MethodView
app = Flask(__name__)
app.debug = True # debug模式,开启了,就会热更新
app.secret_key = 'sdfsdfsdfsdf' # 秘钥,django配置文件中的秘钥
class IndexView(MethodView): # cbv必须要继承MethodView,如果继承view需要重新写dispatch
def get(self):
url=url_for('aaa') # 反向解析
print(url)
return '我是get'
def post(self):
return '我是post'
app.add_url_rule('/index',view_func=IndexView.as_view(name='aaa')) # 必须要指定别名
if __name__ == '__main__':
app.run(port=8888)
总结
1.flask中CBV源码发现与Django相同.
2.CBV源码:
1.执行as_view--返回dispatch,调用dispatch函数,通过反射,最终执行了/get或post请求.
2.flask中CBV源码与Django中相同
cbv用的比较少
继承views.MethodView,只需要写get,post,delete方法
如果加装饰器decorators = [auth, ]
允许的请求方法methods = ['GET']
模板
from flask import Flask,request,render_template,redirect,session,url_for,Markup
from flask.views import View,MethodView
app = Flask(__name__)
app.debug = True # debug模式,开启了,就会热更新
app.secret_key = 'sdfsdfsdfsdf' # 秘钥,django配置文件中的秘钥
def test(a,b):
return a+b
class IndexView(MethodView): # 继承MethodView
def get(self):
url=url_for('aaa') # 反向解析
print(url)
# html页面显示标签
# a=Markup('<a href="http://www.baidu.com">点我看美女</a>')
a='<a href="http://www.baidu.com">点我看美女</a>'
return render_template('test.html',name='lqz',test=test,a=a)
def post(self):
return '我是post'
if __name__ == '__main__':
app.run(port=8888)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{{name}}</h1>
<hr>
{{test(4,5)}} // 调用函数并传参
<hr>
{{a}}
{{a|safe}} // 增加safe过滤器,显示a标签
</body>
</html>
jinja语法提示
如何让PyCharm在编写html模板时显示jinja语法提示
- 启动PyCharm后, 找到
File
-->Settings
- 在打开的窗口中找到
Languages &Frameworks
--> Style Sheets--->Template Languages
- 选择
HTML
, 上面下拉框中默认为None
, 点击选择Jinja2
, 应用,保存就可以了
总结:
与django一样,for,if,
模板语言支持函数加括号执行
模板有没有处理xss攻击,在页面显示标签,内部怎么实现的?
-1 模板层 要渲染的字符串|safe
-2 后端:Markup('<input type="text">')
Markup等价django的mark_safe ,
extends,include一模一样
导包
pipreqs,通过对项目目录扫描,将项目使用的模块进行统计,生成依赖清单即requirements.txt文件。
安装:pip3 install pipreqs
# 快速导出requestment.txt
pipreqs ./ --encoding=utf-8
request请求
请求相关数据
request.method # 提交的方法
print(request.args.get('name')) # get请求提交的数据---GET
print(request.form) # post请求提交数据----POST
print(request.values) # get和post的汇总
print(request.query_string) # b'name=lqz&age=19'
request.args # get请求提及的数据
request.form # post请求提交的数据
request.values # post和get提交的数据总和
request.cookies # 客户端所带的cookie
request.headers # 请求头
request.path # 不带域名,请求路径
request.full_path # 不带域名,带参数的请求路径
request.url # 带域名带参数的请求路径
request.base_url # 带域名请求路径
request.url_root # 域名
request.host_url # 域名
request.host # 127.0.0.1:500
request.files # 获取文件对象
obj = request.files['the_file_name'] # .files获取文件对象
obj.save('/var/www/uploads/' + secure_filename(f.filename))
response响应
from flask import Flask,render_template,jsonify,make_response
# 将render_template('index.html')生成对象,写到make_response对象内
response = make_response(render_template('index.html'))
response = make_response('hello')
# response是flask.wrappers.Response类型
response.delete_cookie('session') # 删除cookie
response.set_cookie('name', 'lqz') # 生成cookie
response.headers['X-Something'] = 'A value' # 往响应头放东西
return response
# return "内容"
if __name__ == '__main__':
app.run(port=8888)
return "字符串"
return render_template('html模板路径',**{})
return redirect('/index.html')
return jsonify({'k1':'v1'}) # 返回json格式
session
session、cookie、token简介
cookie:存放在客户端的键值对
session:存放在客户端的键值对
token:存放在客户端,通过算法来校验
在使用session之前必须设置secret_key
app.secret_key="asdas" #值可以随便写
session的使用
-增:session['name']=lqz
-查:session.get('name')
-删:session.pop('name')
message
闪现:假设在a页面出现错误,会跳转到b页面显示a页面的报错信息
存放在session中
message是一个基于Session实现的用于保存数据的集合。
其特点是:一次性。
使用方式
from flask import Flask,flash,get_flashed_messages,request,redirect
app = Flask(__name__)
app.secret_key = 'asdfasdf'
@app.route('/user', methods=['GET', "POST"])
def login():
try:
a=[1,2,3]
print(a[9])
except Exception as e:
print(e)
# 闪现普通使用(放在某个位置)
flash(str(e))
# 高级使用(闪现分类)
flash('超时错误', category="x1")
flash('xx错误', category="x3")
return response
@app.route('/error', methods=['GET', "POST"])
def error():
# 1.取出闪现(错误信息)
errors=get_flashed_messages()
# 2.取出闪现(高级使用分类)
errors=get_flashed_messages(category_filter=['x1'])
return render_template('error.html',errors=errors)
if __name__ == '__main__':
app.run()
请求扩展
before_request
请求来了会先走before_request
类比django中间件中的process_request,写多个执行顺序是从上往下
#基于它做用户登录认证
@app.before_request
def process_request(*args,**kwargs):
if request.path == '/login':
return None
user = session.get('user_info')
if user:
return None
return redirect('/login')
after_request
从下往上,执行完了,响应走的时候执行
类比django中间件中的process_response,每一个请求之后绑定一个函数,如果请求没有异常
@app.after_request
def process_response1(response):
print('process_response1 走了')
return response
before_first_request
# 只会执行一次,程序启动以后,第一个访问的会触发,以后再也不会了
第一次请求时,跟浏览器无关
@app.before_first_request
def first():
pass
teardown_request
不管当次请求是否出异常,都会执行,出了异常,e就是异常对象,debug=False模式下,必须在上线模式下,False
每一个请求之后绑定一个函数,即使遇到了异常
作用:日志记录
@app.teardown_request
def ter(e):
pass
errorhandler
只要是404错误,都会走它
路径不存在时404,服务器内部错误500
@app.errorhandler(404)
def error_404(arg):
return "404错误了"
template_global
自定义标签
@app.template_global()
def sb(a1, a2):
return a1 + a2
#{{sb(1,2)}}
template_filter
自定义过滤器
copy@app.template_filter()
def db(a1, a2, a3):
return a1 + a2 + a3
#{{ 1|db(2,3)}}
总结
1 重点掌握before_request和after_request,
2 注意有多个的情况,执行顺序
3 before_request请求拦截后(也就是有return值),response所有都执行
flask中间件(了解)
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello World!'
# 模拟中间件
class Md(object):
def __init__(self,old_wsgi_app):
self.old_wsgi_app = old_wsgi_app
def __call__(self, environ, start_response):
print('开始之前')
ret = self.old_wsgi_app(environ, start_response)
print('结束之后')
return ret
if __name__ == '__main__':
#1我们发现当执行app.run方法的时候,最终执行run_simple,最后执行app(),也就是在执行app.__call__方法
#2 在__call__里面,执行的是self.wsgi_app().那我们希望在执行他本身的wsgi之前做点事情。
#3 所以我们先用Md类中__init__,保存之前的wsgi,然后我们用将app.wsgi转化成Md的对象。
#4 那执行新的的app.wsgi_app,就是执行Md的__call__方法。
#把原来的wsgi_app替换为自定义的,
app.wsgi_app = Md(app.wsgi_app)
app.run()
猴子补丁
什么是猴子补丁?
只是一个概念,不属于任何包和模块
利用了python一切皆对象的理念,在程序运行过程中,动态修改方法
有点偷天换日的味道,也像鸭子类型
class Monkey():
def play(self):
print('我是一只大猴子')
class Dog():
def play(self):
print('我是一只狗')
m=Monkey()
m.play()
m.play=Dog().play
m.play()
作用:
很多用到import json,后来发现ujson性能更高,如果觉得把每个文件的import json改成import ujson as json成本较高,或者说想测试一下ujson替换是否符合预期, 只需要在入口加上:
只需要在程序入口
import json
import ujson
def monkey_patch_json():
json.__name__ = 'ujson'
json.dumps = ujson.dumps
json.loads = ujson.loads
monkey_patch_json()
aa=json.dumps({'name':'lqz','age':19})
print(aa)
协程:单线程下实现并发
from gevent import monkey;monkey.patch_all()
import gevent
import time
def eat():
print('eat food 1')
time.sleep(2)
print('eat food 2')
def play():
print('play 1')
time.sleep(1)
print('play 2')
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
gevent.joinall([g1,g2])
print('主')
blueprint
对程序进行目录结构划分 ,没有蓝图之前,都是使用单文件
不使用蓝图,自己分文件
-templates
-views
-__init__.py
-user.py
-order.py
-app.py
每个文件的代码
app.py
from views import app
if __name__ == '__main__':
app.run()
init.py
from flask import Flask,request
app = Flask(__name__)
#不导入这个不行
from . import account
from . import order
from . import user
user.py
from . import app
@app.route('/user')
def user():
return 'user'
order.py
from . import app
@app.route('/order')
def order():
return 'order'
使用蓝图
1.实例化得到一个蓝图对象(可以指定直接的静态文件和模板路径)
2.在app中注册蓝图(可以指定前缀)
app.register_blueprint(user.us)
3.以后再写路由装饰器,使用蓝图对象的.route
@account.route('/login.html', methods=['GET', "POST"])
使用蓝图之中小型系统
-flask_pro
-flask_test
-__init__.py
-static
-templates
-views
-order.py
-user.py
-manage.py
_init.py
from flask import Flask
app=Flask(__name__)
from flask_test.views import user
from flask_test.views import order
app.register_blueprint(user.us)
app.register_blueprint(order.ord)
manage.py
from flask_test import app
if __name__ == '__main__':
app.run(port=8008)
user.py
from flask import Blueprint
us=Blueprint('user',__name__)
@us.route('/login')
def login():
return 'login'
order.py
from flask import Blueprint
ord=Blueprint('order',__name__)
@ord.route('/test')
def test():
return 'order test'
使用蓝图之大型系统
项目名
pro_flask文件夹
__init__.py
web
__init__.py
static
views.py
templates
admin
templates
static
views.py
__init__.py
run.py
threading.local
flask框架有一个全局的request,谁来了就是谁。
多线程下修改数据
local:多个线程访问一个变量的时候,可以保证数据不乱。
实现方式:使用id号来实现。{'id号':{a:1},'id号':{a:2}}
重写了threading.lcal 可以来解释全局的request,线程不同则使用不同的request。
不用local,多线程写同一个数据,会导致错乱
from threading import Thread
import time
xh = -1
def task(arg):
global lqz
xh = arg
time.sleep(2)
print(lqz)
for i in range(10):
t = Thread(target=task,args=(i,))
t.start()
使用local对象,多线程写同一数据不会错乱,因为每个线程操作自己的数据
from threading import Thread
from threading import local
import time
from threading import get_ident
特殊的对象
xh = local()
# {'线程id':{value:1},'线程id':{value:2}....}
def task(arg):
xh.value = arg
time.sleep(2)
print(xh.value)
for i in range(10):
t = Thread(target=task,args=(i,))
t.start()
自己写一个类似local的东西,函数版本
from threading import get_ident,Thread
import time
storage = {}
#{'线程id':{value:1},'线程id':{value:2}....}
def set(k,v):
ident = get_ident()
if ident in storage:
storage[ident][k] = v
else:
storage[ident] = {k:v}
def get(k):
ident = get_ident()
return storage[ident][k]
def task(arg):
set('val',arg)
v = get('val')
print(v)
for i in range(10):
t = Thread(target=task,args=(i,))
t.start()
自己写一个类似local的东西,面向对象版本
from threading import get_ident,Thread
import time
class Local(object):
storage = {}
def set(self, k, v):
ident = get_ident()
if ident in Local.storage:
Local.storage[ident][k] = v
else:
Local.storage[ident] = {k: v}
def get(self, k):
ident = get_ident()
return Local.storage[ident][k]
obj = Local()
def task(arg):
obj.set('val',arg)
time.sleep(1)
v = obj.get('val')
print(v)
for i in range(10):
t = Thread(target=task,args=(i,))
t.start()
g对象
1.专门用来存储用户信息的g对象,g的全称的为global,g对象是全局的
2.g对象在一次请求中的所有的代码的地方,都是可以使用的,g对象在当次请求中一直有效
g对象和session的区别:
session对象是可以跨request的,只要session还未失效,不同的request的请求会获取到同一个session,但是g对象不是,g对象不需要管过期时间,请求一次就g对象就改变了一次,或者重新赋值了一次
使用
from flask import Flask,g,request,session
app = Flask(__name__)
@app.before_request
def first():
session['name']='dlrb'
request.form='ppp'
g.name='xh'
@app.after_request
def after(response):
print('11111',g.name)
return response
@app.route('/')
def hello_world():
print('00000',g.name)
return 'Hello World!'
if __name__ == '__main__':
app.run()
flask-session
将默认保存的签名cookie中的值 保存到 redis/memcached/file/Mongodb/SQLAlchemy
换言之
替换flask内置的session,支持存到redis,存到数据库
安装:
pip3 install flask-session
使用方式一:
from flask import Flask,g,request,session
from flask_session import RedisSessionInterface
app = Flask(__name__)
app.debug=True # 开启debug,没上线为True,方便查询错误
app.secret_key='asdfasdfasdf' # 密钥
# 方式一
from redis import Redis
conn=Redis(host='127.0.0.1',port=6379)
# 使用第三方查询RedisSessionInterface进行将session存入redis
app.session_interface=RedisSessionInterface(redis=conn,key_prefix='flask_session')
# redis : redis地址,端口(不填,默认本地)
# key_prefix : 前缀
@app.route('/')
def hello_world():
session['name']='lqz'
return 'Hello World!'
if __name__ == '__main__':
app.run()
使用方式二:
from flask_session import Session
from redis import Redis
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_KEY_PREFIX']='flask_session'
app.config['SESSION_REDIS'] = Redis(host='127.0.0.1',port='6379')
Session(app) # 将app传入session内
@app.route('/')
def hello_world():
session['name']='lqz'
return 'Hello World!'
if __name__ == '__main__':
app.run()
设置session的过期时间
#源码expires = self.get_expiration_time(app, session)
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),#这个配置文件控制
设置cookie时,如何设定关闭浏览器则cookie失效
app.session_interface=RedisSessionInterface(conn,key_prefix='xxx',permanent=False) # permanent=False 的情况下就会关闭浏览器,cookie失效
数据池
使用pymsql连接数据库
from flask import Flask
import time
import pymysql
app = Flask(__name__)
app.debug=True
app.secret_key='asdfasdfasdf'
@app.route('/')
def hello_world():
# pymysql连接数据库(指定数据库信息)
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='1', database='luffy')
cursor = conn.cursor() # 获得游标对象
cursor.execute('select * from luffy_order') # 查询luffy_order表
time.sleep(1)
print(cursor.fetchall()) # 获取所有
return 'Hello World!'
if __name__ == '__main__':
app.run()
出现的问题:
1.如果使用全局连接对象,会导致数据错乱
2.如果在视图函数中创建数据库连接对象,会导致连接数过多
安装
pip install DBUtils
使用
from dbutils.pooled_db import PooledDB
import pymysql
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='aaa',
charset='utf8')
# 导入进程
from threading import Thread
def task():
# 去池中获取连接
conn = POOL.connection()
# 获取游标
cursor = conn.cursor()
cursor.execute('select * from mytest') # 查询mytest表
print(cursor.fetchall()) # 获取所有
for i in range(100): # 循环100个进程
t=Thread(target=task) # 进程执行
t.start()
# mysql可以看到当前有多少个连接数
wtforms
安装
pip3 install wtforms
使用方式一:
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
app = Flask(__name__, template_folder='templates')
app.debug = True
class LoginForm(Form):
# 字段(内部包含正则表达式)
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired(message='用户名不能为空.'),
validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
],
widget=widgets.TextInput(), # 页面上显示的插件
render_kw={'class': 'form-control'}
)
# 字段(内部包含正则表达式)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空.'),
validators.Length(min=8, message='用户名长度必须大于%(min)d'),
validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}",
message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
form = LoginForm()
return render_template('login.html', form=form)
else:
form = LoginForm(formdata=request.form)
if form.validate():
print('用户提交数据通过格式验证,提交的值为:', form.data)
else:
print(form.errors)
return render_template('login.html', form=form)
if __name__ == '__main__':
app.run()
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<form method="post">
<p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>
<p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
<input type="submit" value="提交">
</form>
</body>
</html>
使用方式二:
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
app = Flask(__name__, template_folder='templates')
app.debug = True
class RegisterForm(Form):
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired()
],
widget=widgets.TextInput(),
render_kw={'class': 'form-control'},
default='alex'
)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空.')
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
pwd_confirm = simple.PasswordField(
label='重复密码',
validators=[
validators.DataRequired(message='重复密码不能为空.'),
validators.EqualTo('pwd', message="两次密码输入不一致")
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
email = html5.EmailField(
label='邮箱',
validators=[
validators.DataRequired(message='邮箱不能为空.'),
validators.Email(message='邮箱格式错误')
],
widget=widgets.TextInput(input_type='email'),
render_kw={'class': 'form-control'}
)
gender = core.RadioField(
label='性别',
choices=(
(1, '男'),
(2, '女'),
),
coerce=int # “1” “2”
)
city = core.SelectField(
label='城市',
choices=(
('bj', '北京'),
('sh', '上海'),
)
)
hobby = core.SelectMultipleField(
label='爱好',
choices=(
(1, '篮球'),
(2, '足球'),
),
coerce=int
)
favor = core.SelectMultipleField(
label='喜好',
choices=(
(1, '篮球'),
(2, '足球'),
),
widget=widgets.ListWidget(prefix_label=False),
option_widget=widgets.CheckboxInput(),
coerce=int,
default=[1, 2]
)
def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))
def validate_pwd_confirm(self, field):
"""
自定义pwd_confirm字段规则,例:与pwd字段是否一致
:param field:
:return:
"""
# 最开始初始化时,self.data中已经有所有的值
if field.data != self.data['pwd']:
# raise validators.ValidationError("密码不一致") # 继续后续验证
raise validators.StopValidation("密码不一致") # 不再继续后续验证
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'GET':
form = RegisterForm(data={'gender': 2,'hobby':[1,]}) # initial
return render_template('register.html', form=form)
else:
form = RegisterForm(formdata=request.form)
if form.validate():
print('用户提交数据通过格式验证,提交的值为:', form.data)
else:
print(form.errors)
return render_template('register.html', form=form)
if __name__ == '__main__':
app.run()
# login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户注册</h1>
<form method="post" novalidate style="padding:0 50px">
{% for field in form %}
<p>{{field.label}}: {{field}} {{field.errors[0] }}</p>
{% endfor %}
<input type="submit" value="提交">
</form>
</body>
</html>
signal
signal即信号, Flask框架中的信号基于blinker,其主要就是让开发者可是在flask执行过程中定制一些用户行为 。
同步操作
信号一般用来记录日志
安装
pip3 install blinker
内置信号
request_started = _signals.signal('request-started') # 请求到来前执行
request_finished = _signals.signal('request-finished') # 请求结束后执行
before_render_template = _signals.signal('before-render-template') # 模板渲染前执行
template_rendered = _signals.signal('template-rendered') # 模板渲染后执行
got_request_exception = _signals.signal('got-request-exception') # 请求执行出现异常时执行
request_tearing_down = _signals.signal('request-tearing-down') # 请求执行完毕后自动执行(无论成功与否)
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 应用上下文执行完毕后自动执行(无论成功与否)
appcontext_pushed = _signals.signal('appcontext-pushed') # 应用上下文push时执行
appcontext_popped = _signals.signal('appcontext-popped') # 应用上下文pop时执行
message_flashed = _signals.signal('message-flashed') # 调用flask在其中添加数据时,自动触发
使用方式
1、写一个函数
2、跟内置信号绑定
3、以后只要触发内置信号,函数就会执行
from flask import Flask,signals,render_template
from flask.signals import _signals
app = Flask(__name__)
# 往信号中注册函数
def func(*args,**kwargs):
print('触发型号',args,kwargs)
# signals信号.内置信号(请求到来前执行).connect(执行函数)
signals.request_started.connect(func)
# 给模板渲染前编写信号
def template_before(*args,**kwargs):
print(args) # app对象
print(kwargs)
print('模板开始渲染了')
# signals信号.内置信号(模板渲染前执行).connect(执行函数)
signals.before_render_template.connect(template_before)
自定义信号
使用方式:
# 1、定义一个信号
xxxxx = _signals.signal('xxxxx')
# 2、定义一个函数
def func3(*args,**kwargs):
import time
time.sleep(1)
print('触发信号',args,kwargs)
# 3、信号跟函数绑定
xxxxx.connect(func3)
# 4、触发信号
xxxxx.send(1,k='2')
# 触发信号时,不支持在一个语句中传多个位置参数,可以使用字典
案例:
# 自定义信号
# 自定制信号 = signals.signal('自定制信号名称')
before_view = _signals.signal('before_view')
# 写函数
def test(*args,**kwargs):
print('我执行了')
print(args)
print(kwargs)
# 绑定给信号
# before_view信号.connect(执行函数)
before_view.connect(test)
@app.route('/index',methods=['GET',"POST"])
def index1():
# 触发信号
# before_view信号.send发送(关键字,关键字)
before_view.send(name='lqz',age=19)
print('视图')
return render_template('index.html',a='lqz')
if __name__ == '__main__':
app.run(port=8080)
app.__call__
多app应用(了解)
已经弃用
多个app实例(弃用)
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
from flask import Flask, current_app
app1 = Flask('app01')
app2 = Flask('app02')
@app1.route('/index')
def index():
return "app01"
@app2.route('/index2')
def index2():
return "app2"
# http://www.oldboyedu.com/index
# http://www.oldboyedu.com/sec/index2
dm = DispatcherMiddleware(app1, {
'/sec': app2,
})
if __name__ == "__main__":
run_simple('localhost', 5000, dm)
# 请求来了,会执行dm()--->__call__
flask-script
flask-script(制定命令): 模拟出类似django的启动方式:python manage.py runserver
安装
pip install flask-script
使用情况较多
使用方式一:
from flask import Flask
from flask_script import Manager
app = Flask(__name__)
manager=Manager(app)
if __name__ == '__main__':
manager.run()
使用方式二:自定制命令
@manager.command
def custom(arg):
print(arg)
@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')
def cmd(name, url):
print(name, url
数据库
SQLAlchemy是一个基于Python实现的ORM框架。该框架建立在 DB API之上,使用关系对象映射进行数据库操作,简言之便是:将类和对象转换成SQL,然后使用数据API执行SQL并获取执行结果。
安装
pip install SQLAlchemy
SQLAlchemy本身无法操作数据库,其必须依赖pymsql等第三方插件
原生sql
import time
import threading
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.engine.base import Engine
# 1、生成一个engine对象
engine = create_engine(
"mysql+pymysql://root:123@127.0.0.1:3306/flask?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 2、创建连接(执行原生sql)
conn = engine.raw_connection()
# 3、获取游标对象
cursor = conn.cursor()
# 4、具体操作
cursor.execute('select * from boy')
res=cursor.fetchall()
print(res)
orm使用
注意事项
创建库需要手动创建库
sqlachemy支持修改字段吗?不支持
models.py
创建一个个类:继承于写字段
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 User(Base):
# 数据库表名称(固定写法),如果不写,默认以类名小写作为表的名字
__tablename__ = 'users'
# id 主键
id = Column(Integer, primary_key=True)
# mysql中主键自动建索引:聚簇索引
# 其他建建的索引叫:辅助索引
name = Column(String(32), index=True, nullable=False) # name列,索引,不可为空
# email = Column(String(32), unique=True) # 唯一
# datetime.datetime.now不能加括号,加了括号,以后永远是当前时间
# ctime = Column(DateTime, default=datetime.datetime.now) # default默认值
# extra = Column(Text, nullable=True)
#类似于djagno的 Meta
# __table_args__ = (
# UniqueConstraint('id', 'name', name='uix_id_name'), #联合唯一
# Index('ix_id_name', 'name', 'email'), #索引
# )
# 创建表
def create_table():
# 创建engine对象
engine = create_engine(
"mysql+pymysql://root:123@127.0.0.1:3306/aaa?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 通过engine对象创建表
Base.metadata.create_all(engine)
# 删除表
def drop_table():
# 创建engine对象
engine = create_engine(
"mysql+pymysql://root:123@127.0.0.1:3306/aaa?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 通过engine对象删除所有表
Base.metadata.drop_all(engine)
if __name__ == '__main__':
# create_table()
drop_table()
线程安全
原因:所有的线程都用一个session,就会有问题
#基于scoped_session实现线程安全
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from models import Users # pycharm报错,不会影响代码正常运行
from sqlalchemy.orm import scoped_session
# 1 制作engine
engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/aaa", max_overflow=0, pool_size=5)
# 2 制造一个 session 类(会话)
Session = sessionmaker(bind=engine) # 得到一个类
# 3 得到一个session对象(线程安全的session)
#现在的session已经不是session对象了
session = scoped_session(Session) # 这个就是线程安全的session
# session=Session() # 原来用这个session
# 4 创建一个对象
obj1 = User(name="2008")
# 5 把对象通过add放入
session.add(obj1)
# session.aaa()
# 6 提交
session.commit()
session.close() # 并没有真正的关闭连接,而是放回数据池中了
scoped_session实现原理:
类不继承Session类,但是有该类的所有方法(通过反射,一个个放进去)
scoped_session.add------->instrument(name)--->do函数内存地址
session.add()--->do()
scoped_session.close----->instrument(name)--->do函数内存地址
orm的增删查改
models.py新建表
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): # Base基类(相当于Django中的models.MODELS)
__tablename__ = 'users' # 数据库表名称
id = Column(Integer, primary_key=True) # id 主键
name = Column(String(32), index=True, nullable=False) # name列,索引,不可为空
# email = Column(String(32), unique=True)
# datetime.datetime.now不能加括号,加了括号,以后永远是当前时间
# ctime = Column(DateTime, default=datetime.datetime.now)
# extra = Column(Text, nullable=True)
# __table_args__ = (
# # id和name (联合唯一名称:uix_id_name)
# UniqueConstraint('id', 'name', name='uix_id_name'),
# # name和email是联合索引 索引名称(ix_id_name)
# Index('ix_id_name', 'name', 'email'), # 索引
# )
# 一对多关系
class Hobby(Base): # 表模型
__tablename__ = 'hobby'
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)
# hobby指的是tablename而不是类名,uselist=False
hobby_id = Column(Integer, ForeignKey("hobby.id")) # 外键
# 跟数据库无关,不会新增字段,只用于快速链表操作
# 类名,backref用于反向查询
# hobby = relationship('Hobby', backref='pers')
# 多对多
# 这个表需要手动建立
class Boy2Girl(Base):
__tablename__ = 'boy2girl'
id = Column(Integer,autoincrement=True,primary_key=True) # autoincrement=True自增,默认为True
girl_id = Column(Integer,ForeignKey('girl.id'))
boy_id = Column(Integer,ForeignKey('boy.id'))
class Boy(Base):
__tablename__ = 'boy'
id = Column(Integer,primary_key=True,autoincrement=True)
hostname = Column(String(64),unique=True,nullable=False)
class Girl(Base):
__tablename__ = 'girl'
id = Column(Integer,primary_key=True)
hostname = Column(String(64),unique=True,nullable=False)
# 创建表
def create_table():
# 创建engine对象
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/aaa?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 通过engine对象创建表
Base.metadata.create_all(engine)
# # 删除表
def drop_table():
# 创建engine对象
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/aaa?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# # 通过engine对象删除所有表
Base.metadata.drop_all(engine)
if __name__ == '__main__':
create_table()
# drop_table()
UseModels.py
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from models import Users,Person,Hobby # pycharm报错,不会影响代码正常运行
from sqlalchemy.orm import scoped_session
engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3306/aaa", max_overflow=0, pool_size=5)
Session = sessionmaker(bind=engine)
# session = scoped_session(Session)
session=Session()
# 新增多个对象,可以增相同的,也可以是不同的
obj1 = Users(name="qqq")
obj2 = Users(name='ppp')
obj3 = Users(name='ggg')
# session.add_all([Person(name='xh'),Hobby()])
# 删除,删除之前要先查
# res=session.query(Users).filter_by(name='2008').delete()
# res=session.query(Users).filter(User.id>=2).delete
# print(res) # 打印的是数字,表示影响的行数
# 修改
# res=session.query(Users).filter_by(id=1).update({'name':'ccc'})
#
# res=session.query(Users).filter_by(id=1).update({Users.name:'ccc'})
#
# 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") # 如果要把它转成数字相加
# 查询
res=session.query(Users).all() # 查所有
# print(type(res))
# res=session.query(User).first() #
# print(res)
#filter传的是表达式,filter_by传的是参数
res=session.query(Users).filter(Users.id==1).all()
res=session.query(Users).filter(Users.id>=1).all()
res=session.query(Users).filter(Users.id<1).all()
# res=session.query(User).filter_by(name='ccc099').all()
session.commit()
session.close()
orm高级操作
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from models import User,Person,Hobby
from sqlalchemy.sql import text
engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/aaa", max_overflow=0, pool_size=5)
Session = sessionmaker(bind=engine)
session=Session()
# 查询名字为lqz的所有user对象
ret = session.query(User).filter_by(name='ccc099').all()
# 表达式,and条件连接
ret = session.query(User).filter(User.id > 1, User.name == 'ggg').all()
# 查找id在1和10之间,并且name=ggg的对象
ret = session.query(User).filter(User.id.between(1, 10), User.name == 'ggg').all()
# in条件(class_,因为这是关键字,不能直接用)
ret = session.query(User).filter(User.id.in_([1,3,4])).all()
# 取反 ~
ret = session.query(User).filter(~User.id.in_([1,3,4])).all()
#二次筛选
# select *
ret = session.query(User).filter(User.id.in_(session.query(User.id).filter_by(name='ggg'))).all()
# select name,id 。。。。
ret = session.query(User.id,User.name).filter(User.id.in_(session.query(User.id).filter_by(name='ggg'))).all()
from sqlalchemy import and_, or_
#or_包裹的都是or条件,and_包裹的都是and条件
#查询id>3并且name=egon的人
ret = session.query(User).filter(and_(User.id > 3, User.name == 'ggg')).all()
# 查询id大于2或者name=ccc099的数据
ret = session.query(User).filter(or_(User.id > 2, User.name == 'ggg')).all()
ret = session.query(User).filter(
or_(
User.id < 2,
and_(User.name == 'ggg', User.id > 3),
User.extra != ""
)).all()
print(ret)
'''
select *from user where id<2 or (name=ggg and id >3) or extra !=''
'''
# 通配符,以e开头,不以e开头
ret = session.query(User).filter(User.name.like('e%')).all()
ret = session.query(User).filter(~User.name.like('e%')).all()
# 限制,用于分页,区间 limit
# 前闭后开区间,1能取到,3取不到
ret = session.query(User)[1:3]
'''
select * from users limit 1,2;
'''
# 排序,根据name降序排列(从大到小)
ret = session.query(User).order_by(User.name.desc()).all()
ret = session.query(User).order_by(User.name.asc()).all()
#第一个条件降序排序后,再按第二个条件升序排
ret = session.query(User).order_by(User.id.asc(),User.name.desc()).all()
ret = session.query(User).order_by(User.name.desc(),User.id.asc()).all()
# 分组
from sqlalchemy.sql import func
ret = session.query(User).group_by(User.name).all()
#分组之后取最大id,id之和,最小id
#sql 分组之后,要查询的字段只能有分组字段和聚合函数
ret = session.query(
func.max(User.id),
func.sum(User.id),
func.min(User.id),
User.name).group_by(User.name).all()
# '''
# select max(id),sum(id),min(id) from user group by name;
# '''
for obj in ret:
print(obj[0],obj[1],obj[2],obj[3])
print(ret)
#haviing筛选
ret = session.query(
func.max(User.id),
func.sum(User.id),
func.min(User.id)).group_by(User.name).having(func.min(User.id) >2).all()
'''
select max(id),sum(id),min(id) from user group by name having min(id)>2;
'''
print(ret)
session.commit()
session.close()
多表操作
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from models import User,Person,Hobby,Boy,Girl,Boy2Girl
from sqlalchemy.sql import text
engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/aaa", max_overflow=0, pool_size=5)
Session = sessionmaker(bind=engine)
session=Session()
1 一对多插入数据
obj=Hobby(caption='足球')
session.add(obj)
p=Person(name='张三',hobby_id=2)
session.add(p)
2 一对多插入数据(默认情况传对象有问题)
注: Person表中要加 hobby = relationship('Hobby', backref='pers')
p=Person(name='李四',hobby=Hobby(caption='美女'))
等同于
p=Person(name='李四2')
p.hobby=Hobby(caption='美女2')
session.add(p)
3 通过反向操作
hb = Hobby(caption='足球')
hb.pers = [Person(name='文飞'), Person(name='博雅')]
session.add(hb)
4 查询(查询:基于连表的查询,基于对象的跨表查询)
1 基于对象的跨表查询(子查询,两次查询)
# 正查
p=session.query(Person).filter_by(name='张三').first()
print(p)
print(p.hobby.caption)
# 反查
h=session.query(Hobby).filter_by(caption='人妖').first()
print(h.pers)
2 基于连表的跨表查(查一次)
默认根据外键连表
isouter=True 左外连,表示Person left join Hobby,没有右连接,反过来即可,不写 inner join
person_list=session.query(Person,Hobby).join(Hobby,isouter=True).all()
print(person_list)
print(person_list)
for row in person_list:
print(row[0].name,row[1].caption)
'''
select * from person left join hobby on person.hobby_id=hobby.id
'''
ret = session.query(Person, Hobby).filter(Person.hobby_id == Hobby.id)
print(ret)
'''
select * from user,hobby where user.id=favor.nid;
'''
join表,默认是inner join
ret = session.query(Person).join(Hobby)
ret = session.query(Hobby).join(Person,isouter=True)
'''
SELECT *
FROM person INNER JOIN hobby ON hobby.id = person.hobby_id
'''
print(ret)
指定连表字段(从来没用过)
ret = session.query(Person).join(Hobby,Person.nid==Hobby.id, isouter=True)
ret = session.query(Person).join(Hobby,Person.hobby_id==Hobby.id,isouter=True).all()
print(ret)
'''
SELECT *
FROM person LEFT OUTER JOIN hobby ON person.nid = hobby.id
'''
print(ret)
5 组合(了解)UNION 操作符用于合并两个或多个 SELECT 语句的结果集
union和union all的区别?
q1 = session.query(User.name).filter(User.id > 2) # 6条数据
q2 = session.query(User.name).filter(User.id < 8) # 2条数据
q1 = session.query(User.id,User.name).filter(User.id > 2) # 6条数据
q2 = session.query(User.id,User.name).filter(User.id < 8) # 2条数据
ret = q1.union_all(q2).all()
ret1 = q1.union(q2).all()
print(ret)
print(ret1)
q1 = session.query(User.name).filter(User.id > 2)
q2 = session.query(Hobby.caption).filter(Hobby.nid < 2)
ret = q1.union_all(q2).all()
多对多操作
session.add_all([
Boy(hostname='哈哈'),
Boy(hostname='呵呵'),
Girl(name='喝喝'),
Girl(name='嘻嘻'),
])
session.add_all([
Boy2Girl(girl_id=1, boy_id=1),
Boy2Girl(girl_id=2, boy_id=1)
])
# 要有girls = relationship('Girl', secondary='boy2girl', backref='boys')
girl = Girl(name='阳')
girl.boys = [Boy(hostname='张'),Boy(hostname='梨')]
session.add(girl)
boy=Boy(hostname='jack')
boy.girls=[Girl(name='haha'),Girl(name='wawa')]
session.add(boy)
session.commit()
基于对象的跨表查
girl=session.query(Girl).filter_by(id=3).first()
print(girl.boys)
基于连表的跨表查询
'''
select girl.name from girl,boy,Boy2Girl where boy.id=Boy2Girl.boy_id and girl.id=Boy2Girl.girl_id where boy.name='jack'
'''
# ret=session.query(Girl.name).filter(Boy.id==Boy2Girl.boy_id,Girl.id==Boy2Girl.girl_id,Boy.hostname=='jack').all()
'''
select girl.name from girl inner join Boy2Girl on girl.id=Boy2Girl.girl_id inner join boy on boy.id=Boy2Girl.boy_id where boy.hostname='蔡徐坤'
'''
ret=session.query(Girl.name).join(Boy2Girl).join(Boy).filter(Boy.hostname=='jack').all()
ret=session.query(Girl.name).join(Boy2Girl).join(Boy).filter_by(hostname='jack').all()
print(ret)
执行原生sql(用的最多的) django中orm如何执行原生sql?
cursor = session.execute('insert into users(name) values(:value)',params={"value":'xxx'})
print(cursor.lastrowid)
session.commit()
session.close()
数据库迁移
flask-sqlalchemy : 让flask更好的集成sqlalchemy
flask_migrate :类似于django的makemigrations和migrate,因为sqlalchemy不支持表修改(删除,增加字段)
1.在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
2.为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。
Flask-Migrate的迁移命令:
python3 manage.py db init 初始化:项目开始只执行一次(生成migrations文件夹)
python3 manage.py db migrate 等同于 makemigartions(models模型层,增删改记录)
python3 manage.py db upgrade 等同于migrate(同步记录到models模型层内)
flask-sqlalchemy的使用
# 1.先导入,实例化得到一个对象
from flask_sqlalchemy import SQLAlchemy
# 2.生成db对象
db = SQLAlchemy()
# 3.所有表模型都继承 db.Model
# 4.在视图函数中查询那个session对象
db.session
Flask-Migrate的使用
安装:pip install flask-migrate
1 from flask_migrate import Migrate,MigrateCommand
2 Migrate(app,db)
3 manager.add_command('db', MigrateCommand)
直接使用
python3 manage.py db init 初始化:只执行一次,创建migrations文件夹
python3 manage.py db migrate 等同于 makemigartions
python3 manage.py db upgrade 等同于migrate