Flask Custom Commands
When developing Flask applications, you'll often find yourself performing repetitive tasks like initializing the database, running tests, or importing data. Flask provides a powerful Command Line Interface (CLI) that allows you to create custom commands to automate these tasks. This tutorial will guide you through creating and using custom Flask commands to make your development workflow more efficient.
Introduction to Flask CLI
Flask's CLI is built on top of the Click library, which provides a simple way to create command-line interfaces. Since Flask 0.11, the CLI has been integrated into the framework, replacing the previous flask-script
extension.
The Flask CLI allows you to:
- Run your application server
- Execute shell commands within your application context
- Create custom commands specific to your application
Basic Flask CLI Commands
Before diving into custom commands, let's familiarize ourselves with some built-in Flask commands:
# Run the development server
flask run
# Start a Python shell with Flask app context
flask shell
# Show all available commands
flask --help
Creating Custom Flask Commands
Method 1: Using @app.cli.command()
Decorator
The simplest way to create custom commands is using the @app.cli.command()
decorator directly on your Flask application instance.
Here's a basic example:
# app.py
from flask import Flask
app = Flask(__name__)
@app.cli.command("hello")
def hello_command():
"""Say hello to the user."""
print("Hello, Developer!")
Now you can run this command using:
flask hello
Output:
Hello, Developer!
Method 2: Using Flask Blueprint Commands
For larger applications with a modular structure, it's better to use blueprint commands:
# commands.py
import click
from flask.cli import with_appcontext
@click.command('init-db')
@with_appcontext
def init_db_command():
"""Initialize the database."""
from .db import init_db
init_db()
click.echo('Initialized the database.')
def register_commands(app):
app.cli.add_command(init_db_command)
Then register these commands in your application factory:
# __init__.py
from flask import Flask
def create_app():
app = Flask(__name__)
# ... other app configuration
from . import commands
commands.register_commands(app)
return app
Adding Command Arguments and Options
Click allows you to add arguments and options to your commands:
@app.cli.command("greet")
@click.argument('name')
@click.option('--loud', is_flag=True, help='Make the greeting loud (uppercase).')
def greet_command(name, loud):
"""Greet the specified user."""
greeting = f"Hello, {name}!"
if loud:
greeting = greeting.upper()
click.echo(greeting)
Now you can use this command like:
flask greet Alice
Output:
Hello, Alice!
Or with the option:
flask greet Alice --loud
Output:
HELLO, ALICE!
Real-World Examples
Let's explore some practical examples of custom Flask commands.
Example 1: Database Management Commands
# commands.py
import click
from flask.cli import with_appcontext
from .models import db, User, Post
@click.command('create-tables')
@with_appcontext
def create_tables_command():
"""Create all database tables."""
db.create_all()
click.echo('Created database tables.')
@click.command('drop-tables')
@click.confirmation_option(prompt='This will delete all data in the database. Continue?')
@with_appcontext
def drop_tables_command():
"""Drop all database tables."""
db.drop_all()
click.echo('Dropped all tables.')
@click.command('seed-data')
@with_appcontext
def seed_data_command():
"""Add seed data to the database."""
# Create admin user
admin = User(username='admin', email='[email protected]', is_admin=True)
admin.set_password('password')
# Create test post
post = Post(
title='Welcome',
content='Welcome to our Flask application!',
author=admin
)
db.session.add(admin)
db.session.add(post)
db.session.commit()
click.echo('Added seed data to database.')
def register_commands(app):
app.cli.add_command(create_tables_command)
app.cli.add_command(drop_tables_command)
app.cli.add_command(seed_data_command)
Example 2: Application Maintenance Commands
@app.cli.command("clear-cache")
@with_appcontext
def clear_cache_command():
"""Clear the application cache."""
from .cache import cache
cache.clear()
click.echo("Cache cleared successfully.")
@app.cli.command("send-digest")
@click.option('--dry-run', is_flag=True, help='Simulate sending without actually sending emails.')
@with_appcontext
def send_digest_command(dry_run):
"""Send weekly digest emails to all users."""
from .models import User
from .email import send_digest_email
users = User.query.filter_by(subscribed=True).all()
count = 0
for user in users:
if dry_run:
click.echo(f"Would send digest to: {user.email}")
else:
send_digest_email(user)
click.echo(f"Sent digest to: {user.email}")
count += 1
click.echo(f"Processed {count} user{'s' if count != 1 else ''}.")
Example 3: Testing and Deployment Commands
@app.cli.command("run-tests")
@click.option('--coverage', is_flag=True, help='Run tests with coverage report.')
def run_tests_command(coverage):
"""Run the test suite."""
import pytest
args = []
if coverage:
args.extend(['--cov=app', '--cov-report=term-missing'])
pytest.main(args)
@app.cli.command("deploy")
@click.option('--migrations', is_flag=True, help='Run database migrations before deploying.')
def deploy_command(migrations):
"""Prepare application for deployment."""
if migrations:
click.echo("Running database migrations...")
# Run Alembic migrations
from flask_migrate import upgrade
upgrade()
click.echo("Compiling static assets...")
# Example command to compile assets
import subprocess
subprocess.run(['npm', 'run', 'build'], check=True)
click.echo("Deployment preparation completed.")
Command Groups
For complex applications with many commands, you can organize them into groups:
import click
from flask.cli import AppGroup
# Create a group called "user"
user_cli = AppGroup('user', help='User management commands.')
@user_cli.command('create')
@click.argument('username')
@click.argument('email')
@click.option('--admin', is_flag=True, help='Create as admin user.')
@with_appcontext
def user_create_command(username, email, admin):
"""Create a new user."""
from .models import User, db
user = User(username=username, email=email, is_admin=admin)
user.set_password(click.prompt('Password', hide_input=True))
db.session.add(user)
db.session.commit()
click.echo(f"Created user: {username}")
@user_cli.command('list')
@with_appcontext
def user_list_command():
"""List all users."""
from .models import User
for user in User.query.all():
click.echo(f"{user.id}: {user.username} ({user.email})")
def register_commands(app):
app.cli.add_command(user_cli) # Register the entire group
Now you can use these grouped commands:
flask user create john [email protected]
flask user list
Best Practices
- Keep commands focused: Each command should do one thing well.
- Add documentation: Always include docstrings and help text.
- Use confirmation for destructive commands: Protect commands that delete data.
- Include feedback: Let the user know what's happening with
click.echo()
. - Handle errors gracefully: Use try/except blocks and return appropriate exit codes.
- Add progress indicators: For long-running commands, show progress.
@app.cli.command("import-data")
@click.argument('filename')
@with_appcontext
def import_data_command(filename):
"""Import data from a CSV file."""
import csv
from .models import Product, db
try:
with open(filename, 'r') as f:
reader = csv.DictReader(f)
# Get total for progress bar
rows = list(reader)
total = len(rows)
with click.progressbar(rows, label='Importing products') as bar:
for row in bar:
product = Product(
name=row['name'],
price=float(row['price']),
description=row['description']
)
db.session.add(product)
db.session.commit()
click.echo(f"Successfully imported {total} products.")
return 0 # Success exit code
except FileNotFoundError:
click.echo(f"Error: File '{filename}' not found.", err=True)
return 1 # Error exit code
except Exception as e:
click.echo(f"Error: {str(e)}", err=True)
return 1 # Error exit code
Summary
Flask custom commands provide a powerful way to automate tasks and improve your development workflow. Using the built-in CLI functionality, you can create commands for database management, testing, deployment, and other routine tasks.
Key takeaways:
- Use
@app.cli.command()
for simple applications - Use blueprint commands with
@click.command()
and@with_appcontext
for larger applications - Organize related commands into groups using
AppGroup
- Add arguments and options to make commands flexible
- Include proper documentation and error handling
Additional Resources
- Flask CLI Documentation
- Click Documentation
- Flask-Migrate - Extension that adds CLI commands for database migrations
Exercises
- Create a
stats
command that displays information about your application (number of users, posts, etc.) - Build a
backup-db
command that dumps your database to a file - Implement a
generate-report
command that creates a PDF report of application activity - Create a command group for administrative tasks with commands to manage users and content
- Add a
check-health
command that verifies all components of your application are working correctly
With these custom commands, you'll streamline your development process and make routine tasks much more efficient in your Flask applications.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)