Getting Started with Custom Commands
When you write CLI commands for a Flask application, you often need access to the application's configuration, database connections, or other extensions. Flask provides the AppGroup class to ensure your commands automatically run within an application context, giving you access to current_app and other Flask globals without manual setup.
In this tutorial, you will build a set of custom commands to manage a hypothetical user system, ranging from simple app-level commands to organized blueprint groups.
Prerequisites
To follow this tutorial, you need a basic Flask application structure. Ensure you have Flask installed and a file named app.py.
Step 1: Create a Simple App Command
The Flask object has a cli attribute which is an instance of AppGroup. When you use @app.cli.command(), Flask automatically wraps your function with the application context.
In app.py:
from flask import Flask, current_app
app = Flask(__name__)
@app.cli.command("hello")
def hello_command():
# current_app is available because AppGroup wraps this in with_appcontext
print(f"Hello from {current_app.name}!")
Run this command from your terminal:
flask hello
What happened?
Because app.cli is an AppGroup, it defaults with_appcontext=True. When you run the command, Flask pushes an application context, executes your function, and then pops the context.
Step 2: Organize Commands with Blueprints
As your application grows, you should organize commands into modules using Blueprints. Like the Flask object, every Blueprint has its own cli attribute (also an AppGroup).
In auth.py:
from flask import Blueprint, current_app
auth_bp = Blueprint("auth", __name__)
@auth_bp.cli.command("init")
def init_auth():
print(f"Initializing auth for {current_app.name}")
In app.py:
from flask import Flask
from auth import auth_bp
app = Flask(__name__)
app.register_blueprint(auth_bp)
Run the blueprint command:
flask auth init
Note: By default, blueprint commands are nested under the blueprint's name. If you want to change this group name, pass cli_group when creating the blueprint: Blueprint("auth", __name__, cli_group="users").
Step 3: Create Nested Command Groups
If you have many related commands, you can create a nested group using AppGroup directly. This ensures that every subcommand in that group also receives the application context.
import click
from flask import Flask
from flask.cli import AppGroup
app = Flask(__name__)
user_cli = AppGroup("user")
@user_cli.command("create")
@click.argument("name")
def create_user(name):
print(f"Creating user: {name}")
@user_cli.command("delete")
@click.argument("name")
def delete_user(name):
print(f"Deleting user: {name}")
app.cli.add_command(user_cli)
Now you can run:
flask user create admin
flask user delete admin
Step 4: Disabling the Application Context
Sometimes you might want a command that doesn't need the overhead of loading the Flask app (e.g., a simple version check or a command that only interacts with the filesystem). You can disable the automatic context by passing with_appcontext=False.
@app.cli.command("simple", with_appcontext=False)
def simple_command():
# current_app would raise a RuntimeError here
print("This command runs without an app context.")
Step 5: Testing Your Commands
Flask provides a test_cli_runner() to verify your commands work as expected. This is useful for CI/CD pipelines.
def test_hello_command(app):
runner = app.test_cli_runner()
result = runner.invoke(args=["hello"])
assert "Hello from" in result.output
assert result.exit_code == 0
Summary
By using AppGroup, you've achieved:
- Automatic Context Management: No need to manually call
with app.app_context(). - Clean Organization: Commands are grouped by blueprint or custom logic.
- Testability: Commands can be invoked and verified using the built-in test runner.
For more advanced CLI structures, you can explore FlaskGroup, which is the specialized AppGroup used by the main flask command itself to manage app discovery and environment variables.