django-siteprefs — API важнее
Небольшой рассказ о том, как и нужно ли искать компромисс между совестью питониста — Дзеном Питона — и удобством ПИП (API).
Однажды, где-то в районе августа 2013 года, мне на ум пришла идея очередного приложения для Django. Идея заключалась в том, чтобы дать пользователям возможность управлять настройками приложения (обычно они расположены в settings.py этого самого приложения) через административный интерфейс Django.
В обязательном порядке, перед тем как создавать что-то новое, требуется проверить, нет ли уже готового решения.
Тот же Django Packages на гора выдаёт сразу несколько вариантов. Проблема с этими вариантами, если я правильно помню, заключалась в том, что практически все они предлагали определять настройки в терминах своего микроязыка, например при помощи классов. Но у меня уже есть мой settings.py, в котором прекрасно живут псевдоконстанты настроек. Как бы так изловчиться, чтобы их и использовать, ведь именно их использует уже написанный ранее код? В общем, как вы уже догадались, существующие решения не подошли, нужно было подумать самому.
Разработка идеи началась с изучения возможности использования в settings.py некой функции-регистратора, которая могла бы указывать на имена [в пространстве имён файла], хранящие настройки. Хотелось, чтобы функция регистрации использовала не строки с именами, а сами имена, а частности, чтобы IDE могли понять, что откуда.
Примерно так:
Итак: store_values получает не имена, а значения, ну, а нам нужно отследить, какое значение с каким именем связано. На ум пришло несколько вариантов решений, с которыми я и отправился на StackOverflow, дабы узнать, как решали бы подобную задачу другие люди.
Как видите, в первом же комментарии мне рассказали о том, что это бессмысленная задача, которую никогда никому решать не придётся. Ну, и вариант решения предложили соответствующий — используй строки с именами и никаких гвоздей. И ещё напугали, что все другие подходы от лукавого (завязаны на чёрную магию и против Дзена), угнетают пользователей, не соответствуют модели данных Питона и такие хрупкие, что вот-вот сломаются. А дальше и вовсе закрыли вопрос с резолюцией: «Вопрос должен демонстрировать хотя бы минимальное понимание решаемой задачи». Пусть так %P
В общем, ответ-то я тогда получил, но зудеть от этого не перестало. Более того, задача расширилась, теперь нужно было, чтобы решение было не слишком хрупким, не заставляло пользователей удивляться, и умело скрывало, что использует чёрную магию %)
И решение нашлось, и оно оформилось в приложение django-siteprefs. Выглядит решение примерно так:
Как это всё работает? Ни дорожной пыли, ни болотной гнили, ни чёрных, ни белых заклинаний. В основе лежит использование инструментов из модуля
Пользователь, вызывая
Чтобы узнать к какому имени какое значение привязано, нам требуется пройти по именам локального контекста и отыскать объект идентичный переданному в функцию-регистратор. Первое, что приходит на ум, использовать
Позже
Что получилось в итоге: чёрная магия, которую нам сулили, оказалась не такой уж чёрной; пользователю, смею надеяться, удивляться не приходится; работает приложение, вроде, стабильно. Но главное в нашем деле — ПИП (API), который похоже, не вызывает у людей отторжения (примерно 2 тысячи скачиваний с PyPI в месяц).
Вот так и вышло, что компромисс между Дзеном и красотой API искать не пришлось, ибо сказано в первых же строках писания: «Красивое лучше безобразного». Не поспоришь.
Иногда полезно идти наперекор.
Удачи вам.
P.S.: Вот, что ещё вспомнил. Слыхал, некоторые интересуются взлетит ли проект pythonz.net, а если взлетит, то когда. Отвечаю: мы тут про пресмыкающихся говорим. Ползём дальше.
В обязательном порядке, перед тем как создавать что-то новое, требуется проверить, нет ли уже готового решения.
Тот же Django Packages на гора выдаёт сразу несколько вариантов. Проблема с этими вариантами, если я правильно помню, заключалась в том, что практически все они предлагали определять настройки в терминах своего микроязыка, например при помощи классов. Но у меня уже есть мой settings.py, в котором прекрасно живут псевдоконстанты настроек. Как бы так изловчиться, чтобы их и использовать, ведь именно их использует уже написанный ранее код? В общем, как вы уже догадались, существующие решения не подошли, нужно было подумать самому.
Разработка идеи началась с изучения возможности использования в settings.py некой функции-регистратора, которая могла бы указывать на имена [в пространстве имён файла], хранящие настройки. Хотелось, чтобы функция регистрации использовала не строки с именами, а сами имена, а частности, чтобы IDE могли понять, что откуда.
Примерно так:
A = True
B = True
C = False
D = 42
store_values(A, B, C, D)
Итак: store_values получает не имена, а значения, ну, а нам нужно отследить, какое значение с каким именем связано. На ум пришло несколько вариантов решений, с которыми я и отправился на StackOverflow, дабы узнать, как решали бы подобную задачу другие люди.
Как видите, в первом же комментарии мне рассказали о том, что это бессмысленная задача, которую никогда никому решать не придётся. Ну, и вариант решения предложили соответствующий — используй строки с именами и никаких гвоздей. И ещё напугали, что все другие подходы от лукавого (завязаны на чёрную магию и против Дзена), угнетают пользователей, не соответствуют модели данных Питона и такие хрупкие, что вот-вот сломаются. А дальше и вовсе закрыли вопрос с резолюцией: «Вопрос должен демонстрировать хотя бы минимальное понимание решаемой задачи». Пусть так %P
В общем, ответ-то я тогда получил, но зудеть от этого не перестало. Более того, задача расширилась, теперь нужно было, чтобы решение было не слишком хрупким, не заставляло пользователей удивляться, и умело скрывало, что использует чёрную магию %)
И решение нашлось, и оно оформилось в приложение django-siteprefs. Выглядит решение примерно так:
# Внутри settings.py вашего приложения.
... # Здесь уже объявлены ранее наши псевдоконстанты настроек.
if 'siteprefs' in settings.INSTALLED_APPS: # Уважаем право людей не использовать siteprefs.
from siteprefs.toolbox import patch_locals, register_prefs, pref, pref_group
# Чтобы пользователи не удивлялись, почему некоторые локальные переменные
# содержат странные прокси-объекты, заставляем их патчить переменные вручную.
patch_locals()
register_prefs( # Регистрируем настройки.
# Например, зарегистрируем группу настроек, относящихся к работе с эл. почтой.
# И разрешим их редактирование в адм. интерфейсе (static=False)
pref_group('Почтовые настройки', (ENABLE_MAIL_RECOVERY, ENABLE_MAIL_BOMBS), static=False),
SLOGAN, # Эта настройка останется неизменяемой, просто выведется в адм. интерфейсе.
# Ну, и ещё одна не статичная настройка, с добавлением описаний.
pref(ENABLE_GRAVATAR_SUPPORT,
verbose_name='Использовать Gravatar',
static=False,
help_text='Разрешает использование аватаров с сервиса Gravatar.'),
)
Как это всё работает? Ни дорожной пыли, ни болотной гнили, ни чёрных, ни белых заклинаний. В основе лежит использование инструментов из модуля
inspect
.Пользователь, вызывая
patch_locals
, собственноручно соглашается на внесение изменений в локальный контекст. Какого рода изменения и зачем они нужны?Чтобы узнать к какому имени какое значение привязано, нам требуется пройти по именам локального контекста и отыскать объект идентичный переданному в функцию-регистратор. Первое, что приходит на ум, использовать
id
объекта, но тут на нашем пути встаёт сам интерпретатор, кеширующий некоторые значения (булево, мелкие целые и пр.). Так, если вернуться к первому примеру, невозможно определить какая True
попала в функцию — из A
или из B
. Поэтому нам остаётся только заменить все объекты в нужном контексте на наши собственные PatchedLocal, имеющие заведомо разные идентификаторы. Добраться до локальных символов в нужном фрейме нам позволяет тот самый inspect
.Позже
register_prefs
бережно вернёт на свои места те объекты, которые не будут считаться настройками приложения, а пока она замещает каждую PatchedLocal, зарегистрированную как настройку приложения, на объект PrefProxy. Именно с PrefProxy, имитирующим оригинальное значение, мы и будем работать в дальнейшем как из кода, так и из административной части Django.Что получилось в итоге: чёрная магия, которую нам сулили, оказалась не такой уж чёрной; пользователю, смею надеяться, удивляться не приходится; работает приложение, вроде, стабильно. Но главное в нашем деле — ПИП (API), который похоже, не вызывает у людей отторжения (примерно 2 тысячи скачиваний с PyPI в месяц).
Вот так и вышло, что компромисс между Дзеном и красотой API искать не пришлось, ибо сказано в первых же строках писания: «Красивое лучше безобразного». Не поспоришь.
Иногда полезно идти наперекор.
Удачи вам.
P.S.: Вот, что ещё вспомнил. Слыхал, некоторые интересуются взлетит ли проект pythonz.net, а если взлетит, то когда. Отвечаю: мы тут про пресмыкающихся говорим. Ползём дальше.
Категории
Язык
Окружение
Область
Интерпретатор
Проект
Уровень
Аспект языка
На заметку
У нас есть новостная группа в Telegram. Там же можно обсудить интересующие вопросы. Ссылка в самом низу страницы.