日課書

编程100小时挑战

Flask程序结构

在编写小型Web程序时我们可以使用单一文件,但是当程序较复杂是,这种结构会导致很多问题。当我们在开发Flask Web程序时,可以使用包和模块组织源码。

1. 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|- myproject
|- app/
|- template/
|- 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

2. 配置选项

使用配置文件,为开发、测试和生产环境进行不同的设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
GRITY_MAIL_SUBJECT_PREFIX = '[Grity]'
GRITY_MAIL_SENDER = 'Grity Admin <grity@example.com>'
GRITY_ADMIN = os.environ.get('GRITY_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_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:///' + os.path.join(basedir, 'data-test.sqlite')

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

config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,

'default': DevelopmentConfig
}

3. 程序包

  • 使用程序工厂函数

使用单文件开发的Flask应用,其程序在全局作用域中创建,所以无法动态修改配置文件。运行脚本时,程序实例已经创建了。为了提高测试覆盖度,必须在不同的配置环境中运行程序,所以必须延迟创建程序实例,把程序创建过程移到可显式调用的工厂函数中。使用这种方法还可可以创建多个程序实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# app/__init__.py
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from config import config

bootstrap = Bootstrap()
db = SQLAlchemy()

def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config.[config_name])
config[config_name].init_app(app)

bootstrap.init_app(app)
db.init_app(app)

# add routes and errorhandlers

return app
  • 在Blueprint中实现程序功能

Blueprint包的构造文件

1
2
3
4
5
6
# app/main/__init__.py
from flask import Blueprint

main = Blueprint('main', __name__)

from . import views, errors

把Blueprint注册到程序上

1
2
3
4
5
6
7
# app/__init__.py
def create_app(config_name):
# ...
from .mian import main as main_blueprint
app.register_blueprint(main_blueprint)

return app

Blueprint中的错误处理程序

1
2
3
4
5
6
7
8
9
10
11
# app/mian/errors.py
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

Blueprint中的路由定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# app/main/views.py
from flask import render_template, session, redirect, url_for

from . import main
from .forms 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_template('index.html', form=form, name=session.get('name'),
known=session.get('known', False))

4. 启动脚本

顶级文件夹中的manage.py文件用于启动程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/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()

5. 需求文件

程序中需要包含一个requirements.txt文件,用于记录所有依赖包及其版本号。这样就可以在另外一台电脑上重新生成虚拟环境。

1
(venv) $ pip freeze > requirements.txt

如要创建这个虚拟环境的副本,运行如下命令:

1
(venv) $ pip install -r requirements.txt

6. 单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 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 tearDown(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'])

在manage.py脚本中添加test自定义命令

1
2
3
4
5
@manager.command
def test():
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)

7. 创建数据库

1
2
3
4
5
6
$ python manage.py db init

$ python manage.py db migrate -m "initial migrate"

$ python manage.py db upgrade
# downgrade