Индексируемый

Определите атрибуты на ORM-сопоставленных классах, которые имеют атрибуты «index» для столбцов с типом Indexable.

«индекс» означает, что атрибут связан с элементом столбца Indexable с заранее определенным индексом для доступа к нему. К типам Indexable относятся такие типы, как ARRAY, JSON и HSTORE.

Расширение indexable предоставляет Column-подобный интерфейс для любого элемента Indexable типизированного столбца. В простых случаях оно может рассматриваться как Column - сопоставленный атрибут.

Синопсис

В качестве модели с первичным ключом и JSON-полем данных дается Person. Хотя в этом поле может быть закодировано любое количество элементов, мы хотели бы обращаться к элементу name отдельно, как к выделенному атрибуту, который ведет себя как отдельный столбец:

from sqlalchemy import Column, JSON, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.indexable import index_property

Base = declarative_base()

class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    data = Column(JSON)

    name = index_property('data', 'name')

Выше атрибут name теперь ведет себя как сопоставленный столбец. Мы можем составить новый Person и установить значение name:

>>> person = Person(name='Alchemist')

Теперь доступно значение:

>>> person.name
'Alchemist'

За кулисами JSON-поле было инициализировано новым пустым словарем, а поле было установлено:

>>> person.data
{"name": "Alchemist'}

Поле является изменяемым по месту:

>>> person.name = 'Renamed'
>>> person.name
'Renamed'
>>> person.data
{'name': 'Renamed'}

При использовании index_property изменение, которое мы вносим в индексируемую структуру, также автоматически отслеживается как история; нам больше не нужно использовать MutableDict для того, чтобы отследить это изменение для единицы работы.

Удаление также работает нормально:

>>> del person.name
>>> person.data
{}

Выше, удаление person.name удаляет значение из словаря, но не сам словарь.

Отсутствие ключа приводит к появлению AttributeError:

>>> person = Person()
>>> person.name
...
AttributeError: 'name'

Если не задано значение по умолчанию:

>>> class Person(Base):
>>>     __tablename__ = 'person'
>>>
>>>     id = Column(Integer, primary_key=True)
>>>     data = Column(JSON)
>>>
>>>     name = index_property('data', 'name', default=None)  # See default

>>> person = Person()
>>> print(person.name)
None

Атрибуты также доступны на уровне класса. Ниже показано использование Person.name для генерации индексированного SQL-критерия:

>>> from sqlalchemy.orm import Session
>>> session = Session()
>>> query = session.query(Person).filter(Person.name == 'Alchemist')

Приведенный выше запрос эквивалентен:

>>> query = session.query(Person).filter(Person.data['name'] == 'Alchemist')

Несколько объектов index_property могут быть соединены в цепочку для получения нескольких уровней индексации:

from sqlalchemy import Column, JSON, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.indexable import index_property

Base = declarative_base()

class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    data = Column(JSON)

    birthday = index_property('data', 'birthday')
    year = index_property('birthday', 'year')
    month = index_property('birthday', 'month')
    day = index_property('birthday', 'day')

Выше приведен запрос типа:

q = session.query(Person).filter(Person.year == '1980')

На внутреннем сервере PostgreSQL приведенный выше запрос будет выглядеть так:

SELECT person.id, person.data
FROM person
WHERE person.data -> %(data_1)s -> %(param_1)s = %(param_2)s

Значения по умолчанию

index_property включает специальное поведение для случая, когда индексируемая структура данных не существует, и вызывается операция set:

  • Для index_property, которому присвоено целочисленное значение индекса, структурой данных по умолчанию будет Python-список значений None, длина которого не меньше значения индекса; значение устанавливается на свое место в списке. Это означает, что при нулевом значении индекса список будет инициализирован до значения [None] перед установкой заданного значения, а при значении индекса, равном пяти, список будет инициализирован до значения [None, None, None, None, None] перед установкой пятого элемента в заданное значение. Обратите внимание, что существующий список не расширяется на месте для получения значения.

  • Для index_property, которому задано любое другое значение индекса (например, обычно строки), в качестве структуры данных по умолчанию используется словарь Python.

  • Структура данных по умолчанию может быть установлена в любой вызываемый элемент Python с помощью параметра index_property.datatype, что отменяет предыдущие правила.

Подклассификация

index_property может быть подклассифицирован, в частности, для такого распространенного случая, как обеспечение коэрцитивности значений или SQL-выражений при обращении к ним. Ниже приведен общий рецепт для использования с типом PostgreSQL JSON, где мы хотим также включить автоматическое приведение плюс astext():

class pg_json_property(index_property):
    def __init__(self, attr_name, index, cast_type):
        super(pg_json_property, self).__init__(attr_name, index)
        self.cast_type = cast_type

    def expr(self, model):
        expr = super(pg_json_property, self).expr(model)
        return expr.astext.cast(self.cast_type)

Приведенный подкласс может быть использован с версией JSON, специфичной для PostgreSQL:

from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects.postgresql import JSON

Base = declarative_base()

class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    data = Column(JSON)

    age = pg_json_property('data', 'age', Integer)

Атрибут age на уровне экземпляра работает как и раньше, однако при визуализации SQL для индексированного доступа будет использоваться оператор PostgreSQL ->>, а не обычный индексный оператор ->:

>>> query = session.query(Person).filter(Person.age < 20)

Приведенный выше запрос выдает:

SELECT person.id, person.data
FROM person
WHERE CAST(person.data ->> %(data_1)s AS INTEGER) < %(param_1)s

Справочник по API

Object Name Description

index_property

Генератор свойств. Сгенерированное свойство описывает атрибут объекта, соответствующий столбцу Indexable.

class sqlalchemy.ext.indexable.index_property

Генератор свойств. Сгенерированное свойство описывает атрибут объекта, соответствующий столбцу Indexable.

См.также

sqlalchemy.ext.indexable

Members

__init__()

Классическая подпись.

класс sqlalchemy.ext.indexable.index_property (sqlalchemy.ext.hybrid.hybrid_property)

method sqlalchemy.ext.indexable.index_property.__init__(attr_name, index, default=<object object>, datatype=None, mutable=True, onebased=True)

Создайте новый index_property.

Параметры:
  • attr_name – Имя атрибута типизированного столбца Indexable или другого атрибута, возвращающего индексируемую структуру.

  • index – Индекс, который будет использоваться для получения и установки данного значения. Это должно быть значение индекса на стороне Python для целых чисел.

  • default – Значение, которое будет возвращено вместо AttributeError в случае отсутствия значения по заданному индексу.

  • datatype – тип данных по умолчанию, используемый, когда поле пусто. По умолчанию он определяется типом используемого индекса: список Python для целочисленного индекса или словарь Python для любого другого типа индекса. Для списка список будет инициализирован списком значений None длиной не менее index элементов.

  • mutable – если False, то записи и удаления в атрибут будут запрещены.

  • onebased – предположить, что SQL-представление этого значения является однобазовым, т.е. первый индекс в SQL равен 1, а не нулю.

Вернуться на верх