第三十六章:flask
一:Flask
1.pythonweb框架介绍
# python web框架 ,本质都一样
-django:大而全,内置的app多,第三方app也多
-Flask:小而精,没有过多的内置组件,只完成web框架最基本的功能,需要借助于第三方,完成更丰富的功能
-web.py:是一个小巧灵活的Python框架,它简单而且功能强大(国内几乎没有用的)
-------异步web框架------
-fastapi:python的异步web框架,不少公司再用,https://fastapi.tiangolo.com/zh/
-sanic:python的异步web框架,供支持异步高并发请求的 web 服务
-tornado:异步框架,用的比较少了
# 同步框架和异步框架的区别
-djagno是同步框架还是异步框架,djagno 3.x以后支持异步
-同步框架的意思:一个线程只处理一个请求
-异步框架的意思:一个线程可以处理多个请求
-异步框架可以很显著的提高并发量
2.flask介绍
Flask 是一个基于 Python 开发并且依赖 jinja2 模板和 Werkzeug WSGI 服务的一个微型框架
-jinja2 模板语法,django 的 dtl,非常像
-Werkzeug WSGI 符合 wsgi 协议的 web 服务器,django 使用的是 wsgiref
#### wsgirf 写 web
from wsgiref.simple_server import make_server
# mya 就等同于 django
def mya(environ, start_response):
#把environ包装成了request
print(environ)
start_response('200 OK', [('Content-Type', 'text/html')])
if environ.get('PATH_INFO') == '/index':
with open('index.html','rb') as f:
data=f.read()
elif environ.get('PATH_INFO') == '/login':
with open('login.html', 'rb') as f:
data = f.read()
else:
data=b'<h1>Hello, web!</h1>'
return [data] # 做成了response
if __name__ == '__main__':
myserver = make_server('', 8008, mya)
print('监听8010')
myserver.serve_forever()
#### 使用 werkzeug 写 web
from werkzeug.wrappers import Request, Response
@Request.application
def hello(request):
return Response('Hello World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, hello)
3.flask快速使用
# 安装:pip install flask 安装依赖: MarkupSafe, Werkzeug, Jinja2, flask
-1.x 没有本质区别
-2.x 没有本质区别,源码上动了,用起来一样
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
# 注册路由---->装饰器
@app.route('/home/')
def index():
return 'home'
if __name__ == '__main__':
# app.run('127.0.0.1',5000)
app.run()
4.登录小案例
4.1 login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<input type="submit" value="登录"> {{error}}
</form>
</body>
</html>
4.2 home.html
<!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>
4.3 detail.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>名字是:{{user.name}}</p>
<p>年龄是:{{user['age']}}</p>
<p>性别是:{{user.get('gender')}}</p>
<p>{{user.text}}</p>
</body>
</html>
4.4 py文件
from flask import Flask, request, render_template, redirect, session, jsonify
app = Flask(__name__)
# 要使用 session,必须设置秘钥,秘钥是配置信息
app.secret_key = 'asdfasdfa33aef3aefads'
USERS = {
1:{'name':'张三','age':18,'gender':'男','text':"道路千万条"},
2:{'name':'李四','age':28,'gender':'男','text':"安全第一条"},
3:{'name':'王五','age':18,'gender':'女','text':"行车不规范"},
}
# 1 创建 templates 文件夹,写 login.html
@app.route('/login', methods=['GET', 'POST'])
def index():
# 没有 request 对象,使用全局的 request
# get 请求,返回模板
if request.method == 'GET':
return render_template('login.html') # 新手四件套之一:返回模板
else:
# post请求,校验数据
# 取出前端传入的用户名密码,校验
username = request.form.get('username') # 等同于django的的request.POST
password = request.form.get('password')
if username == 'lqz' and password == '123':
# 登录成功,保存登录状态 重定向到跟路径 新手四件套之一:重定向
# 保存到session中,session是全局的
session['name'] = username
return redirect('/')
else:
return render_template('login.html', error='用户名或密码错误') # 注意跟django的render区分,要模板渲染的数据,直接key=value传即可
@app.route('/')
def home():
# 校验,登录成功,才能过来,不登录,重定向到登录页面
if session.get('name'): # 有值说明登录了,没有值说明没有登录
return render_template('home.html',user_dict=USERS)
else:
return redirect('/login')
@app.route('/detail/<int:pk>')
def detail(pk):
if session.get('name'): # 有值说明登录了,没有值说明没有登录
user_detail = USERS.get(pk)
return render_template('detail.html', user=user_detail)
else:
return redirect('/login')
@app.route('/test')
def test():
return jsonify([{'name':'lqz','age':19}])
if __name__ == '__main__':
app.run()
1 注册路由 app.route(路径, methods=[请求方式get,post])
2 新手四件套:
-render_template 渲染模板 跟 django 有区别
-redirect 重定向
-return 字符串 返回字符串
-jsonify 返回 json 格式
3 请求的request对象,是全局的,直接导入使用即可,在不同视图函数中不会混乱
request.method 请求方式
request.form post请求的body体的内容转成了字典
4 session 全局的,直接导入使用即可,一定要指定秘钥 app.secret_key = 'asdfasdfa33aef3aefads'
放值:session['name']='lqz'
取值:session.get('name')
5 模板的渲染
- 兼容 django 的 dtl
- 更强大,可以加括号,字典可以 .get .values() .items()
- {% for %}
6 转换器 @app.route('/detail/<int:pk>')
二:Flask详解
1.配置文件方式
# django 有个 settings
# flask 也有配置问题,但是它的使用方式多种:
# 设置的方式一:(测试用)
# app.debug=True # 调试模式,提示信息更详细,修改代码不需要重启,自动重启
# app.secret_key='dasdfasdfasd' # 秘钥,只能 放 debug 和 secret_key
## 设置方式二: 直接使用 app.config 设置
# app.config['DEBUG']=True
# app.config['SECRET_KEY']='sdfasdfasd'
# print(app.config)
## 方式三:使用py文件(不常用)
# app.config.from_pyfile("settings.py")
# print(app.config)
## 方式四:常用的,使用类的方式
app.config.from_object('settings.DevelopmentConfig')
app.config.from_object('settings.ProductionConfig')
print(app.config)
# 新建 settings.py 文件
class DevelopmentConfig:
debug = True
secret_key = 'dasdfasdfasd'
### 其他:
#通过环境变量配置
# app.config.from_envvar("环境变量名称")
# json
# app.config.from_json("json文件名称")
# JSON文件名称,必须是json格式,因为内部会执行json.loads
# 字典格式---》配置中心
# app.config.from_mapping({'DEBUG': True})
# 内置的配置字段,其他可以写自己的,比如 redis 的连接地址,mysql 的连接地址
-DEBUG
-SECRET_KEY
-SESSION_COOKIE_NAME
-PERMANENT_SESSION_LIFETIME
2.路由系统
2.1 路由本质
# django 中配置路由 在 urls.py 中,写 path,写在 列表中
# flask 是基于装饰器的,大部分都用装饰器来做,少量可以抽取到一个 urls.py 种
# 路由的装饰器源码分析
# 咱们这样写
@app.route('/login')
def index():
pass
#本质是 ---》index=app.route('/login')(index)
# app.route('/login') 的执行结果 decorator 函数
-rule 是路径
-其他参数都给了 options
# 然后 decorator(index) ---> 在执行
# f 是 index
endpoint = options.pop("endpoint", None) # 目前没有 endpoint,是 None
# 核心,本质 --》self 就是实例化得到的 app 对象,flask 对象
# app 对象中有个方法 add_url_rule,这是在添加路由
# 不使用装饰器,自己注册路由
self.add_url_rule(rule, endpoint, f, **options)
return f
def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
def decorator(f: T_route) -> T_route:
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
# 可以不使用装饰器的方式,注册路由
app.add_url_rule('/', endpoint=None, view_func=home, methods=['GET'])
# flask 路由的本质是 app 对象的 add_url_rule 完成路由的注册
2.2 路由参数 add_url_rule
# rule URL规则
# view_func 视图函数名称
# defaults = None 默认值, 当URL中无参数,函数需要参数时,使用 defaults = {'k': 'v'} 为函数提供参数
# endpoint = None 路径的别名,名称,用于反向解析URL,即: url_for('名称')
# methods = None 允许的请求方式,如:["GET", "POST"]
# 对URL最后的 / 符号是否严格要求
strict_slashes = None
'''
@app.route('/index', strict_slashes=False)
# 访问 http://www.xx.com/index/ 或http://www.xx.com/index均可
@app.route('/index', strict_slashes=True)
# 仅访问 http://www.xx.com/index
'''
#重定向到指定地址
redirect_to = None,
'''
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')
'''
# 需要记住的
# rule
# view_func
# defaults
# endpoint
# methods
2.3 转换器
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
# 了解:让路由支持正则(忽略掉)
3.源码分析
3.1 cbv分析
# 基于类的视图,写法
from flask import Flask,request
from flask.views import View, MethodView
app = Flask(__name__)
app.debug = True
# 视图类,继承 MethodView 类中写跟请求方式同名的方法即可,之前学的所有都一致
class IndexView(MethodView):
def get(self):
print(request.method)
return 'get 请求'
def post(self):
print(request.method)
return 'post 请求'
app.add_url_rule('/index', endpoint='index', view_func=IndexView.as_view('index'))
if __name__ == '__main__':
app.run()
3.2 源码分析
# 1 IndexView.as_view('index') 执行完的结果,是个函数(view的)内存地址
def as_view(cls, name, *class_args, **class_kwargs):
def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
# 本质是在执行 self.dispatch_request,只是用了异步
return current_app.ensure_sync(self.dispatch_request)(**kwargs)
return view
# 2 请求来了,执行 view()---> 本质在执行 self.dispatch_request ---》MethodView 中的
def dispatch_request(self, **kwargs):
# self是视图类的对象
meth = getattr(self, request.method.lower(), None)
# 用异步执行meth()
return current_app.ensure_sync(meth)(**kwargs)
# 3 总结:执行原理跟 django 一样
# 4 路径如果不传别名,别名就是函数名 ---》分析一下源码
-@app.route('/index') --》没有传 endpoint
-endpoint 就是 None ---》调用了 app.add_url_rule, 传入了 None
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) # type: ignore
-_endpoint_from_view_func 就是返回函数的名字
# 5 as_view('index') 必须传参数,传进来的参数是,是【别名】
# view 是 as_view 内的内层函数,闭包函数
view.__name__ = name # 修改了函数的名字变成了你传入的
# app.add_url_rule('/index',view_func=IndexView.as_view('index'))
简写成:app.add_url_rule('/index',view_func=view)
#如果不传参数, 所有人的别名(endpoint),都是内层函数 view,所以就报错了
# 6 补充:flask 的路由注册使用装饰器,如果写了一个登录认证装饰器,那么应该放在路由装饰器上还是下?
-放在路由下面
-路由必须传 endpoint,如果不传,又报错
# 7 视图类必须继承 MethodView, 如果继承 View,它的 dispatch_request 没有具体实现,你的视图类必须重写 dispatch_request,我们不想重写,继承 MethodView
def dispatch_request(self) -> ft.ResponseReturnValue:
raise NotImplementedError()
# 8 视图类加装饰器,直接配置在类属性上【decorators】即可
decorators = [auth,]
# 源码,cls 是视图类,中有 decorators
if cls.decorators:
for decorator in cls.decorators:
view = decorator(view) # view=auth(view)
# 源码学到的
-1 as_view 执行流程跟 djagno 一样
-2 路径如果不传别名,别名就是函数名(endpoint)
-3 视图函数加多个装饰器(上下顺序和必须传 endpoint)
-4 视图类必须继承 MethodView,否则需要重写 dispatch_request
-5 视图类加装饰器:类属性 decorators = [auth,]
4.模板语法
2.1 py
from flask import Flask, render_template,Markup
app = Flask(__name__, template_folder='templates', static_folder='static') # 模板的路径必须是templates,因为实例化app对象时,传入的
app.debug=True
def add(a,b):
return a+b
@app.route('/')
def index():
a='<a href="http://www.baidu.com">点我看美女</a>' # 不存在xss攻击,处理了xss
a=Markup(a)
return render_template('index.html',name='lqz',a=a,add=add)
if __name__ == '__main__':
app.run()
2.2 html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>模板语法,static</h1>
<img src="/static/1.jpg" alt="">
<h1>模板语法,if</h1>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
<h1>模板语法,标签渲染</h1>
{{a|safe}}
{{a}}
<h1>模板语法,执行函数</h1>
{{add(4,5)}}
</body>
</html>
5.请求与相应
from flask import Flask, request, make_response,render_template
app = Flask(__name__)
app.debug = True
@app.route('/', methods=['GET', 'POST'])
def index():
#### 请求
# request.method 提交的方法
# request.args get 请求提及的数据
# request.form post 请求提交的数据
# request.values post 和 get 提交的数据总和
# request.cookies 客户端所带的 cookie
# request.headers 请求头
# request.path 不带域名,请求路径
# request.full_path 不带域名,带参数的请求路径
# request.script_root
# request.url 带域名带参数的请求路径
# request.base_url 带域名请求路径
# request.url_root 域名
# request.host_url 域名
# request.host 127.0.0.1:500
print(request.method)
print(request.args)
print(request.form)
print(request.values)
print(request.cookies)
print(request.headers)
print(request.path)
print(request.full_path)
print(request.url)
print(request.base_url)
print(request.host_url)
print(request.host)
obj = request.files['file']
obj.save(obj.filename)
### 响应 四件套
# 1 响应中写入 cookie
# response = 'hello'
# res = make_response(response) # flask.wrappers.Response
# print(type(res))
# res.set_cookie('xx','xx')
# return res
# 2 响应头中写数据(新手四件套,都用 make_response 包一下)
response = render_template('index.html')
res = make_response(response) # flask.wrappers.Response
print(type(res))
res.headers['yy']='yy'
return res
if __name__ == '__main__':
app.run()
6.session及源码分析
6.1 session 的使用
from flask import Flask, request, session, render_template, redirect
app = Flask(__name__)
app.debug = True
app.secret_key = 'asdfas33asdfasf'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
name = request.form.get('name')
password = request.form.get('password')
print(password)
session['name'] = name
return redirect('/index')
@app.route('/index', methods=['GET', 'POST'])
def index():
return 'hello %s' % session.get('name', '匿名用户')
if __name__ == '__main__':
app.run()
6.2 源码分析
# cookie: 存在于客户端浏览器的键值对
# session: 存在于服务端的键值对 # djagno 放在了 django_session 表中
# flask 中,叫 session,问题来了,存哪里了?
-加密后,放到了 cookie 中,如果 session 发生了变化,我们的 cookie 也会跟着变
# 源码部分:
# 1 app.session_interface 配置了一个类的对象,这个就是 session 的执行流程
# 2 类中有两个非常重要的方法,请求来了,会执行 open_session,请求走了会执行 save_session
def open_session(self, app, request) :
# 1 根据名字,取出前端传入的 cookie 的 value 值
val = request.cookies.get(self.get_cookie_name(app))
# 2 如果没有 val,构造了一个空 session 对象
if not val:
return self.session_class()
max_age = int(app.permanent_session_lifetime.total_seconds())
try:
# 如果没有过期,解码,做成 session 对象,后续直接用 session 即可
data = s.loads(val, max_age=max_age)
return self.session_class(data)
except BadSignature:
# 如果过期了,也是空 session
return self.session_class()
def save_session(self, app, session, response) :
name = self.get_cookie_name(app)
# 取出过期事件,和把 session 加密转成字符串,放到 cookie 中
expires = self.get_expiration_time(app, session)
val = self.get_signing_serializer(app).dumps(dict(session))
response.set_cookie(
name,
val,
expires=expires,
)
# 扩展,想把 session放到 redis 中,mysql 中,已经有人帮咱们写了,第三方的
只需要写个类,重写 open_session,save_session 自己写
6.3 session 执行原理
7.闪现与请求扩展
7.1 闪现
# flash 翻译过来的
- 当次请求先把一些数据,放在某个位置
- 下一次请求,把这些数据取出来,取完,就没了
# 作用:
1 可以跨请求,来保存数据
2 当次请求,访问出错,被重定向到其他地址,重定向到这个地址后,拿到当时的错误
# djagno中有这个东西吗?
-message框架
# 用法:
-设置 闪现
-flash('%s,我错了'%name) ,可以设置多次,放到列表中
-flash('超时错误',category="debug") 分类存
-获取 闪现
-get_flashed_messages() ,取完就删除
-get_flashed_messages(category_filter=['debug'])分类取
# 本质,放到session中
7.2 请求扩展
# 请求扩展中:在请求来了,或请求走了,可以绑定一些函数,到这里就会执行这个函数,类似于 django 的中间件
# 在 flask 中就用请求扩展,来代替 djagno 的中间件
# 好几个请求扩展
-before_request:请求来了会走,如果他返回了四件套,就结束了
-after_request:请求走了会走,一定要返回 response 对象
-before_first_request:第一次来了会走
-teardown_request:无论是否出异常,会走
-errorhandler:监听状态码,404 500
-template_global:标签
-template_filter:过滤器
from flask import Flask, request,render_template
app = Flask(__name__)
####1 before_request 和 after_request
# 请求来了,执行一个函数,来的时候从上往下执行
# @app.before_request
# def before():
# print('我来了111')
# # if 'index' in request.path:
# return '不让看了' # 如果不是 retrun 了 None,说明被拦截了,直接返回
#
#
# @app.before_request
# def before1():
# print('我来了222')
#
#
# # 请求走了,执行一个函数,走的时候,从下往上执行
# @app.after_request
# def after(response):
# print('我走了111')
# return response
#
#
# @app.after_request
# def after2(response):
# print('我走了222')
# return response
# 2 项目启动后的第一个请求
# @app.before_first_request
# def first():
# print('我的第一次')
# 3 teardown_request,无论视图函数是否出错,都会执行它,做错误日志
# @app.teardown_request
# def teardown(e):
# print(e)
# print('执行我了')
# 4 errorhandler 监听响应状态码,如果符合监听的状态码,就会走它
# @app.errorhandler(404)
# def error_404(arg):
# return "404错误了"
# @app.errorhandler(500)
# def error_500(arg):
# return "500错误了"
##5 template_global 在模板中直接使用该过滤器
@app.template_global()
def add(a1, a2):
return a1 + a2
# 6 template_filter
@app.template_filter()
def db(a1, a2, a3):
return a1 + a2 + a3
@app.route('/')
def index():
# a = [1, 2, 3]
# print(a[9])
return render_template('index1.html')
if __name__ == '__main__':
app.run()
8.蓝图
blueprint 翻译过来的,称之为蓝图。
作用是:之前全在一个 py 中写 flask 项目,后期肯定要划分目录
8.1 使用步骤
# 蓝图的使用步骤
-第一步:导入蓝图类 from flask import Blueprint
-第二步:实例化得到蓝图对象 us=Blueprint('user',__name__)
-第三步:在app中注册蓝图 app.register_blueprint(us)
-第四步:在不同的views.py 使用蓝图注册路由 @us.route('/login')
-补充:蓝图可以有自己的静态文件和模板
-补充:注册蓝图时,可以使用前缀,必须以/ 开头
8.2 项目目录
# 使用蓝图,划分小型项目目录
little_blueprint # 项目名
-src # 核心代码
-static # 静态文件
-1.jpg # 图片
-templates # 模板文件
-user.html # 模板
-views # 视图函数存放位置
-order.py # 订单相关视图
-user.py # 用户相关视图
-__init__.py # 包
-models.py # 表模型
-manage.py # 启动文件
# 使用蓝图,划分大型项目目录 多个 app,像 django 一样
big_blueprint # 项目名
-src # 核心文件
-admin # admin 的 app
-static # 静态文件
-1.jpg # 图片
-templates # 模板文件目录
-admin_home.html # 模板文件
-__init__.py # 包
-models.py # 表模型
-views.py # 视图函数
-home # home app
-order # order app
-__init__.py # 包
-settings.py # 配置文件
-manage.py # 启动文件
9.g对象
9.1 是什么
# g 对象 是什么?
-global 的缩写,再 python 中是个关键字,不能以关键字作为变量名,干脆用了 g
-g 对象,在整个请求的全局,可以放值,可以取值
-全局变量,在任意位置导入使用即可
-它为什么不学 django 使用 request 作为上下文?
-因为使用 request,可能会造成 request 数据污染,在不知情下改了 request 的属性
-建议使用 g 是空的,放入之后在当次请求中全局优先
# 以后想在当次请求中,放入一些数据,后面使用,就可以使用g对象
# g 和 session 有什么区别?
-g 是只针对于当次请求
-session 针对于多次请求
9.2 使用案例
from flask import Flask, g, request
app = Flask(__name__)
app.debug = True
@app.before_request
def before():
if 'home' in request.path:
g.xx = 'xx'
def add(a, b):
# print('---',g.name)
print('---', request.name)
return a + b
@app.route('/')
def index():
print(g.xx)
name = request.args.get('name')
# g.name = name
request.method = name
res = add(1, 2)
print(res)
return 'index'
@app.route('/home')
def home():
print(g.xx)
return 'index'
if __name__ == '__main__':
app.run()
10.数据库连接池
10.1 使用 pymysql
# flask 操作 mysql
-使用 pymysql
-在视图函数中,创建 pymysql 的连接,查数据,查完,返回给前端
-有什么问题? 来一个请求,创建一个连接,请求结束,连接关闭 (djanog 就是这么做的)
-把连接对象,做成全局的,在视图函数中,使用全局的连接,查询,返回给前端
-有什么问题?会出现数据错乱,详见下图
# 解决上面的两个问题
-数据库连接池
-创建一个全局的池
-每次进入视图函数,从池中取一个连接使用,使用完放回到池中,只要控制池的大小,就能控制 mysql 连接数
10.2 使用 dbutils
1、安装 pip install dbutils
2、使用:实例化得到一个池对象
# POOL.py
from dbutils.pooled_db import PooledDB
import pymysql
pool = PooledDB(
creator=pymysql, # 使用链接数据库的模块
maxconnections=10, # 连接池允许的最大连接数,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='',
database='cnblogs',
charset='utf8'
)
3、在视图函数中导入使用
import time
import random
from POOL import pool
@app.route('/article_pool')
def article_pool():
conn = pool.connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute('select id,title,author_img from aritcle limit 2')
res = cursor.fetchall()
print(res)
return jsonify(res)
@app.route('/article')
def article():
conn = pymysql.connect(user='root',
password="",
host='127.0.0.1',
database='cnblogs',
port=3306)
cursor = conn.cursor(pymysql.cursors.DictCursor)
time.sleep(random.randint(1,3))
cursor.execute('select id,title,author_img from aritcle limit 2')
res = cursor.fetchall()
cursor.close()
conn.close()
return jsonify(res)
if __name__ == '__main__':
app.run()
4、压力测试
from threading import Thread
import requests
def task():
res = requests.get('http://127.0.0.1:5000/article_pool')
print(len(res.text))
if __name__ == '__main__':
for i in range(500):
t = Thread(target=task)
t.start()
11.请求上下文分析(源码:request原理)
11.1 导出项目的依赖
# 之前 pip freeze ---> requirments.txt 把当前解释器环境下的所有第三方依赖都导出来
# 使用第三方模块,更精确的导出依赖 pipreqs
第一步:安装 pip3 install pipreqs
第二步:使用命令,导出项目依赖 pipreqs ./
-win 由于编码问题会出错:pipreqs ./ --encoding=utf8
-mac,linx 没有问题
第三步:就会在项目根路径下生成:requirements.txt
11.2 函数和方法
# 只要会自动传值,就是方法,函数,有几个值就要传几个值,否则报错
# 函数就是普通的函数,有几个参数就要传几个参数
# 方法:绑定给对象的方法,绑定给类的方法,绑定给谁的,由谁来调用,会自动把自身传入
# 类的绑定方法,对象可以来调用,会自动把类传入
# 对象的绑定方法,类可以来调用? 类可以调用,但是它就变成了普通函数,有几个值,就要传几个值,没有自动传值了
# MethodType 检查一个对象,是不是方法
# FunctionType 检查一个对象,是不是函数
# isinstance 判断一个对象,是不是一个类的对象
# issubclass 判断一个类,是不是另一个类的子类
from types import MethodType, FunctionType
class Foo(object):
def fetch(self):
pass
@classmethod
def test(cls):
pass
@staticmethod
def test1():
pass
# a=Foo()
# print(isinstance(a,Foo))
# print(isinstance('a',Foo))
#
# class Foo2(Foo):
# pass
# class Foo3():
# pass
# print(issubclass(Foo2,Foo))
# print(issubclass(Foo3,Foo))
def add():
pass
# 类来调用对象的绑定方法,
print(isinstance(Foo.fetch, MethodType)) # False 类来调用对象的绑定方法,该方法就变成了普通函数
obj = Foo()
print(isinstance(obj.fetch, MethodType)) # True 对象来调用自己的绑定方法,fetch 就是方法
print(isinstance(Foo.fetch, FunctionType)) # True 类来调用对象的绑定方法,该方法就变成了普通函数
print(isinstance(add, FunctionType)) # True 就是个普通函数
print(isinstance(add, MethodType)) # False 就是个普通函数
print(isinstance(Foo.test, MethodType)) # True test 是绑定给类的方法,类来调用,就是方法
print(isinstance(obj.test, MethodType)) # True 对象调用类的绑定方法,还是方法
print(isinstance(Foo.test1, MethodType)) # False 是普通函数
print(isinstance(obj.test1, MethodType)) # False 是普通函数
print(isinstance(obj.test1, FunctionType)) # True,静态方法,就是普通函数,对象和类都可以调用,有几个值就传几个值
11.3 threading.local对象
# local 对象
# 并发编程时,多个线程操作同一个变量,会出现并发安全的问题,咱们需要加锁
# 使用local对象,多线程并发操作时,不需要加锁,不会出现数据错乱threading.local
# 其他语言中也有这个东西ThreadLocal,java中面试会被经常问到,python没人问
# 本质原理:
多个线程修改同一个数据,复制多份变量给每个线程用,为每个线程开辟一块空间进行数据存储
每个线程操作自己的那部分数据
11.4 偏函数
# 可以提前传值
from functools import partial
def add(a,b,c):
return a+b+c
# print(add(2,3,4)) # 传少了报错
# 现在只有一个参数,后面的俩参数,需要过一会才知道
# 借助于偏函数,先提前给他把第一个参数传入,后面知道了后面俩参数,再传后面俩
add=partial(add,2)
#
# # 干了很多事
#
print(add(3,4))
11.5 flask 整个生命执行流程(1.1.4版本为例)
# 请求来了---》app()----->Flask.__call__--->self.wsgi_app(environ, start_response)
def wsgi_app(self, environ, start_response):
# environ:http请求拆成了字典
# ctx对象:RequestContext类的对象,对象里有:当次的requets对象,app对象,session对象
ctx = self.request_context(environ)
error = None
try:
try:
#ctx RequestContext类 push方法
ctx.push()
# 匹配成路由后,执行视图函数
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
# RequestContext :ctx.push
def push(self):
# _request_ctx_stack = LocalStack() ---》push(ctx对象)--》ctx:request,session,app
_request_ctx_stack.push(self)
#session相关的
if self.session is None:
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request)
if self.session is None:
self.session = session_interface.make_null_session(self.app)
# 路由匹配相关的
if self.url_adapter is not None:
self.match_request()
# LocalStack() push --->obj 是ctx对象
def push(self, obj):
#self._local _local 就是咱们刚刚自己写的Local的对象---》LocalStack的init初始化的_local---》self._local = Local()---》Local对象可以根据线程协程区分数据
rv = getattr(self._local, "stack", None)
# 一开始没有值
if rv is None:
rv = []
self._local.stack = rv # self._local.stack 根据不同线程用的是自己的数据
rv.append(obj) # self._local.stack.append(obj)
# {'线程id号':{stack:[ctx]},'线程id号2':{stack:[ctx]}}
return rv
# 再往后执行,就会进入到路由匹配,执行视图函数
# request = LocalProxy(partial(_lookup_req_object, "request"))
# LocalProxy 代理类---》method---》代理类去当前线程的stack取出ctx,取出当时放进去的request
视图函数中:print(request.method)
# print(request) 执行LocalProxy类的__str__方法
# request.method 执行LocalProxy类的__getattr__
def __getattr__(self, name): #name 是method
# self._get_current_object() 就是当次请求的request
return getattr(self._get_current_object(), name)
# LocalProxy类的方法_get_current_object
def _get_current_object(self):
if not hasattr(self.__local, "__release_local__"):
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError("no object bound to %s" % self.__name__)
# self.__local 是在 LocalProxy 类实例化的时候传入的local
# 在这里实例化的:request = LocalProxy(partial(_lookup_req_object, "request"))
# local 是 partial(_lookup_req_object, "request")
#_lookup_req_object ,name=request
def _lookup_req_object(name):
top = _request_ctx_stack.top # 取出了ctx,是当前线程的ctx
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name) #从ctx中反射出request,当次请求的request
# 请求上下文执行流程(ctx):
-0 flask项目一启动,有6个全局变量
-_request_ctx_stack:LocalStack对象
-_app_ctx_stack :LocalStack对象
-request : LocalProxy对象
-session : LocalProxy对象
-1 请求来了 app.__call__()---->内部执行:self.wsgi_app(environ, start_response)
-2 wsgi_app()
-2.1 执行:ctx = self.request_context(environ):返回一个RequestContext对象,并且封装了request(当次请求的request对象),session,flash,当前app对象
-2.2 执行: ctx.push():RequestContext对象的push方法
-2.2.1 push方法中中间位置有:_request_ctx_stack.push(self),self是ctx对象
-2.2.2 去_request_ctx_stack对象的类中找push方法(LocalStack中找push方法)
-2.2.3 push方法源码:
def push(self, obj):
#通过反射找self._local,在init实例化的时候生成的:self._local = Local()
#Local(),flask封装的支持线程和协程的local对象
# 一开始取不到stack,返回None
rv = getattr(self._local, "stack", None)
if rv is None:
#走到这,self._local.stack=[],rv=self._local.stack
self._local.stack = rv = []
# 把ctx放到了列表中
#self._local={'线程id1':{'stack':[ctx,]},'线程id2':{'stack':[ctx,]},'线程id3':{'stack':[ctx,]}}
rv.append(obj)
return rv
-3 如果在视图函数中使用request对象,比如:print(request)
-3.1 会调用request对象的__str__方法,request类是:LocalProxy
-3.2 LocalProxy中的__str__方法:lambda x: str(x._get_current_object())
-3.2.1 内部执行self._get_current_object()
-3.2.2 _get_current_object()方法的源码如下:
def _get_current_object(self):
if not hasattr(self.__local, "__release_local__"):
#self.__local() 在init的时候,实例化的,在init中:object.__setattr__(self, "_LocalProxy__local", local)
# 用了隐藏属性
#self.__local 实例化该类的时候传入的local(偏函数的内存地址:partial(_lookup_req_object, "request"))
#加括号返回,就会执行偏函数,也就是执行_lookup_req_object,不需要传参数了
#这个地方的返回值就是request对象(当此请求的request,没有乱)
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError("no object bound to %s" % self.__name__)
-3.2.3 _lookup_req_object函数源码如下:
def _lookup_req_object(name):
#name是'request'字符串
#top方法是把第二步中放入的ctx取出来,因为都在一个线程内,当前取到的就是当次请求的ctx对象
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
#通过反射,去ctx中把request对象返回
return getattr(top, name)
-3.2.4 所以:print(request) 实质上是在打印当此请求的request对象的__str__
-4 如果在视图函数中使用request对象,比如:print(request.method):实质上是取到当次请求的reuquest对象的method属性
-5 最终,请求结束执行: ctx.auto_pop(error),把ctx移除掉
其他的东西:
-session:
-请求来了opensession
-ctx.push()---->也就是RequestContext类的push方法的最后的地方:
if self.session is None:
#self是ctx,ctx中有个app就是flask对象, self.app.session_interface也就是它:SecureCookieSessionInterface()
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request)
if self.session is None:
#经过上面还是None的话,生成了个空session
self.session = session_interface.make_null_session(self.app)
-请求走了savesession
-response = self.full_dispatch_request() 方法内部:执行了before_first_request,before_request,视图函数,after_request,savesession
-self.full_dispatch_request()---->执行:self.finalize_request(rv)-----》self.process_response(response)----》最后:self.session_interface.save_session(self, ctx.session, response)
-请求扩展相关
before_first_request,before_request,after_request依次执行
-flask有一个请求上下文,一个应用上下文
-ctx:
-是:RequestContext对象:封装了request和session
-调用了:_request_ctx_stack.push(self)就是把:ctx放到了那个位置
-app_ctx:
-是:AppContext(self) 对象:封装了当前的app和g
-调用 _app_ctx_stack.push(self) 就是把:app_ctx放到了那个位置
-g是个什么鬼?
专门用来存储用户信息的g对象,g的全称的为global
g对象在一次请求中的所有的代码的地方,都是可以使用的
-代理模式
-request和session就是代理对象,用的就是代理模式
12.wtforms(了解)
# django 有forms组件
- 生成前端模板
- 校验数据
- 渲染错误信息
# flask 中使用第三方的wtforms 实现像django的forms一样的功能
- 第一步:导入,定义一个类,继承forms
-第二步:模板中, for循环生成模板
-第三步:视图函数中,使用form校验数据
# py代码
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()
# html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<form method="post" novalidate>
<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>
13.信号
13.1 flask 信号
# Flask框架中的信号基于blinker(安装这个模块),其主要就是让开发者可是在flask请求过程中定制一些用户行为 flask 和django都有
#观察者模式,又叫发布-订阅(Publish/Subscribe) 23 种设计模式之一
pip3.8 install blinker
# 信号:signial 翻译过来的,并发编程中学过 信号量Semaphore
# 比如:用户表新增一条记录,就记录一下日志
-方案一:在每个增加后,都写一行代码 ---》后期要删除,比较麻烦
-方案二:使用信号,写一个函数,绑定内置信号,只要程序执行到这,就会执行这个函数
# 内置信号: flask少一些,django多一些
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 等待被触发
# 自定义信号
# 1 定义出信号
session_set = _signals.signal('session_set')
# 2 写一个函数
def test1(*args, **kwargs):
print(args)
print(kwargs)
print('session设置值了')
# 3 绑定自定义的信号
# session_set.connect(test1)
# 4 触发信号的执行(咱们做)
# session_set.send('lqz') # 触发信号执行
# django中使用信号
https://www.cnblogs.com/liuqingzheng/articles/9803403.html
13.2 django 信号
Model signals
pre_init # django的modal执行其构造方法前,自动触发
post_init # django的modal执行其构造方法后,自动触发
pre_save # django的modal对象保存前,自动触发
post_save # django的modal对象保存后,自动触发
pre_delete # django的modal对象删除前,自动触发
post_delete # django的modal对象删除后,自动触发
m2m_changed # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发
class_prepared # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
Management signals
pre_migrate # 执行migrate命令前,自动触发
post_migrate # 执行migrate命令后,自动触发
Request/response signals
request_started # 请求到来前,自动触发
request_finished # 请求结束后,自动触发
got_request_exception # 请求异常后,自动触发
Database Wrappers
connection_created # 创建数据库连接时,自动触发
# django中使用内置信号
1 写一个函数
def callBack(*args, **kwargs):
print(args)
print(kwargs)
2 绑定信号
#方式一
post_save.connect(callBack)
# 方式二
from django.db.models.signals import pre_save
from django.dispatch import receiver
@receiver(pre_save)
def my_callback(sender, **kwargs):
print("对象创建成功")
print(sender)
print(kwargs)
3 等待触发
14.flask-script定制命令
# django中,有命令
python manage.py runserver
。。。
#flask启动项目,像djagno一样,通过命令启动
Flask==2.2.2
Flask_Script==2.0.3
#借助于:flask-script 实现
-安装:pip3.8 install flask-script
-修改代码:
from flask_script import Manager
manager=Manager(app)
manager.run()
-用命令启动
python manage.py runserver
# 自定制命令
#1 简单自定制命令
@manager.command
def custom(arg):
# 命令的代码,比如:初始化数据库, 有个excel表格,使用命令导入到mysql中
print(arg)
#2 复杂一些的自定制命令
@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')
def cmd(name, url):
# python run.py cmd -n lqz -u xxx
# python run.py cmd --name lqz --url uuu
print(name, url)
# django 中如何自定制命令
三:sqlalchemy
1.安装
# flask 中没有orm框架,对象关系映射,方便我们快速操作数据库
# flask,fastapi中用sqlalchemy居多
# SQLAlchemy是一个基于Python实现的ORM框架。该框架建立在 DB API之上,使用关系对象映射进行数据库操作,简言之便是:将类和对象转换成SQL,然后使用数据API执行SQL并获取执行结果
# 安装
pip3.8 install sqlalchemy
#了解
SQLAlchemy本身无法操作数据库,其必须以来pymsql等第三方插件
pymysql
mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
cx_Oracle
oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
更多:http://docs.sqlalchemy.org/en/latest/dialects/index.html
2.快速使用
# 先不是 orm,而是原生sql
# 第一步:导入
from sqlalchemy import create_engine
# 第二步:生成引擎对象
engine = create_engine(
"mysql+pymysql://root@127.0.0.1:3306/cnblogs",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 第三步:使用引擎获取连接,操作数据库
conn = engine.raw_connection()
cursor=conn.cursor()
cursor.execute('select * from aritcle')
print(cursor.fetchall())
3.数据操作
3.1 创建操作数据表
# 第一步:导入
from sqlalchemy import create_engine
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index
# 第二步:执行declarative_base,得到一个类
Base = declarative_base()
# 第三步:继承生成的Base类
class User(Base):
# 第四步:写字段
id = Column(Integer, primary_key=True) # 生成一列,类型是Integer,主键
name = Column(String(32), index=True, nullable=False) # name列varchar32,索引,不可为空
email = Column(String(32), unique=True)
# datetime.datetime.now不能加括号,加了括号,以后永远是当前时间
ctime = Column(DateTime, default=datetime.datetime.now)
# extra = Column(Text, nullable=True)
# 第五步:写表名 如果不写以类名为表名
__tablename__ = 'users' # 数据库表名称
# 第六步:建立联合索引,联合唯一
__table_args__ = (
UniqueConstraint('id', 'name', name='uix_id_name'), # 联合唯一
Index('ix_id_name', 'name', 'email'), # 索引
)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
name = Column(String(32))
# 第七步:把表同步到数据库中
# 不会创建库,只会创建表
engine = create_engine(
"mysql+pymysql://root@127.0.0.1:3306/aaa",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 把表同步到数据库 (把被Base管理的所有表,都创建到数据库)
Base.metadata.create_all(engine)
# 把所有表删除
# Base.metadata.drop_all(engine)
3.2 sqlalchemy 快速插入数据
# sqlalchemy是什么 orm框架,跟其他web框架没有必然联系,可以独立使用
# 安装,快速使用,执行原生sql
# 创建表和删除表
-不能创建数据库
-不能修改字段(增加,删除)
# 使用orm插入
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Book
# 第一步:生成engine对象
engine = create_engine(
"mysql+pymysql://root@127.0.0.1:3306/aaa",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 第二步:拿到一个Session类,传入engine
Session=sessionmaker(bind=engine)
# 第三步:拿到session对象,相当于连接对象(会话)
session=Session()
# 第四步,增加数据
book=Book(name='红楼梦',)
session.add(book)
session.commit()
# 第五步:关闭session对象
session.close()
3.3 scoped_session 线程安全
3.3.1 基本使用
from sqlalchemy.orm import scoped_session
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# 第一步:生成engine对象
engine = create_engine(
"mysql+pymysql://root@127.0.0.1:3306/aaa",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 第二步:拿到一个Session类,传入engine
Session = sessionmaker(bind=engine)
# 线程不安全
# session = Session()
# 做成线程安全的:如何做的?
# 内部使用了local对象,取当前线程的session,如果当前线程有,就直接返回用,如果没有,创建一个,放到local中
# session 是 scoped_session 的对象
session = scoped_session(Session)
# 以后全局使用session即可,它线程安全
3.3.2 加在类上的装饰器
# session 是 scoped_session 的对象,类上没有属性和方法,但是,用的时候,确实用
session = scoped_session(Session)
def speak():
print('说话了')
def wrapper(func):
def inner(*args, **kwargs):
res = func()
res.name = 'lqz'
res.speak = speak
return res
return inner
@wrapper # 语法糖会把Person当参数传入到装饰器中 Person=wrapper(Person)
class Person:
pass
p = Person()
print(p.name)
p.speak()
4.基本增删查改
# 增,删,改
# 查 基本查询和高级查询
from models import User, Book
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
from sqlalchemy.sql import text
engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa")
Session = sessionmaker(bind=engine)
session = scoped_session(Session)
# 1 增加:add add_all
# user = User(name='pyy', email='44@qq.com', extra='摄氏度法')
# user1 = User(name='yyy', email='4@qq.com', extra='大沙发斯蒂芬')
# book = Book(name='西游记')
# # session.add(user)
# session.add_all([user, user1, book]) # 多个对象可以是models中任意表模型的对象
#
# session.commit()
# session.close()
# 2 基本查 filter filter_by filer:写条件 filter_by:等于的值
# filter
# 2.1 session.query(User) 中写表模型,可以写多个表模型(连表操作) select * from User;
# 2.2 filter 过滤条件,必须写表达式 == >= <= != select * from user where user.id=1
# 2.3 all:普通列表 first
# user = session.query(User).filter(User.name == 'lqz').first()
# user = session.query(User).filter(User.name != 'lqz').all()
# print(user)
# res = session.query(User).filter(User.id > 1).all()
# print(res)
# filter_by 直接写等式 不能写成 User.name = 'lqz'
# user = session.query(User).filter_by(name='lqz').first()
# user = session.query(User).filter_by(id=2).first()
# user = session.query(User).filter_by(id=2).first()
# print(user)
# 3 删除(查到才能删) filter或filter_by查询的结果 不要all或first出来, .delete()即可
# res = session.query(User).filter_by(id=2).delete()
# session.commit() # 一定不要忘了
# print(res) # 影响的行数
# 4 修改(查到才能改)
# 方式一:update修改
# res = session.query(User).filter_by(id=3).update({"name" : "彭于晏"})
# print(res)
# session.commit()
# 方式二,使用对象修改
# res = session.query(User).filter_by(id=3).first()
# res = session.query(User).filter_by(name='zzz').first()
# res.name='来来来'
# print(res.id)
# session.add(res) # add 如果有主键,就是修改,如果没有主键就是新增
# session.commit()
4.4.1 基本增删查改和高级查询
# 4 查询: filer:写条件 filter_by:等于的值
# 4.1 查询所有 是list对象
# res = session.query(User).all() # 是个普通列表
# print(type(res))
# print(len(res))
# 4.1.1 只查询某几个字段
# select name as xx,email from user;
# res = session.query(User.name.label('xx'), User.email)
# print(res) # 打出原生sql
# # print(res.all())
# for item in res.all():
# print(item[0])
# 4.1.2 filter传的是表达式,filter_by传的是参数
# res = session.query(User).filter(User.name == "lqz").all()
# res = session.query(User).filter(User.name != "lqz").all()
# res = session.query(User).filter(User.name != "lqz", User.email == '3@qq.com').all() # django 中使用 Q
# res = session.query(User).filter_by(name='lqz099').all()
# res = session.query(User).filter_by(name='lqz099',email='47@qq.com').all()
# print(len(res))
# 4.2 取一个 all了后是list,list 没有first方法
# res = session.query(User).first()
# 4.3 查询所有,使用占位符(了解) :value :name
# select * from user where id <20 or name=lqz099
# res = session.query(User).filter(text("id<:value or name=:name")).params(value=10, name='lqz099').all()
# 4.4 自定义查询(了解)
# from_statement 写纯原生sql
# res=session.query(User).from_statement(text("SELECT * FROM users where email=:email")).params(email='3@qq.com').all()
# # print(type(res[0])) # 是book的对象,但是查的是User表 不要这样写
# print(res[0].name) #
# 4.5 高级查询
# 条件
# 表达式,and条件连接
# res = session.query(User).filter(User.id > 1, User.name == 'lqz099').all() # and条件
# between
# res = session.query(User).filter(User.id.between(1, 9), User.name == 'lqz099').all()
# res = session.query(User).filter(User.id.between(1, 9)).all()
# in
# res = session.query(User).filter(User.id.in_([1,3,4])).all()
# res = session.query(User).filter(User.email.in_(['3@qq.com','r@qq.com'])).all()
# ~非,除。。外
# res = session.query(User).filter(~User.id.in_([1,3,4])).all()
# print(res)
# 二次筛选
# res = session.query(User).filter(~User.id.in_(session.query(User.id).filter_by(name='lqz099'))).all()
# print(res)
# and or条件
from sqlalchemy import and_, or_
# or_包裹的都是or条件,and_包裹的都是and条件
# res = session.query(User).filter(and_(User.id >= 3, User.name == 'lqz099')).all() # and条件
# res = session.query(User).filter(User.id < 3, User.name == 'lqz099').all() # 等同于上面
# res = session.query(User).filter(or_(User.id < 2, User.name == 'eric')).all()
# res = session.query(User).filter(
# or_(
# User.id < 2,
# and_(User.name == 'lqz099', User.id > 3),
# User.extra != ""
# )).all()
# 通配符,以e开头,不以e开头
# res = session.query(User).filter(User.email.like('%@%')).all()
# select user.id from user where user.name not like e%;
# res = session.query(User.id).filter(~User.name.like('e%'))
# 分页
# 一页2条,查第5页
# res = session.query(User)[2*5:2*5+2]
# 排序,根据name降序排列(从大到小)
# res = session.query(User).order_by(User.email.desc()).all()
# res = session.query(Book).order_by(Book.price.desc()).all()
# res = session.query(Book).order_by(Book.price.asc()).all()
# 第一个条件重复后,再按第二个条件升序排
# res = session.query(User).order_by(User.name.desc(), User.id.asc())
# 分组查询 5个聚合函数
from sqlalchemy.sql import func
# res = session.query(User).group_by(User.extra) # 如果是严格模式,就报错
# 分组之后取最大id,id之和,最小id 和分组的字段
# res = session.query(
# User.extra,
# func.max(User.id),
# func.sum(User.id),
# func.min(User.id)).group_by(User.extra).all()
# for item in res:
# print(item[2])
# having
# select max(id),sum(id),min(id) from user group by user.extra having id_max>2;
res = session.query(
func.max(User.id),
func.sum(User.id),
func.min(User.id)).group_by(User.extra).having(func.max(User.id) > 2)
4.4.2 原生sql
### 方式一:
# 第一步:导入
from sqlalchemy import create_engine
# 第二步:生成引擎对象
engine = create_engine(
"mysql+pymysql://root@127.0.0.1:3306/cnblogs",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 第三步:使用引擎获取连接,操作数据库
conn = engine.raw_connection()
cursor=conn.cursor()
cursor.execute('select * from aritcle')
print(cursor.fetchall())
### 方式二:
from models import User, Book
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa")
Session = sessionmaker(bind=engine)
session = scoped_session(Session)
# 2.0.9 版本需要使用text包裹一下,原来版本不需要
# cursor = session.execute(text('select * from users'))
# result = cursor.fetchall()
# print(result)
cursor = session.execute(text('insert into books(name) values(:name)'), params={"name": '红楼梦'})
session.commit()
print(cursor.lastrowid)
session.close()
4.4.3 django中执行原生sql
# 选择的查询基表Book.objects.raw ,只是一个傀儡,正常查询出哪些字段,都能打印出来
def index(request):
# books = Book.objects.raw('select * from app01_book where id=1') # RawQuerySet 用起来跟列表一样
# books = Publish.objects.raw('select * from app01_book where id=1') # RawQuerySet 用起来跟列表一样
# print(books[0])
# print(type(books[0]))
# # for book in books:
# # print(book.name)
# # print(books[0].name)
# print(books[0].addr) #也能拿出来,但是是不合理的
res = Book.objects.raw('select * from app01_publish where id=1') # RawQuerySet 用起来跟列表一样
print(res[0])
print(type(res[0]))
print(res[0].name)
# book 没有addr,但是也打印出来了
print(res[0].addr)
return HttpResponse('ok')
5.一对多
# 一对一:本身是一个表,拆成两个表,做一对一的关联;;;本质就是一对多,只不过关联字段唯一
# 一对多:关联字段写在多的一方
# 多对多:需要建立中间表;;本质也是一对多
# 本质就只有一种外键关系
5.5.1 表模型
# 一对多关系
from sqlalchemy import create_engine
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index
from sqlalchemy.orm import relationship
# 第二步:执行declarative_base,得到一个类
Base = declarative_base()
class Hobby(Base):
__tablename__ = 'hobby'
id = Column(Integer, primary_key=True)
caption = Column(String(50), default='篮球')
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = Column(String(32), index=True, nullable=True)
# hobby指的是tablename而不是类名
# 关联字段写在多的一方,写在Person中,跟hobby表中id字段做外键关联
hobby_id = Column(Integer, ForeignKey("hobby.id"))
# 跟数据库无关,不会新增字段,只用于快速链表操作
# 基于对象的跨表查询:就要加这个字段,取对象 person.hobby pserson.hobby_id
# 类名,backref用于反向查询
hobby = relationship('Hobby', backref='pers') # 如果有hobby对象,拿到所有人 hobby.pers
def __repr__(self):
return self.name
engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa", )
# 把表同步到数据库 (把被Base管理的所有表,都创建到数据库)
Base.metadata.create_all(engine)
# 把所有表删除
# Base.metadata.drop_all(engine)
5.5.2 新增和基于对象的查询
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
from models1 import Hobby, Person
engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa")
Session = sessionmaker(bind=engine)
session = scoped_session(Session)
# 一对多新增
# hobby = Hobby(caption='乒乓球')
# session.add(hobby)
# person = Person(name='张三')
# session.add(person)
# hobby=session.query(Hobby).filter(Hobby.caption=='乒乓球').first()
# # person = Person(name='王五',hobby_id=hobby.id)
# person = Person(name='王五',hobby_id=1)
# session.add(person)
# 支持按对象的增加方式,必须加relationship 做关联
# 方式一
# hobby=session.query(Hobby).filter(Hobby.caption=='乒乓球').first()
# person = Person(name='赵六',hobby=hobby)
# 方式二
# hobby = Hobby(caption='羽毛球') # 表中暂时没有
# person = Person(name='赵六', hobby=hobby)
# session.add_all([person, hobby])
# session.commit()
## 基于对象的跨表查询 .
# 正向查询
# person=session.query(Person).filter(Person.name=='王五').first()
# # print(person.hobby_id)
# print(person.hobby) # Hobby 的对象
# 反向查询
# hobby=session.query(Hobby).filter(Hobby.id==1).first()
# print(hobby.pers)
# 基于连表的查询(一会讲)
6.多对多
6.6.1 表模型
# 一对多关系
from sqlalchemy import create_engine
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index
from sqlalchemy.orm import relationship
# 第二步:执行declarative_base,得到一个类
Base = declarative_base()
# 多对多
# 中间表 手动创建
class Boy2Girl(Base):
__tablename__ = 'boy2girl'
id = Column(Integer, primary_key=True, autoincrement=True)
girl_id = Column(Integer, ForeignKey('girl.id'))
boy_id = Column(Integer, ForeignKey('boy.id'))
class Girl(Base):
__tablename__ = 'girl'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True, nullable=False)
def __str__(self):
return self.name
def __repr__(self):
return self.name
class Boy(Base):
__tablename__ = 'boy'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), unique=True, nullable=False)
# 与生成表结构无关,仅用于查询方便,放在哪个单表中都可以
# 方便快速查询,写了这个字段,相当于django 的manytomany,快速使用基于对象的跨表查询
girls = relationship('Girl', secondary='boy2girl', backref='boys')
def __str__(self):
return self.name
def __repr__(self):
return self.name
engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa", )
# 把表同步到数据库 (把被Base管理的所有表,都创建到数据库)
Base.metadata.create_all(engine)
# 把所有表删除
# Base.metadata.drop_all(engine)
6.6.2 增加基于对象的跨表查询
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
from models2 import Girl, Boy, Boy2Girl
engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa")
Session = sessionmaker(bind=engine)
session = scoped_session(Session)
# 新增
# 1 笨办法新增
# girl=Girl(name='刘亦菲')
# boy=Boy(name='彭于晏')
# session.add_all([girl,boy])
# session.add(Boy2Girl(girl_id=1,boy_id=1))
# session.commit()
# 2 使用relationship
# boy = Boy(name='lqz')
# boy.girls = [Girl(name='迪丽热巴'), Girl(name='景田')]
# session.add(boy)
# session.commit()
# 基于对象的跨表查询
# 正向
# boy = session.query(Boy).filter(Boy.id==2).first()
# print(boy.girls)
# 反向
# girl = session.query(Girl).filter(Girl.id==2).first()
# print(girl.boys)
# 如果没有relationship,纯自己操作
# 基于连表的查询(一会讲)
7.连表查询
### 关联关系,基于连表的跨表查询
from models1 import Person,Hobby
# 链表操作
# select * from person,hobby where person.hobby_id=hobby.id;
# res = session.query(Person, Hobby).filter(Person.hobby_id == Hobby.id).all()
# 自己连表查询
# join表,默认是inner join,自动按外键关联
# select * from Person inner join Hobby on Person.hobby_id=Hobby.id;
# res = session.query(Person).join(Hobby).all()
#isouter=True 外连,表示Person left join Favor,没有右连接,反过来即可
# select * from Person left join Hobby on Person.hobby_id=Hobby.id;
# res = session.query(Person).join(Hobby, isouter=True).all()
# 没有right join,通过这个实现
# res = session.query(Hobby).join(Person, isouter=True).all()
# # 自己指定on条件(连表条件),第二个参数,支持on多个条件,用and_,同上
# select * from Person left join Hobby on Person.id=Hobby.id;
# res = session.query(Person).join(Hobby, Person.hobby_id == Hobby.id, isouter=True) # sql本身有问题,只是给你讲, 自己指定链接字段
# 右链接
# print(res)
# 多对多关系连表
# 多对多关系,基于链表的跨表查
#方式一:直接连
res = session.query(Boy, Girl,Boy2Girl).filter(Boy.id == Boy2Girl.boy_id,Girl.id == Boy2Girl.girl_id).all()
# 方式二:join连
res = session.query(Boy).join(Boy2Girl).join(Girl).filter(Person.id>=2).all()