基于Flask的脚本框架

一般来说, Flask是作为Web服务来使用的, 但有时, 我们需要基于Flask, 写一些脚本, 而且希望这些脚本能与web相关的基础模块兼容, 这该如何使用呢

场景

我有一个web服务, 基于flask搭建, 当完成后, 还有一些脚本需要运行.

脚本的运行也需要常规的redis连接, http请求, 数据库连接等等, 这些基础模块在web应用中已经实现了, 但我们在脚本中无法基于flask直接使用,

为什么呢? 因为很多时候, 我们的基础模块内含了很多请求相关的上下文内容, 而这些又是脚本运行时没有的, 这就导致了这些模块不能在脚本服务中直接使用

Flask-cli

在Flask中, 有这么一个插件Flask-cli, 可以帮助我们一定程度上解决上面的问题, 而且还提供了强大的命令行管理工具, 帮助我们快速构建脚本服务

当我们安装好flask后, 我们直接执行flask 命令行命令, 可以看到

...

Options:
  --version  Show the flask version
  --help     Show this message and exit.

Commands:
  run    Runs a development server.
  shell  Runs a shell in the app context

最后的 runshell 命令, 这时Flask 内置的命令, 我们也可以添加属于自己的命令

简单命令

简单命令行实现如下

run.py 文件内容

import click
from flask import Flask
from flask.cli import FlaskGroup

def create_app(param):
    return Flask(__name__)

@click.group(cls=FlaskGroup, create_app=create_app)
def cli():
    click.echo('flask group stuff...')


@cli.command()
def flask_test():
    click.echo('Flask Test')


if __name__ == '__main__':
    cli()

现在, 我们运行该文件

python run.py

可以看到如下

Usage: run.py [OPTIONS] COMMAND [ARGS]...

Options:
  --version  Show the flask version
  --help     Show this message and exit.

Commands:
  flask_test  
  run         Runs a development server.
  shell       Runs a shell in the app context.

我们可以看到, 除了 runshell 还多了一个flask_test, 这个正是我们添加的新的命令行内容

我们运行之,

python run.py flask_test

可以看到, 在cliflask_test下的内容都输出出来了

flask group stuff...
Flask Test

现在我们就实现了简单的命令行命令添加, 添加其他命令就像下面

@cli.command()
def flask_test2():
    click.echo('Flask Test2')

@cli.command 装饰器加上, 就可以作为一个命令了

我们添加的命令还有一个问题, 没有help信息, 这个其实很简单, 我们在实现的命令函数内添加注释块, 块信息就是该命令的help信息了

@cli.command()
def flask_test():
    """This is Flask Test Help Info"""
    click.echo('Flask Test')

现在执行 python run.py, 我们就可以看到相应的帮助信息了

...
Commands:
  flask_test  This is Flask Test Help Info
...

基于上下文环境的处理

我们的Flask系统运行时, 是需要有一个上下文环境的

基于Request请求的业务处理中, 我们能直接获取参数进行处理, 有可能也会像其他服务获(redis, mysql, http)取数据,

很多时候, 这些服务的连接处理, 甚至有些必要参数设置, 我们都将其放在了flask的 before request定义的函数中去做了

这个时候, 当我们的脚本运行时, 无法在每个脚本运行前使用before request定义的资源列表, 导致每次使用相关资源都要重新进行连接, 相当麻烦

在我们的项目中, 我们将这部分资源处理置于独立的模块中, 使用的时候直接引入, 在模块第一次引入的时候就完成了资源的连接处理, 但总有一些还是需要运行前要执行的内容, 比如应用参数设置

基于这部分内容, 我们推荐在 create_app内做Flask相关的初始化工作,

如下

def create_app(info=None):
    """
    创建app, 在此之前, 请指定必要常量
    const.APP_NAME
    Args:
        info: 如果是web服务, info为None, 如果是脚本, info为 InfoScript 对象
    Returns:
        Flask app
    """
     app = Flask(const.APP_NAME)
    app.config['PRESERVE_CONTEXT_ON_EXCEPTION'] = False
    # 这个参数是防止flask 应用拦截异常导致我们自己无法抓不到异常详细信息
    app.config['PROPAGATE_EXCEPTIONS'] = True

    # 获取当前主机ip
    app.config['server_ip'] = get_server_ip('eth0')

    # 插件初始化
    cache.init_app(app)

    return app

在这里将上下环境创建好后, 基于上下文相关的模块我们一般也就可以直接调用了

另外, 注意我们在上面定义的cli函数, 所有基于cli.command装饰器定义的命令函数都会执行该函数, 所以如果有些公用的内容, 可以在cli函数内进行一些预处理

总结

以上是基于实际项目进行的Flask 脚本服务搭建的一点经验, 希望能帮助到各位, 也欢迎各位进行补充

right