Rels

Library for describing data relations in python (Enums, "relational" tables)


Keywords
enums
License
BSD-3-Clause
Install
pip install Rels==0.3.1

Documentation

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

Установка

pip install rels

Введение

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

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

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

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

Благодаря динамической природе 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_<имя столбца>, но оно может быть изменено при описании столбца.

Пример:

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(<имя атрибута>, <ссылка на соответствующий элемент перечисления>), который должен установить объекту атрибут с соответствующим значением.

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

Пример:

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)

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

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

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, покрыты тестами.