Search code examples
pythonsqlalchemysqlmodel

SQLModel many-many relationships not working


I've been struggling with this for weeks now. Here's the relevant code:

Base = declarative_base()

class Band_Genre(SQLModel, table=True):
    genre_id: Optional[int] = Field(default=None, foreign_key="FK_Band_Genre_Genre", primary_key=True)
    band_id: Optional[int] = Field(default=None, foreign_key="FK_Band_Genre_Band", primary_key=True)

class Genre(SQLModel, table=True):
    genre_id: int = Field(
        default=None,
        primary_key=True,
        description="Genre ID",
    )
    genre: Optional[str] = Field(
        default=None,
        description="Name of the genre",
    )
    band_links: List["Band"] = Relationship(back_populates='genres', link_model=Band_Genre)

    def __str__(self):
        return f"{self.genre_id}: {self.genre}"
    
class Band(SQLModel, table=True):
    band_id: Optional[int] = Field(
        default=None,
        primary_key=True,
        description="Band ID",
    )
    band_name: Optional[str] = Field(
        default=None,
        description="Name of the band",
    )
    genres: List[Genre] = Relationship(back_populates='bands',link_model=Band_Genre)
    
    def __str__(self):
        return f"{self.band_id}: {self.band_name}"
 

engine = create_engine(
    f"mssql+pyodbc://{uid}:{pwd}@{svr}/{db}?driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes",
)

Session = sessionmaker(bind=engine)

session = Session()
statement = select(Band)
results = session.execute(statement)

for r in results.all():
    for g in r.genres:
        print(g)

The goal is to get all the genres when retrieving the band (via FastAPI/Pydantic).

But I keep getting an error:

sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Genre.bands - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression

I've tried what feels like every possible combination of SQLAlchemy relationships kwargs in the relationship. Defining only one side of the many-many and a ton of other options, but I can't seem to resolve this. Has anyone else found a solution to this?


Solution

  • You need to update the foreign_key in the link table Band_Genre to point to the actual ID's of the respective models that is Genre and the Band model.

    Also update the back_populates attribute in the band to point band_links in the Genre model

    You can refer to this SQLModel tutorial

    Base = declarative_base()
    
    class Band_Genre(SQLModel, table=True):
        genre_id: Optional[int] = Field(default=None, foreign_key="genre.genre_id", primary_key=True)
        band_id: Optional[int] = Field(default=None, foreign_key="band.band_id", primary_key=True)
    
    class Genre(SQLModel, table=True):
        genre_id: int = Field(
            default=None,
            primary_key=True,
            description="Genre ID",
        )
        genre: Optional[str] = Field(
            default=None,
            description="Name of the genre",
        )
        band_links: List["Band"] = Relationship(back_populates='genres', link_model=Band_Genre)
        def __str__(self):
            return f"{self.genre_id}: {self.genre}"
        
    class Band(SQLModel, table=True):
        band_id: Optional[int] = Field(
            default=None,
            primary_key=True,
            description="Band ID",
        )
        band_name: Optional[str] = Field(
            default=None,
            description="Name of the band",
        )
        genres: List[Genre] = Relationship(back_populates='band_links',link_model=Band_Genre)
        
        def __str__(self):
            return f"{self.band_id}: {self.band_name}"
     
    
    engine = create_engine(
        f"mssql+pyodbc://{uid}:{pwd}@{svr}/{db}?driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes",
    )
    
    Session = sessionmaker(bind=engine)
    
    session = Session()
    statement = select(Band)
    results = session.execute(statement)
    
    for r in results.all():
        for g in r.genres:
            print(g)