昨日内容回顾
1. 简述flask上下文管理-threading.local-偏函数-栈2. 原生SQL和ORM有什么优缺点?
开发效率: ORM>原生SQL
执行效率: 原生SQL>ORM
如:SQLAlchemy依赖pymysql3. SQLAlchemy多线程连接的情况
View Code
一、flask标准目录结构
标准flask目录结构
Project name/ #项目名
├── Project name #应用名,保持和项目名同名
│ ├── __init__.py #初始化程序,用来注册蓝图
│ ├── static #静态目录
│ ├── templates #模板目录
│ └── views #蓝图
└── manage.py #启动程序
注意:应用名和项目名要保持一致
蓝图
修改manage.py
from lastday importcreate_app
app=create_app()if __name__ == '__main__':
app.run()
进入views目录,新建文件account.py
from flask importBlueprint
account= Blueprint('account',__name__)
@account.route('/login')deflogin():return '登陆'@account.route('/logout')deflogout():return '注销'
View Code
修改 __init__.py,注册蓝图
from flask importFlaskfrom lastday.views.account importacdefcreate_app():"""创建app应用
:return:"""app= Flask(__name__)
app.register_blueprint(ac)return app
View Code
执行manage.py,访问首页: http://127.0.0.1:5000/login
效果如下:
进入views目录,新建文件user.py
from flask importBlueprint,render_template
us= Blueprint('user',__name__) #蓝图名
@us.route('/user_list/')def user_list(): #注意:不要和蓝图名重复
return "用户列表"
修改 __init__.py,注册蓝图
from flask importFlaskfrom lastday.views.account importacfrom lastday.views.user importusdefcreate_app():"""创建app应用
:return:"""app= Flask(__name__)#注册蓝图
app.register_blueprint(ac)
app.register_blueprint(us)return app
View Code
进入templates目录,创建文件user_list.html
TitleView Code
修改views-->user.py,渲染模板
from flask importBlueprint,render_template
us= Blueprint('user',__name__) #蓝图名
@us.route('/user_list/')def user_list(): #注意:不要和蓝图名重复
return render_template('user_list.html')
View Code
重启manage.py,访问用户列表
在static目录创建images文件夹,放一个图片meinv.jpg
修改 templates\user_list.html
Title
View Code
刷新页面,效果如下:
如果使用蓝图,上面就是官方推荐的写法!
配置文件
在项目根目录,创建settings.py
classBase(object):
SECRET_KEY= "fasdfasdf" #session加密字符串
classProduct(Base):"""线上环境"""
pass
classTesting(Base):"""测试环境"""DEBUG=FalseclassDevelopment(Base):"""开发环境"""DEBUG= True #开启调试
View Code
这里的Base表示3个环境相同的配置
为什么配置文件要用类呢?待会再说
引入配置(推荐写法)
from flask importFlaskfrom lastday.views.account importacfrom lastday.views.user importusdefcreate_app():"""创建app应用
:return:"""app= Flask(__name__)#引入配置文件并应用
app.config.from_object("settings.Development")#注册蓝图
app.register_blueprint(ac)
app.register_blueprint(us)return app
View Code
配置文件使用类之后,如果要切换环境,这里改一下,就可以了!
那么类的静态属性,就是配置!
路由都写在蓝图里面了,如果要对所有请求,做一下操作,怎么办?
加before_request,要在哪里加?
在__init__.py里面加
from flask importFlaskfrom lastday.views.account importacfrom lastday.views.user importusdefcreate_app():"""创建app应用
:return:"""app= Flask(__name__)#引入配置文件并应用
app.config.from_object("settings.Development")#注册蓝图
app.register_blueprint(ac)
app.register_blueprint(us)
@app.before_requestdefb1():print('b1')return app
View Code
重启manage.py,刷新页面,查看Pycharm控制台输出:b1
那么问题来了,如果b1的视图函数,代码有很多行呢?
在create_app里面有一个b1函数。b1函数就是一个闭包!
先来理解一下装饰器的本质,比如b1函数,加了装饰器之后,可以理解为:
b1 = app.before_request(b1)
由于赋值操作没有用到,代码缩减为
app.before_request(b1)
那么完整代码,就可以写成
from flask importFlaskfrom lastday.views.account importacfrom lastday.views.user importusdefcreate_app():"""创建app应用
:return:"""app= Flask(__name__)#引入配置文件并应用
app.config.from_object("settings.Development")#注册蓝图
app.register_blueprint(ac)
app.register_blueprint(us)
app.before_request(b1)#请求到来之前执行
returnappdefb1():print('app_b1')
View Code
其实蓝图,也可以加before_request
修改 views-->account.py
from flask importBlueprint
ac= Blueprint('account',__name__)
@ac.before_requestdefbb():print('account.bb')
@ac.route('/login')deflogin():return '登陆'@ac.route('/logout')deflogout():return '注销'
View Code
重启 manage.py,访问登录页面,注意:后面不要带"/",否则提示Not Found
查看Pycharm控制台输出:
app_b1
account.bb
可以发现,2个before_request都执行了。注意:在__init__.py中的before_request是所有路由都生效的
而account.py中的before_request,只要访问这个蓝图的路由,就会触发!
因此,访问 http://127.0.0.1:5000/logout,也是可以触发account.py中的before_request
完整目录结构如下:
lastday/├── lastday
│ ├──__init__.py
│ ├── static
│ │ └── images
│ │ └── meinv.jpg
│ ├── templates
│ │ └── user_list.html
│ └── views
│ ├── account.py
│ └── user.py
├── manage.py
└── settings.py
总结:
如果对所有的路由要操作,那么在app实例里面,写before_request
如果对单个的蓝图,则在蓝图里面使用before_request
二、flask使用SQLAlchemy
必须先安装模块sqlalchemy
pip3 install sqlalchemy
准备一台MySQL服务器,创建数据库db1
CREATE DATABASE db1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
默认的用户名为root,密码为空
非主流操作
基于上面的项目lastday,进入lastday应用目录,创建文件models.py
from sqlalchemy.ext.declarative importdeclarative_basefrom sqlalchemy importColumn, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Indexfrom sqlalchemy.orm importrelationship#创建了一个基类:models.Model
Base =declarative_base()#在数据库创建表一张表
classUsers(Base):__tablename__ = 'users'id= Column(Integer, primary_key=True)
name= Column(String(32), index=True, nullable=False)#在数据库创建表一张表
classSchool(Base):__tablename__ = 'school'id= Column(Integer, primary_key=True)
name= Column(String(32), index=True, nullable=False)defdb_init():from sqlalchemy importcreate_engine#创建数据库连接
engine =create_engine(#连接数据库db1
"mysql+pymysql://root:@127.0.0.1:3306/db1?charset=utf8",
max_overflow=0, #超过连接池大小外最多创建的连接
pool_size=5, #连接池大小
pool_timeout=30, #池中没有线程最多等待的时间,否则报错
pool_recycle=-1 #多久之后对线程池中的线程进行一次连接的回收(重置)
)
Base.metadata.create_all(engine)#创建操作
#Base.metadata.drop_all(engine) # 删除操作
if __name__ == '__main__':
db_init()
View Code
此时目录结构如下:
lastday/├── lastday
│ ├──__init__.py
│ ├── models.py
│ ├── static
│ │ └── images
│ │ └── meinv.jpg
│ ├── templates
│ │ └── user_list.html
│ └── views
│ ├── account.py
│ └── user.py
├── manage.py
└── settings.py
执行models.py,注意:它会输出一段警告
Warning: (1366, "Incorrect string value: '\\xD6\\xD0\\xB9\\xFA\\xB1\\xEA...' for column 'VARIABLE_VALUE' at row 484")
result= self._query(query)
这个异常是mysql问题,而非python的问题,这是因为mysql的字段类型是utf-xxx, 而在mysql中这些utf-8数据类型只能存储最多三个字节的字符,而存不了包含四个字节的字符。
这个警告,可以直接忽略,使用Navicat软件查看,发现表已经创建完成
修改 user.py,插入一条记录
from flask importBlueprint,render_templatefrom lastday.models importUsers
us= Blueprint('user',__name__) #蓝图名
@us.route('/user_list/')def user_list(): #注意:不要和蓝图名重复
#创建连接
from sqlalchemy importcreate_enginefrom sqlalchemy.orm importsessionmaker
engine= create_engine("mysql+pymysql://root:@127.0.0.1:3306/s11day139?charset=utf8", max_overflow=0, pool_size=5)
Session= sessionmaker(bind=engine)
session=Session()#添加一条记录
session.add_all([
Users(name='xiao')
])
session.commit()return render_template('user_list.html')
View Code
可以发现,这种操作很麻烦。视图函数每次都需要创建mysql连接!
使用flask_sqlalchemy(推荐)
安装模块flask_sqlalchemy
pip3 install flask_sqlalchemy
修改__init__.py,实例化SQLAlchemy,执行db.init_app(app)
from flask importFlaskfrom flask_sqlalchemy importSQLAlchemy#1. 必须放在引入蓝图的上方
db =SQLAlchemy()from lastday.views.account importacfrom lastday.views.user importusdefcreate_app():"""创建app应用
:return:"""app= Flask(__name__)#引入配置文件并应用
app.config.from_object("settings.Development")#2. 执行init_app,读取配置文件SQLAlchemy中相关的配置文件,用于以后:生成数据库/操作数据库(依赖配置文件)
db.init_app(app)#注册蓝图
app.register_blueprint(ac)
app.register_blueprint(us)
app.before_request(b1)#请求到来之前执行
returnappdefb1():print('app_b1')
View Code
查看init_app的源码,大量用到了app.config.setdefault
definit_app(self, app):"""This callback can be used to initialize an application for the
use with this database setup. Never use a database in the context
of an application not initialized that way or connections will
leak."""
if('SQLALCHEMY_DATABASE_URI' not in app.config and
'SQLALCHEMY_BINDS' not inapp.config
):
warnings.warn('Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS is set.'
'Defaulting SQLALCHEMY_DATABASE_URI to "sqlite:///:memory:".')
app.config.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite:///:memory:')
app.config.setdefault('SQLALCHEMY_BINDS', None)
app.config.setdefault('SQLALCHEMY_NATIVE_UNICODE', None)
app.config.setdefault('SQLALCHEMY_ECHO', False)
app.config.setdefault('SQLALCHEMY_RECORD_QUERIES', None)
app.config.setdefault('SQLALCHEMY_POOL_SIZE', None)
app.config.setdefault('SQLALCHEMY_POOL_TIMEOUT', None)
app.config.setdefault('SQLALCHEMY_POOL_RECYCLE', None)
app.config.setdefault('SQLALCHEMY_MAX_OVERFLOW', None)
app.config.setdefault('SQLALCHEMY_COMMIT_ON_TEARDOWN', False)
track_modifications=app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', None
)
View Code
那么就需要将数据库属性,写到settings.py中
classBase(object):
SECRET_KEY= "fasdfasdf" #session加密字符串
SQLALCHEMY_DATABASE_URI= "mysql+pymysql://root:@127.0.0.1:3306/db1?charset=utf8"SQLALCHEMY_POOL_SIZE= 5SQLALCHEMY_POOL_TIMEOUT= 30SQLALCHEMY_POOL_RECYCLE= -1
#追踪对象的修改并且发送信号
SQLALCHEMY_TRACK_MODIFICATIONS =FalseclassProduct(Base):"""线上环境"""
pass
classTesting(Base):"""测试环境"""DEBUG=FalseclassDevelopment(Base):"""开发环境"""DEBUG= True #开启调试
View Code
因此,只要执行了db.init_app(app),它就会读取settings.py中的配置信息
修改models.py,引入__init__.py中的db变量,优化代码
from lastday importdb#在数据库创建表一张表
classUsers(db.Model):__tablename__ = 'users'id= db.Column(db.Integer, primary_key=True)
name= db.Column(db.String(32), index=True, nullable=False)#在数据库创建表一张表
classSchool(db.Model):__tablename__ = 'school'id= db.Column(db.Integer, primary_key=True)
name= db.Column(db.String(32), index=True, nullable=False)
View Code
修改 views-->user.py,导入db,插入一条记录
from flask importBlueprint,render_templatefrom lastday.models importUsersfrom lastday importdb
us= Blueprint('user',__name__) #蓝图名us
@us.route('/user_list/')def user_list(): #注意:不要和蓝图名重复
#添加一条记录
db.session.add(Users(name='xiao'))
db.session.commit()return render_template('user_list.html')
View Code
重启 manage.py ,访问用户列表,使用Navicat查看用户表
发现多了一条记录
如果需要关闭连接,使用 db.session.remove()
修改 views-->user.py,在commit下面加一行即可!
但是这样没有必要!为什么?因为在settings.py,使用了数据库连接池。
关闭之后,再次开启一个线程,是需要消耗cpu的。
三、flask离线脚本
前戏
流程图
注意:核心就是配置,通过db对象,操作models,为蓝图提供数据!
现在有一个需求,需要将数据库中的表删除或者生成数据库中的表,必须通过脚本来完成!
配置文件加载之后,将setttings.py中的属性添加到app.config对象中。如果有app对象,那么就可以得到以下信息:
- 应用上下文中的有:app/g-flask的配置文件:app.config中- app中包含了:SQLAlchemy相关的数据。
web runtime
启动网站,等待用户请求到来。走 __call__/wsig_app/........
注意:上面这些,必须是flask启动的情况下,才能获取。有一个专有名词,叫做web runtime,翻译过来就是:web 运行时!
它是一个web运行状态。某些操作,必须基于它才能实现!
离线脚本
离线脚本,就是非 web 运行时(web服务器停止)的状态下,也能执行的脚本!
正式开始
先关闭flask项目
在项目根目录创建文件table.py,导入create_app和db,这是关键点
from lastday importcreate_app,db
app=create_app()
with app.app_context():
db.drop_all()#删除
执行弹出一个警告,这不用管。
查看db1数据库,发现表已经没有了!
修改table.py,执行创建方法
from lastday importcreate_app,db
app=create_app()
with app.app_context():#db.drop_all() # 删除
db.create_all() #创建
再次执行,发现表出现了
注意:网站并没有启动,但是实现了删表以及创建表操作!
那么这个with,到底执行了什么呢?查看AppContext源代码,看这2个方法
def __enter__(self):
self.push()returnselfdef __exit__(self, exc_type, exc_value, tb):
self.pop(exc_value)if BROKEN_PYPY_CTXMGR_EXIT and exc_type is notNone:
reraise(exc_type, exc_value, tb)
实际上,with是调用了这2个方法。
写一个类测试一下
classFoo(object):passobj=Foo()
with obj:print(123)
执行报错:
AttributeError: __enter__
提示没有__enter__方法
修改代码,增加__enter__方法
classFoo(object):def __enter__(self):passobj=Foo()
with obj:print(123)
执行报错:
AttributeError: __exit__
再增加__exit__方法
classFoo(object):def __enter__(self):print('__enter__')def __exit__(self, exc_type, exc_val, exc_tb):print('__exit__')
obj=Foo()
with obj:print(123)
执行输出:
__enter__
123
__exit__
也就是说,在执行print(123)之前,先执行了__enter__方法。之后,执行了__exit__。
之前学习的python基础中,打开文件操作,使用了with open方法,也是一个道理!
在__enter__执行了打开文件句柄操作,在__exit__执行了关闭文件句柄操作!
总结:
以后写flask,要么是web运行时,要么是离线脚本。
应用场景
1. 夜间定时操作数据库的表时
2. 数据导入。比如网站的三级联动功能,将网上下载的全国城市.txt文件,使用脚本导入到数据库中。
还有敏感词,网上都有现成的。下载txt文件后,导入到数据库中。限制某些用户不能发送敏感词的内容!
3. 数据库初始化,比如表的创建,索引创建等等
4. 银行信用卡,到指定月的日期还款提醒。使用脚本将用户的还款日期遍历处理,给用户发送一个短信提醒。每天执行一次!
5. 新写一个项目时,数据库没有数据。用户第一次使用时,无法直接看效果。写一个脚本,自动录入示例数据,方便用户观看!
四、flask多app应用
flask支持多个app应用,那么它们之间,是如何区分的呢?
是根据url前缀来区分,django多app也是通过url前缀来区分的。
由于url都在蓝图中,为蓝图加前缀,使用url_prefix。
语法:
xxx = Blueprint('account',__name__,url_prefix='/xxx')
修改 views-->account.py,增加前缀
from flask importBlueprint
ac= Blueprint('account',__name__,url_prefix='/xxx')
@ac.before_requestdefbb():print('account.bb')
@ac.route('/login')deflogin():return '登陆'@ac.route('/logout')deflogout():return '注销'
View Code
也可以在__init__.py里面的app.register_blueprint里面加url_prefix,但是不推荐
在项目根目录创建目录other,在里面创建 multi_app.py,不能使用__name__
from flask importFlask
app1= Flask('app1')#app1.config.from_object('xxx') # db1
@app1.route('/f1')deff1():return 'f1'app2= Flask('app2')#app2.config.from_object('xxx') # db2
@app1.route('/f2')deff2():return 'f2'
View Code
上面2个应用,可以连接不同的数据库。
目录结构如下:
./├── lastday
│ ├──__init__.py
│ ├── models.py
│ ├── static
│ │ └── images
│ │ └── meinv.jpg
│ ├── templates
│ │ └── user_list.html
│ └── views
│ ├── account.py
│ └── user.py
├── manage.py
├── other
│ └── multi_app.py
└── settings.py
multi_app.py有2套程序,没有必要写在一起,使用DispatcherMiddleware
查看源码
classDispatcherMiddleware(object):"""Allows one to mount middlewares or applications in a WSGI application.
This is useful if you want to combine multiple WSGI applications::
app = DispatcherMiddleware(app, {
'/app2': app2,
'/app3': app3
})"""
def __init__(self, app, mounts=None):
self.app=app
self.mounts= mounts or{}def __call__(self, environ, start_response):
script= environ.get('PATH_INFO', '')
path_info= ''
while '/' inscript:if script inself.mounts:
app=self.mounts[script]breakscript, last_item= script.rsplit('/', 1)
path_info= '/%s%s' %(last_item, path_info)else:
app=self.mounts.get(script, self.app)
original_script_name= environ.get('SCRIPT_NAME', '')
environ['SCRIPT_NAME'] = original_script_name +script
environ['PATH_INFO'] =path_inforeturn app(environ, start_response)
View Code
它可以将2个app结合在一起,使用run_simple启动
修改 other\multi_app.py
from flask importFlaskfrom werkzeug.wsgi importDispatcherMiddlewarefrom werkzeug.serving importrun_simple
app1= Flask('app1')#app1.config.from_object('xxx') # db1
@app1.route('/f1')deff1():return 'f1'app2= Flask('app2')#app1.config.from_object('xxx') # db2
@app2.route('/f2')deff2():return 'f2'dispachcer=DispatcherMiddleware(app1, {'/admin':app2, #app2指定前缀admin
})if __name__ == '__main__':
run_simple('127.0.0.1',8009,dispachcer)
View Code
执行multi_app.py,访问url
http://127.0.0.1:8009/f1
效果如下:
访问f2,会出现404
因为它规定了前缀 admin,使用下面访问访问,就不会出错了!
多app应用的场景很少见,了解一下,就可以了!
维护栈的目的
在local对象中,存储的数据是这样的。app_ctx是应用上下文
线程id:{stack:[app_ctx]}
它永远存储的是单条数据,它不是真正的栈。如果搞一个字段,直接让stack=app_ctx,照样可以执行。
那么它为什么要维护一个栈呢?因为它要考虑:
在离线脚本和多app应用的情况下特殊代码的实现。
只有这2个条件满足的情况下,才会用到栈!
web运行时
看上图,是web运行时。本质上,要么开多进程,要么开多线程。那么local对象中维护的栈,永远都只有一条数据。
即使是多app应用,也是一样的。一个请求过来,只能选择一个app。比如上面的f1和f2,要么带前缀,要么不带。带前缀,访问f2,否则访问f1
离线脚本
单app应用
在离线脚本中,单app应用,先来看table.py,它就是离线脚本。
from lastday importcreate_app,db
app=create_app()#app_ctx.push()
with app.app_context():
db.create_all()#创建
它创建了app_ctx对象,调用了push方法。将数据放到Local对象中,注意:只放了一次!
local的数据,如果是一个字典,大概是这个样子
{
stack:[app_ctx,]
}
多app应用
修改table.py,注意:下面的是伪代码,直接运行会报错
from lastday importcreate_app,db
app1= create_app1() #db1
app2 = create_app2() #db2
#app_ctx.push()
"""{
stack:[app1_ctx,]
}"""with app1.app_context():#取栈中获取栈顶的app_ctx,使用top方法取栈顶
db.create_all() #创建
如果app1要获取配置文件,从db1种获取
如果加一行代码with呢?
from lastday importcreate_app,dbfrom flask importglobals
app1= create_app1() #db1
app2 = create_app2() #db2
#app_ctx.push()
"""{
stack:[app1_ctx,]
}"""with app1.app_context():#取栈中获取栈顶的app_ctx,使用top方法取栈顶
db.create_all() #创建
with app2.app_context():
View Code
它们都调用了app_context
看globals源码,看最后一行代码
_request_ctx_stack =LocalStack()
_app_ctx_stack= LocalStack()
这2个静态变量,用的是同一个LocalStack(),那么会用同一个Local()。也就是说放到同一个地方去了
修改table.py
from lastday importcreate_app,dbfrom flask importglobals
app1= create_app1() #db1
app2 = create_app2() #db2
#app_ctx.push()
"""{
stack:[app1_ctx,app2_ctx,]
}"""with app1.app_context():#取栈中获取栈顶的app_ctx,使用top方法取栈顶
db.create_all() #创建
with app2.app_context():
db.create_all()
View Code
执行到with这一行时,stack里面有2个对象,分别是app1_ctx和app2_ctx。
那么执行到with下面的db.create_all()时,它会连接哪个数据库?
答案是: 取栈顶的app2_ctx,配置文件是db2
修改 table.py,增加db.drop_all(),它会删除哪个数据库?
from lastday importcreate_app,dbfrom flask importglobals
app1= create_app1() #db1
app2 = create_app2() #db2
#app_ctx.push()
"""{
stack:[app1_ctx,app2_ctx,]
}"""with app1.app_context():#取栈中获取栈顶的app_ctx,使用top方法取栈顶
db.create_all() #创建
with app2.app_context():
db.create_all()
db.drop_all()
View Code
答案是:db1
为什么呢?因为执行with时,进来时调用了__enter__方法,将app2_ctx加进去了。此时位于栈顶!
结束时,调用__exit__方法,取栈顶,将app2_ctx给pop掉了!也就是删除!
那么执行db.drop_all()时,此时栈里面只有一个数据app1_ctx,取栈顶,就是app1_ctx
这就它设计使用栈的牛逼之处!
通过栈顶的数据不一样,来完成多app操作!
看下面的动态图,就是栈的变化
关于flask维护栈的详细信息,请参考链接:
五、flask_script
Flask Script扩展提供向Flask插入外部脚本的功能,包括运行一个开发用的服务器,一个定制的Python shell,设置数据库的脚本,cronjobs,及其他运行在web应用之外的命令行任务;使得脚本和系统分开;
Flask Script和Flask本身的工作方式类似,只需定义和添加从命令行中被Manager实例调用的命令;
安装模块
pip3 install flask_script
修改 manage.py
from flask_script importManagerfrom lastday importcreate_app
app=create_app()
manager=Manager(app)if __name__ == '__main__':
manager.run()
View Code
执行 manage.py,报错
optional arguments:-?, --help show this help message and exit
是因为不能用原来的方式调用了。
使用以下方式执行
python manage.py runserver -h 127.0.0.1 -p 8009
访问登录页面
自定义命令
flask_script还可以做一些自定义命令,列如:
修改 manage.py
from flask_script importManagerfrom lastday importcreate_app
app=create_app()
manager=Manager(app)
@manager.commanddefc1(arg):"""自定义命令login
python manage.py custom 123
:param arg:
:return:"""
print(arg)
@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')defc2(name, url):"""自定义命令
执行: python manage.py cmd -n wupeiqi -u http://www.oldboyedu.com
:param name:
:param url:
:return:"""
print(name, url)if __name__ == '__main__':
manager.run()
View Code
在终端执行c1命令
python manage.py c1 22
执行输出:22
注意:这里的c1,指的是manage.py中的c1函数
在终端执行c2命令
python manage.py c2 -n 1 -u 9
执行输出:
1 9
以上,可以看到,它和django启动方式很像
六、flask_migrate
flask-migrate是flask的一个扩展模块,主要是扩展数据库表结构的.
安装模块
pip3 install flask_migrate
使用
修改 manage.py
#1.1
from flask_script importManager#2.1
from flask_migrate importMigrate, MigrateCommandfrom lastday importdbfrom lastday importcreate_app
app=create_app()#1.2
manager =Manager(app)#2.2
Migrate(app, db)#2.3
"""# 数据库迁移命名
python manage.py db init
python manage.py db migrate -> makemigrations
python manage.py db upgrade -> migrate"""manager.add_command('db', MigrateCommand)
@manager.commanddefc1(arg):"""自定义命令
python manage.py custom 123
:param arg:
:return:"""
print(arg)
@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')defc2(name, url):"""自定义命令
执行: python manage.py cmd -n wupeiqi -u http://www.oldboyedu.com
:param name:
:param url:
:return:"""
print(name, url)if __name__ == '__main__':#python manage.py runserver -h 127.0.0.1 -p 8999
#1.3
manager.run()
View Code
执行init
必须先执行init,只需要执行一次就可以了!
python manage.py db init
它会在项目根目录创建migrations文件夹
执行migrate
python manage.py db migrate
执行upgrade
python manage.py db upgrade
测试
修改 models.py,去掉School中的name属性
from lastday importdb#在数据库创建表一张表
classUsers(db.Model):__tablename__ = 'users'id= db.Column(db.Integer, primary_key=True)
name= db.Column(db.String(32), index=True, nullable=False)#在数据库创建表一张表
classSchool(db.Model):__tablename__ = 'school'id= db.Column(db.Integer, primary_key=True)#name = db.Column(db.String(32), index=True, nullable=False)
View Code
先执行migrate,再执行upgrade
使用Navicat查看school表,发现name字段没有了!
它是如何实现的呢?在migrations-->versions目录里面,有一个xx.py,它记录的models.py的修改。
那么它和django也是同样,有一个文件记录变化。
那么因此,当flask的插件越装越多时,它和django是一样的
七、pipreqs
介绍
pipreqs可以通过对项目目录的扫描,自动发现使用了那些类库,自动生成依赖清单。缺点是可能会有些偏差,需要检查并自己调整下。
假设一个场景:小a刚去新公司入职。领导让它做2件事情,1. 安装python环境,安装django 2. 看项目代码,一周之后,写需求。
安装django之后,运行项目代码报错了,提示没有安装xx模块!
然后默默的安装了一下xx模块,再次运行,又报错了。再安装....,最后发现安装了30个安装包!
最后再运行,发现还是报错了!不是xx模块问题,是xx语法报错!
这个时候问领导,这些模块,都是什么版本啊?
一般代码上线,交给运维。你要告诉它,这个项目,需要安装xx模块,版本是多少。写一个文件,甩给运维!这样太麻烦了!
为了避免上述问题,出现了pipreps模块,它的作用是:自动找到程序中应用的包和版本
安装模块
pip3 install pipreqs
使用pipreqs
注意:由于windows默认是gbk编码,必须指定编码为utf-8,否则报错!
E:\python_script\Flask框架\day5\lastday> pipreqs ./ --encoding=utf-8
执行输出:
INFO: Successfully saved requirements file in E:\python_script\Flask框架\day5\lastday\requirements.txt
它会在当前目录中,生成一个requirements.txt文件
查看文件内容
Flask_SQLAlchemy==2.3.2Flask==1.0.2
左边是模块名,右边是版本
那么有了这个requirements.txt文件,就可以自动安装模块了
pip3 install -r requirements.txt
它会根据文件内容,自动安装!
因此,写python项目时,一定要有requirements.txt文件才行!
github项目也是一样的!
今日内容总结:
内容详细:1. flask &SQLAlchemy
安装:
flask-sqlalchemy
使用:
a. 在配置文件中设置连接字符串
SQLALCHEMY_DATABASE_URI= "mysql+pymysql://root:@127.0.0.1:3306/s11lastday?charset=utf8"SQLALCHEMY_POOL_SIZE= 5SQLALCHEMY_POOL_TIMEOUT= 30SQLALCHEMY_POOL_RECYCLE= -1
#追踪对象的修改并且发送信号
SQLALCHEMY_TRACK_MODIFICATIONS =False
b.__init__.pyfrom flask importFlaskfrom flask_sqlalchemy importSQLAlchemy#1. 必须放在引入蓝图的上方
db =SQLAlchemy()from lastday.views.user importuserdefcreate_app():
app= Flask(__name__)
app.config.from_object("settings.Development")#2. 执行init_app,读取配置文件SQLAlchemy中相关的配置文件,用于以后:生成数据库/操作数据库(依赖配置文件)
db.init_app(app)
app.register_blueprint(user)returnapp
c. models.pyfrom lastday importdb#在数据库创建表一张表
classUsers(db.Model):__tablename__ = 'users'id= db.Column(db.Integer, primary_key=True)
name= db.Column(db.String(32), index=True, nullable=False)#在数据库创建表一张表
classSchool(db.Model):__tablename__ = 'school'id= db.Column(db.Integer, primary_key=True)
name= db.Column(db.String(32), index=True, nullable=False)
d. 蓝图from flask importBlueprint,render_templatefrom lastday.models importUsersfrom lastday importdb
user= Blueprint('user',__name__)
@user.route('/user_list/')defuser_list():
db.session.add(Users(name='王超'))
db.session.commit()
db.session.remove()return render_template('user_list.html')
疑问:将数据库中的表删除 或 生成数据库中的表。
通过脚本完成。
前戏:- 应用上下文中的有:app/g-flask的配置文件:app.config中-app中包含了:SQLAlchemy相关的数据。
代码:from lastday importcreate_app,db
app=create_app()
with app.app_context():#db.drop_all()
db.create_all()
名词:
web runtime :启动网站,等待用户请求到来。走__call__/wsig_app/........
离线脚本:from lastday importcreate_app,db
app=create_app()
with app.app_context():#db.drop_all()
db.create_all()
应用场景:1. 录入基础数据2. 定时数据处理(定时任务)
赠送:多app应用from flask importFlaskfrom werkzeug.wsgi importDispatcherMiddlewarefrom werkzeug.serving importrun_simple
app1= Flask('app1')#app1.config.from_object('xxx') # db1
@app1.route('/f1')deff1():return 'f1'app2= Flask('app2')#app1.config.from_object('xxx') # db2
@app2.route('/f2')deff2():return 'f2'dispachcer=DispatcherMiddleware(app1, {'/admin':app2,
})if __name__ == '__main__':
run_simple('127.0.0.1',8009,dispachcer)
问题:为什么Flask中要在上下文管理中将Local中的数据:ctx/app_ctx维护成一个栈?
应用flask要考虑,在离线脚本和多app应用的情况下特殊代码的实现。
在web运行时中:Local对象中维护的栈 [ctx, ]
在离线脚本中:-单app应用:from lastday importcreate_app,db
app=create_app()#app_ctx.push()
"""{
stack:[app_ctx, ]
}"""with app.app_context():
db.create_all()-多app应用:from lastday importcreate_app,db
app1= create_app1() #db1
app2 = create_app2() #db2
from flask importglobals#app_ctx.push()
"""{
stack:[app1_ctx,app2_ctx ]
}"""with app1.app_context():#去栈中获取栈顶的app_ctx: app1_ctx,配置文件:db1
db.create_all()
with app2.app_context():
db.create_all()#去栈中获取栈顶的app_ctx: app2_ctx,配置文件:db2
db.drop_all()#去栈中获取栈顶的app_ctx: app1_ctx,配置文件:db1
总结:1. flask-sqlalchemy,帮助用户快速实现Flask中应用SQLAlchemy2. 多app应用3. 离线脚本4. 为什么Flask中要在上下文管理中将Local中的数据:ctx/app_ctx维护成一个栈?- 离线脚本+多app应用才会在栈中存多个上下文对象: [ctx1,ctx2,]-其他:[ctx, ]2. flask-script
安装:flask-script
作用:制作脚本启动3. flask-migrate(依赖flask-script )
安装:flask-migrate
使用:#1.1
from flask_script importManager#2.1
from flask_migrate importMigrate, MigrateCommandfrom lastday importdbfrom lastday importcreate_app
app=create_app()#1.2
manager =Manager(app)#2.2
Migrate(app, db)#2.3
"""# 数据库迁移命名
python manage.py db init
python manage.py db migrate -> makemigrations
python manage.py db upgrade -> migrate"""manager.add_command('db', MigrateCommand)
@manager.commanddefc1(arg):"""自定义命令
python manage.py custom 123
:param arg:
:return:"""
print(arg)
@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')defc2(name, url):"""自定义命令
执行: python manage.py cmd -n wupeiqi -u http://www.oldboyedu.com
:param name:
:param url:
:return:"""
print(name, url)if __name__ == '__main__':#python manage.py runserver -h 127.0.0.1 -p 8999
#1.3
manager.run()4. pipreqs
安装:pipreqs
作用:自动找到程序中应用的包和版本。
pipreqs ./ --encoding=utf-8pip3 install-r requirements.txt
重点:1. 使用(*)- flask-sqlalchemy- flask-migrate- flask-script2. flask上下文相关 (*****)-对象关键字:LocalStack、Local- 离线脚本 &web 运行时-多app应用- Local中为什么维护成栈?
View Code