从Flask-Script迁移到Flask-Cli

Abstrct

flask从0.11版本开始引入了click提供命令行支持,在此之前我们通常会引入Flask-Script来提供。

在《Flask web开发》这本书编写时flask0.11还没有发布,因此书中仍然以flask-script提供命令行支持。因此在flask0.11发布一年后,作者写了这篇文章来帮助大家从flask-script迁移到Flask-Cli,该博文便是作者这篇文章的翻译。

作者对于Click引入flask的意见

回到2014年,当Armin Ronacher向我介绍将Click整合进flask,我是(现在也是)拒绝的,这给Flask内核项目添加了别的依赖,我建议基于click实现命令行支持作为flask的拓展,与flask-script进行公平的竞争。你可以查看该讨论在GitHub的issue中 https://github.com/smurfix/flask-script/issues/97

因此我确信,强制命令行工具基于Click是个错误,并且违反了flask的基本准则‘将最优秀的工具用于每一项任务’ 。而且不幸的是,click成为flask核心的一部分使flask-script的维护者离开了,而且从github的repository来看,这个项目似乎正在缓慢死亡

那么,flask-script有什么问题呢?

flask确实有着一个重要但是同时却足够有趣的缺陷,由于早期设计的flask reloader的问题,你将直接使用app.run()启动你的应用而不是Flask-Script

如果你以debug模式启动flask,那么将有两个flask进程被运行。第一个进程(我们可以称他为watcher)将会观察源代码的运行,第二个进程才是实际的flask服务器。当任何文件被修改时,这个watcher一旦观察到文件改变将会kill服务进程,然后启动一个使用已经更新的源代码的服务器进程。当你更新的某个源文件中存在语法错误时,会出现一个问题。Watcher进程并不知道源码是否更好,它会kill旧的服务器进程,运行一个新的服务器进程。但是新的服务器进程却不会运行成功,因为python解析修改后的文件时会抛出错误。当服务进程存在在错误时,这个watcher进程也会退出,直到你修复了bug并重启。

在你使用新的命令flask run命令行时load将会很完美,直到你第一个request发来之前,应用都不会被加载,当引入源码文件有错误时,他将会被在运行时处理,这意味着你可以使用基于web的debug工具。新的watcher进程也更加智能,在服务端被kill后仍然会坚持监控源码的变化,并在源码发生改变之后重启服务。

新的Flask-CLi

Flask-Cli比Flask-Script更加优秀吗?让我们回顾下新的Flask应用提供的默认命令吧

当你安装Flask0.11或者更新的版本后,你的环境变量将会存在flask命令

(venv) $ flask
Usage: flask [OPTIONS] COMMAND [ARGS]...

  This shell command acts as general utility script for Flask applications.

  It loads the application configured (through the FLASK_APP environment
  variable) and then provides commands either provided by the application or
  Flask itself.

  The most useful commands are the "run" and "shell" command.

  Example usage:

    $ export FLASK_APP=hello.py
    $ export FLASK_DEBUG=1
    $ flask run

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.

当我们使用Flask-Script时,你将创建名为manage.py的驱动脚本。我们来看下flask-script与flask-cli之间的对比

#flask-script -> flask-Cli
./manage.py runserver -> flask run
./manage.py shell ->flask shell

看上去似乎没什么区别。

然和事实上,manage.py和flask发现我们Flask应用实例的方式是完全不一样的。对于Flask-Script提供了Manager类直接在我们的应用工厂中使用。然和新的Flask-Cli却没用提供应用实例而是设定FLASK_APP的环境变量,一般用来设定文件名或者模块名。FLask将会寻找模块中的app或者application的实例来使用。

可惜,Flask-Cli没有直接为工厂函数提供支持,这个方法意味着你如果需要使用工厂函数你必须定义一个工厂函数模块来创建你的app对象,然后再FLASK_APP中引用。这与wsgi.py模块在Django中的概念是一致的

如果你想为Flasky应用提供FLask Cli的支持,你可以在flasky.py中这样编写

import os
from app import create_app

app = create_app(os.getenv('FLASK_CONFIG') or 'default')

我们在将会会完全迁移至新的Cli,不在使用manage.py来。

Windows设置FLASK_APP

set FLASK_APP = flasky.py
flask run

flask run命令提供了配置来启用、禁用reloader和debugger,当然你也可以设置IP 的address和port来控制服务器的监听。这些配置虽然与Flask-Script相近但并不完全相同,可以使用flask run -help来查看提供的配置。

大部分应用可以使用下面的方式启动

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

当你不需要使用CLI支持时你确实可以以这种方式启动。但是当你确实需要新的cli时,你尽可以移除它。

使用Flask Shell 命令

shell命令基本上是相同的,但是在如何定义要自动导入shell上下文中的其他symbols有轻微的差别。这个功能可以节省你在应用程序工作时的大量时间。通常,你在shell中的测试或调试shell中添加model类、数据库实例和其他可能与之交互的对象。

对于FLask-Script,Flasky应用有如下shell上下文定义

def make_shell_context():
    return dict(app=app, db=db, User=User, Follow=Follow, Role=Role,
                Permission=Permission, Post=Post, Comment=Comment)
manager.add_command("shell", Shell(make_context=make_shell_context))

正如你在上文看到的,make_shell_context()函数被add_command引用来定义shell参数。Flask-Script在启动shell前直接调用这个函数,然后返回一个包含这些symbol的字典。

Flask CLI提供了相同功能,但是确实以decorator(装饰器)来判断是不是shell上下文对象。为了和FLask-Script有相同的功能,我们扩展了flasky.py模块。

import os
from app import create_app, db
from app.models import User, Follow, Role, Permission, Post, Comment

app = create_app(os.getenv('FLASK_CONFIG') or 'default')

@app.shell_context_processor
def make_shell_context():
    return dict(app=app, db=db, User=User, Follow=Follow, Role=Role,
                Permission=Permission, Post=Post, Comment=Comment)

正如你看见的,作用时相同的,但是你需要@app.shell_context_processor装饰器使Flask知道这些shell上下文对象

增加Flask拓展的命令

Flask-Script取得成功的一个很大关键在于允许Flask拓展添加他们自己的命令。Flask-Migrate库就最大限度利用了这一个优点。

所以如何迁移这些命令到flask-cli呢?这取决于拓展的作者是如何做迁移的,如果你使用的扩展正未曾为FLask Cli做出适配,那么你得继续使用FLask-Script,至少当你与这些拓展直接进行交互时得这样。Flask migrate提供了对于FLask Cli的更新,直接使用它最新的版本即可。

对于Flask Migrate, ./manage.py db 直接变成了 flask db。在Flask-Script中我们Flask-Migrate在manage.py中被初始化,在FLaskCli中我们需要将Flask-Script版本中manage.py中的内容移入flasky.py中,只有一些微小的不同。

import os
from app import create_app, db
from app.models import User, Follow, Role, Permission, Post, Comment
from flask_migrate import Migrate

app = create_app(os.getenv('FLASK_CONFIG') or 'default')
migrate = Migrate(app, db)

@app.shell_context_processor
def make_shell_context():
    return dict(app=app, db=db, User=User, Follow=Follow, Role=Role,
                Permission=Permission, Post=Post, Comment=Comment)

在示例中,我们使用接下来的命令更新flasky的数据库到最新的版本。(不要忘记设置FLASK_APP)

flask db upgrade

应用命令

Flask Script另一个优秀的特点是允许你把自己的常用任务(custom work)封装函数在命令行中执行。在CLI中你也只需要添加一个装饰器就能够完成迁移

原先的./manage.py test命令

@manager.command
def test(coverage=False):
  """Run the unit tests."""
  # ...

在Flask-Cli中

import click

# ...

@app.cli.command()
@click.option('--coverage/--no-coverage', default=False, help='Enable code coverage')
def test(coverage):
    """Run the unit tests."""
    # ...

@app.cli.command()装饰器提供了接口给Click。因为在Flask-Script中命令函执行是在应用上下文中的,而Flask-Cli中应用上下文如果你不需要是可以禁用的。在此处,函数的实际代码不需要改变,但注意coverage选项需要显式使用@click.option装饰器,而在flask-script中,该选项是自动导出的函数的参数列表。

Flasky其他两个函数也可以使用同样的方法,flasky模块的完全实现如下

import os
from app import create_app, db
from app.models import User, Follow, Role, Permission, Post, Comment
from flask_migrate import Migrate
import click

app = create_app(os.getenv('FLASK_CONFIG') or 'default')
migrate = Migrate(app, db)

@app.shell_context_processor
def make_shell_context():
    return dict(app=app, db=db, User=User, Follow=Follow, Role=Role,
                Permission=Permission, Post=Post, Comment=Comment)

@app.cli.command()
@click.option('--coverage/--no-coverage', default=False, help='aaa')
def test(coverage=False):
    "Test coverage"
    # ...

@app.cli.command()
@click.option('--length', default=25, help='Profile stack length')
@click.option('--profile-dir', default=None, help='Profile directory')
def profile(length, profile_dir):
    """Start the application under the code profiler."""
    # ...

@app.cli.command()
def deploy():
    """Run deployment tasks."""
    # ...

总结

在本文中,我向您展示了新的CLI命令行所提供的功能,这有助于你从Flask-Script中迁移。还有两件事本文并没有提及,

如果你不想使用flask命令,如何创建类似于flask-script的manage.py的你自己的驱动脚本?

如何通过工程中”setup.py"中的’Entry points“注册命令?

你可以查阅[CLI Documention](CLI documenation )来了解他们

posted @ 2017-10-19 16:17  lynskylate  阅读(5911)  评论(0编辑  收藏  举报