Imagine I have a big CLI application with many different commands (think, for example image-magick).
I wanted to organize this application into modules and etc. So, there would be a master click.group
somewhere:
#main.py file
@click.group()
def my_app():
pass
if __name__ == "__main__":
my_app()
that can be imported in each module that define a command:
from main import my_app
# command_x.py
@my_app.command()
def command_x():
pass
The problem is that I run into a circular import problem, since the main.py
file knows nothing about command_x.py
and I would have to import it before calling the main section.
This happens in Flask too and is usually dealt with the app factory pattern. Usually you would have the app being created before the views:
app = Flask("my_app")
@my_app.route("/")
def view_x():
pass
if __name__ == "__main__":
app.run()
In the app factory pattern you postpone the "registration" of the blueprints:
# blueprints.py
blueprint = Blueprint(yaddayadda)
@blueprint.route("/")
def view_x():
pass
And make a factory that knows how to build the app and register the blueprints:
#app_factory.py
from blueprints import view_x
def create_app():
app = Flask()
view_x.init_app(app)
return app
And you can then create a script to run the app:
#main.py
from app_factory import create_app
if __name__ == "__main__":
app = create_app()
app.run()
Can a similar pattern be used with Click? Could I just create a "click app" (maybe extending click.Group
) where I register the "controllers" which are the individual commands?
To run the app outside of the VS Code debugger, use the following steps from a terminal: Set an environment variable for FLASK_APP . On Linux and macOS, use export set FLASK_APP=webapp ; on Windows use set FLASK_APP=webapp . Navigate into the hello_app folder, then launch the program using python -m flask run .
cli module of the Flask project. FlaskGroup is a subclass of AppGroup that provides for loading more commands from a configured Flask app. Generally, only advanced use cases will need to use this class.
route is a decorator used to match URLs to view functions in Flask apps.
Maybe late, but I was also searching for a solution to put commands to separate modules. Simply use a decorator to inject commands from 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()
The separated command can use the usual decorators from click.
#commands.py
import click
@click.command()
def my_command():
pass
Ok, so I thought a little and it seems that the following could work. It's probably not a final solution but it seems to be an initial 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 the commands defined in separated modules:
# commands.py
from my_click_classes import command
@command
def run():
print("run!!!")
@command
def walk():
print("walk...")
and the "app" in a separated 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" pattern.
It maybe not a definitive solution though. If you guys can see any way to improve it, please let me know.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With