flask

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
posted on 2022-10-12 14:04  AprilX  阅读(59)  评论(0编辑  收藏  举报