正文
程序包結構
——————————————————————————————————
flask文件夾結構
其中:
app為程序包,Flask程序保存在這個包中
migrations文件夾包含數據庫遷移腳本
tests包保存單元測試
requirements文件夾中記錄程序的依賴
config.py是程序的配置文件
manage.py是程序的運行文件,用於啟動程序即程序的其他任務
——————————————————————————————————
app包結構
其中:
auth為保存專門用於認證的auth藍本
main為保存main藍本的包
static文件夾用於保存靜態文件,例如HTML代碼中引用的圖片、 JavaScript 源碼文件和 CSS
templates用於保存網頁的模板
———————————————————————————————————————
藍本文件夾結構(以auth文件夾為例,main藍本等不再贅述)
藍本的細節就不在此贅述,在flask框架中用到藍本,可以對不同的程序功能使用不同的藍本,這是保證程序整齊有序的辦法。(想想把所有功能都寫在一起會多么混亂)。這里說明一下到藍本的程序運行原理:app/auth/views.py 模塊引入藍本,然后使用藍本的 route 修飾器定義與認證相關的路由,然后再渲染views中設定的網頁模板。看起來是不是如果程序能運行到藍本這一步,我們就可以對網頁進行操作了。
2. 運行說明
在運行程序的時候,我們在虛擬環境下,通過如下命令來完成。由此可見,程序的運行是由manage.py來開始的
(venv) $ python manage.py runserver
那么,我們來看看這個manage.py吧,期待不期待?興奮不興奮?
#!/usr/bin/env python import os COV = None if os.environ.get('FLASK_COVERAGE'): import coverage COV = coverage.coverage(branch=True, include='app/*') COV.start() if os.path.exists('.env'): print('Importing environment from .env...') for line in open('.env'): var = line.strip().split('=') if len(var) == 2: os.environ[var[0]] = var[1] from app import create_app, db from app.models import User, Follow, Role, Permission, Post, Comment from flask_script import Manager, Shell from flask_migrate import Migrate, MigrateCommand app = create_app(os.getenv('FLASK_CONFIG') or 'default') manager = Manager(app) migrate = Migrate(app, db) def make_shell_context(): return dict(app=app, db=db, User=User, Follow=Follow, Role=Role, Permission=Permission, Post=Post, Comment=Comment) manager.add_command("shell", Shell(make_context=make_shell_context)) manager.add_command('db', MigrateCommand) @manager.command def test(coverage=False): """Run the unit tests.""" if coverage and not os.environ.get('FLASK_COVERAGE'): import sys os.environ['FLASK_COVERAGE'] = '1' os.execvp(sys.executable, [sys.executable] + sys.argv) import unittest tests = unittest.TestLoader().discover('tests') unittest.TextTestRunner(verbosity=2).run(tests) if COV: COV.stop() COV.save() print('Coverage Summary:') COV.report() basedir = os.path.abspath(os.path.dirname(__file__)) covdir = os.path.join(basedir, 'tmp/coverage') COV.html_report(directory=covdir) print('HTML version: file://%s/index.html' % covdir) COV.erase() @manager.command def profile(length=25, profile_dir=None): """Start the application under the code profiler.""" from werkzeug.contrib.profiler import ProfilerMiddleware app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[length], profile_dir=profile_dir) app.run() @manager.command def deploy(): """Run deployment tasks.""" from flask_migrate import upgrade from app.models import Role, User # migrate database to latest revision upgrade() # create user roles Role.insert_roles() # create self-follows for all users User.add_self_follows() if __name__ == '__main__': manager.run()
下面我們按照順序去理一下,看看manage.py到底是如何運行的。
import os COV = None if os.environ.get('FLASK_COVERAGE'): import coverage COV = coverage.coverage(branch=True, include='app/*') COV.start()
這段代碼是關於程序測試覆蓋度方面的,我們可以先行略去,測試本身並不影響程序的運行。
if os.path.exists('.env'): print('Importing environment from .env...') for line in open('.env'): var = line.strip().split('=') if len(var) == 2: os.environ[var[0]] = var[1]
這段代碼的作用是從.env文件中導入環境變量。具體的大家可以去搜索一下這個文件,在程序配置中,有些信息是在環境變量中設置的。但是此處我們也略過細節。
from app import create_app, db from app.models import User, Follow, Role, Permission, Post, Comment from flask_script import Manager, Shell from flask_migrate import Migrate, MigrateCommand
各種導入,從app包中導入create_app工廠函數和數據庫db。從app.models模塊中導入User, Follow, Role, Permission, Post, Comment等類。在flask_script擴展中導入Manager, Shell, 從flask_migrate擴展中導入Migrate, MigrateCommand。什么意思呢?自己去搜索一下。
此處有一個坑:在原文中flask的擴展采用from flask.ext.script import Manager, Shell的導入方式,但是實際用的時候卻會報錯,從報錯的信息中可以知道flask.ext.script已經棄用了,改為flask_script即可。其他的擴展也是一樣。
>>> from flask.ext.script import Manager __main__:1: ExtDeprecationWarning: Importing flask.ext.script is deprecated, use flask_script instead.
這一句是創建工廠函數的實例,create_app是由from app import create_app, db導入的。
這里有一個坑需要注意一下,from app import create_app, db中的app是我們創建的一個包,其放置於頂層文件夾中,其中包括一個__init__.py文件。而語句app = create_app(os.getenv (‘FLASK_CONFIG’) or ‘default’)的app則是給create_app函數創建的實例的一個命名,這兩個的含義是不一樣的。對於我這種小白,剛剛開始也是迷惑了一陣子。
現在我們來看一下具體實現的方法。
os.getenv(‘FLASK_CONFIG’) or ‘default’),這個’FLASK_CONFIG’我們是我們設置的環境變量,通過os.getenv我們可以獲得這個環境參數。因為這是對工廠函數create_app進行實例化,因此我們得到的環境參數就作為create_app的參數,如果沒有設置’FLASK_CONFIG’這個環境變量,那么就將’default’這個默認值賦給工廠函數。那么這個設置的’FLASK_CONFIG’環境變量或這個’default’到底是什么呢?這需要到工廠函數中看一下。
既然create_app是從app包中導入的,那么讓我們一起來看看這個包里面都有什么吧!打開app下面的__init__.py文件,看到沒有create_app藏在這里呢。
此處插播工廠函數廣告,為不影響文章的總體邏輯,特做分割
app / __ init __.py
from flask import Flask from flask_bootstrap import Bootstrap from flask_mail import Mail from flask_moment import Moment from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from flask_pagedown import PageDown from config import config #依舊是各種導入,這里應該不難理解,在工廠函數中導入了flask及其擴展,也通過from config import config導入了程序配置文件config.py中的config屬性 bootstrap = Bootstrap() mail = Mail() moment = Moment() db = SQLAlchemy() pagedown = PageDown() login_manager = LoginManager() login_manager.session_protection = 'strong' login_manager.login_view = 'auth.login' #這里是創建導入的Flask擴展的實例。 def create_app(config_name): app = Flask(__name__) #創建Flask的實例 app.config.from_object(config[config_name]) config[config_name].init_app(app) bootstrap.init_app(app) mail.init_app(app) moment.init_app(app) db.init_app(app) login_manager.init_app(app) pagedown.init_app(app) #創建flask擴展的實例 if not app.debug and not app.testing and not app.config['SSL_DISABLE']: from flask_sslify import SSLify sslify = SSLify(app) from .main import main as main_blueprint app.register_blueprint(main_blueprint) from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint, url_prefix='/auth') from .api_1_0 import api as api_1_0_blueprint app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0') #注冊藍圖到工廠函數中 return app
廣告結束,繼續manage.py
我們來分析
app = create_app(os.getenv ('FLASK_CONFIG') or 'default')
在create_app中有如下的語句:
def create_app(config_name): app = Flask(__name__) app.config.from_object(config[config_name])
這里的os.getenv (‘FLASK_CONFIG’) or 'default’作為參數被傳遞給app.config.from_object(config[config_name]),其中的app是Flask的一個實例。
又一個坑:app.config中的config是Flask類的一個屬性,與我們的配置文件config.py不是同一個東西,也不是config.py中的config屬性。而config[config_name]得config是配置文件config.py中導入的,我們在config.py文件中看一下:
config = { 'development': DevelopmentConfig, 'testing': TestingConfig, 'production': ProductionConfig, 'heroku': HerokuConfig, 'unix': UnixConfig, 'default': DevelopmentConfig }
可以發現,FLASK_CONFIG應該被設置成字典的鍵,根據你需要運行的模式選取’development’, ‘testing’, ‘production’, ‘heroku’, ‘unix’, ‘default’中的一個。當然,如果不在環境變量中設置的話,就默認選取的’default’。那么from_object返回的是該健對應值。例如設置’default’返回的就是’DevelopmentConfig’。
到了這里我們知道app.config.from_object(config[config_name])實際上就是app.config.DevelopmentConfig,這個config.DevelopmentConfig就是config.py中的DevelopmentConfig類。那么看看這個DevelopmentConfig:
class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \ 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
這個DevelopmentConfig的父類是Config類,繼承Config中的所有屬性和方法。這樣app.config.DevelopmentConfig就將DevelopmentConfig設定的配置綁定到了app——即Flask的實例上去。
當然,我們這里是以DevelopmentConfig為例,其他的配置是一樣的原理。
接着執行create_app中的語句是:
config[config_name].init_app(app)
這里的init_app是在DevelopmentConfig類或其他運行環境配置類中的一個初始化方法,但值得注意的是,DevelopmentConfig類中並沒有直接寫入這個方法,而是繼承Config類。
在config類中有一個靜態方法,不執行任何操作。
@staticmethod def init_app(app): pass
因此在DevelopmentConfig中,這個初始化實際上並沒有進行,直接pass。這是因為這里根本不需要初始化,之前執行的配置就完全夠了。
但是值得注意的是,在ProductionConfig中是有重寫這個方法,因此會根據重寫的方法對app進行初始化配置。
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
login_manager.init_app(app)
pagedown.init_app(app)
這時我們已經完成了實例化Flask,並對實例app進行了初始化配置。對於我們用到的flask擴展也進行同樣的初始化配置。
坑:此處的init_app方法是各擴展中自帶的方法,並不是剛才我們用到的在config.py中自己寫的init_app方法,不過功能差不太多。但有時候讓人挺迷惑的。
if not app.debug and not app.testing and not app.config['SSL_DISABLE']: from flask_sslify import SSLify sslify = SSLify(app)
這里是啟用安全HTTP的flask擴展flask_sslify,先略去。
from .main import main as main_blueprint app.register_blueprint(main_blueprint) from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint, url_prefix='/auth') from .api_1_0 import api as api_1_0_blueprint app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0')
剛才說了那么多,無非是完成了實例化和配置初始化,但是我們的對網頁處理的視圖函數以及網頁本身的內容在哪里處理呢?
視圖函數我們是交給藍本來處理的,此處是將藍本注冊到工廠函數中去。
到此有沒有發現,在manage.py中大部分事情都讓工廠函數干去了,首先是創建實例,然后初始化配置,最后把工作交給藍本去干。
根據實現的功能不同分成不同的藍本。比如在這個程序中,認證就放在auth這個藍本中。藍本去分配其中的路由。通過藍本中的視圖文件去操作form和template,對數據庫的操作也是在視圖文件中實現。原理就是這個原理,細節就不多說了。
接下來的代碼:
manager = Manager(app)
migrate = Migrate(app, db)
這是方便程序在腳本下操作的flask_script中的Manage類的實例和數據庫遷移的實例。
可能大家還有一個疑問:數據庫是什么時候創建的,這其實在對工廠函數初始化配置的時候,通過調用config.py中的配置已經完成了。
總結
通過運行manage.py的過程分析,發現總體框架特點如下:
manage.py——公司的總經理
工廠函數——項目經理
config.py——公司財務總監
藍本——各技術部門經理
視圖文件——廣大苦逼工程師
template/form——生產工具(槍/炮)
總經理:干
項目經理:好, 財務總監,這糧和錢…, 部門經理們,我給你們說個事唄
財務總監:有的,你有我有全都有。都在這里,別客氣
技術部門經理:好的。那啥,小明,小紅,抄家伙
工程師:…(端着槍扛着炮就沖出去了)
本文轉自:https://blog.csdn.net/weixin_40834585/article/details/83042179
文章編寫不易,實屬感謝!