Можно ли использовать шаблон фабрики приложений из Flask с приложениями Click CLI?

Представьте, что у меня есть большое приложение 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()

В шаблоне фабрики приложений вы откладываете «регистрацию» чертежей:

# 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.Group), в котором я регистрирую «контроллеры», то есть отдельные команды?


person Rafael S. Calsaverini    schedule 14.04.2015    source источник


Ответы (2)


Может быть, поздно, но я также искал решение для размещения команд в отдельных модулях. Просто используйте декоратор для ввода команд из модулей:

#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()

Отдельная команда может использовать обычные декораторы по щелчку.

#commands.py
import click

@click.command()
def my_command():
    pass
person Karl Nickel    schedule 18.12.2015

Хорошо, я немного подумал и, похоже, может сработать следующее. Вероятно, это не окончательное решение, но, похоже, это первый шаг.

Я могу расширить класс MultiCommand:

# 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)

И класс Command:

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...")

и «приложение» в отдельном модуле:

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()

Или даже используйте шаблон «фабрика приложений».

Хотя, возможно, это не окончательное решение. Если вы, ребята, видите способ его улучшить, дайте мне знать.

person Rafael S. Calsaverini    schedule 14.04.2015