Как преобразовать значения в пользовательский тип в SQLAlchemy с Postgres

В приложении, которое я создаю, я использовал составной тип пользовательских типов для хранения данных. Это в основном список простых объектов типа:

bin = CompositeType(columns=[Column('label', String()), Column('head', Float()), Column('tail', Float())], name='bin')

Это встроено в мой класс таким образом, как:

class Myclass(db.Model):
   id = sa.Column('id', sa.Integer(), nullable=False)
   bin = sa.Column('bin', CompositeArray(CompositeType('bin', [Column('label', String()), Column('head', Float()), Column('tail', Float())])))

Затем я пытаюсь:

   id = '111331'
   bins ={'1':{'start':0,'end':1},'2':{'start':1,'end':2}}
   myclass = Myclass(id = int(id), bins = [(bin_key, bins[bin_key]['start'], bins[bin_key]['end']) for bin_key in bins])
   # The object creation works flawlessly.
   db.session.add(myclass)
   db.session.commit()
   # Error happens in SQLAlchemy commit

Внезапно я получаю эту ошибку:

sqlalchemy.exc.ProgrammingError: (psycopg2.errors.DatatypeMismatch)
column "bin" is of type bin[] but expression is of type record[] (...)
^ HINT:  You will need to rewrite or cast the expression.

Но я не могу найти, как сделать это литье.

Должен ли я создать класс для описания этого типа, используемого в postgres, а затем создать его экземпляр в понимании списка? Если да, то приведите примеры.

Кстати, раньше это работало. Перестал работать, когда я мигрировал с помощью flask db migrate. Однако структура базы данных точно такая же.


person Tiago Duque    schedule 07.10.2020    source источник


Ответы (1)


У меня была такая же проблема однажды. В первую очередь могу порекомендовать обновить Flask, Flask-SQLAlchemy и сопутствующие пакеты. Flask==1.1.2, Flask-SQLAlchemy==2.4.0, SQLAlchemy==1.3.18, SQLAlchemy-Utils==0.36.6 работают нормально:

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgres://postgres:postgres@localhost:5432/test'
db = SQLAlchemy(app)


class Myclass(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    custom_type = db.Column(
       'custom_type',
       CompositeArray(
           CompositeType(
                'custom_type',
                [
                    db.Column('id', db.Text),
                    db.Column('name', db.Text),
                ]
           )
       )
    )


db.create_all()
db.engine.execute("""
    DO $$
    BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'custom_type') THEN
        CREATE TYPE custom_type AS (
            id text,
            name text
        );
    END IF;
    END$$;
""")


@app.route('/')
def test():
    db.session.add(Myclass(custom_type=[
        {'id': 'test_1', 'name': 'first'},
        {'id': 'test_2', 'name': 'second'},
    ]))

    db.session.commit()
    return 'done'

Но несколько лет назад я решил проблему с помощью sqlalchemy_utils.register_composites(). Это было примерно так:

def create_app():
    app = Flask(__name__)
    # blablabla
    engine = db.get_engine()
    engine.dispose()
    register_composites(engine.connect())
    return app

Также вы можете попробовать psycopg2.extras.register_composite.

person Danila Ganchar    schedule 09.10.2020
comment
Спасибо за отзыв Данила. Я сам нашел решение, создав пользовательский класс и реализовав bind_expression и result_processor, но это очень ручное и подверженное ошибкам решение. Я мог бы попытаться использовать ваше второе решение позже, используя register_composites позже. Когда у меня будет отзыв, я либо приму ответ, либо поддержу его, но полезно оставить его здесь для других с той же проблемой. - person Tiago Duque; 09.10.2020