• 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 <flasky@example.com>'
        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
  • 相关阅读:
    2018QBXT刷题游记(4)
    洛谷 P4302 字符串折叠 题解
    hdu5009 Paint Pearls 题解
    CF467C George and Job 题解
    洛谷P2622 关灯问题II 题解
    洛谷 P3049园林绿化 题解
    洛谷 P1064 金明的预算方案 题解
    洛谷P1979 华容道 题解
    2018QBXT刷题游记(3)
    2018QBXT刷题游记(2)
  • 原文地址:https://www.cnblogs.com/senlinyang/p/8391065.html
Copyright © 2020-2023  润新知