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: З'явилися 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. Хабр, ти чому не типографишь тексти? Ось, я навіть штуку написав!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.