##############################
Rels — перечисления для Python
##############################

*********
Установка
*********

::

   pip install rels

********
Введение
********

Rels позволяет создавать сложные перечисляемые типы, со следующими свойствами:

* каждый элемент перечисления — отдельный объект со всеми необходимыми методами;
* наследование перечислений;
* любое количество дополнительных данных, привязанных к элементу перечисления;
* автоматическое создание индексов по данным;
* дополнительные проверки данных;
* формирование обратных ссылок в связанных друг с другом перечислениях (и других объектах);

Общая идея заключается в использовании реляционной модели данных. При описании нового типа описываются поля отношения (столбцы таблицы) и непосредственно сами данные (в виде таблицы).

За счёт наследования можно заранее объявить необходимые общие (абстрактные) типы, а конкретные перечисления уже наследовать от них.

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

Пример использования:

.. code:: python

   from rels import Column, Relation

   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))


   # Варианты работы с элементами перечислений
   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_2                 # True


   class SOME_CONSTANTS_WITH_TEXT(EnumWithText):
       records = ( ('NAME_1', 1, 'constant 1'),
                   ('NAME_2', 2, 'constant 2'))


   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'), )  # добавляем ещё одно значение


*********************
Описание перечисления
*********************

При описании столбцов таблицы, можно указать их свойства:

* name — ``string`` — имя столбца (по умолчанию равно имени, которому присваивается объект столбца);
* unique — ``boolean`` — значения в столбце должны быть уникальными, иначе будет брошено исключение (по умолчанию ``True``);
* primary — ``boolean`` — элементы перечисления будут доступны как атрибуты перечисления с именами, указанными в этом столбце (например, ``ENUM.COLUMN_VALUE``) (по умолчанию ``False``);
* external — ``boolean`` — элемент перечисления можно будет получить по значению этого столбца, передав его в конструктор перечисления (например, ``ENUM(some_value)``) (по умолчанию ``False``);
* single_type — ``boolean`` — все значения в столбце должны быть одного типа (по умолчанию ``True``);
* index_name — ``string`` — имя, по которому будет доступен индекс с ключам из этого столбца (по умолчанию равен ``index_<имя столбца>``), см. подробнее в описании индексов;
* related_name — ``string`` | ``None`` — имя, по которому элемент перечисления будет доступен в объектах, на которые ссылается этот столбец (по умолчанию ``None``), см. подробнее в описании связывания.

При создании перечисления:

* Каждая строка данных в таблице превратится в элемент перечисления.
* В классе перечисления будут установлен атрибут, ссылающиеся на соответствующий элемент перечисления. Имя атрибута будет установлено в значение в столбце, отмеченном как ``primary``.
* Если ``primary`` столбцов больше 1, то будут установлены атрибуты для каждого.


*****************
Атрибуты и методы
*****************

Атрибуты перечисления:

* ``.<имя из primary столбца>`` — ссылка на элемент перечисления (создаётся для всех значений из primary столбцов);
* ``.records`` — список всех элементов перечисления в порядке их объявления в «сырых» данных;
* ``.<имя индекса>``— индексы всех столбцов (по умолчанию ``index_<имя столбца>``);
* ``.__call__`` — принимает значение из столбца с external установленным в ``True``, возвращает элемент перечисления, которому оно соответствует;
* ``.select(*<список имён столбцов>)`` — возвращает таблицу с выборкой данных по указанным столбцам;
* ``.get_from_name(<полное имя элемента перечисления>)`` — принимает строку с именем конкретного элемента перечисления (например, ``"ENUM.NAME"``) и возвращает соответствующий элемент перечисления или бросает исключение.

Атрибуты элемента перечисления:

* ``.<имя столбца>`` — получение данных для соответствующего столбца;
* ``.is_<имя из primary столбца>`` — возвращает ``True``, если

*******
Индексы
*******

Для каждого столбца таблицы формируются индексы с ключами, равными данным в этом столбце, и значениями, равными элементам перечисления (если значения в столбце помечены как уникальные) или спискам элементов перечисления (если значения в столбце не уникальны).

По умолчанию индекс доступен как атрибут перечисления с именем ``index_<имя столбца>``, но оно может быть изменено при описании столбца.

Пример:

.. code:: python

   from rels import Column, Relation

   class ENUM(Relation):
       name = Column(primary=True)
       value = Column(external=True)
       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]}

************
Наследование
************

Наследование позволяет расширять как количество столбцов, так и добавлять новые элементы перечисления (дополняя таблицу данных).

**Добавлять столбцы можно только если в родительском классе не была указана таблица данных.**

Пример наследования можно видеть в самом первом листинге.


**********
Связывание
**********

Если для одного из столбцов указано ``related_name``, то во время создания перечисления, для каждого объекта из этого столбца будет вызван метод ``set_related_name(<имя атрибута>, <ссылка на соответствующий элемент перечисления>)``, который должен установить объекту атрибут с соответствующим значением.

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

Пример:

.. code:: python

   from rels import Column, Relation

   class DESTINATION_ENUM(Relation):
       name = Column(primary=True)
       val_1 = Column()

       records = ( ('STATE_1', 'value_1'),
                   ('STATE_2', 'value_2') )

   class SOURCE_ENUM(Relation):
       name = Column(primary=True)
       val_1 = 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

*********************************
Взаимодействие со сторонним кодом
*********************************

Для взаимодействия с кодом, использующим другую реализацию перечислений, можно использовать значения из столбца с установленным в True параметром ``external``. Для восстановления объекта элемента перечисления, достаточно передать это значение в конструктор перечисления.

Пример использования можно найти в самом первом листинге (``SOME_CONSTANTS(1) == SOME_CONSTANTS.NAME_1``)

## Использование библиотеки

Все необходимые объекты вынесены в корень модуля:

.. code:: python

   import rels

   # Базовые классы
   rels.Column # класс столбца
   rels.Record # класс элемента перечисления (обычно использовать нет необходимости)
   rels.Relation  # базовый клас перечисления

   # Простые перечисления
   rels.Enum         # простое перечисление со столбцами name и value
   rels.EnumWithText # простое перечисление с дополнительным столбцом text

   # Прочее
   rels.NullObject   # объект заглушка для отсутствующих значений в external столбцах
   rels.exceptions   # модуль с исключениями

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

******
Django
******

В модуле ``rels.django`` реализован небольшой функционал для взаимодействия с Django.

==========
DjangoEnum
==========

Перечисление, унаследованное от EnumWithText, дополнительные методы:

* choices — возвращает список ``[<элемент перечисления, текс>, …]``

====================
RelationIntegerField
====================

Наследник ``models.IntegerField``, автоматически конвертирует друг в друга целочисленные значения из базы и элементы перечисления.

Конструктор принимает следующие параметры (кроме стандартных для ``IntegerField``):

* relation — объект отношения
* relation_column — имя столбца, который сохраняется в базу (по умолчанию, равен ``"value"``)

=================
Django Migrations
=================

Django нормально воспринимает ``RelationIntegerField``, но в случае указания параметра ``default``, понадобится править код миграции, так как django ничего о rels не знает.

=====
Тесты
=====

Все «фичи», за исключением связанных с Django, покрыты тестами.
