是否可以将 Flask 中的 "app factory" 模式与 Click CLI 应用程序一起使用?
Is it possible to use the "app factory" pattern from Flask with Click CLI applications?
假设我有一个带有许多不同命令的大型 CLI 应用程序(想想,例如 image-magick)。
我想将这个应用程序组织成模块等。所以,某处会有一个大师click.group
:
#main.py file
@click.group()
def my_app():
pass
if __name__ == "__main__":
my_app()
可以在定义命令的每个模块中导入:
from main import my_app
# command_x.py
@my_app.command()
def command_x():
pass
问题是我 运行 陷入循环导入问题,因为 main.py
文件对 command_x.py
一无所知,我必须在调用主要部分之前导入它。
这种情况在 Flask 中也会发生,通常是应用工厂模式处理的。通常你会在视图之前创建应用程序:
app = Flask("my_app")
@my_app.route("/")
def view_x():
pass
if __name__ == "__main__":
app.run()
在应用工厂模式中,您推迟 "registration" 蓝图:
# blueprints.py
blueprint = Blueprint(yaddayadda)
@blueprint.route("/")
def view_x():
pass
并创建一个知道如何构建应用程序和注册蓝图的工厂:
#app_factory.py
from blueprints import view_x
def create_app():
app = Flask()
view_x.init_app(app)
return app
然后您可以为 运行 应用程序创建一个脚本:
#main.py
from app_factory import create_app
if __name__ == "__main__":
app = create_app()
app.run()
Click 可以使用类似的模式吗?我可以只创建一个 "click app"(可能扩展 click.Group
),我在其中注册 "controllers" 这是单独的命令吗?
好的,我想了想,下面的方法似乎可行。这可能不是最终解决方案,但它似乎是第一步。
我可以扩展 MultiCommand class:
# my_click_classes.py
import click
class ClickApp(click.MultiCommand):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.commands = {}
def add_command(self, command_name, command):
self.commands.update({command_name: command})
def list_commands(self, ctx):
return [name for name, _ in self.commands.items()]
def get_command(self, ctx, name):
return self.commands.get(name)
和命令 class:
class MyCommand(click.Command):
def init_app(self, app):
return app.add_command(self.name, self)
def mycommand(*args, **kwargs):
return click.command(cls=MyCommand)
这允许您在单独的模块中定义命令:
# commands.py
from my_click_classes import command
@command
def run():
print("run!!!")
@command
def walk():
print("walk...")
和单独模块中的"app":
from my_click_classes import ClickApp
from commands import run, walk
app = ClickApp()
run.init_app(app)
walk.init_app(app)
if __name__ == '__main__':
app()
或者甚至使用 "app factory" 模式。
虽然这可能不是最终的解决方案。如果你们能看到任何改进的方法,请告诉我。
可能晚了,但我也在寻找将命令放入单独模块的解决方案。只需使用装饰器从模块注入命令:
#main.py file
import click
import commands
def lazyloader(f):
# f is an instance of click.Group
f.add_command(commands.my_command)
return f
@lazyloader
@click.group()
def my_app():
pass
if __name__ == "__main__":
my_app()
分离的命令可以使用来自 click 的常用装饰器。
#commands.py
import click
@click.command()
def my_command():
pass
假设我有一个带有许多不同命令的大型 CLI 应用程序(想想,例如 image-magick)。
我想将这个应用程序组织成模块等。所以,某处会有一个大师click.group
:
#main.py file
@click.group()
def my_app():
pass
if __name__ == "__main__":
my_app()
可以在定义命令的每个模块中导入:
from main import my_app
# command_x.py
@my_app.command()
def command_x():
pass
问题是我 运行 陷入循环导入问题,因为 main.py
文件对 command_x.py
一无所知,我必须在调用主要部分之前导入它。
这种情况在 Flask 中也会发生,通常是应用工厂模式处理的。通常你会在视图之前创建应用程序:
app = Flask("my_app")
@my_app.route("/")
def view_x():
pass
if __name__ == "__main__":
app.run()
在应用工厂模式中,您推迟 "registration" 蓝图:
# blueprints.py
blueprint = Blueprint(yaddayadda)
@blueprint.route("/")
def view_x():
pass
并创建一个知道如何构建应用程序和注册蓝图的工厂:
#app_factory.py
from blueprints import view_x
def create_app():
app = Flask()
view_x.init_app(app)
return app
然后您可以为 运行 应用程序创建一个脚本:
#main.py
from app_factory import create_app
if __name__ == "__main__":
app = create_app()
app.run()
Click 可以使用类似的模式吗?我可以只创建一个 "click app"(可能扩展 click.Group
),我在其中注册 "controllers" 这是单独的命令吗?
好的,我想了想,下面的方法似乎可行。这可能不是最终解决方案,但它似乎是第一步。
我可以扩展 MultiCommand class:
# my_click_classes.py
import click
class ClickApp(click.MultiCommand):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.commands = {}
def add_command(self, command_name, command):
self.commands.update({command_name: command})
def list_commands(self, ctx):
return [name for name, _ in self.commands.items()]
def get_command(self, ctx, name):
return self.commands.get(name)
和命令 class:
class MyCommand(click.Command):
def init_app(self, app):
return app.add_command(self.name, self)
def mycommand(*args, **kwargs):
return click.command(cls=MyCommand)
这允许您在单独的模块中定义命令:
# commands.py
from my_click_classes import command
@command
def run():
print("run!!!")
@command
def walk():
print("walk...")
和单独模块中的"app":
from my_click_classes import ClickApp
from commands import run, walk
app = ClickApp()
run.init_app(app)
walk.init_app(app)
if __name__ == '__main__':
app()
或者甚至使用 "app factory" 模式。
虽然这可能不是最终的解决方案。如果你们能看到任何改进的方法,请告诉我。
可能晚了,但我也在寻找将命令放入单独模块的解决方案。只需使用装饰器从模块注入命令:
#main.py file
import click
import commands
def lazyloader(f):
# f is an instance of click.Group
f.add_command(commands.my_command)
return f
@lazyloader
@click.group()
def my_app():
pass
if __name__ == "__main__":
my_app()
分离的命令可以使用来自 click 的常用装饰器。
#commands.py
import click
@click.command()
def my_command():
pass