Dependency Injector для Flask — параметры эндпоинтов
Несколько дней назад мне в голову пришла интересная идея, которую я хотел протестировать, но работа и блог заставили меня на время отложить написание прототипа. Сегодня же я выкроил пару минут и сделал простенькую версию, которая отлично отражает весь концепт.
Все идеи я разделил на несколько заметок, чтобы материал было легко читать, а найти нужное по названию проще. Весь приведенный код писался и тестировался левой пяткой на коленке и предназначен исключительно для вдохновения и объяснения идей.
Первую часть серии начну издалека. Те, кто работает с фласком в средних и больших проектах, наверняка сталкивались с неприятной проблемой неиспользуемых параметров в вьюхах, когда у нас есть несколько параметров в урле, но часть из них либо не используется вовсе, либо утилизируются где-то на уровне промежуточных слоёв:
@app.route('/<lang>/<page>')
def get_page(lang: str, page: str) -> str:
return pages.render(page)
В итоге, когда дело доходит до условного линтера, мы получаем абсолютно ненужный нам артефакт, который не только снижает наш условный code quality, так ещё и не даёт нам спать, ведь мы ощущаем как эта странная неиспользуемая переменная дышит нам в затылок по ночам, а днём путает приходящих к нам на проект молодых разработчиков.
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)
Более того, мы можем пойти дальше и добавлять в вызов метода любые другие параметры, но это мы разберём детально уже во второй части.
И не смотрите на меня так, рабочая неделя же в разгаре! Ну когда мне было настроить подсветку синтаксиса в блоге...