Наблюдая за горячими спорами, я решил в качестве эксперимента сделать свой велосипед, добавив в него несколько фич, появление которых в официальной реализации было маловероятно.
На текущий момент эксперименты закончены, библиотека хорошо показала себя в моих проектах, поэтому я решил поделиться ей с сообществом.
В большинстве случаев, когда мы описываем отношение вида <имя, значение>, у нас имеется много информации, которую желательно привязать к имени: вспомогательный текст для пользовательского интерфейса, связи с родственными перечислениями, ссылки на другие объекты или функции. Приходится городить дополнительные структуры данных, что не есть хорошо — лишние сущности как-никак.
Поэтому, вдохновившись реляционной моделью данных, я решил отказаться от реализации перечислений в виде бинарных отношений и расширить их до полноценной таблицы.
Заодно добавил:
- наследование;
- несколько вспомогательных методов и проверок;
- построение индексов по всем столбцам таблицы;
- формирование обратных ссылок в связанных друг с другом отношениях;
В итоге получилось вот такая вещь (примеры решил не дробить, чтобы не увеличивать и так длинное «полотно»):
######################## # Базовое использование ######################## from rels import Column, Relation # Enum и EnumWithText уже объявлены в библиотеке # и доступны как rels.Enum и rels.EnumWithText # тут их объявления привидены для упрощения понимания class Enum(Relation): # объявляем абстраткное перечисление name = Column(primary=True) # столбец с именами value = Column(external=True) # столбец со значениями # наследование — добавляем дополнительный столбец для какого-нибудь текста # например, для использования в пользовательском интерфейсе class EnumWithText(Enum): text = Column() class SOME_CONSTANTS(Enum): # объявляем конкретное перечисление records = ( ('NAME_1', 1), # и указываем данные для него ('NAME_2', 2)) class SOME_CONSTANTS_WITH_TEXT(EnumWithText): # ещё одно конкретное перечисление records = ( ('NAME_1', 1, 'constant 1'), ('NAME_2', 2, 'constant 2')) # Работаем с перечислениями # доступ к данным SOME_CONSTANTS.NAME_1.name == 'NAME_1' # True SOME_CONSTANTS.NAME_1.value == 1 # True # получение элемента перечисления из «сырых» данных SOME_CONSTANTS(1) == SOME_CONSTANTS.NAME_1 # True # сравнения SOME_CONSTANTS.NAME_2 == SOME_CONSTANTS.NAME_2 # True SOME_CONSTANTS.NAME_2 != SOME_CONSTANTS.NAME_1 # True # теперь для проверок не надо всюду тягать импорты перечисления SOME_CONSTANTS.NAME_2.is_NAME_1 # False SOME_CONSTANTS.NAME_2.is_NAME_2 # True # каждый элемент перечисления — отдельный объект, # поэтому даже объекты с одинаковыми данными равны не будут SOME_CONSTANTS.NAME_2 != SOME_CONSTANTS_WITH_TEXT.NAME_2 # True SOME_CONSTANTS.NAME_1 != SOME_CONSTANTS_WITH_TEXT.NAME_1 # True # наследование — добавляем новые элементы class EXTENDED_CONSTANTS(SOME_CONSTANTS_WITH_TEXT): # расширяем набор данных в перечислении records = ( ('NAME_3', 3, 'constant 3'), ) # добавляем ещё одно значение ######################## # Индексы ######################## class ENUM(Relation): name = Column(primary=True) # для этого столбца имя индекса будет .index_name value = Column(external=True) # для этого столбца имя индекса будет .index_value text = Column(unique=False, index_name='by_key') # указываем своё имя для индекса records = ( ('NAME_1', 0, 'key_1'), ('NAME_2', 1, 'key_2'), ('NAME_3', 2, 'key_2'), ) # если данные в столбце уникальны, значением в словаре будет элемент перечисления ENUM.index_name # {'NAME_1': ENUM.NAME_1, 'NAME_2': ENUM.NAME_2, 'NAME_3': ENUM.NAME_3} # если данные в столбце не уникальны, значением в словаре будет список элементов перечисления ENUM.by_key # {'key_1': [ENUM.NAME_1], 'key_2': [ENUM.NAME_2, ENUM.NAME_3]} ######################## # Обратные ссылки ######################## # объявляем отношение, на которое будем ссылаться class DESTINATION_ENUM(Relation): name = Column(primary=True) val = Column() records = ( ('STATE_1', 'value_1'), ('STATE_2', 'value_2') ) # объявляем отношение, которое будет ссылаться class SOURCE_ENUM(Relation): name = Column(primary=True) val = Column() rel = Column(related_name='rel_source') records = ( ('STATE_1', 'value_1', DESTINATION_ENUM.STATE_1), ('STATE_2', 'value_2', DESTINATION_ENUM.STATE_2) ) # проверяем работу ссылок DESTINATION_ENUM.STATE_1.rel_source == SOURCE_ENUM.STATE_1 # True DESTINATION_ENUM.STATE_2 == SOURCE_ENUM.STATE_2.rel # True
Отдельно замечу, что не обязательно перечисления объявлять в коде. Во многих случаях, удобнее ограничиться объявлением модели данных, а сами данные грузить из сторонних источников, например, электронных таблиц.
Репозиторий и подробная документация на github
P.S. библиотека разрабатывалась в расчёте на Python 2.7, с третьим не проверялась.
ссылка на оригинал статьи http://habrahabr.ru/post/208066/
Добавить комментарий