#10 Расширение возможностей Flask с помощью Flask-Script

Расширения Flask

Расширения Flask — это пакеты, которые можно установить, чтобы расширить возможности Flask. Их суть в том, чтобы обеспечить удобный и понятный способ интеграции пакетов во Flask. Посмотреть все доступные расширения можно на странице https://flask.pocoo.org/extenstions/. На странице есть пакеты, возможности которых варьируются от отправки email до создания полноценных интерфейсов администратора. Важно помнить, что расширять возможности Flask можно не только с помощью его расширений. На самом деле, подойдет любой пакет из стандартной библиотеки Python или PyPi. Оставшаяся часть урока посвящена тому, как установить и интегрировать удобное расширение для Flask под названием Flask-Script.

Расширение Flask-Script

Flask-Script — это удобное миниатюрное расширение, которое позволяет создавать интерфейсы командной строки, запускать сервер и консоль Python в контексте приложений, делать определенные переменные видимыми в консоли автоматически и так далее.

Стоит напомнить то, что обсуждалось в уроке «Основы Flask». Для запуска сервера разработки на конкретном хосте и порте, их нужно передать в качестве аргументов-ключевых слов методу run():

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

Проблема в том, что такой подход не гибкий. Намного удобнее передать хост и порт в виде параметров командной строки при запуске сервера. Flask-Script позволяет сделать это. Установить Flask-Script можно с помощью pip:

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

Чтобы использовать Flask-Script сперва нужно импортировать класс Manager из пакета flask_script и создать экземпляр объекта Manager, передав ему экземпляр приложения. Таким образом расширения Flask интегрируются. Сначала импортируется нужный класс из пакета, а затем создается экземпляр с помощью передачи ему экземпляра приложения. Нужно открыть файл main2.py и изменить его следующим образом:

from flask import Flask, render_template
from flask_script import Manager

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

#...

Созданный объект Manager также имеет метод run(), который помимо запуска сервера разработки может считывать аргументы командной строки. Следует заменить строку app.run(debug=True) на manager.run(). К этому моменту main2.py должен выглядеть вот так:

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()

Теперь у приложения есть доступ к базовым командам. Чтобы посмотреть, какие из них доступны, необходимо запустить файл main2.py:

(env) gvido@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

Как показывает вывод, сейчас есть всего две команды: shell и runserver. Начнем с команды runserver.

runserver запускает веб-сервер. По умолчанию, он запускается на 127.0.0.1 на порте 5000. Чтобы увидеть варианты для любой команды нужно ввести --help и саму команду. Например:

(env) gvido@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

Самые широко используемые варианты для runserver — это --host и --post. С их помощью можно запустить сервер разработки на конкретном интерфейсе и порте. Например:

(env) gvido@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)

По умолчанию команда runserver запускает сервер без отладчика. Включить его вручную можно следующим образом:

(env) gvido@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)

Более простой способ запустить отладчик — выбрать значение True для атрибута debug у экземпляра объекта (app). Для этого нужно открыть main2.py и изменить файл следующим образом:

#...
app = Flask(__name__)
app.debug = True
manager = Manager(app)
#...

Далее о команде shell.

shell запускает консоль Python в контексте приложения Flask. Это значит, что все объекты внутри контекстов приложения и запроса будут доступны в консоли без создания дополнительных контекстов. Для запуска консоли нужно ввести следующую команду.

(env) gvido@vm:~/flask_app$ python main2.py shell

Получим доступ к определенным объектам.

>>>
>>> from flask import current_app, url_for, request
>>>
>>> current_app.name
'main2'
>>>
>>>
>>> url_for("user_profile", user_id=10)
'/user/10/'
>>>
>>> request.path
'/'
>>>

Как и ожидалось, это можно сделать, не создавая контексты приложения и запроса.

Создание команд

Когда экземпляр Manager создан, можно приступать к созданию собственных команд. Есть два способа:

  1. С помощью класса Command
  2. С помощью декоратора @command

Создание команд с помощью класса Command

В файле main2.py добавим класс Faker:

#...
from flask_script import Manager, Command
#...

manager = Manager(app)

class Faker(Command):
    'Команда для добавления поддельных данных в таблицы'
    def run(self):
        # логика функции
        print("Fake data entered")
       
@app.route('/')
#...

Команда Faker была создана с помощью наследования класса Command. Метод run() вызывается при исполнении команды. Чтобы выполнить команду через командную строку, ее нужно добавить в экземпляр Manager с помощью метода add_command():

#...
class Faker(Command):
    'Команда для добавления поддельных данных в таблицы'
    def run(self):
        # логика функции
        print("Fake data entered")
       
manager.add_command("faker", Faker())
#...

Теперь нужно снова вернуться в терминал и запустить файл main2.py:

(env) gvido@vm:~/flask_app$ python main2.py
usage: main2.py [-?] {faker,shell,runserver} ...

positional arguments:
  {faker,shell,runserver}
    faker  		Команда для добавления поддельных данных в таблицы
    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

Стоит обратить внимание, что теперь, в дополнение к shell и runserver, есть команда faker. Описание перед самой командой взято из строки документации класса Faker. Для запуска нужно ввести следующую команду:

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

Создание команд с помощью декоратора @command

Создание команд с помощью класса Command достаточно объемно. Как вариант, можно использовать декоратор @command экземпляра класса Manager. Для этого нужно открыть файл main2.py и изменить его следующим образом:

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

@manager.command
def foo():
    "Это созданная команда"
    print("foo command executed")

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

Была создана простая команда foo, которая выводит foo command executed при вызове. Декоратор @command автоматически добавляет команду к существующему экземпляру Manager, так что не нужно вызывать метод add_command(). Чтобы увидеть, как используются команды, нужно вернуться обратно в терминал и запустить main2.py.

(env) gvido@vm:~/flask_app$ python main2.py
usage: main2.py [-?] {faker,foo,shell,runserver} ...

positional arguments:
  {faker,foo,shell,runserver}
    faker  		Команда для добавления поддельных данных в таблицы
    foo 		Это созданная команда
    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

Поскольку команда foo теперь доступна, ее можно исполнить, введя следующую команду.

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

Автоматический импорт объектов

Импорт большого количества объектов в командной строке может быть утомительным. С помощью Flask-Script объекты можно сделать видимыми в терминале без явного импорта.

Команда Shell запускает оболочку. Функция конструктора оболочки Shell принимает аргумент-ключевое слово make_context. Аргумент, передаваемый make_context должен быть вызываемым и возвращать словарь. По умолчанию вызываемый объект возвращает словарь, содержащий только экземпляр приложения, то есть app. Это значит, что по умолчанию в оболочке можно получить доступ только к экземпляру приложения (app), специально не импортируя его. Чтобы изменить это поведение, нужно назначить новому объекту (функции), поддерживающему вызов, make_context. Это вернет словарь с объектами, к которым требуется получить доступ внутри оболочки.

Откроем файл main2.py, чтобы добавить следующий код после функции foo().

#...
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))
#...

Здесь вызываемой функции shell_context() передается аргумент-ключевое слово make_context. Функция shell_context возвращает словарь с тремя объектами: app, os и sys. В результате, внутри оболочки теперь есть доступ к этим объектам, хотя их импорт не производился.

(env) gvido@vm:~/flask_app$ python main2.py shell
>>>
>>> app
<Flask 'main2'>
>>>
>>> os.name
'posix'
>>>
>>> sys.platform
'linux'
>>>
>>>