What the flask?

    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. Хабр, ты почему не типографишь тексты? Вот, я даже штуку написал!

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 66

      +1
      Там даже на одном из пайконов парень рассказывал, почему flask — можно использовать для себя, но для продакшена — это настоящее мучение.
        0
        Ну что вы так сразу =) ИХМО Фласк самое то для микросервисов. У меня на флаксе написано одно довольно простое API
          0

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

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

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

                0
                Вполне. Участвовал в написании не самого маленького проекта на нём в середине 2016. Даже понравилось.
          –4
          Если уж хочешь поизвращаться пиши на Go
          + один из 100500 новомодных мини-фреймворков (например gin).

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

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

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

              +1
              Например?
                0
                image

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

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

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

                      +1

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


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

                +2
                select_related/prefetch_related не используете?
                И запросы, которые делает ORM, надо представлять ещё во время написания кода, а не как-нибудь потом об этом узнавать.
                  +1
                  Согласен. Поэтому сейчас я пишу код на Go, а запросы на чистом SQL
                    +10
                    И в итоге у вас получится собственный ORM.
                  +3

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


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

                    +1

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

                  +1

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


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


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

                    0
                    Я использую просто pytest. А что вам дает это расширение pytest-flask? Там, на мой взгляд, добавлено только 2-3 fixture-объекта, которые пишутся достаточно быстро.
                      0
                      Нууу, мне не надо писать эти 2-3 fixture-объекта самому в каждом проекте с фласком?
                        0
                        Зачем в каждом? Один раз conftest.py написал и всюду где надо юзаешь
                          0
                          По одному разу в каждом проекте…
                      0

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

                      +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

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

                          0
                          А скажите, пожалуйста, зачем вам flask_restful?
                          Я вот обнаружил, что для простых случаев он не нужен, а для сложных — flask_potion удивителен и неповторим.
                          Ну и лично мне peewee ORM кажется намного более удобным при написании.
                        • НЛО прилетело и опубликовало эту надпись здесь
                            0
                            Можно и в джанго одним файлом http://blog.simonwillison.net/post/57956850422/djng
                              +1
                              если нужна простая страница в вебе, то вам и Flask/Django не нужны. Фреймворки же не просто так фпеймворками называются — у них есть предназначение. И если под предназначением понимается создание пусть даже небольшого API из нескольких методов, то я предпочту не держать все в одном файле.
                                –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)
                                
                                  0

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


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

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

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

                                    0

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

                                      0

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

                                        0

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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


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


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

                                                  0
                                                  используйте докер контейнеры для создания окружения и будет все одинаковое для прода и дева
                                                0
                                                По поводу вашей штуки для типографики: коль уж подразумевается поддержка кириллицы, то и кавычки надо кошерные ставить: «».
                                                0
                                                ребят, так а что есть достойного для написания json rest api на python? без фронта, без куков, хтмл, javascript и всего такого. голый rest api.
                                                  +2
                                                  Попробуйте Falcon
                                                    0
                                                    Cornice хорошая штука. Это набор хелперов для построения REST на базе Pyramid.
                                                    0
                                                    Flask в большом проекте эта попаболь. Имеется пример перед глазами.
                                                      0
                                                      На фласке удобно готовить API и делать различные наброски. Все остальное — жуть.
                                                        0
                                                        В Flask и Django мне не нравится концепции для расширения приложения (blueprints, apps). Подход Pyramid более лаконичен, где не выходят за рамки Pyrhon пакетов. А с выходом отличного фреймворка Websauna (посути расширения Pyramid) мне кажется что больше ничего и не нужно для счастья :)
                                                          0
                                                          У кого-нибудь есть опыт работы с Sanic? Он позиционируется как близкий фласку по духу, но при этом асинхронный.
                                                            0
                                                            Ну фиг знает. У меня такие проблемы были в первом проекте на фласке, потом как-то пристрелялся в ногу.
                                                            Местами приходится использовать прокси (в аду должен быть отдельный thread-locals-котёл), бывают проблемы с перекрёстными импортами, дурацкий init_app(). И когда наконец получается хорошо структурированное приложение, оно удивительным образом напоминает джангу.

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

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

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

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

                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                0

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

                                                                  +1

                                                                  Я работал с несколькими огромными проектами на Django. Никаких проблем, затыки начинаются вовсе не из-за ограничений Django, а из-за производительности БД, организации данных и эффективности алгоритмов.


                                                                  У меня наверное предвзятое мнение, т.к. в основном я уже много лет использую Django, а другие фреймворки либо недолго щупал, либо использовал эпизодически. Но Django удивительно подходит для проектов любого масштаба. С моей точки зрения.

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

                                                                Самое читаемое