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

先说,我是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的各个部分。

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

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文件夹

重点来了。

看看这个文件夹的结构。

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

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

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

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

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

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

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

好了。这个就是一个基础的开发好的可拔插的包了。下面看看怎么做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包,来示例一下。

先放一下最终的目录结构

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

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

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

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

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

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

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

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

好吧,想起DispatcherMiddleware的设定没?

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

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

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

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

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

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

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

Fin。

你可能感兴趣的:(一个flask的可拔插的大型架构,通过应用工厂和应用分发来实现。)