大型程序的结构(三)【程序包】重点!!!

来源:互联网 发布:爱国 知乎 编辑:程序博客网 时间:2024/05/22 23:28

程序包用来保存程序的所有代码,模板和静态文件。
这个包直接称为app(应用),也可使用一个程序专用的名字。
templates和static文件夹是程序包的一部分,因此两个文件夹被移到app文件夹中。
数据库模型和电子邮件支持函数也被移到了这个包中,分别保存为app/models.py和app/email.py。

使用程序工厂函数

课外资料

Flask 扩展开发
自定义的flask扩展flask_sqlite3
扩展的代码flask_sqlite3.py如下:
下面是用来复制/粘贴的 flask_sqlite3.py 的内容:

import sqlite3from flask import current_app# Find the stack on which we want to store the database connection.# Starting with Flask 0.9, the _app_ctx_stack is the correct one,# before that we need to use the _request_ctx_stack.try:    from flask import _app_ctx_stack as stackexcept ImportError:    from flask import _request_ctx_stack as stackclass SQLite3(object):    def __init__(self, app=None):        self.app = app        if app is not None:            self.init_app(app)    def init_app(self, app):        app.config.setdefault('SQLITE3_DATABASE', ':memory:')        # Use the newstyle teardown_appcontext if it's available,        # otherwise fall back to the request context        if hasattr(app, 'teardown_appcontext'):            app.teardown_appcontext(self.teardown)        else:            app.teardown_request(self.teardown)    def connect(self):        return sqlite3.connect(current_app.config['SQLITE3_DATABASE'])    def teardown(self, exception):        ctx = stack.top        if hasattr(ctx, 'sqlite3_db'):            ctx.sqlite3_db.close()    @property    def connection(self):        ctx = stack.top        if ctx is not None:            if not hasattr(ctx, 'sqlite3_db'):                ctx.sqlite3_db = self.connect()            return ctx.sqlite3_db

仔细看其中一段代码:

def __init__(self, app=None):        self.app = app        if app is not None:            self.init_app(app)

__init__方法接受一个可选的应用对象,并且如果提供,会调用 init_app 。
init_app 方法使得 SQLite3 对象不需要应用对象就可以实例化。这个方法支持工厂模式来创建应用。 init_app 会为数据库设定配置,如果不提供配置,默认是一个内存中的数据库。此外, init_app 方法附加了 teardown 处理器。 它会试图使用新样式的应用上下文处理器,并且如果它不存在,退回到请求上下文处理器。

此外, init_app 方法用于支持创建应用的工厂模式:

db = Sqlite3()# Then later on.app = create_app('the-config.cfg')db.init_app(app)


正文

flasky/app/_init_.py
构造文件

from flask import Flaskfrom flask_bootstrap import Bootstrapfrom flask_mail import Mailfrom flask_moment import Momentfrom flask_sqlalchemy import SQLAlchemyfrom config import config###构造文件导入了大多数正在使用的Flask扩展。bootstrap = Bootstrap()mail = Mail()moment = Moment()db = SQLAlchemy()###由于尚未初始化所需的程序实例app,所以没有初始化扩展,创建扩展类是没有向构造函数传入参数。def create_app(config_name):###create_app()函数就是程序的工厂函数,接受一个参数,是程序使用的配置名    app = Flask(__name__)    ###初始化Flask的程序实例app    app.config.from_object(config[config_name])    ###配置类在config.py文件中定义,其中保存的配置可以使用Flask app.config配置对象提供的from_object()方法直接导入程序。    config[config_name].init_app(app)    bootstrap.init_app(app)    mail.init_app(app)    moment.init_app(app)    db.init_app(app)    from .main import main as main_blueprint     ###从本级目录下的main文件夹中引入main作为main_blueprint蓝本    app.register_blueprint(main_blueprint)    ###main_blueprint蓝本注册到程序app上。    ###在蓝本中定义的路由处于休眠状态,直到蓝本注册程序上后,路由才真正成为程序的一部分。    return app

config[config_name]调用init_app的方法是在config类中定义的,flask的扩展也能调用init_app方法是因为在flask的扩展Bootstrap,Mail,Moment,SQLAlchemy等等也定义了init_app方法,这两个调用的不是同一个。
flask实例化成app之后,通过app.config.from_object(config[config_name])关联的相关配置,而flask的扩展Bootstrap,Mail,Moment,SQLAlchemy等等只要关联上app这个实例就可以直接访问配置。flask扩展在关联app一般会看到两种方式,一种直接app实例化,像这样Bootstrap(app);一种是先实例化,再init_app(app) 。
这里写图片描述
init_app函数的讲解
1. 把 db 和 app 联系起来,这是 Flask 的插件机制,这样可以将你的设置同 sqlalchemy 关联起来,这个关联就是用的init_app
2. init_app 函数的作用就是把你在 Flask 中的 config 和 sqlalchemy关联,这个可以查看源代码:
flask-sqlalchemy/flask_sqlalchemy/init.py
,而且可以在每次请求结束后自动session.remove()
3. 这个插件同时提供了很多有用的功能,比如分页等,以及一些数据库方面的设置
4. 把 sqlalchemy 的查询等操作方式改为了类似 Django 的方式,这点我比较喜欢
5. create_all()时记得执行这个函数的源文件导入了你创建的 Model
6. 这个插件只是对 sqlalchemy 的封装,只是让其在 Flask 中更加易用,在 June 的 Tornado中也使用了这个插件的一点功能。如果你觉得它的方式让自己觉得不爽,可以直接使用 sqlalchemy。
7. 在 Flask 中的插件貌似都要跟 app 关联。
部分源代码:

def init_app(self, app):        """This callback can be used to initialize an application for the        use with this database setup.  Never use a database in the context        of an application not initialized that way or connections will        leak.        """        app.config.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite://')        app.config.setdefault('SQLALCHEMY_BINDS', None)        app.config.setdefault('SQLALCHEMY_NATIVE_UNICODE', None)        app.config.setdefault('SQLALCHEMY_ECHO', False)        app.config.setdefault('SQLALCHEMY_RECORD_QUERIES', None)        app.config.setdefault('SQLALCHEMY_POOL_SIZE', None)        app.config.setdefault('SQLALCHEMY_POOL_TIMEOUT', None)        app.config.setdefault('SQLALCHEMY_POOL_RECYCLE', None)        app.config.setdefault('SQLALCHEMY_MAX_OVERFLOW', None)        app.config.setdefault('SQLALCHEMY_COMMIT_ON_TEARDOWN', False)        track_modifications = app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', None)        if track_modifications is None:            warnings.warn(FSADeprecationWarning('SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.'))        if not hasattr(app, 'extensions'):            app.extensions = {}        app.extensions['sqlalchemy'] = _SQLAlchemyState(self, app)        # 0.9 and later        if hasattr(app, 'teardown_appcontext'):            teardown = app.teardown_appcontext        # 0.7 to 0.8        elif hasattr(app, 'teardown_request'):            teardown = app.teardown_request        # Older Flask versions        else:            if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:                raise RuntimeError("Commit on teardown requires Flask >= 0.7")            teardown = app.after_request        @teardown        def shutdown_session(response_or_exc):            if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:                if response_or_exc is None:                    self.session.commit()            self.session.remove()            return response_or_exc

在蓝本中实现程序功能

flasky/app/main/_init_.py
在单脚本程序中,程序实例存在于全局作用域中,路由可以直接使用app.route修饰器定义。但现在程序在运行时创建,只有调用create_app()之后才能使用app.route修饰器,这时定义路由就太晚了。
Flask提供的蓝本和程序类似,也可以定义路由。
不同的是在蓝本中定义的路由处于休眠状态,直到蓝本注册程序上后,路由才真正成为程序的一部分。
使用位于全局作用域中的蓝本时,定义路由的方法几乎和单脚本程序一样。

from flask import Blueprint###从flask导入BluePrint模块main = Blueprint('main', __name__)###通过实例化一个Blueprint类对象可以创建蓝本。这个构造函数有两个必须指定的参数:蓝本的名字和蓝本所在的包或者模块。###???和程序一样。大多数情况下第二个参数使用Python的__name__变量即可。from . import views, errors###导入这两个模块就能把路由和错误处理程序与蓝本关联起来。###???在最后一行导入,在其他模块导入main时就不导入最后一句了,避免了循环导入依赖。

flasky/app/main/views.py

from flask import render_template, session, redirect, url_for, current_appfrom .. import db###从上级目录下的__init__.py导入db对象【SQLAlchemy类实例,表示程序正在使用的数据库】from ..models import User###从上级目录下的models.py导入User模型from ..email import send_email###从上级目录下的email.py导入send_email()函数from . import main###从本目录__init__.py导入main###【.后面不写明哪个模块的话默认导入本目录下的 __init__.py】from .forms import NameForm###从本目录下的forms.py导入NameForm函数@main.route('/', methods=['GET', 'POST'])###路由修饰器有蓝本提供def index():    form = NameForm()    if form.validate_on_submit():        user = User.query.filter_by(username=form.name.data).first()        if user is None:            user = User(username=form.name.data)            db.session.add(user)            session['known'] = False            if current_app.config['FLASKY_ADMIN']:                send_email(current_app.config['FLASKY_ADMIN'], 'New User',                           'mail/new_user', user=user)        else:            session['known'] = True        session['name'] = form.name.data        return redirect(url_for('.index'))    return render_template('index.html',                           form=form, name=session.get('name'),                           known=session.get('known', False))

url_for()函数的第一个参数是路由的端点名,程序路由中默认为视图函数的名字。
在单脚本程序中,index()视图函数的URL可使用url_for("index")获取。
命名空间就是蓝本的名字(Blueprint构造函数的第一个参数),所以视图函数index()注册的端点名是main.index,其URL使用url_for('main.index')获取,如果命名空间是当前请求所在的蓝本,可以使用省略蓝本名,例如url_for('.index')
flasky/app/main/errors.py

from flask import render_templatefrom . import main@main.app_errorhandler(404)###如果使用errorhandler修饰器,那么只有蓝本中的错误才能触发处理程序。###要想注册处理全局的错误处理程序,必须使用app_errorhandler。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

flasky/app/main/forms.py
表单对象也要移到蓝本中,保存于app/main/forms.py模块

from flask_wtf import Formfrom wtforms import StringField, SubmitFieldfrom wtforms.validators import Requiredclass NameForm(Form):    name = StringField('What is your name?', validators=[Required()])    submit = SubmitField('Submit')



app程序包下的所有蓝本共同使用的模块

模型函数【定义Role和User模型】

flasky/app/models.py

from . import dbclass Role(db.Model):    __tablename__ = 'roles'    id = db.Column(db.Integer, primary_key=True)    name = db.Column(db.String(64), unique=True)    users = db.relationship('User', backref='role', lazy='dynamic')    def __repr__(self):        return '<Role %r>' % self.nameclass User(db.Model):    __tablename__ = 'users'    id = db.Column(db.Integer, primary_key=True)    username = db.Column(db.String(64), unique=True, index=True)    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))    def __repr__(self):        return '<User %r>' % self.username

发送邮件的模块

flasky/app/email.py

from threading import Threadfrom flask import current_app, render_templatefrom flask_mail import Messagefrom . import maildef send_async_email(app, msg):    with app.app_context():        mail.send(msg)def send_email(to, subject, template, **kwargs):    app = current_app._get_current_object()    ###!!!!!!!!!!!!    msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' ' + subject,                  sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])    msg.body = render_template(template + '.txt', **kwargs)    msg.html = render_template(template + '.html', **kwargs)    thr = Thread(target=send_async_email, args=[app, msg])    thr.start()    return thr

课外知识:

链接传递代理作为发送端
current_app._get_current_object()
发送信号
如果你想要发出信号,调用 send() 方法可以做到。 它接受发送端作为第一个参数,和一些推送到信号订阅者的可选关键字参数:

class Model(object):    ...    def save(self):        model_saved.send(self)

永远尝试选择一个合适的发送端。如果你有一个发出信号的类,把 self 作为发送端。如果你从一个随机的函数发出信号,把 current_app._get_current_object() 作为发送端。

传递代理作为发送端
永远不要向信号传递 current_app 作为发送端,使用 current_app._get_current_object() 作为替代。这样的原因是, current_app 是一个代理,而不是真正的应用对象。

静态文件夹放在app程序包下面

static/favicon.ico → app/static/favicon.ico
File renamed without changes.(没变)

模板文件夹放在app程序包下面

templates/404.html → app/templates/404.html
File renamed without changes.(没变)
templates/500.html → app/templates/500.html
File renamed without changes.(没变)
templates/base.html → app/templates/base.html
href=”/”更改成href=”{{ url_for(‘main.index’) }}
这个的具体到哪个命名空间下的端点名
【蓝本文件夹的名字.这个文件夹下面templates里视图函数的名字】
命名空间就是蓝本的名字(Blueprint构造函数的第一个参数),所以视图函数index()注册的端点名是main.index,其URL使用url_for(‘main.index’)获取。

 {% extends "bootstrap/base.html" %} {% block title %}Flasky{% endblock %} {% block head %} {{ super() }} <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon"> <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon"> {% endblock %} {% block navbar %} <div class="navbar navbar-inverse" role="navigation">     <div class="container">         <div class="navbar-header">             <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">                 <span class="sr-only">Toggle navigation</span>                 <span class="icon-bar"></span>                  <span class="icon-bar"></span>                  <span class="icon-bar"></span>              </button> -            <a class="navbar-brand" href="/">Flasky</a> #####################减少 +            <a class="navbar-brand" href="{{ url_for('main.index') }}">Flasky</a> ####################增加          </div>          <div class="navbar-collapse collapse">              <ul class="nav navbar-nav"> -                <li><a href="/">Home</a></li> #####################减少 +                <li><a href="{{ url_for('main.index') }}">Home</a></li> ####################增加              </ul>          </div>      </div> </div> {% endblock %} {% block content %} <div class="container">     {% for message in get_flashed_messages() %}     <div class="alert alert-warning">         <button type="button" class="close" data-dismiss="alert">&times;</button>         {{ message }}     </div>     {% endfor %}     {% block page_content %}{% endblock %} </div> {% endblock %} {% block scripts %} {{ super() }} {{ moment.include_moment() }} {% endblock %}

templates/index.html → app/templates/index.html
File renamed without changes.(没变)
templates/mail/new_user.html → app/templates/mail/new_user.html
File renamed without changes.(没变)
templates/mail/new_user.txt → app/templates/mail/new_user.txt
File renamed without changes.(没变)

0 0
原创粉丝点击