Describe the bug
In the attached example, multiple classes have an overlapping sa.orm.relationship to the class RevisionTrackedHistory.
As SQLAlchemy discovers the overlaps, the suggested fixes in the error message iteratively build up:
- To silence this warning, add the parameter 'overlaps="revision_tracked_history__all"' to the 'Bar.revision_tracked_history__all' relationship.
- To silence this warning, add the parameter 'overlaps="revision_tracked_history__all,revision_tracked_history__all"' to the 'Biz.revision_tracked_history__all'
Based on the errors and the documentation, when the relationships share the same name, as in this example it becomes unclear if the argument to "overlaps" is supposed to simply be the name of THIS relationship, or of the other relationships.
Note how the relationship name revision_tracked_history__all is duplicated. In the production instance, this gets around 30 repeats of the same relationship name.
Changing the names of each relationship to a discretely indentifiable/unique name will show the name should be the OTHER relationships (e.g. adding the suffix 1/2/3 to each item, in order):
- To silence this warning, add the parameter 'overlaps="revision_tracked_history__all1"' to the 'Bar.revision_tracked_history__all2" relationship.
- To silence this warning, add the parameter 'overlaps="revision_tracked_history__all1,revision_tracked_history__all2"' to the 'Biz.revision_tracked_history__all3'
This, however, is still confusing as the items are not easily cross-referenced. They are also not uniquely addressable. One can not configure the relationship with :
overlaps="Foo.revision_tracked_history__all,Bar.revision_tracked_history__all"
SQLAlchemy demands the following for all affected classes
overlaps="revision_tracked_history__all"
IMHO, the "overlaps" error should point to the fully-qualified name of the overlapping relationships (Class + attribute), and the relationship constructor should require fully qualified relationship names as well.
To Reproduce
import sqlalchemy
from sqlalchemy import Column
from sqlalchemy import DateTime
from sqlalchemy import create_engine
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import UnicodeText
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import aliased
from sqlalchemy.orm import relationship
engine = create_engine("sqlite:///:memory:")
metadata_obj = MetaData()
DeclaredTable = declarative_base(metadata=metadata_obj)
class Mixin_RevisionTracking(object):
_revision_tracked_object_type_id = None
class RevisionTrackedHistory(DeclaredTable):
__tablename__ = "revision_tracked_history"
id = Column(Integer, primary_key=True)
revision_tracked_object_type_id = Column(Integer, nullable=False)
revision_tracked_object_id = Column(Integer, nullable=False)
revision_id = Column(Integer, nullable=False)
class Foo(Mixin_RevisionTracking, DeclaredTable):
__tablename__ = "foo"
_revision_tracked_object_type_id = 1
id = Column(Integer, primary_key=True)
revision_tracked_history__all = relationship(
"RevisionTrackedHistory",
primaryjoin="""and_(Foo.id==foreign(RevisionTrackedHistory.revision_tracked_object_id),
RevisionTrackedHistory.revision_tracked_object_type_id==%s
)"""
% _revision_tracked_object_type_id,
order_by="RevisionTrackedHistory.revision_id.desc()",
)
class Bar(Mixin_RevisionTracking, DeclaredTable):
__tablename__ = "bar"
_revision_tracked_object_type_id = 2
id = Column(Integer, primary_key=True)
revision_tracked_history__all = relationship(
"RevisionTrackedHistory",
primaryjoin="""and_(Bar.id==foreign(RevisionTrackedHistory.revision_tracked_object_id),
RevisionTrackedHistory.revision_tracked_object_type_id==%s
)"""
% _revision_tracked_object_type_id,
order_by="RevisionTrackedHistory.revision_id.desc()",
)
class Biz(Mixin_RevisionTracking, DeclaredTable):
__tablename__ = "biz"
_revision_tracked_object_type_id = 3
id = Column(Integer, primary_key=True)
revision_tracked_history__all = relationship(
"RevisionTrackedHistory",
primaryjoin="""and_(Biz.id==foreign(RevisionTrackedHistory.revision_tracked_object_id),
RevisionTrackedHistory.revision_tracked_object_type_id==%s
)"""
% _revision_tracked_object_type_id,
order_by="RevisionTrackedHistory.revision_id.desc()",
)
sqlalchemy.orm.configure_mappers()
metadata_obj.create_all(engine)
Error
/Users/jvanasco/Desktop/example_overlaps.py:74: SAWarning: relationship 'Bar.revision_tracked_history__all' will copy column bar.id to column revision_tracked_history.revision_tracked_object_id, which conflicts with relationship(s): 'Foo.revision_tracked_history__all' (copies foo.id to revision_tracked_history.revision_tracked_object_id). If this is not the intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. To silence this warning, add the parameter 'overlaps="revision_tracked_history__all"' to the 'Bar.revision_tracked_history__all' relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx)
sqlalchemy.orm.configure_mappers()
/Users/jvanasco/Desktop/example_overlaps.py:74: SAWarning: relationship 'Biz.revision_tracked_history__all' will copy column biz.id to column revision_tracked_history.revision_tracked_object_id, which conflicts with relationship(s): 'Bar.revision_tracked_history__all' (copies bar.id to revision_tracked_history.revision_tracked_object_id), 'Foo.revision_tracked_history__all' (copies foo.id to revision_tracked_history.revision_tracked_object_id). If this is not the intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. To silence this warning, add the parameter 'overlaps="revision_tracked_history__all,revision_tracked_history__all"' to the 'Biz.revision_tracked_history__all' relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx)
sqlalchemy.orm.configure_mappers()
Versions
- OS:
- Python: 3.8
- SQLAlchemy: 1.4.26
- Database:
- DBAPI (eg: psycopg, cx_oracle, mysqlclient):
Additional context
No response
Describe the bug
In the attached example, multiple classes have an overlapping
sa.orm.relationshipto the classRevisionTrackedHistory.As SQLAlchemy discovers the overlaps, the suggested fixes in the error message iteratively build up:
Based on the errors and the documentation, when the relationships share the same name, as in this example it becomes unclear if the argument to "overlaps" is supposed to simply be the name of THIS relationship, or of the other relationships.
Note how the relationship name
revision_tracked_history__allis duplicated. In the production instance, this gets around 30 repeats of the same relationship name.Changing the names of each relationship to a discretely indentifiable/unique name will show the name should be the OTHER relationships (e.g. adding the suffix 1/2/3 to each item, in order):
This, however, is still confusing as the items are not easily cross-referenced. They are also not uniquely addressable. One can not configure the relationship with :
SQLAlchemy demands the following for all affected classes
IMHO, the "overlaps" error should point to the fully-qualified name of the overlapping relationships (Class + attribute), and the relationship constructor should require fully qualified relationship names as well.
To Reproduce
Error
Versions
Additional context
No response