Where should I implement flask custom commands (cli)
Asked Answered
S

7

30

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 ?

Seigler answered 25/7, 2019 at 13:9 Comment(3)
I am not sure, but you need probably with app.app_context():, check thisWaldon
@Waldon yes, but I need to import app first to be able to do with app.app_context():. So the same problem remains.Seigler
You can also check the response in an older and similar question https://mcmap.net/q/500371/-moving-custom-cli-commands-to-another-file in short create a factory function and pass your app as an argument def register_cli(app: Flask):Sabah
S
32

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
Sabah answered 7/10, 2019 at 20:50 Comment(5)
Yes, it's documented as away to register custom commands: flask.palletsprojects.com/en/1.1.x/cli/…. However, is there a way to register custom commands without using Blueprints?Anglican
this is the best feasible solutionSupremacy
Just a note that a blueprint adds a command group by default, check the cli_group option in the docs if this is not what you want.Present
It is also working in Flask 2Urtication
The above solution works for Flask v2.2.2Millenary
K
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

Kindig answered 21/2, 2020 at 9:59 Comment(2)
This is the only solution worked for me out of "ALL flask cli solution found over google search, including the flask cli documentation itself"Wellpreserved
How are you importing app into commands.py?Evars
E
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()
 ...
Evars answered 2/4, 2021 at 19:43 Comment(0)
P
2

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
Popover answered 11/8, 2023 at 15:40 Comment(0)
R
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
Redpoll answered 18/4, 2020 at 7:42 Comment(0)
C
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')
Cirenaica answered 13/1, 2021 at 21:45 Comment(1)
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)Ramon
T
0

My solution:

app.py:

from flask import Flask

def create_app():
    import commands
    application = Flask(__name__)

    application.cli.add_command(commands.hello)
    return application


if __name__ == '__main__':
    app = create_app()
    app.run()

commands.py:

import click


@click.command("say_hello")
@click.argument("name")
def hello(name):
    """ Say hello to user """
    print(f"Hello {name}")
Tripetalous answered 23/10, 2023 at 8:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.