OverIQ.com

Extending Flask with Flask-Script

Last updated on July 27, 2020


Flask Extensions #

Flask Extensions refers to the packages you can install to extend Flask. The idea behind Flask extensions is to provide a consistent and predictable way to integrate packages with Flask. To view, all the available extensions visit http://flask.pocoo.org/extensions/. The page contains packages ranging from sending mail to building full-fledged admin interfaces. Remember that you are not limited to Flask Extensions to extend Flask, In fact, you can use any package from Python standard library or PyPI. The rest of the lesson discusses how to install and integrate a useful Flask extension named Flask-Script.

Flask-Script Extension #

Flask-Script is a nice little extension which allows us to create command line interfaces, run server, start Python shell within the application context, make certain variables available inside the shell automatically and so on.

Recall that in lesson Flask Basics we have disscussed that in order to run the developement server on a specific host and port, we need to pass them as keyword arguments to the run() method as follows:

1
2
if __name__ == "__main__":
    app.run(debug=True, host="127.0.0.10", port=9000)

The problem is that this approach is not very flexible, a better way would be to pass host and port as command line options while starting the server. The Flask-Script extension allows us to do just that. Without further ado, let's install Flask-Script by entering the following command:

(env) overiq@vm:~/flask_app$ pip install flask-script

To use Flask-Script first import Manager class from flask_script package and instantiate Manager object by passing application instance to it. That's how you integrate Flask Extensions, you import the necessary class from the extension package and instantiate it by passing application instance to it. Open main2.py and modify the file as follows:

flask_app/main2.py

1
2
3
4
5
6
7
from flask import Flask, render_template
from flask_script import Manager

app = Flask(__name__)
manager = Manager(app)

...

The Manager object we just created also provides the run() method but in addition to starting the development server it can also parse command line arguments. Replace the line app.run(debug=True) with manager.run(). At this point main2.py should look like this:

flask_app/main2.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, render_template
from flask_script import Manager

app = Flask(__name__)
manager = Manager(app)

@app.route('/')
def index():
    return render_template('index.html', name='Jerry')

@app.route('/user/<int:user_id>/')
def user_profile(user_id):
    return "Profile page of user #{}".format(user_id)

@app.route('/books/<genre>/')
def books(genre):
    return "All Books in {} category".format(genre)

if __name__ == "__main__":    
    manager.run()

Our application now has access to some basic commands. To see the commands available, run main2.py as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(env) overiq@vm:~/flask_app$ python main2.py
usage: main2.py [-?] {shell,runserver} ...

positional arguments:
  {shell,runserver}
    shell            Runs a Python shell inside Flask application context.
    runserver        Runs the Flask development server i.e. app.run()

optional arguments:
  -?, --help         show this help message and exit

As the output shows, currently we have two commands: shell and runserver. Let's start with the runserver command.

The runserver command starts the web server. By default, it starts the development server at 127.0.0.1 on port 5000. To see the available options for any command. Type --help followed by the command name. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(env) overiq@vm:~/flask_app$ python main2.py runserver --help
usage: main2.py runserver [-?] [-h HOST] [-p PORT] [--threaded]
                          [--processes PROCESSES] [--passthrough-errors] [-d]
                          [-D] [-r] [-R] [--ssl-crt SSL_CRT]
                          [--ssl-key SSL_KEY]

Runs the Flask development server i.e. app.run()

optional arguments:
  -?, --help            show this help message and exit
  -h HOST, --host HOST
  -p PORT, --port PORT
  --threaded
  --processes PROCESSES
  --passthrough-errors
  -d, --debug           enable the Werkzeug debugger (DO NOT use in production
                        code)
  -D, --no-debug        disable the Werkzeug debugger
  -r, --reload          monitor Python files for changes (not 100% safe for
                        production use)
  -R, --no-reload       do not monitor Python files for changes
  --ssl-crt SSL_CRT     Path to ssl certificate
  --ssl-key SSL_KEY     Path to ssl key

The most commonly used options for runserver command are --host and --post, which allows us to start development server at a specific interface and port. For example:

1
2
(env) overiq@vm:~/flask_app$ python main2.py runserver --host=127.0.0.2 --port 8000
 * Running on http://127.0.0.2:8000/ (Press CTRL+C to quit)

The runserver command by default starts the server without debugger and reloader. We could turn on debugger and reloader manually as follows:

1
2
3
4
5
(env) overiq@vm:~/flask_app$ python main2.py runserver -d -r
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 250-045-653
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

A much simpler way to start the debugger and reloader is to set debug attribute of the application instance (app) to True. Open main2.py and modify the file as follows:

flask_app/main2.py

1
2
3
4
5
#...
app = Flask(__name__)
app.debug = True
manager = Manager(app)
#...

Let's now explore the shell command.

The shell command starts the Python shell inside Flask application context. This simply means that all objects provided by the application and request context are available to you inside the console without creating application or request context. To start shell enter the following command.

1
2
3
>>>
(env) overiq@vm:~/flask_app$ python main2.py shell
>>>

Now let's try accessing some objects.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
>>>
>>> from flask import current_app, url_for, request
>>>
>>> current_app.name
'main2'
>>>
>>>
>>> url_for("user_profile", user_id=10)
'/user/10/'
>>>
>>> request.path
'/'
>>>

As expected, we can access the desired object without creating application or request context.

Creating Commands #

Once Manager instance is created, we are ready to create our own commands. There are two ways to create commands:

  1. Using Command class.
  2. Using @command decorator.

Creating Commands using Command class #

Open main2.py and add the Faker class as follows:

flask_app/main2.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#...
from flask_script import Manager, Command
#...

manager = Manager(app)

class Faker(Command):
    'A command to add fake data to the tables'
    def run(self):
        # add logic here
        print("Fake data entered")

@app.route('/')
#...

Here we have created a command Faker by inheriting the Command class. The run() method is called when the command is executed. Now to run this command from the command line, add it to the Manager instance using the add_command() method as follows:

flask_app/main2.py

1
2
3
4
5
6
7
8
9
#...
class Faker(Command):
    'A command to add fake data to the tables'
    def run(self):
        # add logic here
        print("Fake data entered")

manager.add_command("faker", Faker())
#...

Now head back to terminal and run main2.py again:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(env) overiq@vm:~/flask_app$ python main2.py
usage: main2.py [-?] {faker,shell,runserver} ...

positional arguments:
  {faker,shell,runserver}
    faker               A command to add fake data to the tables
    shell               Runs a Python shell inside Flask application context.
    runserver           Runs the Flask development server i.e. app.run()

optional arguments:
  -?, --help            show this help message and exit

Note that in addition to shell and runserver, we now have an entry for faker command as well. The description in front of the faker command comes from doc string in the Faker class. To run it enter the following command:

1
2
(env) overiq@vm:~/flask_app$ python main2.py faker
Fake data entered

Creating commands using @command decorator #

Creating commands using Command class is slightly verbose. Alternatively, we can use @command decorator of the Manager instance. Open main2.py and modify the file as follows:

flask_app/main2.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#...
manager.add_command("faker", Faker())

@manager.command
def foo():
    "Just a simple command"
    print("foo command executed")

@app.route('/')
#...

We have created a simple command foo which prints foo command executed when called. The @command decorator automatically adds the command to the existing Manager instance, so we don't need to call add_command() method. Head back to the terminal again and run main2.py file to see command usage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(env) overiq@vm:~/flask_app$ python main2.py 
usage: main2.py [-?] {faker,foo,shell,runserver} ...

positional arguments:
  {faker,foo,shell,runserver}
    faker               A command to add fake data to the tables
    foo                 Just a simple command
    shell               Runs a Python shell inside Flask application context.
    runserver           Runs the Flask development server i.e. app.run()

optional arguments:
  -?, --help            show this help message and exit

As you can see our foo command is now available, we can execute it by entering the following command.

1
2
(env) overiq@vm:~/flask_app$ python main2.py foo
foo command executed

Importing Objects Automatically #

Importing lots of objects inside a shell can be tedious. Using Flask-Script, we can make objects available inside the shell without explicitly importing them.

The Shell command starts a shell. The Shell constructor function accepts a keyword argument named make_context. The argument passed to make_context must be a callable returning a dictionary. By default, the callable returns a dictionary containing just the application instance i.e app. That means, by default in the shell you only have access to application instance (app) without explicitly importing it. To override this default, assign a new callable (function) to make_context which returns a dictionary of objects that you want to access inside the shell.

Open main2.py and add the following code after the foo() function.

flask_app/main2.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#...
from flask_script import Manager, Command, Shell

#...
def shell_context():
    import os, sys
    return dict(app=app, os=os, sys=sys)

manager.add_command("shell", Shell(make_context=shell_context))
#...

Here we are assigning a callable named shell_context to the make_context keyword argument. The shell_context() function returns a dictionary containing three objects: app, os and sys. As a result, inside the shell we will have access to these objects without explicitly importing them.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(env) overiq@vm:~/flask_app$ python main2.py shell
>>>
>>> app
<Flask 'main2'>
>>>
>>> os.name
'posix'
>>>
>>> sys.platform
'linux'
>>>
>>>