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中爲何維護成棧?