大型程序的结构(三)【程序包】重点!!!
来源:互联网 发布:爱国 知乎 编辑:程序博客网 时间: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">×</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.(没变)
- 大型程序的结构(三)【程序包】重点!!!
- 大型程序的结构
- 大型程序的结构(一)
- 大型程序的结构(四)
- 介绍大型程序的结构
- 大型程序的结构(二)【配置选项config.py】
- Flask(7)-大型程序的结构
- 创建ROS程序包(三)
- Unity3D 大型游戏 最后一站 源码 部分重点NetworkManager(三)(9)
- Unity3D 大型游戏 最后一站 源码 部分重点匹配战斗(三)(12)
- flask大型程序的结构学习中遇到的问题
- 程序的三种结构
- 三、程序的控制结构
- 大型站点SEO优化的核心重点
- (二)2.1程序的三种基本结构
- ROS教程(三):创建ROS程序包
- 程序的三种控制结构
- 程序的三种基本结构
- sqlalchemy ORM使用总结
- 导航栏 $_ajax异步局部刷新加载内容
- DB2的TRUNCATE功能
- kafka集群安装详细说明
- C控制语句:循环
- 大型程序的结构(三)【程序包】重点!!!
- mysql连接权限测试
- 【MVVM】Android Data Binding实战(一)
- 自定义雅虎新闻闪屏加载动画
- ScheduledExecutorService 不能进行任务调度
- c#调用笔记本摄像头并使用tcp传输图像
- Android NDK学习笔记3-入门案例篇
- Android中常见的热门标签的流式布局的实现
- nodejs 本地应用部署