13g10n
На главную

Dependency Injector для Flask — параметры эндпоинтов

3 минуты

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

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

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

@app.route('/<lang>/<page>')
def get_page(lang: str, page: str) -> str:
    return pages.render(page)

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

Terminal
app\main.py:21:14: W0613: Unused argument 'name' (unused-argument)

Конечно, можно добавить комментарий с игнор-инструкцией для линтера, ну а что если таких мест сотня? И не кажется ли вам, что это именно тот случай, когда можно и нужно бороться с источником проблемы, а не с последствиями?

Если вы знакомы с модулями стандартной библиотеки python или когда-либо делали что-нибудь завязанное на тайп хинтах, то наверняка слышали про модуль inspect. Так вот в нём есть одна замечательная вещь — inspect.signature, которая позволяет нам заглянуть в объект функции и узнать её параметры — да ещё и с аннотациями типов!

Более того, если вы настоящий python разработчик, то вы ещё и про декораторы одним ухом слышали на какой-то конференции или в офисе у кулера. Постоянно ведь говорят про эти декораторы, неужели такая сильная вещь? 😉

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

def filter_params(func):
    signature = inspect.signature(func)

    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **{
            k: v for k, v in kwargs.items()
            if k in signature.parameters
        })

    return wrapper

Ну и используем этот декоратор в нужном нам месте. Обратите внимание на порядок декораторов, он важен.

@app.route('/<lang>/<page>')
@filter_params
def get_page(page: str) -> str:
    return pages.render(page)

Тот же код, но без неиспользуемого параметра. Элегантно? А если ваше приложение использует кастомный route декоратор, то зная как развернуть @filter_params в filter_params(), вы можете расширить его логику и применить фильтрацию system-wide, как говорится.

@route('/<lang>/<page>')
def get_page(page: str) -> str:
    return pages.render(page)

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

И не смотрите на меня так, рабочая неделя же в разгаре! Ну когда мне было настроить подсветку синтаксиса в блоге...

PythonFlask