Skip to main content

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:

bash
# 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:

python
# 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:

bash
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:

python
# 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:

python
# __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:

python
@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:

bash
flask greet Alice

Output:

Hello, Alice!

Or with the option:

bash
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

python
# 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

python
@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

python
@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:

python
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:

bash
flask user create john [email protected]
flask user list

Best Practices

  1. Keep commands focused: Each command should do one thing well.
  2. Add documentation: Always include docstrings and help text.
  3. Use confirmation for destructive commands: Protect commands that delete data.
  4. Include feedback: Let the user know what's happening with click.echo().
  5. Handle errors gracefully: Use try/except blocks and return appropriate exit codes.
  6. Add progress indicators: For long-running commands, show progress.
python
@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

Exercises

  1. Create a stats command that displays information about your application (number of users, posts, etc.)
  2. Build a backup-db command that dumps your database to a file
  3. Implement a generate-report command that creates a PDF report of application activity
  4. Create a command group for administrative tasks with commands to manage users and content
  5. 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! :)