1. 简述flask上下文管理 - threading.local - 偏函数 - 栈 2. 原生SQL和ORM有什么优缺点? 开发效率: ORM > 原生SQL 执行效率: 原生SQL> ORM 如:SQLAlchemy依赖pymysql 3. SQLAlchemy多线程链接的状况
Project name/ # 项目名 ├── Project name # 应用名,保持和项目名同名 │ ├── __init__.py # 初始化程序,用来注册蓝图 │ ├── static # 静态目录 │ ├── templates # 模板目录 │ └── views # 蓝图 └── manage.py # 启动程序
注意:应用名和项目名要保持一致html
修改manage.pypython
from lastday import create_app app = create_app() if __name__ == '__main__': app.run()
进入views目录,新建文件account.pymysql
from flask import Blueprint account = Blueprint('account',__name__) @account.route('/login') def login(): return '登录' @account.route('/logout') def logout(): return '注销'
修改 __init__.py,注册蓝图git
from flask import Flask from lastday.views.account import ac def create_app(): """ 建立app应用 :return: """ app = Flask(__name__) app.register_blueprint(ac) return app
执行manage.py,访问首页: http://127.0.0.1:5000/logingithub
效果以下:web
进入views目录,新建文件user.pysql
from flask import Blueprint,render_template us = Blueprint('user',__name__) # 蓝图名 @us.route('/user_list/') def user_list(): # 注意:不要和蓝图名重复 return "用户列表"
修改 __init__.py,注册蓝图shell
from flask import Flask from lastday.views.account import ac from lastday.views.user import us def create_app(): """ 建立app应用 :return: """ app = Flask(__name__) # 注册蓝图 app.register_blueprint(ac) app.register_blueprint(us) return app
进入templates目录,建立文件user_list.html数据库
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>用户列表</h1> </body> </html>
修改views-->user.py,渲染模板django
from flask import Blueprint,render_template us = Blueprint('user',__name__) # 蓝图名 @us.route('/user_list/') def user_list(): # 注意:不要和蓝图名重复 return render_template('user_list.html')
重启manage.py,访问用户列表
在static目录建立images文件夹,放一个图片meinv.jpg
修改 templates\user_list.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>用户列表</h1> <div>桥本环奈</div> <img src="/static/images/meinv.jpg"> </body> </html>
刷新页面,效果以下:
若是使用蓝图,上面就是官方推荐的写法!
在项目根目录,建立settings.py
class Base(object): SECRET_KEY = "fasdfasdf" # session加密字符串 class Product(Base): """ 线上环境 """ pass class Testing(Base): """ 测试环境 """ DEBUG = False class Development(Base): """ 开发环境 """ DEBUG = True # 开启调试
这里的Base表示3个环境相同的配置
为何配置文件要用类呢?待会再说
from flask import Flask from lastday.views.account import ac from lastday.views.user import us def create_app(): """ 建立app应用 :return: """ app = Flask(__name__) # 引入配置文件并应用 app.config.from_object("settings.Development") # 注册蓝图 app.register_blueprint(ac) app.register_blueprint(us) return app
配置文件使用类以后,若是要切换环境,这里改一下,就能够了!
那么类的静态属性,就是配置!
路由都写在蓝图里面了,若是要对全部请求,作一下操做,怎么办?
加before_request,要在哪里加?
在__init__.py里面加
from flask import Flask from lastday.views.account import ac from lastday.views.user import us def create_app(): """ 建立app应用 :return: """ app = Flask(__name__) # 引入配置文件并应用 app.config.from_object("settings.Development") # 注册蓝图 app.register_blueprint(ac) app.register_blueprint(us) @app.before_request def b1(): print('b1') return app
重启manage.py,刷新页面,查看Pycharm控制台输出:b1
那么问题来了,若是b1的视图函数,代码有不少行呢?
在create_app里面有一个b1函数。b1函数就是一个闭包!
先来理解一下装饰器的本质,好比b1函数,加了装饰器以后,能够理解为:
b1 = app.before_request(b1)
因为赋值操做没有用到,代码缩减为
app.before_request(b1)
那么完整代码,就能够写成
from flask import Flask from lastday.views.account import ac from lastday.views.user import us def create_app(): """ 建立app应用 :return: """ app = Flask(__name__) # 引入配置文件并应用 app.config.from_object("settings.Development") # 注册蓝图 app.register_blueprint(ac) app.register_blueprint(us) app.before_request(b1) # 请求到来以前执行 return app def b1(): print('app_b1')
其实蓝图,也能够加before_request
修改 views-->account.py
from flask import Blueprint ac = Blueprint('account',__name__) @ac.before_request def bb(): print('account.bb') @ac.route('/login') def login(): return '登录' @ac.route('/logout') def logout(): return '注销'
重启 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
必须先安装模块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 import declarative_base from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index from sqlalchemy.orm import relationship # 建立了一个基类:models.Model Base = declarative_base() # 在数据库建立表一张表 class Users(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String(32), index=True, nullable=False) # 在数据库建立表一张表 class School(Base): __tablename__ = 'school' id = Column(Integer, primary_key=True) name = Column(String(32), index=True, nullable=False) def db_init(): from sqlalchemy import create_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()
此时目录结构以下:
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 import Blueprint,render_template from lastday.models import Users us = Blueprint('user',__name__) # 蓝图名 @us.route('/user_list/') def user_list(): # 注意:不要和蓝图名重复 # 建立链接 from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker 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')
能够发现,这种操做很麻烦。视图函数每次都须要建立mysql链接!
安装模块flask_sqlalchemy
pip3 install flask_sqlalchemy
修改__init__.py,实例化SQLAlchemy,执行db.init_app(app)
from flask import Flask from flask_sqlalchemy import SQLAlchemy # 1. 必须放在引入蓝图的上方 db = SQLAlchemy() from lastday.views.account import ac from lastday.views.user import us def create_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) # 请求到来以前执行 return app def b1(): print('app_b1')
查看init_app的源码,大量用到了app.config.setdefault
def init_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 in app.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 )
那么就须要将数据库属性,写到settings.py中
class Base(object): SECRET_KEY = "fasdfasdf" # session加密字符串 SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:@127.0.0.1:3306/db1?charset=utf8" SQLALCHEMY_POOL_SIZE = 5 SQLALCHEMY_POOL_TIMEOUT = 30 SQLALCHEMY_POOL_RECYCLE = -1 # 追踪对象的修改而且发送信号 SQLALCHEMY_TRACK_MODIFICATIONS = False class Product(Base): """ 线上环境 """ pass class Testing(Base): """ 测试环境 """ DEBUG = False class Development(Base): """ 开发环境 """ DEBUG = True # 开启调试
所以,只要执行了db.init_app(app),它就会读取settings.py中的配置信息
修改models.py,引入__init__.py中的db变量,优化代码
from lastday import db # 在数据库建立表一张表 class Users(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(32), index=True, nullable=False) # 在数据库建立表一张表 class School(db.Model): __tablename__ = 'school' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(32), index=True, nullable=False)
修改 views-->user.py,导入db,插入一条记录
from flask import Blueprint,render_template from lastday.models import Users from lastday import db 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')
重启 manage.py ,访问用户列表,使用Navicat查看用户表
发现多了一条记录
若是须要关闭链接,使用 db.session.remove()
修改 views-->user.py,在commit下面加一行便可!
可是这样没有必要!为何?由于在settings.py,使用了数据库链接池。
关闭以后,再次开启一个线程,是须要消耗cpu的。
注意:核心就是配置,经过db对象,操做models,为蓝图提供数据!
如今有一个需求,须要将数据库中的表删除或者生成数据库中的表,必须经过脚原本完成!
配置文件加载以后,将setttings.py中的属性添加到app.config对象中。若是有app对象,那么就能够获得如下信息:
- 应用上下文中的有:app/g - flask的配置文件:app.config中 - app中包含了:SQLAlchemy相关的数据。
启动网站,等待用户请求到来。走 __call__/wsig_app/........
注意:上面这些,必须是flask启动的状况下,才能获取。有一个专有名词,叫作web runtime,翻译过来就是:web 运行时!
它是一个web运行状态。某些操做,必须基于它才能实现!
离线脚本,就是非 web 运行时(web服务器中止)的状态下,也能执行的脚本!
先关闭flask项目
在项目根目录建立文件table.py,导入create_app和db,这是关键点
from lastday import create_app,db app = create_app() with app.app_context(): db.drop_all() # 删除
执行弹出一个警告,这不用管。
查看db1数据库,发现表已经没有了!
修改table.py,执行建立方法
from lastday import create_app,db app = create_app() with app.app_context(): # db.drop_all() # 删除 db.create_all() # 建立
再次执行,发现表出现了
注意:网站并无启动,可是实现了删表以及建立表操做!
那么这个with,到底执行了什么呢?查看AppContext源代码,看这2个方法
def __enter__(self): self.push() return self def __exit__(self, exc_type, exc_value, tb): self.pop(exc_value) if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None: reraise(exc_type, exc_value, tb)
实际上,with是调用了这2个方法。
写一个类测试一下
class Foo(object): pass obj = Foo() with obj: print(123)
执行报错:
AttributeError: __enter__
提示没有__enter__方法
修改代码,增长__enter__方法
class Foo(object): def __enter__(self): pass obj = Foo() with obj: print(123)
执行报错:
AttributeError: __exit__
再增长__exit__方法
class Foo(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应用,那么它们之间,是如何区分的呢?
是根据url前缀来区分,django多app也是经过url前缀来区分的。
因为url都在蓝图中,为蓝图加前缀,使用url_prefix。
语法:
xxx = Blueprint('account',__name__,url_prefix='/xxx')
修改 views-->account.py,增长前缀
from flask import Blueprint ac = Blueprint('account',__name__,url_prefix='/xxx') @ac.before_request def bb(): print('account.bb') @ac.route('/login') def login(): return '登录' @ac.route('/logout') def logout(): return '注销'
也能够在__init__.py里面的app.register_blueprint里面加url_prefix,可是不推荐
在项目根目录建立目录other,在里面建立 multi_app.py,不能使用__name__
from flask import Flask app1 = Flask('app1') # app1.config.from_object('xxx') # db1 @app1.route('/f1') def f1(): return 'f1' app2 = Flask('app2') # app2.config.from_object('xxx') # db2 @app1.route('/f2') def f2(): return 'f2'
上面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
查看源码
class DispatcherMiddleware(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 '/' in script: if script in self.mounts: app = self.mounts[script] break script, 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_info return app(environ, start_response)
它能够将2个app结合在一块儿,使用run_simple启动
修改 other\multi_app.py
from flask import Flask from werkzeug.wsgi import DispatcherMiddleware from werkzeug.serving import run_simple app1 = Flask('app1') # app1.config.from_object('xxx') # db1 @app1.route('/f1') def f1(): return 'f1' app2 = Flask('app2') # app1.config.from_object('xxx') # db2 @app2.route('/f2') def f2(): return 'f2' dispachcer = DispatcherMiddleware(app1, { '/admin':app2, # app2指定前缀admin }) if __name__ == '__main__': run_simple('127.0.0.1',8009,dispachcer)
执行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运行时。本质上,要么开多进程,要么开多线程。那么local对象中维护的栈,永远都只有一条数据。
即便是多app应用,也是同样的。一个请求过来,只能选择一个app。好比上面的f1和f2,要么带前缀,要么不带。带前缀,访问f2,不然访问f1
在离线脚本中,单app应用,先来看table.py,它就是离线脚本。
from lastday import create_app,db app = create_app() # app_ctx.push() with app.app_context(): db.create_all() # 建立
它建立了app_ctx对象,调用了push方法。将数据放到Local对象中,注意:只放了一次!
local的数据,若是是一个字典,大概是这个样子
{
stack:[app_ctx,]
}
修改table.py,注意:下面的是伪代码,直接运行会报错
from lastday import create_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 import create_app,db from flask import globals 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():
它们都调用了app_context
看globals源码,看最后一行代码
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
这2个静态变量,用的是同一个LocalStack(),那么会用同一个Local()。也就是说放到同一个地方去了
修改table.py
from lastday import create_app,db from flask import globals 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()
执行到with这一行时,stack里面有2个对象,分别是app1_ctx和app2_ctx。
那么执行到with下面的db.create_all()时,它会链接哪一个数据库?
答案是: 取栈顶的app2_ctx,配置文件是db2
修改 table.py,增长db.drop_all(),它会删除哪一个数据库?
from lastday import create_app,db from flask import globals 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()
答案是:db1
为何呢?由于执行with时,进来时调用了__enter__方法,将app2_ctx加进去了。此时位于栈顶!
结束时,调用__exit__方法,取栈顶,将app2_ctx给pop掉了!也就是删除!
那么执行db.drop_all()时,此时栈里面只有一个数据app1_ctx,取栈顶,就是app1_ctx
这就它设计使用栈的牛逼之处!
经过栈顶的数据不同,来完成多app操做!
看下面的动态图,就是栈的变化
关于flask维护栈的详细信息,请参考连接:
http://www.javashuo.com/article/p-aexrsilv-ec.html
Flask Script扩展提供向Flask插入外部脚本的功能,包括运行一个开发用的服务器,一个定制的Python shell,设置数据库的脚本,cronjobs,及其余运行在web应用以外的命令行任务;使得脚本和系统分开;
Flask Script和Flask自己的工做方式相似,只需定义和添加从命令行中被Manager实例调用的命令;
官方文档:http://flask-script.readthedocs.io/en/latest/
pip3 install flask_script
修改 manage.py
from flask_script import Manager from lastday import create_app app = create_app() manager = Manager(app) if __name__ == '__main__': manager.run()
执行 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 import Manager from lastday import create_app app = create_app() manager = Manager(app) @manager.command def c1(arg): """ 自定义命令login python manage.py custom 123 :param arg: :return: """ print(arg) @manager.option('-n', '--name', dest='name') @manager.option('-u', '--url', dest='url') def c2(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()
在终端执行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的一个扩展模块,主要是扩展数据库表结构的.
官方文档:http://flask-migrate.readthedocs.io/en/latest/
pip3 install flask_migrate
修改 manage.py
# 1.1 from flask_script import Manager # 2.1 from flask_migrate import Migrate, MigrateCommand from lastday import db from lastday import create_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.command def c1(arg): """ 自定义命令 python manage.py custom 123 :param arg: :return: """ print(arg) @manager.option('-n', '--name', dest='name') @manager.option('-u', '--url', dest='url') def c2(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()
必须先执行init,只须要执行一次就能够了!
python manage.py db init
它会在项目根目录建立migrations文件夹
python manage.py db migrate
python manage.py db upgrade
修改 models.py,去掉School中的name属性
from lastday import db # 在数据库建立表一张表 class Users(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(32), index=True, nullable=False) # 在数据库建立表一张表 class School(db.Model): __tablename__ = 'school' id = db.Column(db.Integer, primary_key=True) # name = db.Column(db.String(32), index=True, nullable=False)
先执行migrate,再执行upgrade
使用Navicat查看school表,发现name字段没有了!
它是如何实现的呢?在migrations-->versions目录里面,有一个xx.py,它记录的models.py的修改。
那么它和django也是一样,有一个文件记录变化。
那么所以,当flask的插件越装越多时,它和django是同样的
pipreqs能够经过对项目目录的扫描,自动发现使用了那些类库,自动生成依赖清单。缺点是可能会有些误差,须要检查并本身调整下。
假设一个场景:小a刚去新公司入职。领导让它作2件事情,1. 安装python环境,安装django 2. 看项目代码,一周以后,写需求。
安装django以后,运行项目代码报错了,提示没有安装xx模块!
而后默默的安装了一下xx模块,再次运行,又报错了。再安装....,最后发现安装了30个安装包!
最后再运行,发现仍是报错了!不是xx模块问题,是xx语法报错!
这个时候问领导,这些模块,都是什么版本啊?
通常代码上线,交给运维。你要告诉它,这个项目,须要安装xx模块,版本是多少。写一个文件,甩给运维!这样太麻烦了!
为了不上述问题,出现了pipreps模块,它的做用是: 自动找到程序中应用的包和版本
pip3 install 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.2
Flask==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 = 5 SQLALCHEMY_POOL_TIMEOUT = 30 SQLALCHEMY_POOL_RECYCLE = -1 # 追踪对象的修改而且发送信号 SQLALCHEMY_TRACK_MODIFICATIONS = False b. __init__.py from flask import Flask from flask_sqlalchemy import SQLAlchemy # 1. 必须放在引入蓝图的上方 db = SQLAlchemy() from lastday.views.user import user def create_app(): app = Flask(__name__) app.config.from_object("settings.Development") # 2. 执行init_app,读取配置文件SQLAlchemy中相关的配置文件,用于之后:生成数据库/操做数据库(依赖配置文件) db.init_app(app) app.register_blueprint(user) return app c. models.py from lastday import db # 在数据库建立表一张表 class Users(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(32), index=True, nullable=False) # 在数据库建立表一张表 class School(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 import Blueprint,render_template from lastday.models import Users from lastday import db user = Blueprint('user',__name__) @user.route('/user_list/') def user_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 import create_app,db app = create_app() with app.app_context(): # db.drop_all() db.create_all() 名词: web runtime :启动网站,等待用户请求到来。走 __call__/wsig_app/........ 离线脚本: from lastday import create_app,db app = create_app() with app.app_context(): # db.drop_all() db.create_all() 应用场景: 1. 录入基础数据 2. 定时数据处理(定时任务) 赠送:多app应用 from flask import Flask from werkzeug.wsgi import DispatcherMiddleware from werkzeug.serving import run_simple app1 = Flask('app1') # app1.config.from_object('xxx') # db1 @app1.route('/f1') def f1(): return 'f1' app2 = Flask('app2') # app1.config.from_object('xxx') # db2 @app2.route('/f2') def f2(): 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 import create_app,db app = create_app() # app_ctx.push() """ { stack:[app_ctx, ] } """ with app.app_context(): db.create_all() - 多app应用: from lastday import create_app,db app1 = create_app1() # db1 app2 = create_app2() # db2 from flask import globals # 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中应用SQLAlchemy 2. 多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 import Manager # 2.1 from flask_migrate import Migrate, MigrateCommand from lastday import db from lastday import create_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.command def c1(arg): """ 自定义命令 python manage.py custom 123 :param arg: :return: """ print(arg) @manager.option('-n', '--name', dest='name') @manager.option('-u', '--url', dest='url') def c2(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-8 pip3 install -r requirements.txt 重点: 1. 使用(*) - flask-sqlalchemy - flask-migrate - flask-script 2. flask上下文相关 (*****) - 对象关键字:LocalStack、Local - 离线脚本 & web 运行时 - 多app应用 - Local中为何维护成栈?