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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!