26

Creating custom commands in flask needs access to the app, which is generally created in app.py like this:

import click
from flask import Flask

app = Flask(__name__)

@app.cli.command("create-user")
@click.argument("name")
def create_user(name):
    ...

However, in order not to bloat my app.py, I want to put my custom commands in a separate file e.g. commands.py, but this doesn't work because the entrypoint to my project is app.py, so I'll have to import app in commands.pyand import my commands in app.py which results in a circular import error.

How can I create custom commands in separate files ?

m0etaz
  • 953
  • 1
  • 8
  • 21
  • I am not sure, but you need probably `with app.app_context():`, check [this](https://flask.palletsprojects.com/en/1.0.x/appcontext/) – Roman Jul 25 '19 at 13:18
  • @Roman yes, but I need to import app first to be able to do `with app.app_context():`. So the same problem remains. – m0etaz Jul 25 '19 at 13:28
  • 1
    You can also check the response in an older and similar question https://stackoverflow.com/a/54824126/606826 in short create a factory function and pass your app as an argument `def register_cli(app: Flask):` – emont01 Oct 07 '19 at 20:56

6 Answers6

29

One way to achieve this would be using blueprints

I have tested it using Flask 1.1.1, so be sure to check the documentation of the correct version that you have.

Here is the general idea:

  1. Create one or more Blueprints in a different file, let's say it's called commands.py
  2. Then import the new blueprints and register them to your app

==> app.py <==

from flask import Flask
from commands import usersbp

app = Flask(__name__)
# you MUST register the blueprint
app.register_blueprint(usersbp)

==> commands.py <==

import click
from flask import Blueprint

usersbp = Blueprint('users', __name__)

@usersbp.cli.command('create')
@click.argument('name')
def create(name):
    """ Creates a user """
    print("Create user: {}".format(name))

Upon executing flask users you should get a response like the following:

flask users
Usage: flask users [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  create  Creates a user
emont01
  • 3,010
  • 1
  • 22
  • 18
7

just import it in your app factory

dir tree

my_app
     L app.py
     L commands.py

commands.py

@app.cli.command('resetdb')
def resetdb_command():
    """Here info that will be shown in flask --help"""
    pass

app.py

def create_app():
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    db.init_app(app)

    with app.app_context():
        from . import routes
        from . import commands  # <----- here

        return app
$ export FLASK_APP=my_app/app.py
$ flask resetdb

but there have to be better way ;) of which I am unaware right now

quester
  • 534
  • 5
  • 18
4

If you're using an app factory (you have a create_app() function), then there isn't even an app variable for you to import.

The best way to keep your code organized is to define the function somewhere else, and then register it when building the application instance.

E.g.

my_app/
 | main.py
 | app/
 |  | __init__.py
 |  | commands.py

commands.py

def foo():
   print("Running foo()")

init.py

def create_app():
 app = Flask(__name__)
 ...
 from .commands import foo
 @app.cli.command('foo')
 def foo_command():
   foo()
 ...
G. Shand
  • 388
  • 3
  • 12
1

I have this layout:

baseapp.py

from flask import Flask

app = Flask("CmdAttempt")

app.py

from .baseapp import app

def main():
    app.run(
        port=5522,
        load_dotenv=True,
        debug=True
    )

if __name__ == '__main__':
    main()

commands.py

import click

from .baseapp import app


@app.cli.command("create-super-user")
@click.argument("name")
def create_super_user(name):
    print("Now creating user", name)


if __name__ == '__main__':
    from .app import main
    main()

In the console where you run the commands first define the FLASK_APP to be commands.py, then run the commands that you define.

set FLASK_APP=commands.py
export FLASK_APP=commands.py
flask create-super-user me

You can either use a separate terminal for built-in commands or clear the FLASK_APP variable before issuing them. In Linux is even easier because you can do

FLASK_APP=commands.py flask create-super-user me
Nicu Tofan
  • 1,052
  • 14
  • 34
1

What worked for me in case you are not using app factory pattern, similar to @quester:

app.py

import os

from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv("DATABASE_URL")
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
migrate = Migrate(app, db)

with app.app_context():
    # needed to make CLI commands work
    from commands import *

commands.py

from app import app

@app.cli.command()
def do_something():
    print('hello i am so nice I posted this even though I have 100 other things to do')
Casey
  • 2,611
  • 6
  • 34
  • 60
  • Is this working? I am getting circular dependency issue. `ImportError: cannot import name 'app' from partially initialized module 'app' (most likely due to a circular import)` – Shiv Krishna Jaiswal Sep 03 '21 at 12:54
0

My practice:

├── app.py

├── commands.py

in app.py:

from commands import register_commands

def create_app():
    app = Flask(__name__)
    ....

    register_commands(app)
    return app

in commands.py

from sqlalchemyseeder import ResolvingSeeder
from app import db

def register_commands(app):
    @app.cli.command("seed_courses")
    def seed_courses():
        seeder = ResolvingSeeder(db.session)
        seeder.load_entities_from_json_file("app/db/seed/data.json")
        db.session.commit()
        db.session.close()

    @app.cli.command("command2")
    def another_command():
        print("another command")

run:

flask seed_courses
flask command2