Пользователь
0,0
рейтинг
25 февраля 2015 в 11:59

Разработка → Flask. Наполняем «флягу» функционалом из песочницы

Предисловие


В прошлом году решил для себя вплотную познакомиться c Python, а в последствии перебраться на него с PHP. На данный момент моя стезя — веб-разработка, а потому осваивать новый язык я начал именно со стороны веба, в частности, с обзора доступных фреймворков и проектов на них. Познакомившись с возможностями TurboGears, web2py, Django, я всё таки поддался «тренду» и погрузился в мир Django.

На протяжении почти года я честно пытался подружиться с ним. Написал несколько простеньких проектов, но монструозность фреймворка отпугивала, обилие «батареек» путало выбор, а с некоторыми ограничениями не хотелось мириться. Душа требовала лаконичности и однозначности, что в конечном счете привело меня к знакомству с Flask. Изучив документацию по фреймворку и смежным проектам (Jinja2, Werkzeug), я проникся идеологией и стал вплотную изучать фреймворк.

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

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

Структура и конфигурация


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

app/
--commands/
--migrations/
--static/
--templtaes/
--app.py
--config.py
--forms.py
--manage.py
--models.py
--views.py

  • Каталог commands содержит команды для обслуживания приложения, подключаемые в модуле manage.py.
  • Каталог migrations — файлы и конфигурацию миграций. Обычно создается автоматически при инициализации миграций.
  • Каталог static — ресурсы проекта: js, css, scss и картинки.
  • Каталог templates — шаблоны.
  • Файл app.py — это головной модуль приложения, где определяются основные настройки и регистрируются расширения, он же реализует и веб-сервер.
  • Файл manage.py служит для управления и обслуживания проектом.
  • Файл config.py содержит объект конфигурации приложения. Отмечу, что Flask можно конфигурировать различными способами, но мне наиболее удобным показался способ на основе объектов. В упрощенном виде содержимое файла выглядит так:

    config.py
    import os
    basedir = os.path.abspath(os.path.dirname(__file__))
    
    class Config(object):
        DEBUG = False
        CSRF_ENABLED = True
        WTF_CSRF_SECRET_KEY = 'dsofpkoasodksap'
        SECRET_KEY = 'zxczxasdsad'
        SQLALCHEMY_DATABASE_URI = 'mysql+mysqlconnector://webuser:web_password@localhost/webuser_db'
    
    class ProductionConfig(Config):
        DEBUG = False
    
    class DevelopConfig(Config):
        DEBUG = True
        ASSETS_DEBUG = True
    

    А его применение так:

    app.py
    app.config.from_object('config.DevelopConfig')
    

Для крупных проектов официальная документация рекомендует дробить функционал на так называемые blueprints — модули, структурно похожие на приложение Flask, а сам проект организовывать в пакет python. Но сегодня не об этом.

Расширения


Flask-SQLAlchemy


Любое серьезное приложение использует базы данных. Данное расширение дружит Flask с самой популярной на Python ORM-библиотекой — SQLAlchemy, позволяя использовать любые поддерживаемые ей СУБД, а также отображение таблиц в объекты Python, аналогично Django. Впрочем, SQLAlchemy позволяет обойтись и без ORM.

Использование
# config.py
class Config(object):
    ...
    # определяем DSN в конфигурации
    SQLALCHEMY_DATABASE_URI = 'mysql+mysqlconnector://webuser:web_password@localhost/webuser_db'


# app.py
# импортируем расширение 
from flask.ext.sqlalchemy import SQLAlchemy

# инициализируем объект БД
db = SQLAlchemy(app)

# models.py
from app import db

# Модель User  - отображение таблицы users в БД
class User(db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(255))

Документация: pythonhosted.org/Flask-SQLAlchemy

Flask-Script


Добавляет поддержку обслуживающих проект скриптов: запуск dev-сервера, миграции баз данных, cron-задачи и тому подобное. Следуя рекомендациям, я создаю для каждого проекта файл manage.py, где добавляются все необходимые для обслуживания команды. По-умолчанию доступна команда runserver. Запуск команд осуществляется следующим образом:

$ python manage.py command <action>
$ python manage.py runserver
$ python manage.py db migate

Добавить команду можно, например, реализовав потомок класса Command, входящего в пакет, и зарегистрировав ее в менеджере. Команда может содержать действия(подкоманды), ей могут передаваться параметры командной строки.

manage.py
from flask.ext.script import Manager
from flask.ext.migrate import Migrate, MigrateCommand
from app import app, db

from models import *
migrate = Migrate(app, db)

# Инициализируем менеджер
manager = Manager(app)
# Регистрируем команду, реализованную в виде потомка класса Command
manager.add_command('db', MigrateCommand)

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

Документация: flask-script.readthedocs.org/en/latest

Flask-Migrate


Позволяет настроить миграции для ORM SQLAlchemy. Пакет предоставляет класс MigrateCommand, который можно использовать в связке с вышеописанным расширением Flask-Script. Для использования миграций необходимо подключить команду(пример выше), произвести начальную инициализацию, выполнив manage.py db init, затем использовать действия migrate, upgrade и downgrade данной команды для управления миграциями. Стоит отметить, что список действий для команды и их краткое описание можно получить, выполнив manage.py db help.

Документация: flask-migrate.readthedocs.org/en/latest

Flask-WTF


Реализует привязку к WTForms — замечательной библиотеке для работы с формами. Опять же, налицо аналогия с Django. В коробке: солидный набор классов полей и валидаторов для них, наследование, вложенные формы и многое другое.

forms.py
from flask_wtf import Form
from wtforms import StringField, PasswordField, TextAreaField, SelectField
from wtforms.validators import Email, DataRequired, EqualTo

class LoginForm(Form):
    email = StringField('E-mail', validators=[Email(), DataRequired()])
    password = PasswordField('Пароль', validators=[DataRequired()])

class RegistrationForm(LoginForm):
    password_repeat = PasswordField('Повторите пароль', validators=[DataRequired(), EqualTo('password')])

Также есть расширение wtforms-alchemy для создания форм на основе моделей SQLAlchemy. Наткнулся на него совсем недавно, посему опыта работы пока нет. Впрочем, думаю, и здесь применима аналогия с Django.

Документация: flask-wtf.readthedocs.org/en/latest

Flask-Login


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

Использование Flask-Login
# app.py
# подключаем плагин
from flask.ext.login import LoginManager, current_user

# Инициализируем его и задаем действие "входа"
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

# Задаем обработчик, возвращающий пользователя по Id, либо None. Здесь пользователь запрашивается из базы.
@login_manager.user_loader
def load_user(userid):
    from models import User
    return User.query.get(int(userid))

# Задаем обработчик before_request, в котором добавляем к глобально-локальному контексту текущего пользователя  
@app.before_request
def before_request():
    g.user = current_user


# models.py
class User(db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(255))

    # Расширением предъявляются некоторые требования к классу User, а именно реализация следующих методов
    def is_authenticated():
        return True

    def is_active():
        return True

    def is_anonymous():
        return False

    def get_id(self):
        return str(self.id)

Документация: flask-login.readthedocs.org/en/latest

Flask-Bcrypt


Добавляет функционал для хеширования и проверки паролей.

models.py
from flask.ext.bcrypt import generate_password_hash, check_password_hash

class User(db.Model):
    ...
    def check_password(self, password):
        return check_password_hash(self.password, password)

    @staticmethod
    def hash_password(password):
        return generate_password_hash(password)

Flask-Assets


Дружит Flask с библиотекой webassets, позволяя невероятно изящно работать с ресурсами проекта. Умеет объединять ресурсы в пакеты, компилировать scss(sass) и less, минифицировать js и css и удобно подключать их в шаблонах.

Использование
# app.py
# Подключаем
from flask.ext.assets import Environment, Bundle

assets = Environment(app)

# Формируем и регистрируем пакеты
js = Bundle('jquery.js', 'jquery.file-upload.js', filters='jsmin', output='assets/jquery-min.js')
css = Bundle('main.css', 'form.css', 'flashes.css', filters='cssmin', output='assets/all-min.css')
assets.register('js_all', js)
assets.register('css_all', css)

# templates/index.html
{% assets "js_all" %}
    <script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
{% assets "css_all" %}
    <link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}

Указав в пакете(Bundle) параметр filters, мы заставим пропустить файлы пакета через заданный фильтр(ы). В нашем случае файлы минифицируются и объяденяться в один. Некоторые фильтры потребуют установки дополнительных python-модулей.

Если в конфигурации задан параметр ASSETS_DEBUG = True, то файлы пакетов не будут пропускаться через фильтры и склеиваться, а в шаблоне для каждого файла будет сгенерирован отдельный url.

P.S. При достаточно большом количестве ресурсов, следует вынести функционал по формированию пакетов(Bundle) и их регистрации в отдельный файл — например, assets.py.

Документация: flask-assets.readthedocs.org/en/latest

Flask-DebugToolbar


Какая разработка обойдется без удобного дебаггера? Расширение добавляет debug-панель, портированную из Django, с исчерпывающей информацией о запросе. Панель отображается при заданном в конфигурации параметре DEBUG = True.

app.py
# config.py
class Config(object):
    ...
    # Задаем токен для генерации cookie
    SECRET_KEY = 'xv3gavkxc04n3mzx7oksd6q'


# app.py
# Подключаем
from flask_debugtoolbar import DebugToolbarExtension

# Регистрируем
dtb = DebugToolbarExtension(app)

Документация: flask-debugtoolbar.readthedocs.org/en/latest

Вместо заключения


В статье представлены расширения, которые мне довелось использовать в своих проектах, однако это далеко не полный список того, что уже существует для Flask. Более исчерпывающий список актуальных расширений представлен на официальном сайте фреймворка по ссылке flask.pocoo.org/extensions.

Дополнения


Пользователь danSamara указал на проект скелета приложения на Flask, в котором включены многие из описанных расширений, а также кеширование Flask-Cache, тема на bootstrap3, а сам скелет оформлен в виде пакета python и использует blueprints. Весьма годная штука. ;-)
Александр @bIbI4k0
карма
8,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (20)

  • +7
    А можно подробнее по поводу нелаконичности, неоднозначности и ограничений Django? Складывается впечатление, что Вы в итоге то же самое собрали, только не такое надежное и поддерживаемое в целом.
    • +1
      Присоединяюсь к запросу, мне то же очень интересно узнать какие в Django есть ограничения, с которыми сложно смириться. И что же там монстроузного? Возможно, это будет поводом рассмотреть их внимательнее.
      • +2
        Попробуйте сделать на Django ORM JOIN с дополнительным условием, вида:

        SELECT a.author_name, b.book_name FROM authors AS a LEFT OUTER JOIN books AS b ON a.id = b.author_id AND b.language = 'French';
        

        Причем, еще желательно туда добавить туда какие-нибудь дочерние объекты select_related'ом дополнительным, чтобы не было желания использовать raw() для QuerySet'а (который вместо трех объектов Author с кучей книг в obj.books_set выдаст кучу объектов Author, каждый с одной книгой в отдельных полях).

        Или попобуйте инжектировать request.user в какое-нибудь кастомное поле в админке. Задолбаетесь наследоваться. Это в каждой форме, для каждой модели где есть это поле нужно будет придумывать какие-то миксины или копировать код.

        Или кастомные list_display'и для админки с абстрактной моделью сделать. Тоже адские миксины будут. Или переиспользование кода.

        Проблема джанги даже не в том, что она большая или перегруженная. А в том, что там очень тесные связи между элементами. Если нужно одно звено поменять/модифицировать — приходится переделывать/наследовать соседние по иерархии использования (а иногда не только соседние) элементы.

        Таким образом получаем дикую кашу различных сущностей с непроизносимыми именами на 20+ символов и зависящих друг от друга — наследующих или вызвающих друг друга, — или просто копи-паст разных частей кода. А с учетом того, что Python — нетипизированный язык, а Django еще и имеет кучу динамически создаваемых вещей (те же менеджеры моделей) с этим порой сложно справляться даже при помощи таких замечательных вещей, как PyCharm.

        А еще у Джанги нет нормальной* документации по всем методам/свойствам классов. Или хотя бы их списка.

        P.S. Также хочу заметить, что я не совсем согласен с автором статьи в том плане, что, на мой взгляд, не стоит из Flask'а (и ему подобных) делать Django с преферансом и куртизанками, копируя его структуру и принципы построения приложений, а пользуясь гибкостью различных компонентов и разумно сочетая их между собой, стоит пытаться строить приложения, которые наилучшим образом подходят именно под ту задачу, которая решается. Не стоит пытаться изобрести универсальное решение (== вторую Джангу), ни к чему хорошему это не приведет — рано или поздно появится та же проблема: новые инструменты или подходы, которые нужны для новых задач, не вписываются в концепцию монолитной платформы.

        P.P.S. Спасибо автору за библиотечку webassets, как-то я упустил ее из виду. Возможно, когда-нибудь пригодится.

        * — см. документацию Qt или стандартной библиотеки C++, или стандартных библиотек Go, или классов Android.
        • 0
          очень часто джанге приписывают сложности кастомизации админки, не приводя при этом сколь нибудь достойнного аналога
          • +1
            Я боюсь, что его и нет ;) Суть проста: хочешь быстро и просто — используй джангу, хочешь сложнее и кастомизабельнее — пиши сам/собирай из разных компонентов. Маленький сайт быстро и просто писать на джанге, когда пишешь большой и сложный — больше копаешься в исходниках джанги, чем пишешь свой вариант.
    • +5
      1. Django состоит из огромного количества модулей, хелперов, приложений. Уверен, кем-то это всё используется по-полной, но в случае несложных проектов монструозный функционал избыточен: если, скажем, нужно прикрутить аунтефикацию, то используется contib.auth, а там багажом получим в базе groups и permissions. Вроде и не мешают, а вроде никто и не приглашал.
      2. Django многие вещи позволяет сделать несколькими путями. Кому-то это по душе, но точно не мне. Пример? — render_to_respose, render, HttpResponse.
      3. Ограничения? На структуру проекта, на используемую модель юзера(да, я в курсе про изменения в 1.5), на ORM. Можно, конечно, и прикрутить, что надо, но вряд ли это будет проще, чем во Flask или Pyramid.
      Я ни в коем случае не противник Django, просто это не мой выбор. Может быть, я просто его не понял.
      • +1
        1 <...> Вроде и не мешают, а вроде никто и не приглашал.

        Я под термином «монстроузность» понимаю избыточный функционал который либо мешает, либо жрёт дополнительные ресурсы в недопустимых количествах. Приведённый вами пример, лично для меня не есть признак монстроузности. Лежат, кушать не просят. Вдруг понадобятся — ничего не нужно делать, только настроить. Не понадобятся? Ну и Бог с ними.
        2. Django многие вещи позволяет сделать несколькими путями. Кому-то это по душе, но точно не мне. Пример? — render_to_respose, render, HttpResponse.

        Приведённые вами функции не равнозначны, и служат как раз для гибкости и удобства. Нужно вернуть статус 200? return HttpResponse(200). Нужен просто обычный ответ? render_to_response(). Нужно что-то накрутить хитрое? render с кучей параметров. Я с одной стороны согласен, что это избыточно. Но с другой — не избыточный путь это оставить только HttpResponse, что вынудит любого из нас написать свою обёртку для удобства. Чтобы не передавать каждый раз кучу вещей вроде content_type, статус и т.д. Как по мне — гораздо удобнее использовать raise Http404 чем возвращать HttpResponse(status=404). Но соглашусь, что это дело вкуса. Хуже того, в наших проектах на Django используется собственный декоратор render_to, построенный на основе render, насколько я помню. И в 95% случаев мы работаем именно через него. Нам так удобнее.
        3. Ограничения? На структуру проекта

        Хм, ни разу не испытывал неудобства из-за структуры джанго-проектов. Но, возможно, мне просто не попадалась такая задача. Про юзеров — согласен, было не очень удобно и приходилось отчасти костылить. Однако решается всё довольно просто и очевидно. К сожалению что там после 1.5 сделали я знаю только из прочтения changelog-а. Старые проекты пока держим на 1.4, а в новых юзеры просты до тупости, так что на практике не щупал :(
        А с ограничениями ORM согласен. Если надо что-то сложнее чем примитив — бывает геморрой.
        Я ни в коем случае не противник Django, просто это не мой выбор. Может быть, я просто его не понял.

        Я спрашивал не для спора о том, что лучше. Исключительно с целью разобраться. Может быть я привык к каким-то вещам и просто их не замечаю? Критичный взгляд порой полезен.
  • 0
    А почему именно Flask-Migrate, а не alembic? Есть какие-то причины, или «просто так сложилось»?

    Отдельное спасибо за wtforms-alchemy, не знал про неё, а boilerplate формы клепать уже достало…

    Ещё с Фласком регулярно используют шаблонизатор Jinja2, но это наверняка и так всем известно =)
    • 0
      Если я правильно помню, flask-migrate — это и есть тонкая обертка над alembic.
    • 0
      Migrae — это тоже обертка вокруг Alembic, но выбор пал на неё именно благодаря наличию готовой команды для управления миграциями. Хотя, признаюсь, я уже и не помню, рассматривал ли альтернативы. :)
  • +3
    Первый раз читаю применительно к django о том, что обилие сторонних библиотек отпугивает. Так сходу и не смогу назвать категорию функционала, которую хорошо покрывают сразу множество подобных друг другу актуальных (это важно!) библиотек.
    • 0
      Насчет актуальности есть вопросы. Если попробовать, например, найти рабочую и простую библиотеку, скажем, для OpenID — их примерно 5-6 альтернатив и некоторые из них в новой версии джанги уже не работают.
      • 0
        Я это и имел ввиду. Если и имеется реально больше 2-3х аналогов, то работают на текущей версии django отсилы 1-2
    • +2
      Поддерживая проект на Django, я чаще не программирую, а ищу нужный функционал в батарейках. :) Была как-то необходимость добавить select2 в проект. После рассмотрения нескольких примочек для django, забил и сделал всё «вручную».
      • 0
        Я в этом вижу только плюсы: первый раз вы искали, второй раз просто используете. Просто не забывайте читать код этих самых батареек, тем более он в своем большинстве очень хорошего качества.
  • +2
    Чтобы получить описанный автором статьи скелет проекта, готовый к использованию, можно воспользоваться «печенько-резем»: github.com/sloria/cookiecutter-flask
    • 0
      Не знал об этом проекте. Спасибо!
      • 0
        Можно в пост добавить ;)
  • 0
    сложности кастомизации админки, не приводя при этом сколь нибудь достоенного аналога
    • 0
      прошу прощения, с приложения как-то коряво получилось отправить комментарий

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