Django-Admin.ru

Flask для начинающих: первое веб-приложение на Python

Flask на Python с нуля: установка, минимальное приложение, маршруты, шаблоны Jinja2, формы и request, flask run и отладка, структура проекта.

Редакция Django-Admin.ru
· обновлено · 10 мин чтения

Содержание · 12 разделов
  1. Установка Flask
  2. Минимальное приложение и flask run
  3. Маршруты: URL, параметры и методы
  4. Шаблоны Jinja2
  5. Формы и объект request
  6. Отладка
  7. Структура проекта на Flask
  8. Запуск в продакшне
  9. Когда выбрать Flask, а когда Django или FastAPI
  10. Частые ошибки
  11. Error: Could not locate a Flask application
  12. jinja2.exceptions.TemplateNotFound
  13. 405 Method Not Allowed
  14. RuntimeError: The session is unavailable because no secret key was set
  15. Порт 5000 занят на macOS
  16. Частые вопросы
  17. Как подключить CSS, картинки и JavaScript?
  18. Почему кириллица в JSON выводится как \u041c\u0430...?
  19. Как подключить базу данных к Flask?
  20. Можно ли писать асинхронные view во Flask?
  21. Итог

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

Быстрый старт: установите Flask, создайте файл app.py и запустите сервер разработки.

python -m pip install flask
flask --app app run --debug
# app.py
from flask import Flask

app = Flask(__name__)


@app.route("/")
def index():
    return "Привет, Flask!"

После запуска откройте http://127.0.0.1:5000/. Ниже разберём, как устроены маршруты, шаблоны и формы, как отлаживать приложение и раскладывать его по файлам. Примеры проверены на Flask 3.1 и Python 3.12.

Установка Flask

Flask ставят в виртуальное окружение, чтобы зависимости проекта не смешивались с системными. Если Python ещё не установлен, начните со статьи «Как установить Python».

Windows (PowerShell):

mkdir flask-app
cd flask-app
py -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install flask

macOS и Linux:

mkdir flask-app && cd flask-app
python3 -m venv .venv
source .venv/bin/activate
python -m pip install flask

Вместе с Flask установятся его зависимости: Werkzeug (обработка HTTP и сервер разработки), Jinja2 (шаблоны), Click (командная строка) и другие. Проверить версию можно командой flask --version.

Минимальное приложение и flask run

Разберём код из начала статьи:

  • app = Flask(__name__) создаёт приложение. __name__ нужен Flask, чтобы найти папки templates и static рядом с модулем.
  • @app.route("/") связывает URL / с функцией index.
  • Функция-представление (view) возвращает ответ: строку, HTML, словарь или список (их Flask превратит в JSON).

Запуск сервера разработки:

flask --app app run --debug
 * Serving Flask app 'app'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!

Если файл называется app.py или wsgi.py, параметр --app можно опустить: flask run --debug. Порт и адрес меняются флагами --port 8000 и --host 0.0.0.0 (второй открывает сервер для других устройств в сети).

Старый способ — вызвать app.run(debug=True) внутри if __name__ == "__main__": и запустить файл как обычный скрипт: python app.py. Он работает, но для разработки удобнее flask run: команда сама находит приложение, а режим отладки и порт задаются флагами без правки кода.

Маршруты: URL, параметры и методы

from flask import Flask, url_for
from markupsafe import escape

app = Flask(__name__)


@app.route("/hello/<name>")
def hello(name):
    return f"Привет, {escape(name)}!"


@app.route("/books/<int:book_id>")
def book_detail(book_id):
    return f"Книга №{book_id}"


@app.get("/api/status")
def status():
    return {"status": "ok"}  # → JSON

Часть URL в угловых скобках становится аргументом функции. Конвертер перед двоеточием задаёт тип: string (по умолчанию, без слешей), int, float, path (со слешами), uuid. Если в /books/<int:book_id> передать abc, Flask сам вернёт 404.

escape() из пакета MarkupSafe экранирует HTML: без него ссылка /hello/<script>... выполнила бы скрипт в браузере. В шаблонах Jinja2 экранирование включено автоматически, поэтому в обычном коде HTML лучше отдавать через шаблоны.

По умолчанию маршрут принимает только GET-запросы. Другие методы разрешают параметром methods=["GET", "POST"] или декораторами-сокращениями @app.get, @app.post, @app.put, @app.delete.

Ссылки не пишут вручную — их строит url_for по имени функции:

url_for("book_detail", book_id=3)  # → '/books/3'

Список всех маршрутов приложения покажет команда flask --app app routes.

Шаблоны Jinja2

HTML выносят в папку templates рядом с app.py и отдают через render_template:

flask-app/
├── app.py
└── templates/
    ├── base.html
    └── index.html

Базовый шаблон с общими частями страницы:

<!-- templates/base.html -->
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8" />
    <title>{% block title %}Библиотека{% endblock %}</title>
  </head>
  <body>
    {% block content %}{% endblock %}
  </body>
</html>

Дочерний шаблон наследует его и заполняет блоки:

<!-- templates/index.html -->
{% extends "base.html" %} {% block content %}
<h1>Книги</h1>
<ul>
  {% for id, book in books.items() %}
  <li>
    <a href="{{ url_for('book_detail', book_id=id) }}">{{ book.title }}</a>
    — {{ book.author }}
  </li>
  {% else %}
  <li>Пока пусто</li>
  {% endfor %}
</ul>
{% endblock %}
from flask import Flask, render_template

app = Flask(__name__)

BOOKS = {
    1: {"title": "Мастер и Маргарита", "author": "Булгаков"},
    2: {"title": "Пикник на обочине", "author": "Стругацкие"},
}


@app.route("/")
def index():
    return render_template("index.html", books=BOOKS)

Основной синтаксис Jinja2: {{ переменная }} выводит значение, {% for %} и {% if %} управляют логикой, {{ value|default("—") }} применяет фильтр, {% extends %} и {% block %} строят наследование. Внутри шаблонов доступна функция url_for. Если вы знакомы с шаблонами Django, синтаксис почти тот же.

Формы и объект request

Данные запроса лежат в объекте request: request.args — параметры строки запроса (?q=...), request.form — поля HTML-формы, request.get_json() — тело JSON, request.files — загруженные файлы, request.method — метод.

from flask import Flask, abort, flash, redirect, render_template, request, url_for

app = Flask(__name__)
app.config["SECRET_KEY"] = "dev"  # в продакшне — из переменной окружения


@app.route("/books/<int:book_id>")
def book_detail(book_id):
    book = BOOKS.get(book_id)
    if book is None:
        abort(404)
    return render_template("book.html", book=book)


@app.route("/books/new", methods=["GET", "POST"])
def book_create():
    if request.method == "POST":
        title = request.form.get("title", "").strip()
        author = request.form.get("author", "").strip()
        if not title:
            flash("Укажите название книги")
            return render_template("book_form.html"), 400
        new_id = max(BOOKS) + 1
        BOOKS[new_id] = {"title": title, "author": author}
        return redirect(url_for("book_detail", book_id=new_id))
    return render_template("book_form.html")


@app.get("/api/books")
def api_books():
    q = request.args.get("q", "").lower()
    return [book for book in BOOKS.values() if q in book["title"].lower()]

Шаблон формы:

<!-- templates/book_form.html -->
{% extends "base.html" %} {% block content %} {% for message in get_flashed_messages() %}
<p class="error">{{ message }}</p>
{% endfor %}
<form method="post">
  <input name="title" placeholder="Название" />
  <input name="author" placeholder="Автор" />
  <button type="submit">Сохранить</button>
</form>
{% endblock %}

Что здесь происходит:

  • после успешного POST делаем redirect — так обновление страницы не отправит форму повторно;
  • abort(404) прерывает обработку и возвращает страницу «Не найдено»;
  • flash() сохраняет сообщение в сессии до следующего запроса; сессия подписывается ключом, поэтому без SECRET_KEY Flask выдаст ошибку;
  • view может вернуть кортеж (тело, статус), например (..., 400).

Для валидации и CSRF-защиты форм обычно подключают расширение Flask-WTF — во Flask «из коробки» CSRF-защиты нет, в отличие от Django.

Отладка

Флаг --debug включает два механизма:

  • автоперезагрузку — сервер перезапускается при сохранении файлов;
  • интерактивный отладчик — при исключении в браузере открывается трейсбек, и в каждом кадре стека можно выполнять Python-код. Доступ защищён PIN-кодом, который печатается в консоли.

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

Без браузера ошибки видны в консоли — как их читать, разобрано в статье «Traceback в Python». Для проверки маршрутов без сервера удобен тестовый клиент:

with app.test_client() as client:
    response = client.get("/books/1")
    print(response.status_code)  # → 200

Структура проекта на Flask

Один app.py удобен для начала, но с ростом проекта приложение раскладывают по модулям: фабрика create_app() создаёт и настраивает приложение, а маршруты группируются в blueprints.

flask-app/
├── library/
│   ├── __init__.py      # create_app()
│   ├── books.py         # blueprint с маршрутами книг
│   ├── templates/
│   └── static/
└── .venv/
# library/__init__.py
import os

from flask import Flask


def create_app():
    app = Flask(__name__)
    app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "dev")

    from .books import bp as books_bp

    app.register_blueprint(books_bp, url_prefix="/books")
    return app
# library/books.py
from flask import Blueprint, render_template

bp = Blueprint("books", __name__)


@bp.route("/")
def book_list():
    return render_template("books/list.html")

Запуск не меняется: flask --app library run --debug — Flask сам найдёт и вызовет функцию create_app. Имена маршрутов внутри blueprint получают префикс: url_for("books.book_list").

Типовые расширения, которые подключают дальше: Flask-SQLAlchemy (ORM и база данных), Flask-Migrate (миграции), Flask-WTF (формы), Flask-Login (вход пользователей).

Запуск в продакшне

Встроенный сервер flask run — только для разработки. В продакшне приложение запускают через WSGI-сервер:

# Linux/macOS
python -m pip install gunicorn
gunicorn -w 4 --bind 127.0.0.1:8000 "app:app"
# Windows: Gunicorn не работает, используйте Waitress
python -m pip install waitress
waitress-serve --host 127.0.0.1 --port 8000 app:app

Перед WSGI-сервером обычно ставят Nginx — схема такая же, как в статье про деплой Django с Gunicorn и Nginx.

Когда выбрать Flask, а когда Django или FastAPI

  • Flask — небольшие сайты, внутренние инструменты, прототипы, учебные проекты; когда хочется самому выбрать ORM, формы и структуру.
  • Django — сайты с базой данных, пользователями и админкой: всё это есть «из коробки». Начать можно со статьи «Django для начинающих».
  • FastAPI — JSON API с автоматической валидацией и документацией OpenAPI, асинхронный код. См. руководство по FastAPI.

Подробное сравнение — в обзоре веб-фреймворков Python и статье FastAPI vs Django vs Flask.

Частые ошибки

Error: Could not locate a Flask application

Flask не нашёл приложение. Укажите его явно: flask --app имя_файла run, или назовите файл app.py/wsgi.py и запускайте команду из папки с ним.

jinja2.exceptions.TemplateNotFound

Шаблон должен лежать в папке templates рядом с модулем, где создан Flask(__name__) (или blueprint с параметром template_folder). Проверьте имя папки — именно templates, во множественном числе.

405 Method Not Allowed

Форма отправляет POST на маршрут, который принимает только GET. Добавьте methods=["GET", "POST"].

RuntimeError: The session is unavailable because no secret key was set

flash() и session требуют SECRET_KEY. Задайте app.config["SECRET_KEY"], а в продакшне берите значение из переменной окружения.

Порт 5000 занят на macOS

Начиная с macOS Monterey (12) порт 5000 занимает служба «Приёмник AirPlay» (AirPlay Receiver). Запустите Flask на другом порту: flask run --port 8000 — или найдите «Приёмник AirPlay» через поиск в Системных настройках и выключите его.

Частые вопросы

Как подключить CSS, картинки и JavaScript?

Положите файлы в папку static рядом с app.py — Flask раздаёт её по адресу /static/. В шаблоне ссылку строят через url_for:

<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />

Для файла static/css/style.css получится адрес /static/css/style.css. В продакшне статику обычно отдаёт Nginx.

Почему кириллица в JSON выводится как \u041c\u0430...?

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

app = Flask(__name__)
app.json.ensure_ascii = False

После этого {"title": "Мастер и Маргарита"} уходит в ответ без \u-последовательностей. Атрибут app.json появился во Flask 2.2, а ключ JSON_AS_ASCII из старых примеров удалён во Flask 2.3.

Как подключить базу данных к Flask?

Чаще всего — через расширение Flask-SQLAlchemy (pip install flask-sqlalchemy):

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class Base(DeclarativeBase):
    pass


db = SQLAlchemy(model_class=Base)
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///library.db"
db.init_app(app)


class Book(db.Model):
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str]


with app.app_context():
    db.create_all()

Внутри view запросы выполняются через db.session, например db.session.scalars(db.select(Book)).all(). Для изменения схемы подключают Flask-Migrate. Основы самой ORM — в статье про SQLAlchemy в Python.

Можно ли писать асинхронные view во Flask?

Можно, начиная с Flask 2.0: установите pip install "flask[async]" и объявите view через async def. Flask при этом остаётся WSGI-фреймворком: каждый запрос по-прежнему занимает рабочий поток, а асинхронность помогает только выполнить несколько await-операций параллельно внутри одного запроса. Если сервис строится вокруг асинхронного кода, удобнее ASGI-фреймворк — например, FastAPI.

Итог

Flask даёт минимальный набор: маршруты, request, шаблоны Jinja2 и сервер разработки с отладчиком. Этого хватает для первого веб-приложения, а остальное добавляется расширениями. Чтобы выбрать инструмент для следующего проекта, загляните в обзор фреймворков Python для веб-разработки и раздел «Веб-разработка на Python».

  • #Flask
  • #Python
  • #Jinja2
  • #Веб-разработка
  • #Для начинающих
↑ ↓ выборEnter открытьEsc закрыть