昨日內容回顧

1. 簡述flask上下文管理 - threading.local - 偏函數 - 棧 2. 原生SQL和ORM有什么優缺點? 開發效率: ORM > 原生SQL 執行效率: 原生SQL> ORM 如:SQLAlchemy依賴pymysql 3. SQLAlchemy多線程連接的情況
一、flask標准目錄結構
標准flask目錄結構
Project name/ # 項目名 ├── Project name # 應用名,保持和項目名同名 │ ├── __init__.py # 初始化程序,用來注冊藍圖 │ ├── static # 靜態目錄 │ ├── templates # 模板目錄 │ └── views # 藍圖 └── manage.py # 啟動程序
注意:應用名和項目名要保持一致
藍圖
修改manage.py
from lastday import create_app app = create_app() if __name__ == '__main__': app.run()
進入views目錄,新建文件account.py

from flask import Blueprint account = Blueprint('account',__name__) @account.route('/login') def login(): return '登陸' @account.route('/logout') def logout(): return '注銷'
修改 __init__.py,注冊藍圖

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/login
效果如下:
進入views目錄,新建文件user.py
from flask import Blueprint,render_template us = Blueprint('user',__name__) # 藍圖名 @us.route('/user_list/') def user_list(): # 注意:不要和藍圖名重復 return "用戶列表"
修改 __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.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,渲染模板

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
二、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 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(推薦)
安裝模塊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的。
三、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 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應用
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運行時
看上圖,是web運行時。本質上,要么開多進程,要么開多線程。那么local對象中維護的棧,永遠都只有一條數據。
即使是多app應用,也是一樣的。一個請求過來,只能選擇一個app。比如上面的f1和f2,要么帶前綴,要么不帶。帶前綴,訪問f2,否則訪問f1
離線腳本
單app應用
在離線腳本中,單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,]
}
多app應用
修改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維護棧的詳細信息,請參考鏈接:
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/
安裝模塊
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-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
必須先執行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 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
介紹
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.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中為什么維護成棧?