#3 Основы Flask

Hello World во фреймворке Flask

Начать знакомство с Flask можно с создания простого приложения, которое выводит “Hello World”. Создаем новый файл main.py и вводим следующий код.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World'

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

Это приложение “Hello World”, созданное с помощью фреймворка Flask. Если код в main.py не понятен, это нормально. В следующих разделах все будет разбираться подробно. Чтобы запустить main.py, нужно ввести следующую команду в виртуальную среду Python.

(env) gvido@vm:~/flask_app$ python main.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Запуск файла main.py запускает локальный сервер для разработки на порте 5000. Осталось открыть любимый браузер и зайти на https://127.0.0.1:5000/, чтобы увидеть приложение Hello World в действии.
Hello World во фреймворке Flask

Остановить сервер можно с помощью комбинации CTRL+C.

Создание приложения Flask

У каждого Flask-приложения должен быть экземпляр класса. Экземпляр — это WSGI-приложение (WSGI – это интерфейс для взаимодействия сервера с фреймворком), которое показывает, что сервер передает все полученные запросы экземпляру для дальнейшей обработки. Объект класса Flask создается следующим образом:

from flask import Flask

app = Flask(__name__)

В первой строке класс Flask импортируется из пакета flask.

Во второй строке создается объект Flask. Для этого конструктору Flask назначается аргумент __name__. Конструктор Flask должен иметь один обязательный аргумент. Им служит название пакета. В большинстве случаев значение __name__ подходит. Название пакета приложения используется фреймворком Flask, чтобы находить статические файлы, шаблоны и т. д.

Создание route (путей)

Маршрут (или путь) используется во фреймворке Flask для привязки URL к функции представления. Эта функция отвечает на запрос. Во Flask декоратор route используется, чтобы связать URL адрес с функций. Вот как маршрут создается.

@app.route('/')
def index():
    return 'Hello World'

Этот код назначает функцию index() обработчиком корневого URL в приложении. Другими словами, каждый раз, когда приложение будет получать запрос, где путь — /, вызывается функция index(), и на этом запрос завершается.

Как вариант можно использовать метод add_url_rule() вместо декоратора route для маршрутизации. add_url_rule() — это простой метод, а не декоратор. Помимо URL он принимает конечную точку и название функции представления. Конечная точка относится к уникальному имени маршрута. Обычно, название функции представления — это и есть конечная точка. Flask может генерировать URL из конечной точки, но об этом позже. Предыдущий код аналогичен следующему:

def index():
    return 'Hello World'

app.add_url_rule('/', 'index', index)

Декоратор route используется в большинстве случаев, но у add_url_rule() есть свои преимущества.

Функция представления должна вернуть строку. Если пытаться вернуть что-то другое, сервер ответит ошибкой 500 Internal Sever Error.

Можно создать столько столько, сколько нужно приложению. Например, в следующем списке 3 пути.

@app.route('/')
def index():
    return 'Home Page'

@app.route('/career/')
def career():
    return 'Career Page'

@app.route('/feedback/')
def feedback():
    return 'Feedback Page'

Когда URL в маршруте заканчивается завершающим слешем (/), Flask перенаправляет запрос без слеша на URL со слешем. Так, запрос к /career будет перенаправлен на /career/.

Для одной функции представления может быть использовано несколько URL. Например:

@app.route('/contact/')
@app.route('/feedback/')
def feedback():
    return 'Feedback Page'

В этом случае в ответ на запросы /contact/ или /feedback/, будет вызвана функция feedback().

Если перейти по адресу, для которого нет соответствующей функции представления, появится ошибка 404 Not Found.

Эти маршруты статичны. Большая часть современных приложений имеют динамичные URL. Динамичный URL – это адрес, который состоит из одной или нескольких изменяемых частей, влияющих на вывод страницы. Например, при создании веб-приложения со страницами профилей, у каждого пользователя будет уникальный id. Профиль первого пользователя будет на странице /user/1, второго — на /user/2 и так далее. Очень неудобный способ добиться такого результата — создавать маршруты для каждого пользователя отдельно.

Вместе этого можно отметить динамические части URL как <variable_name> (переменные). Эти части потом будут передавать ключевые слова функции отображения. Следующий код демонстрирует путь с динамическим элементом.

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

В этом примере на месте <id> будет указываться часть URI, которая идет после /user/. Например, если зайти на /user/100/, ответ будет следующим.

Profile page of user #100

Этот элемент не ограничен числовыми id. Адрес может быть /user/cowboy/, /user/foobar10/, /user/@@##/ и так далее. Но он не будет работать со следующими URI: /user/, /user/12/post/. Можно ограничить маршрут, чтобы он работал только с числовыми id после /user/. Это делается с помощью конвертера.

По умолчанию динамические части URL передаются в функцию в виде строк. Это можно изменить с помощью конвертера, который указывается перед динамическими элементами URL с помощью <converter:variable_name>. Например, /user/<int:id>/ будет работать с адресами /user/1/, /user/200/ и другими. Но /user/cowboy/, /user/foobar10/ и /user/@@##/ не подойдут.

В этом списке все конвертеры, доступные во Flask:

КонвертерОписание
stringпринимает любые строки (значение по умолчанию).
intпринимает целые числа.
floatпринимает числа с плавающей точкой.
pathпринимает полный путь включая слеши и завершающий слеш.
uuidпринимает строки uuid (символьные id).

Запуск сервера

Для запуска сервера разработки нужно использовать метод run() объекта Flask.

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

Условие __name__ == "__main__" гарантирует, что метод run() будет вызван только в том случае, если main.py будет запущен, как основная программа. Если попытаться использовать метод run() при импорте main.py в другой модуль Python, он не вызовется.

Важно: сервер разработки Flask используется исключительно для тестирования, поэтому его производительность невысокая.

Теперь должно быть понятнее, как работает main.py.

Режим отладки (Debug)

Баги неизбежны в программировании. Поэтому так важно знать, как находить ошибки в программе и исправлять их. Во Flask есть мощный интерактивный отладчик, который по умолчанию отключен. Когда он выключен, а в программе обнаруживается ошибка, функция показывает 500 Internal Sever Error. Чтобы увидеть это поведение наглядно, можно специально добавить баг в файл main.py.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    print(i)
    return 'Hello World'
    
if __name__ == "__main__":
    app.run()

В этом случае программа пытается вывести значение неопределенной переменной i, что приводит к ошибке. Если открыть https://127.0.0.1:5000/, то появится ошибка 500 Internal Sever Error:
ошибка 500 Internal Sever Error

Тем не менее сам браузер не сообщает о типе ошибки. Если посмотреть в консоль, то можно увидеть отчет об ошибке. В данном случае он выглядит вот так:


File "/home/gvido/flask_app/env/lib/python3.5/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/gvido/flask_app/env/lib/python3.5/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "main.py", line 13, in index
    print(i)
NameError: name 'i' is not defined

Когда режим отладки выключен, после изменения кода нужно каждый раз вручную запускать сервер, чтобы увидеть изменения. Но режим отладки будет перезапускать его после любых изменений в коде.

Чтобы включить режим, нужно передать аргумент debug=True методу run():

if __name__ == "__main__":
    app.run(debug=True)

Еще один способ — указать значение True для атрибута debug.

from flask import Flask

app = Flask(__name__)
app.debug = True

После обновления файл main.py следующим образом его можно запускать.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    print(i)
    return 'Hello World'

if __name__ == "__main__":
    app.run(debug=True) # add debug mode

Теперь при открытии https://127.0.0.1:5000/ на странице будет отладчик.

Теперь, когда отладчик включен, вместо ошибки 500 Internal Server на странице будет отображаться отчет об ошибке. Он в полной мере описывает, какая ошибка случилась. Внизу страницы видно, что оператор print пытался вывести значение неопределенной переменной i. Там же указан тип ошибки, NameError, что подтверждает то, что ошибка заключается в том, что имя i не определено.

Кликнув на строчку кода на странице вывода ошибки, можно получить исходный код, где эта ошибка обнаружена, а также предыдущие и следующие строчки. Это помогает сразу понять контекст ошибки.

исходный код, где эта ошибка обнаружена

При наведении на строчку кода отображается иконка терминала. Нажав на нее, откроется консоль, где можно ввести любой код Python.

консоль, где можно ввести любой код Python

В ней можно проверить локальные переменные.

локальные переменные

Если консоль открывается первый раз, то нужно ввести PIN-код.

Если консоль открывается первый раз, то нужно ввести PIN-код

Это мера безопасности, призванная ограничить доступ к консоли неавторизованным пользователям. Посмотреть код можно в консоли при запуске сервера. Он будет указан в начале вывода.

 Посмотреть код можно в консоли при запуске сервера.

Завершить урок стоит созданием еще одного приложения Flask с применением всех имеющихся знаний.


Создаем еще один файл main2.py со следующим кодом:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello Flask'

@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__":
    app.run(debug=True)

Если запустить файл и зайти на https://127.0.0.1:5000/, браузер поприветствует выражением «Hello Flask»:

Hello Flask

В этой новой версии приложения есть два динамических пути. Если в браузере ввести https://127.0.0.1:5000/user/123/, откроется страница со следующим содержанием:

https://127.0.0.1:5000/user/123/

Стоит заметить, что путь /user/<int:user_id>/ будет работать только с теми URL, где динамическая часть (user_id) представлена числом.

Чтобы проверить второй динамический путь, нужно открыть https://127.0.0.1:5000/books/sci-fi/. В этот раз получится следующее:

https://127.0.0.1:5000/books/sci-fi/

Если сейчас попробовать открыть URL, который не определен в путях, выйдет ошибка 404 Not Found. Например, такой ответ получите при попытке перейти на https://127.0.0.1:5000/products.

Как Flask обрабатывает запрос?

Откуда Flask знает, какую функцию выводить, когда он получает запрос от клиента?

Flask сопоставляет URL и функции отображения, которые будут выводиться. Определение соответствий (маршрутизация) создается с помощью декоратора route или метода add_url_rule() в экземпляре Flask. Получить доступ к этим соответствиям можно с помощью атрибута url_map у экземпляра Flask.

>>>
>>> from main2 import app
>>> app.url_map
Map([<Rule '/' (OPTIONS, GET, HEAD) -> index>,
 <Rule '/static/<filename>' (OPTIONS, GET, HEAD) ->  static>,
 <Rule '/books/<genre>' (OPTIONS, GET, HEAD) ->  books>,
 <Rule '/user/<user_id>' (OPTIONS, GET, HEAD) ->  user_profile>])
>>>

Как видно, есть 4 правила. Flask определяет соответствия URL в следующем формате:

url pattern, (comma separated list of HTTP methods handled by the route) -> view function to execute

Путь /static/<filename> автоматически добавляется для статических файлов Flask. О работе со статическими файлами речь пойдет в отдельном уроке «Обслуживание статических файлов во Flask».