Възможно ли е да се използва моделът на фабриката за приложения от 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()

Разделената команда може да използва обичайните декоратори от click.

#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