昨日内容回顾

  1. 1. 简述flask上下文管理
  2. - threading.local
  3. - 偏函数
  4. -
  5. 2. 原生SQLORM有什么优缺点?
  6. 开发效率: ORM > 原生SQL
  7. 执行效率: 原生SQL> ORM
  8. 如:SQLAlchemy依赖pymysql
  9. 3. SQLAlchemy多线程连接的情况

一、flask标准目录结构

标准flask目录结构

  1. Project name/ # 项目名
  2. ├── Project name # 应用名,保持和项目名同名
  3. ├── __init__.py # 初始化程序,用来注册蓝图
  4. ├── static # 静态目录
  5. ├── templates # 模板目录
  6. └── views # 蓝图
  7. └── manage.py # 启动程序

注意:应用名和项目名要保持一致

蓝图

修改manage.py

  1. from lastday import create_app
  2. app = create_app()
  3. if __name__ == '__main__':
  4. app.run()

进入views目录,新建文件account.py

  1. from flask import Blueprint
  2. account = Blueprint('account',__name__)
  3. @account.route('/login')
  4. def login():
  5. return '登陆'
  6. @account.route('/logout')
  7. def logout():
  8. return '注销'

修改 init.py,注册蓝图

  1. from flask import Flask
  2. from lastday.views.account import ac
  3. def create_app():
  4. """
  5. 创建app应用
  6. :return:
  7. """
  8. app = Flask(__name__)
  9. app.register_blueprint(ac)
  10. return app

执行manage.py,访问首页: http://127.0.0.1:5000/login

效果如下:

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图1

进入views目录,新建文件user.py

  1. from flask import Blueprint,render_template
  2. us = Blueprint('user',__name__) # 蓝图名
  3. @us.route('/user_list/')
  4. def user_list(): # 注意:不要和蓝图名重复
  5. return "用户列表"

修改 init.py,注册蓝图

  1. from flask import Flask
  2. from lastday.views.account import ac
  3. from lastday.views.user import us
  4. def create_app():
  5. """
  6. 创建app应用
  7. :return:
  8. """
  9. app = Flask(__name__)
  10. # 注册蓝图
  11. app.register_blueprint(ac)
  12. app.register_blueprint(us)
  13. return app

进入templates目录,创建文件user_list.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <h1>用户列表</h1>
  9. </body>
  10. </html>

修改views—>user.py,渲染模板

  1. from flask import Blueprint,render_template
  2. us = Blueprint('user',__name__) # 蓝图名
  3. @us.route('/user_list/')
  4. def user_list(): # 注意:不要和蓝图名重复
  5. return render_template('user_list.html')

重启manage.py,访问用户列表

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图2

在static目录创建images文件夹,放一个图片meinv.jpg

修改 templates\user_list.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <h1>用户列表</h1>
  9. <div>桥本环奈</div>
  10. <img src="/static/images/meinv.jpg">
  11. </body>
  12. </html>

刷新页面,效果如下:

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图3

如果使用蓝图,上面就是官方推荐的写法!

配置文件

在项目根目录,创建settings.py

  1. class Base(object):
  2. SECRET_KEY = "fasdfasdf" # session加密字符串
  3. class Product(Base):
  4. """
  5. 线上环境
  6. """
  7. pass
  8. class Testing(Base):
  9. """
  10. 测试环境
  11. """
  12. DEBUG = False
  13. class Development(Base):
  14. """
  15. 开发环境
  16. """
  17. DEBUG = True # 开启调试

这里的Base表示3个环境相同的配置

为什么配置文件要用类呢?待会再说

引入配置(推荐写法)

  1. from flask import Flask
  2. from lastday.views.account import ac
  3. from lastday.views.user import us
  4. def create_app():
  5. """
  6. 创建app应用
  7. :return:
  8. """
  9. app = Flask(__name__)
  10. # 引入配置文件并应用
  11. app.config.from_object("settings.Development")
  12. # 注册蓝图
  13. app.register_blueprint(ac)
  14. app.register_blueprint(us)
  15. return app

配置文件使用类之后,如果要切换环境,这里改一下,就可以了!

那么类的静态属性,就是配置!

路由都写在蓝图里面了,如果要对所有请求,做一下操作,怎么办?

加before_request,要在哪里加?

init.py里面加
  1. from flask import Flask
  2. from lastday.views.account import ac
  3. from lastday.views.user import us
  4. def create_app():
  5. """
  6. 创建app应用
  7. :return:
  8. """
  9. app = Flask(__name__)
  10. # 引入配置文件并应用
  11. app.config.from_object("settings.Development")
  12. # 注册蓝图
  13. app.register_blueprint(ac)
  14. app.register_blueprint(us)
  15. @app.before_request
  16. def b1():
  17. print('b1')
  18. return app

重启manage.py,刷新页面,查看Pycharm控制台输出:b1

那么问题来了,如果b1的视图函数,代码有很多行呢?

在create_app里面有一个b1函数。b1函数就是一个闭包!

先来理解一下装饰器的本质,比如b1函数,加了装饰器之后,可以理解为:

  1. b1 = app.before_request(b1)

由于赋值操作没有用到,代码缩减为

  1. app.before_request(b1)

那么完整代码,就可以写成

  1. from flask import Flask
  2. from lastday.views.account import ac
  3. from lastday.views.user import us
  4. def create_app():
  5. """
  6. 创建app应用
  7. :return:
  8. """
  9. app = Flask(__name__)
  10. # 引入配置文件并应用
  11. app.config.from_object("settings.Development")
  12. # 注册蓝图
  13. app.register_blueprint(ac)
  14. app.register_blueprint(us)
  15. app.before_request(b1) # 请求到来之前执行
  16. return app
  17. def b1():
  18. print('app_b1')

其实蓝图,也可以加before_request

修改 views—>account.py

  1. from flask import Blueprint
  2. ac = Blueprint('account',__name__)
  3. @ac.before_request
  4. def bb():
  5. print('account.bb')
  6. @ac.route('/login')
  7. def login():
  8. return '登陆'
  9. @ac.route('/logout')
  10. def logout():
  11. return '注销'

重启 manage.py,访问登录页面,注意:后面不要带”/“,否则提示Not Found

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图4

查看Pycharm控制台输出:

  1. app_b1
  2. account.bb

可以发现,2个beforerequest都执行了。注意:在_init.py中的before_request是所有路由都生效的

而account.py中的before_request,只要访问这个蓝图的路由,就会触发!

因此,访问 http://127.0.0.1:5000/logout,也是可以触发account.py中的before_request

完整目录结构如下:

  1. lastday/
  2. ├── lastday
  3. ├── __init__.py
  4. ├── static
  5. └── images
  6. └── meinv.jpg
  7. ├── templates
  8. └── user_list.html
  9. └── views
  10. ├── account.py
  11. └── user.py
  12. ├── manage.py
  13. └── settings.py

总结:

如果对所有的路由要操作,那么在app实例里面,写before_request

如果对单个的蓝图,则在蓝图里面使用before_request

二、flask使用SQLAlchemy

必须先安装模块sqlalchemy

  1. pip3 install sqlalchemy

准备一台MySQL服务器,创建数据库db1

  1. CREATE DATABASE db1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

默认的用户名为root,密码为空

非主流操作

基于上面的项目lastday,进入lastday应用目录,创建文件models.py

  1. from sqlalchemy.ext.declarative import declarative_base
  2. from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index
  3. from sqlalchemy.orm import relationship
  4. # 创建了一个基类:models.Model
  5. Base = declarative_base()
  6. # 在数据库创建表一张表
  7. class Users(Base):
  8. __tablename__ = 'users'
  9. id = Column(Integer, primary_key=True)
  10. name = Column(String(32), index=True, nullable=False)
  11. # 在数据库创建表一张表
  12. class School(Base):
  13. __tablename__ = 'school'
  14. id = Column(Integer, primary_key=True)
  15. name = Column(String(32), index=True, nullable=False)
  16. def db_init():
  17. from sqlalchemy import create_engine
  18. # 创建数据库连接
  19. engine = create_engine(
  20. # 连接数据库db1
  21. "mysql+pymysql://root:@127.0.0.1:3306/db1?charset=utf8",
  22. max_overflow=0, # 超过连接池大小外最多创建的连接
  23. pool_size=5, # 连接池大小
  24. pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
  25. pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
  26. )
  27. Base.metadata.create_all(engine) # 创建操作
  28. # Base.metadata.drop_all(engine) # 删除操作
  29. if __name__ == '__main__':
  30. db_init()

此时目录结构如下:

  1. lastday/
  2. ├── lastday
  3. ├── __init__.py
  4. ├── models.py
  5. ├── static
  6. └── images
  7. └── meinv.jpg
  8. ├── templates
  9. └── user_list.html
  10. └── views
  11. ├── account.py
  12. └── user.py
  13. ├── manage.py
  14. └── settings.py

执行models.py,注意:它会输出一段警告

  1. Warning: (1366, "Incorrect string value: '\\xD6\\xD0\\xB9\\xFA\\xB1\\xEA...' for column 'VARIABLE_VALUE' at row 484")
  2. result = self._query(query)

这个异常是mysql问题,而非python的问题,这是因为mysql的字段类型是utf-xxx, 而在mysql中这些utf-8数据类型只能存储最多三个字节的字符,而存不了包含四个字节的字符。

这个警告,可以直接忽略,使用Navicat软件查看,发现表已经创建完成

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图5

修改 user.py,插入一条记录

  1. from flask import Blueprint,render_template
  2. from lastday.models import Users
  3. us = Blueprint('user',__name__) # 蓝图名
  4. @us.route('/user_list/')
  5. def user_list(): # 注意:不要和蓝图名重复
  6. # 创建连接
  7. from sqlalchemy import create_engine
  8. from sqlalchemy.orm import sessionmaker
  9. engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/s11day139?charset=utf8", max_overflow=0, pool_size=5)
  10. Session = sessionmaker(bind=engine)
  11. session = Session()
  12. # 添加一条记录
  13. session.add_all([
  14. Users(name='xiao')
  15. ])
  16. session.commit()
  17. return render_template('user_list.html')

可以发现,这种操作很麻烦。视图函数每次都需要创建mysql连接!

使用flask_sqlalchemy(推荐)

安装模块flask_sqlalchemy

  1. pip3 install flask_sqlalchemy

修改init.py,实例化SQLAlchemy,执行db.init_app(app)

  1. from flask import Flask
  2. from flask_sqlalchemy import SQLAlchemy
  3. # 1. 必须放在引入蓝图的上方
  4. db = SQLAlchemy()
  5. from lastday.views.account import ac
  6. from lastday.views.user import us
  7. def create_app():
  8. """
  9. 创建app应用
  10. :return:
  11. """
  12. app = Flask(__name__)
  13. # 引入配置文件并应用
  14. app.config.from_object("settings.Development")
  15. # 2. 执行init_app,读取配置文件SQLAlchemy中相关的配置文件,用于以后:生成数据库/操作数据库(依赖配置文件)
  16. db.init_app(app)
  17. # 注册蓝图
  18. app.register_blueprint(ac)
  19. app.register_blueprint(us)
  20. app.before_request(b1) # 请求到来之前执行
  21. return app
  22. def b1():
  23. print('app_b1')

查看init_app的源码,大量用到了app.config.setdefault

  1. def init_app(self, app):
  2. """This callback can be used to initialize an application for the
  3. use with this database setup. Never use a database in the context
  4. of an application not initialized that way or connections will
  5. leak.
  6. """
  7. if (
  8. 'SQLALCHEMY_DATABASE_URI' not in app.config and
  9. 'SQLALCHEMY_BINDS' not in app.config
  10. ):
  11. warnings.warn(
  12. 'Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS is set. '
  13. 'Defaulting SQLALCHEMY_DATABASE_URI to "sqlite:///:memory:".'
  14. )
  15. app.config.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite:///:memory:')
  16. app.config.setdefault('SQLALCHEMY_BINDS', None)
  17. app.config.setdefault('SQLALCHEMY_NATIVE_UNICODE', None)
  18. app.config.setdefault('SQLALCHEMY_ECHO', False)
  19. app.config.setdefault('SQLALCHEMY_RECORD_QUERIES', None)
  20. app.config.setdefault('SQLALCHEMY_POOL_SIZE', None)
  21. app.config.setdefault('SQLALCHEMY_POOL_TIMEOUT', None)
  22. app.config.setdefault('SQLALCHEMY_POOL_RECYCLE', None)
  23. app.config.setdefault('SQLALCHEMY_MAX_OVERFLOW', None)
  24. app.config.setdefault('SQLALCHEMY_COMMIT_ON_TEARDOWN', False)
  25. track_modifications = app.config.setdefault(
  26. 'SQLALCHEMY_TRACK_MODIFICATIONS', None
  27. )

那么就需要将数据库属性,写到settings.py中

  1. class Base(object):
  2. SECRET_KEY = "fasdfasdf" # session加密字符串
  3. SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:@127.0.0.1:3306/db1?charset=utf8"
  4. SQLALCHEMY_POOL_SIZE = 5
  5. SQLALCHEMY_POOL_TIMEOUT = 30
  6. SQLALCHEMY_POOL_RECYCLE = -1
  7. # 追踪对象的修改并且发送信号
  8. SQLALCHEMY_TRACK_MODIFICATIONS = False
  9. class Product(Base):
  10. """
  11. 线上环境
  12. """
  13. pass
  14. class Testing(Base):
  15. """
  16. 测试环境
  17. """
  18. DEBUG = False
  19. class Development(Base):
  20. """
  21. 开发环境
  22. """
  23. DEBUG = True # 开启调试

因此,只要执行了db.init_app(app),它就会读取settings.py中的配置信息

修改models.py,引入init.py中的db变量,优化代码

  1. from lastday import db
  2. # 在数据库创建表一张表
  3. class Users(db.Model):
  4. __tablename__ = 'users'
  5. id = db.Column(db.Integer, primary_key=True)
  6. name = db.Column(db.String(32), index=True, nullable=False)
  7. # 在数据库创建表一张表
  8. class School(db.Model):
  9. __tablename__ = 'school'
  10. id = db.Column(db.Integer, primary_key=True)
  11. name = db.Column(db.String(32), index=True, nullable=False)

修改 views—>user.py,导入db,插入一条记录

  1. from flask import Blueprint,render_template
  2. from lastday.models import Users
  3. from lastday import db
  4. us = Blueprint('user',__name__) # 蓝图名us
  5. @us.route('/user_list/')
  6. def user_list(): # 注意:不要和蓝图名重复
  7. # 添加一条记录
  8. db.session.add(Users(name='xiao'))
  9. db.session.commit()
  10. return render_template('user_list.html')

重启 manage.py ,访问用户列表,使用Navicat查看用户表

发现多了一条记录

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图6

如果需要关闭连接,使用 db.session.remove()

修改 views—>user.py,在commit下面加一行即可!

但是这样没有必要!为什么?因为在settings.py,使用了数据库连接池。

关闭之后,再次开启一个线程,是需要消耗cpu的。

三、flask离线脚本

前戏

流程图

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图7

注意:核心就是配置,通过db对象,操作models,为蓝图提供数据!

现在有一个需求,需要将数据库中的表删除或者生成数据库中的表,必须通过脚本来完成!

配置文件加载之后,将setttings.py中的属性添加到app.config对象中。如果有app对象,那么就可以得到以下信息:

  1. - 应用上下文中的有:app/g
  2. - flask的配置文件:app.config
  3. - app中包含了:SQLAlchemy相关的数据。

web runtime

启动网站,等待用户请求到来。走 call/wsig_app/……..

注意:上面这些,必须是flask启动的情况下,才能获取。有一个专有名词,叫做web runtime,翻译过来就是:web 运行时!

它是一个web运行状态。某些操作,必须基于它才能实现!

离线脚本

离线脚本,就是非 web 运行时(web服务器停止)的状态下,也能执行的脚本!

正式开始

先关闭flask项目

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图8

在项目根目录创建文件table.py,导入create_app和db,这是关键点

  1. from lastday import create_app,db
  2. app = create_app()
  3. with app.app_context():
  4. db.drop_all() # 删除

执行弹出一个警告,这不用管。

查看db1数据库,发现表已经没有了!

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图9

修改table.py,执行创建方法

  1. from lastday import create_app,db
  2. app = create_app()
  3. with app.app_context():
  4. # db.drop_all() # 删除
  5. db.create_all() # 创建

再次执行,发现表出现了

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图10

注意:网站并没有启动,但是实现了删表以及创建表操作!

那么这个with,到底执行了什么呢?查看AppContext源代码,看这2个方法

  1. def __enter__(self):
  2. self.push()
  3. return self
  4. def __exit__(self, exc_type, exc_value, tb):
  5. self.pop(exc_value)
  6. if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
  7. reraise(exc_type, exc_value, tb)

实际上,with是调用了这2个方法。

写一个类测试一下

  1. class Foo(object):
  2. pass
  3. obj = Foo()
  4. with obj:
  5. print(123)

执行报错:

  1. AttributeError: __enter__

提示没有enter方法

修改代码,增加enter方法

  1. class Foo(object):
  2. def __enter__(self):
  3. pass
  4. obj = Foo()
  5. with obj:
  6. print(123)

执行报错:

  1. AttributeError: __exit__

再增加exit方法

  1. class Foo(object):
  2. def __enter__(self):
  3. print('__enter__')
  4. def __exit__(self, exc_type, exc_val, exc_tb):
  5. print('__exit__')
  6. obj = Foo()
  7. with obj:
  8. print(123)

执行输出:

  1. __enter__
  2. 123
  3. __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。

语法:

  1. xxx = Blueprint('account',__name__,url_prefix='/xxx')

修改 views—>account.py,增加前缀

  1. from flask import Blueprint
  2. ac = Blueprint('account',__name__,url_prefix='/xxx')
  3. @ac.before_request
  4. def bb():
  5. print('account.bb')
  6. @ac.route('/login')
  7. def login():
  8. return '登陆'
  9. @ac.route('/logout')
  10. def logout():
  11. return '注销'

也可以在init.py里面的app.register_blueprint里面加url_prefix,但是不推荐

在项目根目录创建目录other,在里面创建 multiapp.py,不能使用_name

  1. from flask import Flask
  2. app1 = Flask('app1')
  3. # app1.config.from_object('xxx') # db1
  4. @app1.route('/f1')
  5. def f1():
  6. return 'f1'
  7. app2 = Flask('app2')
  8. # app2.config.from_object('xxx') # db2
  9. @app1.route('/f2')
  10. def f2():
  11. return 'f2'

上面2个应用,可以连接不同的数据库。

目录结构如下:

  1. ./
  2. ├── lastday
  3. ├── __init__.py
  4. ├── models.py
  5. ├── static
  6. └── images
  7. └── meinv.jpg
  8. ├── templates
  9. └── user_list.html
  10. └── views
  11. ├── account.py
  12. └── user.py
  13. ├── manage.py
  14. ├── other
  15. └── multi_app.py
  16. └── settings.py

multi_app.py有2套程序,没有必要写在一起,使用DispatcherMiddleware

查看源码

  1. class DispatcherMiddleware(object):
  2. """Allows one to mount middlewares or applications in a WSGI application.
  3. This is useful if you want to combine multiple WSGI applications::
  4. app = DispatcherMiddleware(app, {
  5. '/app2': app2,
  6. '/app3': app3
  7. })
  8. """
  9. def __init__(self, app, mounts=None):
  10. self.app = app
  11. self.mounts = mounts or {}
  12. def __call__(self, environ, start_response):
  13. script = environ.get('PATH_INFO', '')
  14. path_info = ''
  15. while '/' in script:
  16. if script in self.mounts:
  17. app = self.mounts[script]
  18. break
  19. script, last_item = script.rsplit('/', 1)
  20. path_info = '/%s%s' % (last_item, path_info)
  21. else:
  22. app = self.mounts.get(script, self.app)
  23. original_script_name = environ.get('SCRIPT_NAME', '')
  24. environ['SCRIPT_NAME'] = original_script_name + script
  25. environ['PATH_INFO'] = path_info
  26. return app(environ, start_response)

它可以将2个app结合在一起,使用run_simple启动

修改 other\multi_app.py

  1. from flask import Flask
  2. from werkzeug.wsgi import DispatcherMiddleware
  3. from werkzeug.serving import run_simple
  4. app1 = Flask('app1')
  5. # app1.config.from_object('xxx') # db1
  6. @app1.route('/f1')
  7. def f1():
  8. return 'f1'
  9. app2 = Flask('app2')
  10. # app1.config.from_object('xxx') # db2
  11. @app2.route('/f2')
  12. def f2():
  13. return 'f2'
  14. dispachcer = DispatcherMiddleware(app1, {
  15. '/admin':app2, # app2指定前缀admin
  16. })
  17. if __name__ == '__main__':
  18. run_simple('127.0.0.1',8009,dispachcer)

执行multi_app.py,访问url

  1. http://127.0.0.1:8009/f1

效果如下:

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图11

访问f2,会出现404

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图12

因为它规定了前缀 admin,使用下面访问访问,就不会出错了!

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图13

多app应用的场景很少见,了解一下,就可以了!

维护栈的目的

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图14

在local对象中,存储的数据是这样的。app_ctx是应用上下文

  1. 线程id:{stack:[app_ctx]}

它永远存储的是单条数据,它不是真正的栈。如果搞一个字段,直接让stack=app_ctx,照样可以执行。

那么它为什么要维护一个栈呢?因为它要考虑:

  1. 在离线脚本和多app应用的情况下特殊代码的实现。

只有这2个条件满足的情况下,才会用到栈!

web运行时

看上图,是web运行时。本质上,要么开多进程,要么开多线程。那么local对象中维护的栈,永远都只有一条数据。

即使是多app应用,也是一样的。一个请求过来,只能选择一个app。比如上面的f1和f2,要么带前缀,要么不带。带前缀,访问f2,否则访问f1

离线脚本

单app应用

在离线脚本中,单app应用,先来看table.py,它就是离线脚本。

  1. from lastday import create_app,db
  2. app = create_app()
  3. # app_ctx.push()
  4. with app.app_context():
  5. db.create_all() # 创建

它创建了app_ctx对象,调用了push方法。将数据放到Local对象中,注意:只放了一次!

local的数据,如果是一个字典,大概是这个样子

  1. {
  2. stack:[app_ctx,]
  3. }

多app应用

修改table.py,注意:下面的是伪代码,直接运行会报错

  1. from lastday import create_app,db
  2. app1 = create_app1() # db1
  3. app2 = create_app2() # db2
  4. # app_ctx.push()
  5. """
  6. {
  7. stack:[app1_ctx,]
  8. }
  9. """
  10. with app1.app_context():
  11. # 取栈中获取栈顶的app_ctx,使用top方法取栈顶
  12. db.create_all() # 创建

如果app1要获取配置文件,从db1种获取

如果加一行代码with呢?

  1. from lastday import create_app,db
  2. from flask import globals
  3. app1 = create_app1() # db1
  4. app2 = create_app2() # db2
  5. # app_ctx.push()
  6. """
  7. {
  8. stack:[app1_ctx,]
  9. }
  10. """
  11. with app1.app_context():
  12. # 取栈中获取栈顶的app_ctx,使用top方法取栈顶
  13. db.create_all() # 创建
  14. with app2.app_context():

它们都调用了app_context

看globals源码,看最后一行代码

  1. _request_ctx_stack = LocalStack()
  2. _app_ctx_stack = LocalStack()

这2个静态变量,用的是同一个LocalStack(),那么会用同一个Local()。也就是说放到同一个地方去了

修改table.py

  1. from lastday import create_app,db
  2. from flask import globals
  3. app1 = create_app1() # db1
  4. app2 = create_app2() # db2
  5. # app_ctx.push()
  6. """
  7. {
  8. stack:[app1_ctx,app2_ctx,]
  9. }
  10. """
  11. with app1.app_context():
  12. # 取栈中获取栈顶的app_ctx,使用top方法取栈顶
  13. db.create_all() # 创建
  14. with app2.app_context():
  15. db.create_all()

执行到with这一行时,stack里面有2个对象,分别是app1_ctx和app2_ctx。

那么执行到with下面的db.create_all()时,它会连接哪个数据库?

答案是: 取栈顶的app2_ctx,配置文件是db2

修改 table.py,增加db.drop_all(),它会删除哪个数据库?

  1. from lastday import create_app,db
  2. from flask import globals
  3. app1 = create_app1() # db1
  4. app2 = create_app2() # db2
  5. # app_ctx.push()
  6. """
  7. {
  8. stack:[app1_ctx,app2_ctx,]
  9. }
  10. """
  11. with app1.app_context():
  12. # 取栈中获取栈顶的app_ctx,使用top方法取栈顶
  13. db.create_all() # 创建
  14. with app2.app_context():
  15. db.create_all()
  16. db.drop_all()

答案是:db1

为什么呢?因为执行with时,进来时调用了enter方法,将app2_ctx加进去了。此时位于栈顶!

结束时,调用exit方法,取栈顶,将app2_ctx给pop掉了!也就是删除!

那么执行db.drop_all()时,此时栈里面只有一个数据app1_ctx,取栈顶,就是app1_ctx

这就它设计使用栈的牛逼之处!

通过栈顶的数据不一样,来完成多app操作!

看下面的动态图,就是栈的变化

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图15

关于flask维护栈的详细信息,请参考链接:

https://blog.csdn.net/u010301542/article/details/78302450

五、flask_script

Flask Script扩展提供向Flask插入外部脚本的功能,包括运行一个开发用的服务器,一个定制的Python shell,设置数据库的脚本,cronjobs,及其他运行在web应用之外的命令行任务;使得脚本和系统分开;

Flask Script和Flask本身的工作方式类似,只需定义和添加从命令行中被Manager实例调用的命令;

官方文档:http://flask-script.readthedocs.io/en/latest/

安装模块

  1. pip3 install flask_script

修改 manage.py

  1. from flask_script import Manager
  2. from lastday import create_app
  3. app = create_app()
  4. manager = Manager(app)
  5. if __name__ == '__main__':
  6. manager.run()

执行 manage.py,报错

  1. optional arguments:
  2. -?, --help show this help message and exit

是因为不能用原来的方式调用了。

使用以下方式执行

  1. python manage.py runserver -h 127.0.0.1 -p 8009

访问登录页面

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图16

自定义命令

flask_script还可以做一些自定义命令,列如:

修改 manage.py

  1. from flask_script import Manager
  2. from lastday import create_app
  3. app = create_app()
  4. manager = Manager(app)
  5. @manager.command
  6. def c1(arg):
  7. """
  8. 自定义命令login
  9. python manage.py custom 123
  10. :param arg:
  11. :return:
  12. """
  13. print(arg)
  14. @manager.option('-n', '--name', dest='name')
  15. @manager.option('-u', '--url', dest='url')
  16. def c2(name, url):
  17. """
  18. 自定义命令
  19. 执行: python manage.py cmd -n wupeiqi -u http://www.oldboyedu.com
  20. :param name:
  21. :param url:
  22. :return:
  23. """
  24. print(name, url)
  25. if __name__ == '__main__':
  26. manager.run()

在终端执行c1命令

  1. python manage.py c1 22

执行输出:22

注意:这里的c1,指的是manage.py中的c1函数

在终端执行c2命令

  1. python manage.py c2 -n 1 -u 9

执行输出:

  1. 1 9

以上,可以看到,它和django启动方式很像

六、flask_migrate

flask-migrate是flask的一个扩展模块,主要是扩展数据库表结构的.

官方文档:http://flask-migrate.readthedocs.io/en/latest/

安装模块

  1. pip3 install flask_migrate

使用

修改 manage.py

  1. # 1.1
  2. from flask_script import Manager
  3. # 2.1
  4. from flask_migrate import Migrate, MigrateCommand
  5. from lastday import db
  6. from lastday import create_app
  7. app = create_app()
  8. # 1.2
  9. manager = Manager(app)
  10. # 2.2
  11. Migrate(app, db)
  12. # 2.3
  13. """
  14. # 数据库迁移命名
  15. python manage.py db init
  16. python manage.py db migrate -> makemigrations
  17. python manage.py db upgrade -> migrate
  18. """
  19. manager.add_command('db', MigrateCommand)
  20. @manager.command
  21. def c1(arg):
  22. """
  23. 自定义命令
  24. python manage.py custom 123
  25. :param arg:
  26. :return:
  27. """
  28. print(arg)
  29. @manager.option('-n', '--name', dest='name')
  30. @manager.option('-u', '--url', dest='url')
  31. def c2(name, url):
  32. """
  33. 自定义命令
  34. 执行: python manage.py cmd -n wupeiqi -u http://www.oldboyedu.com
  35. :param name:
  36. :param url:
  37. :return:
  38. """
  39. print(name, url)
  40. if __name__ == '__main__':
  41. # python manage.py runserver -h 127.0.0.1 -p 8999
  42. # 1.3
  43. manager.run()

执行init

必须先执行init,只需要执行一次就可以了!

  1. python manage.py db init

它会在项目根目录创建migrations文件夹

执行migrate

  1. python manage.py db migrate

执行upgrade

  1. python manage.py db upgrade

测试

修改 models.py,去掉School中的name属性

  1. from lastday import db
  2. # 在数据库创建表一张表
  3. class Users(db.Model):
  4. __tablename__ = 'users'
  5. id = db.Column(db.Integer, primary_key=True)
  6. name = db.Column(db.String(32), index=True, nullable=False)
  7. # 在数据库创建表一张表
  8. class School(db.Model):
  9. __tablename__ = 'school'
  10. id = db.Column(db.Integer, primary_key=True)
  11. # name = db.Column(db.String(32), index=True, nullable=False)

先执行migrate,再执行upgrade

使用Navicat查看school表,发现name字段没有了!

Day142 flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs - 图17

它是如何实现的呢?在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模块,它的作用是: 自动找到程序中应用的包和版本

安装模块

  1. pip3 install pipreqs

使用pipreqs

注意:由于windows默认是gbk编码,必须指定编码为utf-8,否则报错!

  1. E:\python_script\Flask框架\day5\lastday> pipreqs ./ --encoding=utf-8

执行输出:

  1. INFO: Successfully saved requirements file in E:\python_script\Flask框架\day5\lastday\requirements.txt

它会在当前目录中,生成一个requirements.txt文件

查看文件内容

  1. Flask_SQLAlchemy==2.3.2
  2. Flask==1.0.2

左边是模块名,右边是版本

那么有了这个requirements.txt文件,就可以自动安装模块了

  1. pip3 install -r requirements.txt

它会根据文件内容,自动安装!

因此,写python项目时,一定要有requirements.txt文件才行!

github项目也是一样的!

今日内容总结:

  1. 内容详细:
  2. 1. flask & SQLAlchemy
  3. 安装:
  4. flask-sqlalchemy
  5. 使用:
  6. a. 在配置文件中设置连接字符串
  7. SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:@127.0.0.1:3306/s11lastday?charset=utf8"
  8. SQLALCHEMY_POOL_SIZE = 5
  9. SQLALCHEMY_POOL_TIMEOUT = 30
  10. SQLALCHEMY_POOL_RECYCLE = -1
  11. # 追踪对象的修改并且发送信号
  12. SQLALCHEMY_TRACK_MODIFICATIONS = False
  13. b. __init__.py
  14. from flask import Flask
  15. from flask_sqlalchemy import SQLAlchemy
  16. # 1. 必须放在引入蓝图的上方
  17. db = SQLAlchemy()
  18. from lastday.views.user import user
  19. def create_app():
  20. app = Flask(__name__)
  21. app.config.from_object("settings.Development")
  22. # 2. 执行init_app,读取配置文件SQLAlchemy中相关的配置文件,用于以后:生成数据库/操作数据库(依赖配置文件)
  23. db.init_app(app)
  24. app.register_blueprint(user)
  25. return app
  26. c. models.py
  27. from lastday import db
  28. # 在数据库创建表一张表
  29. class Users(db.Model):
  30. __tablename__ = 'users'
  31. id = db.Column(db.Integer, primary_key=True)
  32. name = db.Column(db.String(32), index=True, nullable=False)
  33. # 在数据库创建表一张表
  34. class School(db.Model):
  35. __tablename__ = 'school'
  36. id = db.Column(db.Integer, primary_key=True)
  37. name = db.Column(db.String(32), index=True, nullable=False)
  38. d. 蓝图
  39. from flask import Blueprint,render_template
  40. from lastday.models import Users
  41. from lastday import db
  42. user = Blueprint('user',__name__)
  43. @user.route('/user_list/')
  44. def user_list():
  45. db.session.add(Users(name='王超'))
  46. db.session.commit()
  47. db.session.remove()
  48. return render_template('user_list.html')
  49. 疑问:将数据库中的表删除 生成数据库中的表。
  50. 通过脚本完成。
  51. 前戏:
  52. - 应用上下文中的有:app/g
  53. - flask的配置文件:app.config
  54. - app中包含了:SQLAlchemy相关的数据。
  55. 代码:
  56. from lastday import create_app,db
  57. app = create_app()
  58. with app.app_context():
  59. # db.drop_all()
  60. db.create_all()
  61. 名词:
  62. web runtime :启动网站,等待用户请求到来。走 __call__/wsig_app/........
  63. 离线脚本:
  64. from lastday import create_app,db
  65. app = create_app()
  66. with app.app_context():
  67. # db.drop_all()
  68. db.create_all()
  69. 应用场景:
  70. 1. 录入基础数据
  71. 2. 定时数据处理(定时任务)
  72. 赠送:多app应用
  73. from flask import Flask
  74. from werkzeug.wsgi import DispatcherMiddleware
  75. from werkzeug.serving import run_simple
  76. app1 = Flask('app1')
  77. # app1.config.from_object('xxx') # db1
  78. @app1.route('/f1')
  79. def f1():
  80. return 'f1'
  81. app2 = Flask('app2')
  82. # app1.config.from_object('xxx') # db2
  83. @app2.route('/f2')
  84. def f2():
  85. return 'f2'
  86. dispachcer = DispatcherMiddleware(app1, {
  87. '/admin':app2,
  88. })
  89. if __name__ == '__main__':
  90. run_simple('127.0.0.1',8009,dispachcer)
  91. 问题:为什么Flask中要在上下文管理中将Local中的数据:ctx/app_ctx维护成一个栈?
  92. 应用flask要考虑,在离线脚本和多app应用的情况下特殊代码的实现。
  93. web运行时中:Local对象中维护的栈 [ctx, ]
  94. 在离线脚本中:
  95. - app应用:
  96. from lastday import create_app,db
  97. app = create_app()
  98. # app_ctx.push()
  99. """
  100. {
  101. stack:[app_ctx, ]
  102. }
  103. """
  104. with app.app_context():
  105. db.create_all()
  106. - app应用:
  107. from lastday import create_app,db
  108. app1 = create_app1() # db1
  109. app2 = create_app2() # db2
  110. from flask import globals
  111. # app_ctx.push()
  112. """
  113. {
  114. stack:[app1_ctx,app2_ctx ]
  115. }
  116. """
  117. with app1.app_context():
  118. # 去栈中获取栈顶的app_ctx: app1_ctx,配置文件:db1
  119. db.create_all()
  120. with app2.app_context():
  121. db.create_all() # 去栈中获取栈顶的app_ctx: app2_ctx,配置文件:db2
  122. db.drop_all() # 去栈中获取栈顶的app_ctx: app1_ctx,配置文件:db1
  123. 总结:
  124. 1. flask-sqlalchemy,帮助用户快速实现Flask中应用SQLAlchemy
  125. 2. app应用
  126. 3. 离线脚本
  127. 4. 为什么Flask中要在上下文管理中将Local中的数据:ctx/app_ctx维护成一个栈?
  128. - 离线脚本+多app应用才会在栈中存多个上下文对象: [ctx1,ctx2,]
  129. - 其他:[ctx, ]
  130. 2. flask-script
  131. 安装:flask-script
  132. 作用:制作脚本启动
  133. 3. flask-migrate(依赖flask-script
  134. 安装:flask-migrate
  135. 使用:
  136. # 1.1
  137. from flask_script import Manager
  138. # 2.1
  139. from flask_migrate import Migrate, MigrateCommand
  140. from lastday import db
  141. from lastday import create_app
  142. app = create_app()
  143. # 1.2
  144. manager = Manager(app)
  145. # 2.2
  146. Migrate(app, db)
  147. # 2.3
  148. """
  149. # 数据库迁移命名
  150. python manage.py db init
  151. python manage.py db migrate -> makemigrations
  152. python manage.py db upgrade -> migrate
  153. """
  154. manager.add_command('db', MigrateCommand)
  155. @manager.command
  156. def c1(arg):
  157. """
  158. 自定义命令
  159. python manage.py custom 123
  160. :param arg:
  161. :return:
  162. """
  163. print(arg)
  164. @manager.option('-n', '--name', dest='name')
  165. @manager.option('-u', '--url', dest='url')
  166. def c2(name, url):
  167. """
  168. 自定义命令
  169. 执行: python manage.py cmd -n wupeiqi -u http://www.oldboyedu.com
  170. :param name:
  171. :param url:
  172. :return:
  173. """
  174. print(name, url)
  175. if __name__ == '__main__':
  176. # python manage.py runserver -h 127.0.0.1 -p 8999
  177. # 1.3
  178. manager.run()
  179. 4. pipreqs
  180. 安装:pipreqs
  181. 作用:自动找到程序中应用的包和版本。
  182. pipreqs ./ --encoding=utf-8
  183. pip3 install -r requirements.txt
  184. 重点:
  185. 1. 使用(*)
  186. - flask-sqlalchemy
  187. - flask-migrate
  188. - flask-script
  189. 2. flask上下文相关 (*****)
  190. - 对象关键字:LocalStackLocal
  191. - 离线脚本 & web 运行时
  192. - app应用
  193. - Local中为什么维护成栈?