文档章节

一个flask的可拔插的大型架构,通过应用工厂和应用分发来实现。

Klaus88
 Klaus88
发布于 2014/12/01 10:38
字数 2026
阅读 257
收藏 1

先说,我是python的新手,所以多有谬误,请各位大神放过。

这篇文章只是自己折腾的研究的经验结果,分享出来,献丑了。

一直以来一直寻找一种比较好的方式来组织flask的程序,无论是blueprint、application factories还是底层的werkzeug的DispatcherMiddleware都试过,一直都有这样那样的问题。

实际来说,比如你的网站,突然需要加入一个模块,这个模块的耦合度不是很高,有独立的数据库,然后可能用过一段时间后就卸下来了。这样其实对于flask来说,很多人说用blueprint就好了。其实不然,最简单的,blueprint的耦合度其实不低,单纯的“有独立数据库”这条,你用sqlalchemy就很难实现,当然有人说有SQLALCHEMY_BINDS,但都不是我想要的。因为这种模块,很可能只是一个新人来了之后随便急急忙忙开发的,代码质量不需要去保证很多,因为是临时性的。

好了,不多说了,这篇文章,其实就是为了展现一种标题上描述的,适用于大型架构,同时不失灵活性、各种模块可以方便拔插的flask应用的组织结构,这种方式很类似于django的app,各个模块在不需要耦合的时候可以独立建立project单独开发,之后再合并。

下面来step by step:

1、下载附件中的my_basic_flask.rar,那是一个我用pycharm写的工程,是组成架构的app。(旁边那个显示不出来的图,右键另存后,改后缀名为rar即可解压

解释一下那个project的各个部分。


A、根目录

结构很明显:根目录是run.py和alembic的配置文件。

run.py其实没什么用,是在pycharm里运行和测试项目用的。

# run.py
from basic_app import create_app

if __name__ == '__main__':
    create_app().run()

run.py和alembic.ini都不用改。

B、alembic文件夹

然后到alembic文件夹。用过alembic的都知道,其实它是用alembic init命令生成的,但是我做了修改。基本上里面最重要的就是env.py这个文件了。其实你也可以都不用修改,它就能正常运行。好吧一句话,不要动这个文件夹,让它就那样吧!

另外切记,version文件夹不要删,不然因为权限问题,alembic没办法生成文件夹,就一堆fail了。

C、basic_app文件夹

重点来了。

看看这个文件夹的结构。


2个文件夹admin和auth是2个blueprint,后面再说。先说根目录。

config.py就是放一些设置了

#config.py

import os

parent_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))

DEBUG = True
SECRET_KEY = "dev"

SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(parent_path, 'app.db')

注意那个parent_path,其实是为了和alembic能一样,所以不要改吧。

__init__.py,这个是应用工厂的基础,所有magic的重点!我贴出来吧。

# __init__.py
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from . import config

db = SQLAlchemy()


def load_models():
    from .auth import models
    from .admin import models


load_models()


def init_extensions(app):
    db.init_app(app)


def init_views(app):
    from . import auth
    from . import admin

    app.register_blueprint(auth.bp, url_prefix="/auth")
    app.register_blueprint(admin.bp, url_prefix='/admin')


def create_app(config=config):
    app = Flask(__name__)
    app.config.from_object(config)

    init_extensions(app)
    init_views(app)

    return app


几个注意点。

1、是包名的引用,都是用的相对引用,因为这样才能在之后放到werkzeug.wsgi.DispatcherMiddleware没有问题。

2、是load_models,用这种方法把models加载入内存,才能让alembic正确的识别出数据库对象。

3、是用create_app的应用工厂方法,这个好处很多,包括alembic、middleware和unittest。结构简单明了。

D、admin文件夹

解释一个blueprint,admin包。

static和templates不用说了。

看看__init__.py

from flask import Blueprint

bp = Blueprint("admin", __name__, template_folder="templates")

from . import views


在这里定义Blueprint,并引入views

# views.py

from . import bp
from .models import Admin


@bp.route('/')
def hello():
    return 'my basic flask structure is here! Admin'


@bp.route('/<username>')
def show_user(username='batman'):
    u = None
    try:
        u = Admin.query.filter_by(username=username).first_or_404()
    except():
        pass
    finally:
        if u is not None:
            return 'hello, Admin {} from DB!'.format(u.username)
        else:
            return 'hello, Admin {} from param!'.format(username)

这里注意相对引用包。

这里还演示了一个数据库查询。

最后看看models.py

# models.py
from .. import db


class Admin(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False, unique=True, index=True)
    password = db.Column(db.String(100), nullable=False)

还是注意相对引用。

E、说说一个难点,alembic的用法

打开cmd,看看我的操作记录。

注意,是引用FlaskEnv这个虚拟Env下的alembic.exe,在my_basic_flask文件夹(当前工程的文件夹)下运行命令行。

D:\>cd D:\PythonProjects\my_basic_flask

D:\PythonProjects\my_basic_flask>D:\PythonProjects\FlaskEnv\Scripts\alembic.exe
init alembic

D:\PythonProjects\my_basic_flask>D:\PythonProjects\FlaskEnv\Scripts\alembic.exe
revision --autogenerate -m "create model tables"
INFO  [alembic.migration] Context impl SQLiteImpl.
INFO  [alembic.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'group'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_group_name' on '['
name']'
INFO  [alembic.autogenerate.compare] Detected added table 'admin'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_admin_name' on '['
name']'
INFO  [alembic.autogenerate.compare] Detected added table 'user'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_username' on
'['username']'
INFO  [alembic.autogenerate.compare] Detected added table 'user_group'
Generating D:\PythonProjects\my_basic_flask\alembic\versions\cb328c966ae_create_
model_tables.py ... done

做到这步,对于sqlite数据库来说,要去做一个小动作。

进入

D:\PythonProjects\my_basic_flask\alembic\versions\cb328c966ae_create_model_tables.py

删除这几段。

    with op.batch_alter_table('group', schema=None) as batch_op:
        batch_op.create_index(batch_op.f('ix_group_name'), ['name'], unique=True)

    with op.batch_alter_table('admin', schema=None) as batch_op:
        batch_op.create_index(batch_op.f('ix_admin_name'), ['name'], unique=True)

    with op.batch_alter_table('user', schema=None) as batch_op:
        batch_op.create_index(batch_op.f('ix_user_username'), ['username'], unique=True)

最后继续运行命令,就OK了

D:\PythonProjects\my_basic_flask>D:\PythonProjects\FlaskEnv\Scripts\alembic.exe
upgrade head
INFO  [alembic.migration] Context impl SQLiteImpl.
INFO  [alembic.migration] Will assume non-transactional DDL.
INFO  [alembic.migration] Running upgrade  -> cb328c966ae, create model tables

F、如何在middleware整合这些app

在生成了数据库后,你的目录结构应该是这样的


ok,把这个项目拷出来,放到一个文件夹下,叫app1吧,并新建一个空的__init__.py,放在根目录下,使这个文件夹成为一个包。run.py可以删了。

现在的目录结构应该是这样了。


好了。这个就是一个基础的开发好的可拔插的包了。下面看看怎么做middleware的分发。

建立一个文件夹all_apps,把app1放进去。

然后放在apache里设定的文件夹,还记得吗?不记得的话,翻一下之前的文章吧!

我的文件夹叫 TestInApache。

然后是2个重要的文件,其实在之前我的说middleware的文章里有过描述。还是放出来吧。这两个文件是在TestInApache文件夹下的,和all_apps同级。

先是run_server.wsgi

#run_server.wsgi

activate_this = 'D:/PythonProjects/FlaskEnv/Scripts/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))
import site
import sys
# Remember original sys.path.
prev_sys_path = list(sys.path) 
# Add site-packages directory.
site.addsitedir('D:/PythonProjects/FlaskEnv/Lib/site-packages')
# Reorder sys.path so new directories at the front.
new_sys_path = [] 
for item in list(sys.path): 
    if item not in prev_sys_path: 
        new_sys_path.append(item) 
        sys.path.remove(item) 
        sys.path[:0] = new_sys_path 
sys.path.insert(0, 'D:/PythonProjects/TestInApache')
sys.path.insert(0, 'D:/PythonProjects/TestInApache/all_apps')

from app import app as application

和之前的文章提过的有一行不同,就在这里:

sys.path.insert(0, 'D:/PythonProjects/TestInApache/all_apps')

然后是app.py

#app.py

# -*- coding: utf-8 -*-

import sys
sys.path.insert(0, 'D:/PythonProjects/TestInApache/all_apps')

from werkzeug.serving import run_simple
from werkzeug.wsgi import DispatcherMiddleware

from all_apps.app1 import basic_app
from all_apps.index import app as app_index

app = DispatcherMiddleware(app_index, {
    '/app1': basic_app.create_app()
})

基本上都是固定的。

如果要引入新的app,就在里面仿照下面的,引入对应的包,然后在DispatcherMiddleware注册上去,就OK了。

from all_apps.app1 import basic_app
from all_apps.index import app as app_index

app = DispatcherMiddleware(app_index, {
    '/app1': basic_app.create_app()
})

这里我做了个很简单的Index包,来示例一下。

先放一下最终的目录结构

index包的内容其实可以不放的,很简单,但是放上来吧。

#__init__.py

from flask import Flask
from . import views

app = Flask(__name__)
app.register_blueprint(views.bp)

#views.py
from flask import Blueprint

bp = Blueprint("app1", __name__, url_prefix='')

@bp.route('/')
def index():
    return 'simple app!'

基本上可以结束了。最后。

G、说说访问的Url问题

这个可不是个小问题,会迷惑很多人。

我在本机搭建了apache的测试环境,对于这个项目,我做了个example.com的解析来测试。

先看直接访问example.com


明显,是直接由middleware分发到了index包内。

还记得我们做的basic_app包内吗?url应该是什么呢?

应该是http://example.com/admin 吗?


好吧,想起DispatcherMiddleware的设定没?

app = DispatcherMiddleware(app_index, {
    '/app1': basic_app.create_app()
})

试试 http://example.com/app1/admin ,出来了。

然后在试试传入个值,访问 http://example.com/app1/admin/admin_name


好了,基本上没什么说的了。

最后放上整个文件夹,同样是改后缀rar即可。

Fin。

© 著作权归作者所有

共有 人打赏支持
Klaus88
粉丝 5
博文 15
码字总数 7457
作品 0
南宁
高级程序员
私信 提问
加载中

评论(3)

hoosir
hoosir

引用来自“Klaus88”的评论

引用来自“hoosir”的评论

很有用的博文,学习了,谢谢分享。

已出py的坑,nodejs大好
哈哈,才入python的坑,你这篇文章对组织构建大型应用平台很有用处,和我想的差不多,我现在就是想做一个综合业务管理平台的基础框架。
Klaus88
Klaus88

引用来自“hoosir”的评论

很有用的博文,学习了,谢谢分享。

已出py的坑,nodejs大好
hoosir
hoosir
很有用的博文,学习了,谢谢分享。
服务端架构技术——基于OSGI服务端的架构设计和实现

摘要: OSGI架构一个服务端, 满足可插拔, 低耦合, 重用率高的问题. 并且服务端接收到指定协议的数据后, 能自动分发到相应的服务进行处理。 目的是为了满足后期的可插拔特性。本文是服务端设置...

晨曦之光
2012/03/09
333
0
Python进阶(五十)-解析Flask运行原理

Python进阶(五十)-解析Flask运行原理   在学习Python Web开发过程中,掌握了Flask的开发方法。经过一段时间的视频学习,回过头来对Flask的运行原理做一简要解析,以增强自己对Flask的了解。...

sunhuaqiang1
2017/05/30
0
0
我如何组织 Flask 应用结构

Flask很早就是我的首选Web框架。我认为它有一些很棒的核心功,Armin,它的主要作者,在保持其API简洁小巧方面已经做很容易让人接受,即使对Python初学者也是如此。然而,鉴于它是一个相当小的...

greatghoul
2013/07/29
10.1K
1
Python超级明星WEB框架Flask

Flask简介 Flask是一个相对于Django而言轻量级的Web框架。 和Django大包大揽不同,Flask建立于一系列的开源软件包之上,这其中 最主要的是WSGI应用开发库Werkzeug和模板引擎Jinja: 策略 :w...

笔阁
2015/10/27
1K
3
Flask框架的学习与实战:实战小项目

昨天写了一篇flask开发环境搭建,今天继续,进行一个实战小项目-blog系统。 blog系统很简单,只有一个页面,然而麻雀虽小五脏俱全。这里目的不是为了做项目而做项目,这篇文章本意是通过这次...

yzy121403725
2018/05/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

深入理解Spring MVC 思想

目录 一、前言 二、spring mvc 核心类与接口 三、spring mvc 核心流程图 四、spring mvc DispatcherServlet说明 五、spring mvc 父子上下文的说明 六、springMVC-mvc.xml 配置文件片段讲解 ...

呵呵哒灬
38分钟前
1
0
数据库技术-Mysql主从复制与数据备份

数据库技术-Mysql 主从复制的原理: MySQL中数据复制的基础是二进制日志文件(binary log file)。一台MySQL数据库一旦启用二进制日志后,其作为master,它的数据库中所有操作都会以“事件”...

须臾之余
昨天
12
0
Git远程仓库——GitHub的使用(一)

Git远程仓库——GitHub的使用(一) 一 、 Git远程仓库 由于你的本地仓库和GitHub仓库之间的传输是通过SSH加密的,所以需要一下设置: 步骤一、 创建SSH key 在用户主目录下,看看有没有.ss...

lwenhao
昨天
2
0
SpringBoot 整合

springBoot 整合模板引擎 SpringBoot 整合Mybatis SpringBoot 整合redis SpringBoot 整合定时任务 SpringBoot 整合拦截器...

细节探索者
昨天
1
0
第二个JAVA应用

第二个JAVA应用 方法一:配置文件: # cd /usr/local/tomcat/conf/# vim server.xml</Host> <Host name="www.wangzb.cc" appBase="/data/wwwroot/www.wangzb.cc" //引用所......

wzb88
昨天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部