Postgres / SQLAlchemy:没有匹配的唯一约束

时间:2018-06-07 16:47:08

标签: python postgresql flask-sqlalchemy

我正在开发一个系统,它需要在SQLalchemy中使用Postgres复合外键,并提出以下示例TestCase作为概念证明。我们的想法是,每个Thing必须引用ThingFeatureType的唯一组合,通过两列tt_id(事物类型ID)和feature(字符串)。 ThingTypeFeature也有自己的表格。

当我使用pytest运行下面的代码时,我收到以下错误,抱怨UniqueConstraint上没有(thing_feature_type.tt_id, feature)。但是,绝对有!

对此的任何帮助都非常感谢!

错误:

self = <sqlalchemy.dialects.postgresql.psycopg2.PGDialect_psycopg2 object at 0x7f4f61ee4320>, cursor = <cursor object at 0x7f4f61ee1808; closed: -1>
statement = '\nCREATE TABLE thing (\n\tt_id SERIAL NOT NULL, \n\ttt_id INTEGER, \n\tfeature VARCHAR(64), \n\tname VARCHAR(128) NOT...RY KEY (t_id), \n\tFOREIGN KEY(tt_id, feature) REFERENCES thing_feature_type (tt_id, feature) ON DELETE CASCADE\n)\n\n'
parameters = {}, context = <sqlalchemy.dialects.postgresql.psycopg2.PGExecutionContext_psycopg2 object at 0x7f4f5f4a91d0>

    def do_execute(self, cursor, statement, parameters, context=None):
>       cursor.execute(statement, parameters)
E       sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) there is no unique constraint matching given keys for referenced table "thing_feature_type"
E        [SQL: '\nCREATE TABLE thing (\n\tt_id SERIAL NOT NULL, \n\ttt_id INTEGER, \n\tfeature VARCHAR(64), \n\tname VARCHAR(128) NOT NULL, \n\tPRIMARY KEY (t_id), \n\tFOREIGN KEY(tt_id, feature) REFERENCES thing_feature_type (tt_id, feature) ON DELETE CASCADE\n)\n\n'] (Background on this error at: http://sqlalche.me/e/f405)

venv/lib/python3.5/site-packages/SQLAlchemy-1.2.7-py3.5-linux-x86_64.egg/sqlalchemy/engine/default.py:507: ProgrammingError

代码:

from unittest import TestCase

from sqlalchemy import (case,
                        Column,
                        Float,
                        ForeignKey,
                        Integer,
                        String,
                        Table,
                        Text, )
from sqlalchemy.orm import relationship
from sqlalchemy.schema import ForeignKeyConstraint, UniqueConstraint

from concept_back_end.run import app
from concept_back_end.database import db


def define_feature(model):
    class Feature(model):
        feature = Column(String(64), primary_key=True)

        @classmethod
        def _define_relationships(cls):
            cls.feature_types = relationship('FeatureType',
                                             back_populates='the_feature',
                                             cascade='save-update, delete',
                                             lazy='select')

    return Feature


def define_thing_type(model):
    class ThingType(model):
        tt_id = Column(Integer, primary_key=True, autoincrement=True)
        name = Column(String(128), nullable=False)

        @classmethod
        def _define_relationships(cls):
            cls.things = relationship('Thing',
                                      back_populates='thing_type',
                                      cascade='save-update, delete',
                                      lazy='select')

            cls.thing_feature_types = relationship(
                'ThingFeatureType',
                back_populates='thing_type',
                cascade='save-update, delete',
                lazy='select'
            )


    return ThingType



def define_thing_feature_type(model):
    class ThingFeatureType(model):
        __tablename__ = 'thing_feature_type'

        ft_id = Column(Integer, primary_key=True, autoincrement=True)
        feature = Column(String(64),
                         ForeignKey('feature.feature'))
        tt_id = Column(Integer, ForeignKey('thing_type.tt_id'))

        __table_args__ = (
            UniqueConstraint('tt_id', 'feature'),
        )

        @classmethod
        def _define_relationships(cls):
            cls.the_feature = relationship('Feature',
                                           back_populates='feature_types',
                                           lazy='select')

            cls.thing_type = relationship('ThingType',
                                          back_populates='feature_types',
                                          lazy='select')

            cls.things = relationship('Thing',
                                      back_populates='feature_type',
                                      lazy='select')

    return ThingFeatureType


def define_thing(model):
    class Thing(model):
        t_id = Column(Integer, primary_key=True, autoincrement=True)
        tt_id = Column(Integer)
        feature = Column(String(64))

        name = Column(String(128), nullable=False)

        __table_args__ = (
            ForeignKeyConstraint(
                ('tt_id', 'feature'),
                ('thing_feature_type.tt_id', 'thing_feature_type.feature'),
                ondelete='CASCADE'
            ),
            {},
        )

        @classmethod
        def _define_relationships(cls):
            cls.thing_type = relationship('ThingType',
                                          back_populates='things',
                                          lazy='select')

            cls.feature_type = relationship('ThingFeatureType',
                                            back_populates='things',
                                            lazy='select')

    return Thing


model_factories = [
    define_feature,
    define_thing_type,
    define_thing_feature_type,
    define_thing,
]
"""List of factory functions"""


class ForeignKeyExampleTestCase(TestCase):

    def setUp(self):
        with app.app_context():
            models = [m(db.Model) for m in model_factories]
            for m in models:
                m._define_relationships()

            db.create_all()
            db.session.commit()

    def test_can_connect_to_db(self):
        with app.app_context():
            db.session.execute('SELECT * FROM thing;')

    def tearDown(self):
        """And then tear them down again"""
        with app.app_context():
            db.session.close()
            db.drop_all()

1 个答案:

答案 0 :(得分:0)

这似乎是数据库中陈旧数据的问题。最终的工作代码如下。

from unittest import TestCase

from sqlalchemy import (case,
                        Column,
                        Float,
                        ForeignKey,
                        ForeignKeyConstraint,
                        Integer,
                        String,
                        Table,
                        Text,
                        UniqueConstraint, )
from sqlalchemy.orm import relationship
from sqlalchemy.schema import CreateTable

from concept_back_end.run import app
from concept_back_end.database import db


def define_feature(model):
    class Feature(model):
        feature = Column(String(64), primary_key=True, unique=True)

        @classmethod
        def _define_relationships(cls):
            cls.feature_types = relationship('FeatureType',
                                             back_populates='the_feature',
                                             cascade='save-update, delete',
                                             lazy='select')

    return Feature


def define_thing_type(model):
    class ThingType(model):
        tt_id = Column(Integer, primary_key=True, autoincrement=True)
        name = Column(String(128), nullable=False)

        @classmethod
        def _define_relationships(cls):
            cls.things = relationship('Thing',
                                      back_populates='thing_type',
                                      cascade='save-update, delete',
                                      lazy='select')

            cls.thing_feature_types = relationship(
                'ThingFeatureType',
                back_populates='thing_type',
                cascade='save-update, delete',
                lazy='select'
            )


    return ThingType



def define_thing_feature_type(model):
    class ThingFeatureType(model):
        __tablename__ = 'thing_feature_type'
        __table_args__ = (
            UniqueConstraint('tt_id', 'feature'),
            {},
        )

        ft_id = Column(Integer, primary_key=True, autoincrement=True)
        feature = Column(String(64),
                         ForeignKey('feature.feature'))
        tt_id = Column(Integer, ForeignKey('thing_type.tt_id'))

        @classmethod
        def _define_relationships(cls):
            cls.the_feature = relationship('Feature',
                                           back_populates='feature_types',
                                           lazy='select')

            cls.thing_type = relationship('ThingType',
                                          back_populates='feature_types',
                                          lazy='select')

            cls.things = relationship('Thing',
                                      back_populates='feature_type',
                                      lazy='select')

    return ThingFeatureType


def define_thing(model):
    class Thing(model):
        t_id = Column(Integer, primary_key=True, autoincrement=True)
        tt_id = Column(Integer)
        feature = Column(String(64))

        name = Column(String(128), nullable=False)

        __table_args__ = (
            ForeignKeyConstraint(
                ['tt_id', 'feature'],
                ['thing_feature_type.tt_id', 'thing_feature_type.feature'],
                ondelete='CASCADE'
            ),
            {},
        )

        @classmethod
        def _define_relationships(cls):
            cls.thing_type = relationship('ThingType',
                                          back_populates='things',
                                          lazy='select')

            cls.feature_type = relationship('ThingFeatureType',
                                            back_populates='things',
                                            lazy='select')

    return Thing


model_factories = [
    define_feature,
    define_thing_type,
    define_thing_feature_type,
    define_thing,
]
"""List of factory functions"""


class ForeignKeyExampleTestCase(TestCase):

    def setUp(self):
        with app.app_context():
            models = [m(db.Model) for m in model_factories]
            for i, m in enumerate(models):
                m._define_relationships()
                models[i] = m

            for m in models:
                print(CreateTable(m.__table__))

            db.create_all()
            db.session.commit()

    def test_can_connect_to_db(self):
        with app.app_context():
            db.session.execute('SELECT * FROM thing;')

    def tearDown(self):
        """And then tear them down again"""
        with app.app_context():
            db.session.close()
            db.drop_all()