I\'m trying to build an example with several related models, like the following. We have a model B with a 1:n relation with a model C; then we have a model A with a n:1 relation
The issue here is that you have to declare the ForeignKeyConstraint
in __table_args__
, not in the body of the class.
In other words, the following code will NOT apply the foreign key constraint to the child table ...
from sqlalchemy import (
create_engine,
Column,
Integer,
text,
ForeignKeyConstraint,
String,
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
connection_uri = (
r"mssql+pyodbc://@.\SQLEXPRESS/myDb?driver=ODBC+Driver+17+for+SQL+Server"
)
engine = create_engine(connection_uri, echo=True,)
with engine.connect() as conn:
for tb_name in ["tbl_child", "tbl_parent"]:
conn.execute(text(f"DROP TABLE IF EXISTS [{tb_name}]"))
Base = declarative_base()
class Parent(Base):
__tablename__ = "tbl_parent"
id1 = Column(Integer, primary_key=True)
id2 = Column(Integer, primary_key=True)
parent_name = Column(String(50))
children = relationship("Child", back_populates="parent")
def __repr__(self):
return f"<Parent(id1={self.id1}, id2={self.id2}), parent_name='{self.parent_name}'>"
class Child(Base):
__tablename__ = "tbl_child"
id = Column(Integer, primary_key=True, autoincrement=False)
child_name = Column(String(50))
parent_id1 = Column(Integer)
parent_id2 = Column(Integer)
ForeignKeyConstraint(
["parent_id1", "parent_id2"], ["tbl_parent.id1", "tbl_parent.id2"]
)
parent = relationship(
"Parent",
foreign_keys="[Child.parent_id1, Child.parent_id2]",
back_populates="children",
)
def __repr__(self):
return f"<Child(id={self.id}, child_name={self.child_name})>"
Base.metadata.create_all(engine)
""" console output:
2020-04-30 06:57:13,899 INFO sqlalchemy.engine.Engine
CREATE TABLE tbl_parent (
id1 INTEGER NOT NULL,
id2 INTEGER NOT NULL,
parent_name VARCHAR(50),
PRIMARY KEY (id1, id2)
)
2020-04-30 06:57:13,899 INFO sqlalchemy.engine.Engine ()
2020-04-30 06:57:13,900 INFO sqlalchemy.engine.Engine COMMIT
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine
CREATE TABLE tbl_child (
id INTEGER NOT NULL,
child_name VARCHAR(50),
parent_id1 INTEGER,
parent_id2 INTEGER,
PRIMARY KEY (id)
)
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine ()
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine COMMIT
"""
... but this will ...
from sqlalchemy import (
create_engine,
Column,
Integer,
text,
ForeignKeyConstraint,
String,
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
connection_uri = (
r"mssql+pyodbc://@.\SQLEXPRESS/myDb?driver=ODBC+Driver+17+for+SQL+Server"
)
# connection_uri = "sqlite:///:memory:"
engine = create_engine(connection_uri, echo=True,)
with engine.connect() as conn:
for tb_name in ["tbl_child", "tbl_parent"]:
conn.execute(text(f"DROP TABLE IF EXISTS [{tb_name}]"))
Base = declarative_base()
class Parent(Base):
__tablename__ = "tbl_parent"
id1 = Column(Integer, primary_key=True)
id2 = Column(Integer, primary_key=True)
parent_name = Column(String(50))
children = relationship("Child", back_populates="parent")
def __repr__(self):
return f"<Parent(id1={self.id1}, id2={self.id2}), parent_name='{self.parent_name}'>"
class Child(Base):
__tablename__ = "tbl_child"
__table_args__ = (
ForeignKeyConstraint(
["parent_id1", "parent_id2"], ["tbl_parent.id1", "tbl_parent.id2"]
),
)
id = Column(Integer, primary_key=True, autoincrement=False)
child_name = Column(String(50))
parent_id1 = Column(Integer)
parent_id2 = Column(Integer)
parent = relationship(
"Parent",
foreign_keys="[Child.parent_id1, Child.parent_id2]",
back_populates="children",
)
def __repr__(self):
return f"<Child(id={self.id}, child_name={self.child_name})>"
Base.metadata.create_all(engine)
""" console output:
CREATE TABLE tbl_parent (
id1 INTEGER NOT NULL,
id2 INTEGER NOT NULL,
parent_name VARCHAR(50) NULL,
PRIMARY KEY (id1, id2)
)
2020-04-30 07:52:43,771 INFO sqlalchemy.engine.Engine ()
2020-04-30 07:52:43,776 INFO sqlalchemy.engine.Engine COMMIT
2020-04-30 07:52:43,778 INFO sqlalchemy.engine.Engine
CREATE TABLE tbl_child (
id INTEGER NOT NULL,
child_name VARCHAR(50) NULL,
parent_id1 INTEGER NULL,
parent_id2 INTEGER NULL,
PRIMARY KEY (id),
FOREIGN KEY(parent_id1, parent_id2) REFERENCES tbl_parent (id1, id2)
)
2020-04-30 07:52:43,778 INFO sqlalchemy.engine.Engine ()
2020-04-30 07:52:43,802 INFO sqlalchemy.engine.Engine COMMIT
"""
Reference:
https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/table_config.html#table-configuration