kdpv
Вообще-то, это картинка от wtforms, но у меня гимп почему-то не запускается.


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


Однажды, меня спросили: что плохого во flask? Тогда меня полностью устраивал этот милый фреймворк. Поработав с ним какое-то время, я написал все, что думаю, в рабочий слак, на что мне ответили: "Мурад, будь добрее". Вообще, я добрый и пушистый, но wtf?!


Стоит отметить, что я являюсь большим поклонником работы Армина. Его пакеты используются во многих мои проектах. А еще он невероятная заноза в сообществе Python. И это хорошо.


Flask


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


Если попытаться описать все проблемы фласка в двух пунктах:


  1. импорты
  2. контекст реквеста

Все. Дальше можно не читать. Но если все еще не понятно, листаем дальше.


Blueprints


Если в джанге все приложения подключаются в INSTALLED_APPS, то во фласке используется концепция blueprints. Этакое приложение и обособленный неймспейс урлов в одном флаконе:


from flask import Flask
from yourapplication.simple_page import simple_page

app = Flask(__name__)
app.register_blueprint(simple_page, end_point='/simple_page')

Далее, можно роутить урлы так: url_for('simple_page.index').


Вложенность


А ее нет. Нельзя просто так взять и сделать вложенный неймспейс. В сети можно найти "решения", но здесь я буду рассматривать только коробочный фласк, потому что написать-то можно все.


Импорты


Когда вы делаете так в продакшен коде:


if foo == 3:
    do_stuff(foo)

Где-то в мире грущу я! Берегите меня, выносите это в сеттинги.


from myapp import app

class Foo(FlaskForm):
    choices = SelectField(choices=app.conf.FOO_CHOICES)

Концептуально. Но работать не будет. Потому что пару строчек назад мы импортировали пакет в myapp и навечно заказали себе путь туда.


Погодите, должен быть выход! Ага!


from flask import current_app

И это не работает. Потому что current_app доступен только в контектсте реквеста!


— Вынеси это наконец в файл сеттингов приложения, больной ублюдок! — голос из зала.
— А как я буду их подменять на проде или тестовом, а?


Кстати, для джанги на этот случай у меня есть специальная батарейка.
Просто представьте себе прекрасный дивный мир, где django.conf.settings доступен только в контексте реквеста!


flask.g


Нельзя не пошутить про одноименную точку. А главное, ее не нужно искать, она всегда тут: flask.g. Бада-бум-тсс!
Вот поэтому Армин — мой кумир!


В нее можно пробросить все необходимое:


@app.before_request
def before_request():
    g.locale = get_locale()
    g.foo = foo

Однако, это будет работать только в контексте реквеста, как и любые другие магические объекты фласка.


Роутинг урлов и их методы


У меня на сайте есть такой кусок урлов:


bp.add_url_rule('/api/v1/', view_func=ApiView.as_view('api_view'))
...
bp.add_url_rule('/<path:path>/', view_func=PageView.as_view('page_view'))

ApiView обрабатывает только один метод — POST. Угадайте, что будет если спросить GET? Ага, 404. Ее обеспечивает вторая вьюшка.
Чтобы получить NOT ALLOWED, нужно явно вернуть 405 в ApiView!


Fask, что с тобой не так?


Стейк!


А. Погодите. Это мне. Омн-омн-омн.


flask-wtf. CSRF


Ох. Допустим, нам нужно отключить проверку в одной вьюхе:


@app.route('/foo', methods=('GET', 'POST'))
@csrf.exempt
def my_handler():
    # ...
    return 'ok'

Значит, нам нужен app. Помните про импорты, да? Ищем выход, лезем в сорцы:


def exempt(self, view):
    ...
    if isinstance(view, string_types):
        view_location = view
    else:
        view_location = '.'.join((view.__module__, view.__name__))

    self._exempt_views.add(view_location)
    return view

Ура! Можно передать путь до вьюхи (в версии, которая вышла две недели назад)! Пробуем:


csrf.exempt('website.apps.typus_web.views.ApiView')

Не работает. На самом деле (ненавижу это выражение), мы подменили имя вьюхи, когда вызывали ApiView.as_view('api_view'):


csrf.exempt('website.apps.typus_web.views.api_view')

И "все равно", что мы указываем путь до объекта, которого нет. Работает! Не работает.


А знаете почему? Потому что форма. Она ничегошеньки не знает про вьюху:


class ApiForm(ViewForm):
    ...
    class Meta(ViewForm.Meta):
        csrf = False

Вот теперь работает.


url_for()


Допустим, вы хотите сделать так:


NAVIGATION = (
    (url_for('flatpages:index'), _('Home page')),
)

Забудьте. Вне контекста не работает. Наверное, можно сделать свой ленивый объект, в конце-концов, в джанге это тоже не сразу появилось.


flask-testing


Штука, призванная помочь с тестами. Например, можно заглянуть в контекст, который передается в шаблон. А давайте попробуем:


AssertionError: Popped wrong request context.

Ой. Что-то пошло не так. А знаете что? Я вот тоже не знаю.
На самом деле (ненавижу это выражение), я схватил NotImplementedError в одном из методов, которые не переопределил. Но поинт в том, что уронив тесты, вам ни за что не понять в чем причина.


Всякое разное


В процессе ковыряния фласка, нашел несколько моментов:


def jsonify(*args, **kwargs):
    ...
    if args and kwargs:
        raise TypeError('jsonify() behavior undefined when passed both args and kwargs')
    elif len(args) == 1:  # single args are passed directly to dumps()
        data = args[0]
    else:
        data = args or kwargs

Здесь что-то происходит. Это все, что я понимаю.


И это:


def make_response(*args):
    if not args:
        return current_app.response_class()
    if len(args) == 1:
        args = args[0]
    return current_app.make_response(args)

А теперь ягодки:


class Flask(_PackageBoundObject):
    def make_response(self, rv):
        status_or_headers = headers = None
        if isinstance(rv, tuple):
            rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))

Все, жгите!


P.S. А чего я один эээ статьи пишу, м? Кто хочет на той неделе, скажем, во вторник (чтобы больше вместить) пойти в бар (в Питере, в районе Звездной)?.. Пишите в инбокс.


P.P.S. Хабр, ты почему не типографишь тексты? Вот, я даже штуку написал!

Поделиться с друзьями
-->

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


  1. Terras
    25.01.2017 03:35
    +1

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


    1. saintbyte
      25.01.2017 11:37

      Ну что вы так сразу =) ИХМО Фласк самое то для микросервисов. У меня на флаксе написано одно довольно простое API


      1. madkite
        25.01.2017 15:21

        В 2017-м можно уже и aiohttp юзать для этих целей, если API реально простое.


        1. EvilsInterrupt
          25.01.2017 15:22

          А он уже готов к production?


          1. madkite
            25.01.2017 18:45
            +1

            А что с ним не так? Стабильная версия больше 1.0.0 имеется, в официальной документации рекомендации по использованию в продакшене есть, т.е. такое использование подразумевается. Я знаю несколько компаний, где он используется в продакшене. Причем в одной из них к этому приложил руку я, причем сфера услуг — финансы, т.е. требования к стабильности высокие. Потому мне очень интересно узнать, чем он не готов к продакшену?


          1. Miketsukami
            25.01.2017 18:57

            Вполне. Участвовал в написании не самого маленького проекта на нём в середине 2016. Даже понравилось.


  1. Duke565
    25.01.2017 04:05
    -4

    Если уж хочешь поизвращаться пиши на Go
    + один из 100500 новомодных мини-фреймворков (например gin).

    А нет? — используй Django. Как бы странно это не звучало…

    Нарывался я уже на «чудо-минималистичный» код на flask'e.
    Когда чувак думал, что нужна всего одна вьюха, а в итоге…


    1. evocatus
      25.01.2017 08:41

      После того как я увидел какие запросы к базе делает Django ORM я больше не могу его использовать.


      1. kalombo
        25.01.2017 09:29
        +1

        Например?


        1. evocatus
          25.01.2017 10:04

          image


          1. renskiy
            25.01.2017 10:15
            +6

            такое впечатление, что на слайде пропущено слово WHERE.

            Данный пример явно выдран из контекста. Скорее всего это второй запрос и двух после применения refetch_related — что на самом деле является как раз оптимизацией. Альтернатива такому запросу — запрос каждой сущности из table (точнее из другой, которая заранее читается при помощи prefetch_related) по одному.


            1. pacahon
              26.01.2017 14:28

              Пропущено, вы правы. Это из статьи "Производительность запросов в PostgreSQL". В статье такие оптимизации ORM предлагают ускорить кастованием списка id в ResultSet с помощью конструкции VALUES (или можно ждать N лет, пока postgres станет ещё умнее). Но вот от проблемы передачи этих самых id по сети это не избавляет. Почему-то есть уверенность, что такую задачу эффективно и универсально никто не умеет решать. И по-моему тут уже становится ясно, что ORM не так и плох, а надо искать компромисс (как обычно)


              1. immaculate
                26.01.2017 15:48
                +1

                Есть интуитивное ощущение, что в некоторых случаях prefetch_related можно было бы сделать более эффективным. Но в код не лазил, не знаю, насколько это сложно.


                Честно говоря, не понимаю наездов на Django ORM и ORM вообще. Единственная часто встречающаяся ситуация, в которой он генерирует кривые запросы — это prefetch_related. Автор той презентации нашел самую удачную цель для критики и не предложил никакой альтернативы. А она существует?


      1. OnYourLips
        25.01.2017 09:51
        +2

        select_related/prefetch_related не используете?
        И запросы, которые делает ORM, надо представлять ещё во время написания кода, а не как-нибудь потом об этом узнавать.


        1. evocatus
          25.01.2017 10:06
          +1

          Согласен. Поэтому сейчас я пишу код на Go, а запросы на чистом SQL


          1. kalombo
            25.01.2017 10:28
            +10

            И в итоге у вас получится собственный ORM.


      1. m1kola
        25.01.2017 16:23
        +3

        Запросы пишет не Django ORM, а человек. Django ORM — это инструмент. Если человек не понимает/не знает как работает инструмент — это проблема человека, а не инструмента.


        Если с Django ORM действительно что-то не так — у человека есть возможность исправить инструмент.


      1. immaculate
        25.01.2017 20:29
        +1

        А какой ORM лучше?
        Я много занимался оптимизацией запросов Django и ничего криминального не видел. Плохой код на Python приводит к генерации плохого SQL. А разве где-то бывает иначе?


  1. vasachi
    25.01.2017 08:53
    +1

    Flask в купе с json-schema отлично подходит для голых API серверов с отдельным фронтом или же вообще без него.


    Тестирую через pytest-flask и наслаждаюсь.


    Проблема с роутами неплохо так решается без проблем через @blueprint.route('/url', methods=["POST"])


    1. EvilsInterrupt
      25.01.2017 15:03

      Я использую просто pytest. А что вам дает это расширение pytest-flask? Там, на мой взгляд, добавлено только 2-3 fixture-объекта, которые пишутся достаточно быстро.


      1. vasachi
        25.01.2017 15:08

        Нууу, мне не надо писать эти 2-3 fixture-объекта самому в каждом проекте с фласком?


        1. EvilsInterrupt
          25.01.2017 15:26

          Зачем в каждом? Один раз conftest.py написал и всюду где надо юзаешь


          1. vasachi
            25.01.2017 15:28

            По одному разу в каждом проекте…


    1. magic4x
      26.01.2017 12:27

      Эти методы у меня определены во вью. Докиньте после нее роутинг любого урла, аля флатпейджс, вот тогда будет готча.


  1. skywalk7
    25.01.2017 09:29
    +1

    choices в формах заполнять можно так:

    form = EditFooForm(obj=foo_obj)
    form.foo.choices = current_app.config['FOO_CHOICES']
    

    С остальными проблемами не сталкивался, видимо пока везет :)
    Использую flask_login, flask_restful, flask_wtf и flask_sqlalchemy — все работает вполне неплохо, включая тестирование через test_client.


    1. magic4x
      25.01.2017 09:30
      +1

      Так можно и на инит повесить. Только это не спортивно совсем.


    1. Spiritschaser
      25.01.2017 17:11

      А скажите, пожалуйста, зачем вам flask_restful?
      Я вот обнаружил, что для простых случаев он не нужен, а для сложных — flask_potion удивителен и неповторим.
      Ну и лично мне peewee ORM кажется намного более удобным при написании.


  1. etho0
    25.01.2017 09:29
    +6

    Куча всякой критики про flask, но при этом никто не говорит о том что если мне надо простая страница в вебе, то для джанго нужно создавать целый проект с кучей файлов внутри, а для flask всего один модуль…


    1. kalombo
      25.01.2017 10:00

      Можно и в джанго одним файлом http://blog.simonwillison.net/post/57956850422/djng


    1. renskiy
      25.01.2017 10:08
      +1

      если нужна простая страница в вебе, то вам и Flask/Django не нужны. Фреймворки же не просто так фпеймворками называются — у них есть предназначение. И если под предназначением понимается создание пусть даже небольшого API из нескольких методов, то я предпочту не держать все в одном файле.


    1. nitrous321
      25.01.2017 11:29
      -1

      Необязательно создавать целый проект:

      app.py

      from django.conf import settings
      from django.conf.urls import url
      from django.http import HttpResponse
      
      settings.configure(
          ROOT_URLCONF=__name__,
      )
      
      
      def index(request):
          return HttpResponse("Hello, world !")
      
      urlpatterns = [
          url(r'^$', index)
      ]
      
      
      if __name__ == '__main__':
          
          from django.core.servers.basehttp import run
          from django.core.wsgi import get_wsgi_application
      
          app = get_wsgi_application()
      
          run('127.0.0.1', 8888, app)
      


    1. magic4x
      26.01.2017 01:36

      Так фласк не позиционирует себя как "одностраничный сайт". Та же система blueprints это про getting bigger — когда проект разбивается на модули. Только работает это все очень плохо.


      А так да, я тоже делал сайт с одной страничкой. Так всегда начинается.


  1. dotZero
    25.01.2017 10:10

    P.P.S. Хабр, ты почему не типографишь тексты?

    Потому что для языка на котором написан хабр нет вменяемого типографа :(


    1. magic4x
      25.01.2017 10:34

      Ммм. У typus есть веб-апи на этот случай. А на чем он написан-то, пхп?


      1. dotZero
        25.01.2017 10:43

        Нее, на внешнее апи мы не можем полагаться. Но может еще что-то придумаем.


        1. magic4x
          25.01.2017 11:36

          Можно запускать из консоли. Но я понимаю, хочется нативное решение.


  1. ffsdmad
    25.01.2017 11:36
    -4

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

    ps: и да, flask нуждается в популяризации, но только адекватными методами, чтобы не превратить его в очередную кучу джанга


    1. magic4x
      25.01.2017 12:36
      +1

      Я, к сожалению, по фене не ботаю, но могу конструктивно ответить на конструктивный вопрос, если, конечно же, у вас он есть.


      У фласка серьезные архитектурные ограничения. Популяризируй или нет, они никуда не денутся.


  1. hardtop
    25.01.2017 12:42

    Отличный текст! Едкий, с толковыми примерами! А что используете в повседневной жизни? Лично я давно подсел на Джангу и невзирая на её недостатки, не вижу ей замену даже в условиях новомодных Ангуляров. Или плохо смотрю?

    За typus — отдельное спасибо!


    1. kivsiak
      25.01.2017 13:28

      Я боюсь спросить а как новомодный ангуляр может составить конкуренцию джанге?

      Еще в повседневных условиях понравился aiohttp — но он скорее альтернатива фласку чем джанге. Очень хорош для асинхронных апи.


      1. hardtop
        25.01.2017 14:11

        Ангуляр тут выступает как сферический buzzword в вакууме. Не более.


      1. alexgrom
        25.01.2017 18:57

        Еще в повседневных условиях понравился aiohttp — но он скорее альтернатива фласку чем джанге

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


        1. kivsiak
          25.01.2017 19:38

          Почему нет? Авторы не скрывали что хотели сделать нечто подобное фласку но на евентлупе и без его архитектурных недостатков.


    1. magic4x
      25.01.2017 14:00
      +2

      Спасибо, на здоровье.
      Я работаю с джангой. В мире python аналогов, в общем-то, нет. До недавнего времени я был уверен, что flask способен составить конкуренцию. А теперь все.


  1. Spiritschaser
    25.01.2017 16:49
    +1

    class Foo(FlaskForm):
    choices = SelectField(choices=app.conf.FOO_CHOICES)

    Мне кажется, какой-то синтетический случай. Такbе параметры у меня, если и захардкожены, то лежат вместе с моделями в models.py


    1. magic4x
      25.01.2017 19:03

      1. пишем приложение, которое работает с пабликами в соц. сетях. На проде один список (настоящих), на тестовом — тестовый.
      2. загрузка картинок: в проде один путь, на деве другой (локальный).
      3. дефолтный имел для отсылки чего-либо: на проде один — ну вы поняли.

      Занесите это в сеттинги и бед знать не будете.


      1. Spiritschaser
        25.01.2017 21:43

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


        1. magic4x
          26.01.2017 01:28

          Посмотрите вот тут. Не знаю как по-другому объяснить. Если коротко: парсить файлы — это вообще атас. Вот, как выглядит файл конфига моего сайта на фласке. Видите, как легко читается?


          В первую очередь: наследование. Общий класс конфига и далее вариации для раличных кейсов. Это такая частная реализация пакета django-configurations, только на фласке. Забавно, что фласк это умеет сам, а джанга — нет.


          Теперь отметьте, что присутствуют настройки для отдельных приложений (формы, бейбл и т.д.). Это тоже привычная практика: приложение определяет внутри пакета конфигурацию по-умолчанию. А в мастер-конфигурации переопределяются специфичные для проекта настройки. Тоже своеобразное наследование.


      1. misant
        26.01.2017 00:59

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


  1. 3gee
    25.01.2017 18:57

    По поводу вашей штуки для типографики: коль уж подразумевается поддержка кириллицы, то и кавычки надо кошерные ставить: «».


    1. magic4x
      25.01.2017 18:59

      Релевантный пост на хабре.
      Демо с "елочками".
      Пример в ридми.


  1. turbo_exe
    25.01.2017 23:06

    ребят, так а что есть достойного для написания json rest api на python? без фронта, без куков, хтмл, javascript и всего такого. голый rest api.


    1. random1st
      25.01.2017 23:18
      +2

      Попробуйте Falcon


    1. Infernion
      26.01.2017 13:47

      Cornice хорошая штука. Это набор хелперов для построения REST на базе Pyramid.


  1. random1st
    25.01.2017 23:17

    Flask в большом проекте эта попаболь. Имеется пример перед глазами.


  1. l0rda
    26.01.2017 07:49

    На фласке удобно готовить API и делать различные наброски. Все остальное — жуть.


  1. Infernion
    26.01.2017 13:48

    В Flask и Django мне не нравится концепции для расширения приложения (blueprints, apps). Подход Pyramid более лаконичен, где не выходят за рамки Pyrhon пакетов. А с выходом отличного фреймворка Websauna (посути расширения Pyramid) мне кажется что больше ничего и не нужно для счастья :)


  1. pilosus
    27.01.2017 19:07

    У кого-нибудь есть опыт работы с Sanic? Он позиционируется как близкий фласку по духу, но при этом асинхронный.


  1. JTG
    30.01.2017 18:27

    Ну фиг знает. У меня такие проблемы были в первом проекте на фласке, потом как-то пристрелялся в ногу.
    Местами приходится использовать прокси (в аду должен быть отдельный thread-locals-котёл), бывают проблемы с перекрёстными импортами, дурацкий init_app(). И когда наконец получается хорошо структурированное приложение, оно удивительным образом напоминает джангу.

    И ещё одно: не следует рассматривать блюпринты как аналог Django apps. Это просто способ чуть удобнее структурировать код, если проект начал пухнуть. Flask extensions больше похожи, например, flask-debugtoolbar.


    1. magic4x
      30.01.2017 18:31
      +1

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


      1. EvilsInterrupt
        31.01.2017 11:39

        А что скажете по поводу комментария от pilosus?


        1. magic4x
          31.01.2017 13:05

          Я не работал с асинхронными фреймворками на продакшене. Вернее, не работал настолько плотно, чтобы поймать какие-либо грабли. Торнадо — прекрасен, но сейчас по планете шагает py 3.6 и там тоже много вкусного. А про sanic я узнал из того комментария.


  1. AbstractGaze
    31.01.2017 13:03

    Понимаю что следующий вопрос скорее для тостера, а не хабра, но в данной статье все же можно получить более компетентный ответ (я надеюсь).
    Я новичок и можно сказать буквально на дня сидел перебирал различные материалы и выступления по джанге и фласку. Сложилось мнение что джанга подходит на средненькие проекты, а фласк на маленькие, или наоборот так называемый продакшен. Тут же все на оборот.
    Так как вы используете и то, и другое, собственно вопрос от меня и других читающих не определившихся — стоит ли тратить время на ковыряние фласка с заделом на будущее (продакшен) или лучше начинать изучение сразу с джанги? А то в статье вроде как все не в пользу фласка, но так же упоминаете что продолжаете его использовать.


    1. magic4x
      31.01.2017 13:12

      В статье собраны грабли, она не призвана отговорить использовать фласк.
      Джанга подходит и для очень крупных проектов. Некоторые ее части можно бы сделать лучше, некоторые так и делают. Однако, есть крайне важный момент, который окупает все ее недостатки: у джанги очень большое комьюнити, очень много батареек, очень много вкусного появляется от релиза к релизу, и, самое главное, она развивается по принципу совместимости. Я уверен, что могу взять средний проект для 0.9 и за пару часов прикрутить туда 1.10. А разница по времени релизов огромная.
      Фласк я использую и буду ) Потому что люблю экспериментировать. В нем, кстати, некоторые штуки реализованы по-лучше, чем в джанге.