2019-06-09第六集

Flask之大型程序結(jié)構(gòu)

使用包和模塊組織大型程序

項(xiàng)目結(jié)構(gòu):

|-flasky
  |-app/
    |-templates/
    |-static/
    |-main/
      |-__init__.py
      |-errors.py
      |-forms.py
      |-views.py
    |-__init__.py
    |-email.py
    |-models.py
  |-migrations/
  |-tests/
    |-__init__.py
    |-test*.py
  |-venv/
  |-requirements.txt
  |-config.py
  |-manage.py

四個(gè)頂級(jí)文件夾

  • Flask程序一般都保存在名為app的包中
  • 和之前一樣,migrations文件夾包含數(shù)據(jù)庫(kù)遷移的腳本
  • 單元測(cè)試編寫在tests包中
  • 和之前一樣,側(cè)女文件夾包含Python虛擬環(huán)境

并同時(shí)創(chuàng)建一些新文件:

  • requirements.txt列出了所有依賴包,便于在其他電腦中重新生成相同的虛擬環(huán)境
  • config.py存儲(chǔ)配置
  • manage.py用于啟動(dòng)程序以及其他的程序任務(wù)

把hello.py轉(zhuǎn)換成這種結(jié)構(gòu)的過(guò)程

配置選項(xiàng)

程序經(jīng)常需要設(shè)定多個(gè)配置,開(kāi)發(fā)、測(cè)試和生產(chǎn)環(huán)境要使用不同的數(shù)據(jù)庫(kù),這樣才不會(huì)彼此影響。
使用層次結(jié)構(gòu)的配置類。

#程序的配置:基類 Config 中包含通用配置,子類分別定義專用的配置
import os 
basedir=os.path.abspath(os.path.dirname(__file__))

class Config:
  SECRET_KEY=os.environ.get('SECRET_KET') or 'hard ro guess string'
  #Flask(以及相關(guān)的擴(kuò)展extension)需要進(jìn)行加密
  SQLALCHEMY_COMMIT_ON_TEARDOWN=True
  #將其設(shè)為True時(shí),每次請(qǐng)求結(jié)束后都會(huì)自動(dòng)提交數(shù)據(jù)庫(kù)中的變動(dòng)。
  FLASKY_MAIL_SUBJECT_PREFIX=['Flasky']
  FLASKY_MAIL_SENDER='Flasky Admin <flasky@example.com>'
  FLASKy_ADMIN=os.environ.get('FLASKY_ADMIN')

  @staticmethod
  def init_app(app):
    pass

class DevelopmentConfig(Config):
  DEBUG=True
  MAIL_SERVER='smtp.googlemail.com'
  MAIL_PORT=587
  MAIL_USE_TLS=True
  MAIL_USERNAME=os.environ.get('MAIL_USERNAME')
  MAIL_PASSWORD=os.environ.get('MAIL_PASSWORD')
  SQLALCHEMY_DATABASE_URL=os.environ.get('DEV_DATABASE_URL') or \'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')

class TestingConfig(Config):
  TESTING=True
  SQLALCHEMY_DATABASE_URL=os.environ.get('TEST_DATABASE_URL') or \'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')

class ProductionConfig(Congfig):
  SQLCHEMY_DATABASE_URL=os.environ.get('DATABASE_URL') or \'sqlite:///' + os.path.join(basedir, 'data.sqlite')

#在 3 個(gè)子類中, SQLALCHEMY_DATABASE_URI 變量都被指定了不同的值。這樣程序就可在不同的配置環(huán)境中運(yùn)行,每個(gè)環(huán)境都使用不同的數(shù)據(jù)庫(kù)。

config={
      'development':DevelopmentConfig,
      'testing':TestingConfig,
      'production':ProduvtionConfig,
      'default':DevelopmentConfig
}

程序包

程序包用來(lái)保存程序的所有代碼,模板和靜態(tài)文件

使用程序工廠函數(shù)

為了可以修改配置,可以延遲創(chuàng)建程序?qū)嵗?,把?chuàng)建過(guò)程移到可顯式調(diào)用的工廠函數(shù)中,這樣可以創(chuàng)建多個(gè)程序?qū)嵗?br>

程序的工廠函數(shù)在app包的構(gòu)造文件中定義

#app/__init__.py:程序包的構(gòu)造文件
from flask import Flask,render_template
from flask.ext.boorstrap import Bootstrap
from flask.ext.mail import Mail
from flask.ext.moment import Moment
from flask.ext.sqlalchemy import SQLALchemy
from onfig import config

bootstrap=Bootstrap()
mail=Mail()
moment=Moment()
db=SQLALchemy()

#creat_app()函數(shù)就是程序的工廠函數(shù)
def creat_app(config_name):
  app=Flask(__name__)
  app.config.from_object(config[config_name])
  #配置類在 config.py 文件中定義,其中保存的配置可以使用 Flask app.config 配置對(duì)象提供的 from_object() 方法直接導(dǎo)入程序。
  config[config_name].init_app(app)

  bootstrap.init_app(app)
  mail.init_app(app)
  moment.init_app(app)
  db.init_app(app)

  #附加路由和自定義的錯(cuò)誤界面
  return app

在藍(lán)本中實(shí)現(xiàn)程序功能

在藍(lán)本中定義的路由處于休眠狀態(tài),直到藍(lán)本注冊(cè)到程序上后,路由才真正成為程序的一部分。為了獲得最大的靈活性,程序包中創(chuàng)建了一個(gè)子包,用于保存藍(lán)本。

#app/main/__init__.py:創(chuàng)建藍(lán)本
from flask import Blueprint

main-Blueprint('main',__name__)     #通過(guò)實(shí)例化一個(gè) Blueprint 類對(duì)象可以創(chuàng)建藍(lán)本。這個(gè)構(gòu)造函數(shù)有兩個(gè)必須指定的參數(shù):藍(lán)本的名字和藍(lán)本所在的包或模塊。

from . import views,errors

程序的路由保存在包里的 app/main/views.py 模塊中,而錯(cuò)誤處理程序保存在 app/main/errors.py 模塊中。導(dǎo)入這兩個(gè)模塊就能把路由和錯(cuò)誤處理程序與藍(lán)本關(guān)聯(lián)起來(lái)。

#app/__init__.py:注冊(cè)藍(lán)本
def creat_app(config_name):
  #...
  from .main import main as main_blueprint
  app.register_blueprint(main_blueprint)

  return app

#app/main/errors.oy 藍(lán)本中的錯(cuò)誤處理程序
在藍(lán)本中編寫錯(cuò)誤處理程序稍有不同,如果使用 errorhandler 修飾器,那么只有藍(lán)本中的錯(cuò)誤才能觸發(fā)處理程序。要想注冊(cè)程序全局的錯(cuò)誤處理程序,必須使用 app_errorhandler 。
from flask import render_template
from . import main

@main.app_errorhandler(404)
def page_not_found(e):
  return render_template('404.html'),404

@main.app_errorhandler(500)
def internal_server_error(e):
  return render_template('500.html'),500

#app/main/views.py藍(lán)本中定義的程序路由
from datetime import datetime
from flask import render_tempalte,session,redirect,url_for

from . import main
from .froms import NameForm
from .. import db 
from ..models import User

@main.route('/',methods=['GET','POST'])
def index():
  form=NameForm()
  if form.validate_on_submit():
    #...
    return redirect(url_for('.index'))
  return render_tempalte('index.html',form=form,name=session.get('name'),known=session.get('known',Flase),current_time=datetime.utcnow())
#url_for() 函數(shù)還支持一種簡(jiǎn)寫的端點(diǎn)形式,在藍(lán)本中可以省略藍(lán)本名,例如 url_for('.index') 。在這種寫法中,命名空間是當(dāng)前請(qǐng)求所在的藍(lán)本。

啟動(dòng)腳本

頂級(jí)文件夾中的manage.py文件用于啟動(dòng)程序

#manage.py:?jiǎn)?dòng)腳本
import os 
from app import creat_app,db
from app.models import User,Role
from flask.ext.script import Manager.Shell
from flask.ext.migrate import Migrate,MigrateCommand

app=creat_app(os.getenv('FLASK_CONFIG') or 'default')
manager=Manger(app)
migrate=Migrate(app,db)

def make_shell_context():
  return dict(app=app,db=db,User=User,Role=Role)
manager.add_command('shell',shell(make_context=make_shell_context))
manager.add_command('db',MigrateCommand)

if __name__=='__main__':
  manager.run()    

需求文件

程序中必須包含一個(gè) requirements.txt 文件,用于記錄所有依賴包及其精確的版本號(hào)。

Flask==0.10.1
Flask-Bootstrap==3.0.3.1
Flask-Mail==0.9.0
Flask-Migrate==1.1.0
Flask-Moment==0.2.0
Flask-SQLAlchemy==1.0
Flask-Script==0.6.6
Flask-WTF==0.9.4
Jinja2==2.7.1
Mako==0.9.1
MarkupSafe==0.18
SQLAlchemy==0.8.4
WTForms==1.0.5
Werkzeug==0.9.4
alembic==0.6.2
blinker==1.3
itsdangerous==0.23

如果你要?jiǎng)?chuàng)建這個(gè)虛擬環(huán)境的完全副本,可以創(chuàng)建一個(gè)新的虛擬環(huán)境,并在其上運(yùn)行以下命令:

(venv) $ pip install -r requirements.txt

單元測(cè)試

tests/test_basics.py:單元測(cè)試
from unittest
from flask import current_app
from app import create_app,db

class BasicsTestCase(unittest.TestCase):
  def setUp(self):
    self.app=create_app('testing')
    self.app_context=self.app.app_context()
    self.app_context.push()
    db.create_all()

  def teatDown(self):
    db.session.remove()
    db.drop_all()
    self.app_context.pop()

  def test_app_exists(self):
    self.assertFalse(current_app is None)

  def test_app_is_testing(self):
    self.assertTrue(current_app.config['TESTING'])
#如果你想進(jìn)一步了解如何使用 Python 的 unittest 包編寫測(cè)試,請(qǐng)閱讀官方文檔(https://docs.python.org/2/library/unittest.html)。

#manger.py:啟動(dòng)單元測(cè)試的命令
@manager.command
def test():
"""Run the unit tests."""
  import unittest
  tests = unittest.TestLoader().discover('tests')
  unittest.TextTestRunner(verbosity=2).run(tests)

創(chuàng)建數(shù)據(jù)庫(kù)

首選從環(huán)境變量中讀取數(shù)據(jù)庫(kù)的 URL,同時(shí)還提供了一個(gè)默認(rèn)的 SQLite 數(shù)據(jù)庫(kù)做備用。3
種配置環(huán)境中的環(huán)境變量名和 SQLite 數(shù)據(jù)庫(kù)文件名都不一樣。例如,在開(kāi)發(fā)環(huán)境中,數(shù)據(jù)
庫(kù) URL 從環(huán)境變量 DEV_DATABASE_URL 中讀取,如果沒(méi)有定義這個(gè)環(huán)境變量,則使用名為
data-dev.sqlite 的 SQLite 數(shù)據(jù)庫(kù)。


不管從哪里獲取數(shù)據(jù)庫(kù) URL,都要在新數(shù)據(jù)庫(kù)中創(chuàng)建數(shù)據(jù)表。如果使用 Flask-Migrate 跟
蹤遷移,可使用如下命令創(chuàng)建數(shù)據(jù)表或者升級(jí)到最新修訂版本:

(venv) $ python manage.py db upgrade
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容