Flask從入門到精通之大型程序的結構一
盡管在單一腳本中編寫小型Web 程序很方便,但這種方法並不能廣泛使用。程序變復雜後,使用單個大型源碼文件會導致很多問題。不同於大多數其他的Web 框架,Flask 並不強制要求大型項目使用特定的組織方式,程序結構的組織方式完全由開發者決定。在本節,我們將介紹一種使用包和模塊組織大型程序的方式。
一.項目結構
Flask 程序的基本結構如下所示:
|-blogs |-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
這種結構有4 個頂級文件夾:
- Flask程序一般都保存在名為app 的包中
- 和之前一樣,migrations文件夾包含數據庫遷移腳本
- 單元測試編寫在tests包中
- 和之前一樣,venv 文件夾包含Python 虛擬環境
同時還創建了一些新文件:
- requirements.txt列出了所有依賴包,便於在其他電腦中重新生成相同的虛擬環境
- config.py 存儲配置
- manage.py用於啟動程序以及其他的程序任務
為了幫助你完全理解這個結構,下面幾節講解把前面介紹的hello.py 程序轉換成這種結構的過程
二.配置選項
程序經常需要設定多個配置。這方面最好的例子就是開發、測試和生產環境要使用不同的數據庫,這樣才不會彼此影響。我們不再使用hello.py 中簡單的字典狀結構配置,而使用層次結構的配置類。config.py 文件的內容如下示例所示:
import os basedir = os.path.abspath(os.path.dirname(__file__)) class Config: SECRET_KEY = os.environ.get(‘SECRET_KEY‘) or ‘hard to guess string‘ MAIL_SERVER = os.environ.get(‘MAIL_SERVER‘, ‘smtp.googlemail.com‘) MAIL_PORT = int(os.environ.get(‘MAIL_PORT‘, ‘587‘)) MAIL_USE_TLS = os.environ.get(‘MAIL_USE_TLS‘, ‘true‘).lower() in [‘true‘, ‘on‘, ‘1‘] MAIL_USERNAME = os.environ.get(‘MAIL_USERNAME‘) MAIL_PASSWORD = os.environ.get(‘MAIL_PASSWORD‘) FLASKY_MAIL_SUBJECT_PREFIX = ‘[Flasky]‘ FLASKY_MAIL_SENDER = ‘Flasky Admin <[email protected]>‘ FLASKY_ADMIN = os.environ.get(‘FLASKY_ADMIN‘) SSL_REDIRECT = False SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_RECORD_QUERIES = True FLASKY_POSTS_PER_PAGE = 20 FLASKY_FOLLOWERS_PER_PAGE = 50 FLASKY_COMMENTS_PER_PAGE = 30 FLASKY_SLOW_DB_QUERY_TIME = 0.5 @staticmethod def init_app(app): pass class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = os.environ.get(‘DEV_DATABASE_URL‘) or ‘sqlite:///‘ + os.path.join(basedir, ‘data-dev.sqlite‘) class TestingConfig(Config): TESTING = True SQLALCHEMY_DATABASE_URI = os.environ.get(‘TEST_DATABASE_URL‘) or ‘sqlite://‘ WTF_CSRF_ENABLED = False class ProductionConfig(Config): SQLALCHEMY_DATABASE_URI = os.environ.get(‘DATABASE_URL‘) or ‘sqlite:///‘ + os.path.join(basedir, ‘data.sqlite‘) @classmethod def init_app(cls, app): Config.init_app(app) # email errors to the administrators import logging from logging.handlers import SMTPHandler credentials = None secure = None if getattr(cls, ‘MAIL_USERNAME‘, None) is not None: credentials = (cls.MAIL_USERNAME, cls.MAIL_PASSWORD) if getattr(cls, ‘MAIL_USE_TLS‘, None): secure = () mail_handler = SMTPHandler( mailhost=(cls.MAIL_SERVER, cls.MAIL_PORT), fromaddr=cls.FLASKY_MAIL_SENDER, toaddrs=[cls.FLASKY_ADMIN], subject=cls.FLASKY_MAIL_SUBJECT_PREFIX + ‘ Application Error‘, credentials=credentials, secure=secure) mail_handler.setLevel(logging.ERROR) app.logger.addHandler(mail_handler) class HerokuConfig(ProductionConfig): SSL_REDIRECT = True if os.environ.get(‘DYNO‘) else False @classmethod def init_app(cls, app): ProductionConfig.init_app(app) # handle reverse proxy server headers from werkzeug.contrib.fixers import ProxyFix app.wsgi_app = ProxyFix(app.wsgi_app) # log to stderr import logging from logging import StreamHandler file_handler = StreamHandler() file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) class DockerConfig(ProductionConfig): @classmethod def init_app(cls, app): ProductionConfig.init_app(app) # log to stderr import logging from logging import StreamHandler file_handler = StreamHandler() file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) class UnixConfig(ProductionConfig): @classmethod def init_app(cls, app): ProductionConfig.init_app(app) # log to syslog import logging from logging.handlers import SysLogHandler syslog_handler = SysLogHandler() syslog_handler.setLevel(logging.INFO) app.logger.addHandler(syslog_handler) config = { ‘development‘: DevelopmentConfig, ‘testing‘: TestingConfig, ‘production‘: ProductionConfig, ‘heroku‘: HerokuConfig, ‘docker‘: DockerConfig, ‘unix‘: UnixConfig, ‘default‘: DevelopmentConfig }
基類Config 中包含通用配置,子類分別定義專用的配置。如果需要,你還可添加其他配置類。為了讓配置方式更靈活且更安全,某些配置可以從環境變量中導入。例如,SECRET_KEY 的值,這是個敏感信息,可以在環境中設定,但系統也提供了一個默認值,以防環境中沒有定義。在3 個子類中,SQLALCHEMY_DATABASE_URI 變量都被指定了不同的值。這樣程序就可在不同的配置環境中運行,每個環境都使用不同的數據庫。配置類可以定義init_app() 類方法,其參數是程序實例。在這個方法中,可以執行對當前環境的配置初始化。現在,基類Config 中的init_app() 方法為空。在這個配置腳本末尾,config 字典中註冊了不同的配置環境,而且還註冊了一個默認配置。
三.啟動腳本
頂級文件夾下的manage.py 文件用於啟動程序。腳本內容如下:
#!/usr/bin/env python import os from app import create_app, db from app.models import User, Role from flask.ext.script import Manager, Shell from flask.ext.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, Role=Role) manager.add_command("shell", Shell(make_context=make_shell_context)) manager.add_command(‘db‘, MigrateCommand) if __name__ == ‘__main__‘: manager.run()
這個腳本先創建程序。如果已經定義了環境變量FLASK_CONFIG,則從中讀取配置名;否則使用默認配置。然後初始化Flask-Script、Flask-Migrate 和為Python shell 定義的上下文。出於便利,腳本中加入了shebang 聲明,所以在基於Unix 的操作系統中可以通過./manage.py 執行腳本,而不用使用復雜的python manage.py。
四.需求文件
程序中必須包含一個requirements.txt 文件,用於記錄所有依賴包及其精確的版本號。如果要在另一臺電腦上重新生成虛擬環境,這個文件的重要性就體現出來了,例如部署程序時使用的電腦。pip 可以使用如下命令自動生成這個文件:
pip freeze >requirements.txt
安裝或升級包後,最好更新這個文件。需求文件的內容示例如下:
alembic==0.9.3 bleach==2.0.0 blinker==1.4 click==6.7 dominate==2.3.1 Flask==0.12.2 Flask-Bootstrap==3.3.7.1 Flask-HTTPAuth==3.2.3 Flask-Login==0.4.0 Flask-Mail==0.9.1 Flask-Migrate==2.0.4 Flask-Moment==0.5.1 Flask-PageDown==0.2.2 Flask-SQLAlchemy==2.2 Flask-WTF==0.14.2 html5lib==0.999999999 itsdangerous==0.24 Jinja2==2.9.6 Mako==1.0.7 Markdown==2.6.8 MarkupSafe==1.0 python-dateutil==2.6.1 python-dotenv==0.6.5 python-editor==1.0.3 six==1.10.0 SQLAlchemy==1.1.11 visitor==0.1.3 webencodings==0.5.1 Werkzeug==0.12.2 WTForms==2.1
如果你要創建這個虛擬環境的完全副本,可以創建一個新的虛擬環境,並在其上運行以下命令:
pip install -r requirements.txt
五.創建數據庫
不管從哪裏獲取數據庫URL,都要在新數據庫中創建數據表。如果使用Flask-Migrate 跟蹤遷移,可使用如下命令創建數據表或者升級到最新修訂版本
python manage.py db upgrade
Flask從入門到精通之大型程序的結構一