Can I use the "app factory" template from Flask using CLI Click apps?

Imagine that I have a large CLI application with many different commands (I think, for example, image magic).

I wanted to organize this application in modules, etc. So somewhere would be:

#main.py file @click.group() def my_app(): pass if __name__ == "__main__": my_app() 

which can be imported into each module that defines the command:

 from main import my_app # command_x.py @my_app.command() def command_x(): pass 

The problem is that I ran into a cyclic import problem, since the main.py file main.py not know anything about command_x.py , and I will have to import it before invoking the main section.

This also happens in flash and is usually seen with a factory application. Usually you should create an application before the views:

 app = Flask("my_app") @my_app.route("/") def view_x(): pass if __name__ == "__main__": app.run() 

In the factory application template, you defer the "registration" of drawings:

 # blueprints.py blueprint = Blueprint(yaddayadda) @blueprint.route("/") def view_x(): pass 

And create a factory that knows how to create an application and register drawings:

 #app_factory.py from blueprints import view_x def create_app(): app = Flask() view_x.init_app(app) return app 

And then you can create a script to run the application:

 #main.py from app_factory import create_app if __name__ == "__main__": app = create_app() app.run() 

Can I use a similar template for Click? Can I just create a “click application” (possibly a click.Group extension) where I register “controllers” that are separate commands?

+6
source share
2 answers

Maybe late, but I was also looking for a solution for placing commands to separate modules. Just use the decorator to enter commands from the modules:

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

A detachable command can use ordinary decorators from a click.

 #commands.py import click @click.command() def my_command(): pass 
+3
source

Ok, so I thought a little, and it seems that the following might work. This is probably not the final decision, but it seems to be the first step.

I can extend the 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) 

And the Command 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) 

This allows you to have commands defined in separate modules:

 # commands.py from my_click_classes import command @command def run(): print("run!!!") @command def walk(): print("walk...") 

and "application" in a separate module:

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

Or even use the "app factory" template.

Perhaps this is not a final decision. If you guys can see any way to improve it, please let me know.

+1
source

Source: https://habr.com/ru/post/985128/


All Articles