利用Python Flask蓝图加自定义蓝图划分优雅的目录结构

ming / 2023-08-08 / 原文

我们在用Flask开发网站的时候。经常看到有很多人把所有的路由函数放到了入口文件,这种做法是非常不可取的,如果我们的视图函数有几百个了都写到一个文件里肯定是不行的。还有在实现中我们都在比较大型项目里面我们可能有十几个甚至几十个这种不同模型。我们需要考虑把这些模型分文别类归属到不同的文件中去。而不是都集中在同一个文件中。如下图这种:

 

是不是很乱,那么我们怎么拆分这些函数了。接下来我们就来简单实现一下:


一、 规划目录结构

 

二、新建app包构造核心目录
api/v1/init.py 接口目录为版本号模块初始化文件
api/v1/user.py 接口目录为版本号加上模块名称文件
config/settings.py 包为配置文件
lib/redprint.py核心类文件模仿蓝图重写
app.py 核心入口文件
当然还有其他包比如model为数据库validate为验证等等就不在列举

api/v1/init.py 用蓝图托管版本号导入不同模块

from flask import Blueprint
from app.api.v1 import user, website
def create_blueprint_v1():
    bp_v1 = Blueprint('v1', __name__)
    user.api.register(bp_v1)
    website.api.register(bp_v1)
    return bp_v1

api/v1/user.py 用lib下自定义redprint托管模块

from app.lib.redprint import Redprint
api = Redprint('user')

@api.route('/get')
def get_user():
    return 'get'

@api.route('/create')
def create_user():
    return 'create'

settings.py 一些数据库等配置信息

TOKEN_EXPIRATION = 30 * 24 * 3600
SQLALCHEMY_DATABASE_URI = \
    'mysql+cymysql://root:123456@localhost/test'
SECRET_KEY = 'test'

redprint.py模仿蓝图重写register和route方法

class Redprint(object):
    def __init__(self, name):
        self.name = name
        self.mound = []

    def route(self, rule, **options):
        def decorator(f):
            self.mound.append((f, rule, options))
            return f

        return decorator

    def register(self, bp, url_prefix=None):
        if url_prefix is None:
            url_prefix = '/' + self.name
        for f, rule, options in self.mound:
            endpoint = options.pop("endpoint", f.__name__)
            bp.add_url_rule(url_prefix + rule, endpoint, f, **options)

app.py 核心入口文件

from flask import Flask


def register_blueprints(app):
    from app.api.v1 import create_blueprint_v1
    app.register_blueprint(create_blueprint_v1(), url_prefix='/v1')


def create_app():
    app = Flask(__name__)
    app.config.from_object('app.config.settings')
    register_blueprints(app)
    return app

三、main.py入口文件

from app.app import create_app

app = create_app()

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5001)

测试看一下效果:

 

认识flask 蓝图(blueprint)技术

1. 一个最小的应用

flask教程都喜欢用一个非常小的应用示例向你展示flask的小巧灵活,例如下面的这个应用

from flask import Flask
app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'



if __name__ == '__main__':
    app.run(port=5678)

真正的flask应用,绝不可能是如此的短小,而是划分许多模块,要提供很多功能。

2. 一个稍大点的flask应用

下面的flask应用里,有一个user模块专门提供和用户有关的功能,例如用户注册,登录,登出,修改密码。还有一个admin模块,用来做后台管理,本示例只是为了向你展示如何在一个脚本里编写所有的模块,因此这些视图函数并没有具体的实现。

from flask import Flask
app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


# user模块
@app.route('/user/register')
def register():
    return 'register'

@app.route('/user/login')
def login():
    return 'login'

@app.route('/user/modify_password')
def modify_password():
    return 'modify_password'


# admin模块
@app.route('/admin/alluser')
def alluser():
    return 'alluser'


@app.route('/admin/deluser')
def deluser():
    return 'deluser'



if __name__ == '__main__':
    app.run(port=5678)

和最小的flask应用相比,这个应用多了两个模块,5个视图函数,这仍然是一个非常小的flask应用。在实践项目中,子模块和视图函数会非常的多,因此,不可能将他们都写在同一个脚本里,那样的话,这个脚本会非常的大,难以维护。

3. BluePrint 蓝图技术

蓝图技术,可以帮助你实现flask应用的模块划分,对于没有亲身经历过大项目的人来说,很难理解模块划分的好处,换一种阐述方式,没有经历过大项目的人,根本不知道不划分模块或者模块划分不合理所带来的麻烦。

我将第2小节的应用划分出两个模块,划分后,项目结构不再是一个单一的脚本,一个模块拥有一个属于自己的文件目录,与之相关的代码都将写在这里,项目结构如下:

blue-example/
├── admin
│   ├── __init__.py
│   └── views.py
├── app.py
└── user
    ├── __init__.py
    └── views.py

接下来,展示这些脚本里的内容

3.1 app.py

from flask import Flask
app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'



from admin import admin_blue
from user import user_blue

app.register_blueprint(admin_blue)
app.register_blueprint(user_blue)



if __name__ == '__main__':
    app.run(port=5678)

app.py 明显瘦身了,代码看起来干净整洁

3.2 user模块

user/init.py

from flask import Blueprint

user_blue = Blueprint('user', __name__, url_prefix='/user')
from . import views

user/views.py

from user import user_blue


# user模块
from user import user_blue


# user模块
@user_blue.route('/register')
def register():
    return 'register'

@user_blue.route('/login')
def login():
    return 'login'

@user_blue.route('/modify_password')
def modify_password():
    return 'modify_password'

  

3.3 admin

admin/init.py

from flask import Blueprint


admin_blue = Blueprint('admin', __name__, url_prefix='/admin')
from . import views

admin/views.py

from admin import admin_blue


# admin模块
@admin_blue.route('/alluser')
def alluser():
    return 'alluser'


@admin_blue.route('/deluser')
def deluser():
    return 'deluser'

第3小节的项目代码,多了一些模块的划分,自然也多了一些文件目录,而且咋看起来,比第2小节的代码变复杂了些,但这种“复杂”是值得的, 它换来了整个项目清晰的结构,很好的控制了单个脚本的代码规模。

4. 两种代码组织形式

蓝图在组织flask代码时,有两种形式

  1. 功能式架构
  2. 分区式架构

前面所展示的就是功能式架构,一个功能,一个模块组织成一个蓝图,他们共用相同的静态资源,静态资源放在static目录下,本文所举实例太简单,因此没有创建静态资源,功能式架构类似于下面的结构

__init__.py
static/
templates/
    home/
    control_panel/
    admin/
views/
    __init__.py
    home.py
    control_panel.py
    admin.py
models.py

home, control_panel,admin 都是蓝图,他们共用static和 templates。

分区式架构,适用于子模块有特殊需要的情况,在创建蓝图构造Blueprint对象时,可以指定static和templates, 其结构类似于下面这样

yourapp/
    __init__.py
    admin/
        __init__.py
        views.py
        static/
        templates/
    home/
        __init__.py
        views.py
        static/
        templates/
    control_panel/
        __init__.py
        views.py
        static/
        templates/
    models.py

试想,如果admin, home, control_panel有各自不同的页面样式和风格,那么他们就需要不同的静态资源,css, javascript,每个模块拥有各自的静态资源就是一个合理的选择。这样组织,还有一个好处,一个模块,或者说一个蓝图拥有自身全部的资源,包括static和templates,那么它可以很容易从一个项目里拆分出来放在另一个项目中使用。

具体使用哪种组织架构,并没有强制要求,完全是开发人员随心所欲的,重要的是从项目管理的角度出发,哪一种更利于你所项目的管理,就用哪一种。