SqlAlchemy: массив пользовательских типов Postgresql - PullRequest
5 голосов
/ 01 марта 2012

Так что в моей базе данных postgres у меня есть следующий пользовательский тип:

create type my_pg_type as (  
    sting_id varchar(32),
    time_diff interval,
    multiplier integer
);

Чтобы еще больше усложнить ситуацию, он используется в качестве массива:

alter table my_table add column my_keys my_pg_type [];

Я бы хотелчтобы сопоставить это с SQLAlchemy (0.6.4) !!

(извинения за эликсир)

from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.types import Enum
from elixir import Entity, Field    

class MyTable(Entity):
    # -- snip --
    my_keys = Field(ARRAY(Enum))

Я знаю, что 'Enum' неверен в приведенном выше.

ДляПример значения, возвращаемого из базы данных для этого столбца массива, я показал ниже значения в ARRAY.result_processor(self, dialect, coltype):

class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
    # -- snip --  
    def result_processor(self, dialect, coltype):
        item_proc = self.item_type.result_processor(dialect, coltype)
        if item_proc:
            def convert_item(item):
                if isinstance(item, list):
                    return [convert_item(child) for child in item]
                else:
                    return item_proc(item)
        else:
            def convert_item(item):
                if isinstance(item, list):
                    return [convert_item(child) for child in item]
                else:
                    return item
        def process(value):
            if value is None:
                return value
            """
            # sample value:
             >>> value
            '{"(key_1,07:23:00,0)","(key_2,01:00:00,20)"}'
            """
            return [convert_item(item) for item in value]
        return process

Так что вышеприведенная функция process неправильно разбивает строку, предполагая, что онауже список.

До сих пор я успешно разделил подклассы ARRAY для правильного разбиения строки, и вместо Enum я попытался написать свой собственный тип (реализующий Unicode) для воссоздания кортежа (string, timedelta, integer), но столкнулись с множеством трудностей, в частности, правильное преобразование interval в Python timedelta.

Я публикую здесь на случай, если мне не хватает очевидного прецедентного способа сделать это

Ответы [ 2 ]

6 голосов
/ 03 марта 2012

ОБНОВЛЕНИЕ См. Рецепт внизу для обхода проблемы

Я разработал некоторый пример кода, чтобы увидеть, что здесь делает psycopg2, и это хорошо в их сфере - psycopg2 не такинтерпретировать значение как массив вообще.psycopg2 должен иметь возможность анализировать ARRAY, когда он возвращается, поскольку тип ARRAY в SQLA предполагает, что, по крайней мере, многое было сделано.Вы, конечно, можете взломать ARRAY в SQLAlchemy, что в данном случае означало бы, что он вообще не используется в пользу чего-то, что анализирует это конкретное строковое значение, которое psycopg2 возвращает нам.и даже не используют механизм psycopg2 для преобразования таймдельт, что обычно не нужно беспокоить SQLAlchemy.В этом случае я чувствую, что возможности DBAPI используются недостаточно, а psycopg2 - очень способный DBAPI.

Поэтому я бы посоветовал вам поработать с механикой нестандартного типа psycopg2 на http://initd.org/psycopg/docs/extensions.html#database-types-casting-functions.

Если вы хотите отправить по почте список рассылки , вот тестовый пример:

import psycopg2

conn = psycopg2.connect(host="localhost", database="test", user="scott", password="tiger")
cursor = conn.cursor()
cursor.execute("""
create type my_pg_type as (  
    string_id varchar(32),
    time_diff interval,
    multiplier integer
)
""")

cursor.execute("""
    CREATE TABLE my_table (
        data my_pg_type[]
    )
""")

cursor.execute("insert into my_table (data) "
            "values (CAST(%(data)s AS my_pg_type[]))", 
            {'data':[("xyz", "'1 day 01:00:00'", 5), ("pqr", "'1 day 01:00:00'", 5)]})

cursor.execute("SELECT * from my_table")
row = cursor.fetchone()
assert isinstance(row[0], (tuple, list)), repr(row[0])

Регистрация типа PG поддерживает глобальную регистрацию.Вы также можете зарегистрировать типы для каждого соединения в SQLAlchemy, используя приемник пула в 0,6 или событие подключения в 0,7 и далее.

ОБНОВЛЕНИЕ - из-за https://bitbucket.org/zzzeek/sqlalchemy/issue/3467/array-of-enums-does-not-allow-assigning Я, вероятно, пока рекомендую людям использовать этот тип обходного пути, пока psycopg2 не добавит дополнительную встроенную поддержку для этого:

class ArrayOfEnum(ARRAY):

    def bind_expression(self, bindvalue):
        return sa.cast(bindvalue, self)

    def result_processor(self, dialect, coltype):
        super_rp = super(ArrayOfEnum, self).result_processor(dialect, coltype)

        def handle_raw_string(value):
            inner = re.match(r"^{(.*)}$", value).group(1)
            return inner.split(",")

        def process(value):
            return super_rp(handle_raw_string(value))
        return process
4 голосов
/ 01 февраля 2016

Оформление документации sqlalchemy_utils :

CompositeType provides means to interact with
`PostgreSQL composite types`_. Currently this type features:

* Easy attribute access to composite type fields
* Supports SQLAlchemy TypeDecorator types
* Ability to include composite types as part of PostgreSQL arrays
* Type creation and dropping

Использование:

from collections import OrderedDict

import sqlalchemy as sa
from sqlalchemy_utils import Composite, CurrencyType


class Account(Base):
    __tablename__ = 'account'
    id = sa.Column(sa.Integer, primary_key=True)
    balance = sa.Column(
        CompositeType(
            'money_type',
            [
                sa.Column('currency', CurrencyType),
                sa.Column('amount', sa.Integer)
            ]
        )
    )

Массив композитов:

from sqlalchemy_utils import CompositeArray


class Account(Base):
    __tablename__ = 'account'
    id = sa.Column(sa.Integer, primary_key=True)
    balances = sa.Column(
        CompositeArray(
            CompositeType(
                'money_type',
                [
                    sa.Column('currency', CurrencyType),
                    sa.Column('amount', sa.Integer)
                ]
            )
        )
    )
...