Я работаю над системой, которая требует составного внешнего ключа в SQLalchemy с Postgres, и в качестве доказательства концепции придумала следующий пример TestCase
.Идея состоит в том, что каждый Thing
должен ссылаться на уникальную комбинацию ThingFeatureType
через два столбца tt_id
(идентификатор типа вещи) и feature
(строка).ThingType
и Feature
также имеют свои собственные таблицы.
Когда я запускаю приведенный ниже код с помощью 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()