'Table' object has no attribute 'id' on SQLAlchemy relation The Next CEO of Stack OverflowHow to sort a list of objects based on an attribute of the objects?How to know if an object has an attribute in PythonDetermine the type of an object?Formalchemy - form for one to many relationHow to force aliases for columns in SQLAlchemy?Getting warning when 2 models inherit the same object in flask-sqlalchemyPython: sqlalchemy the result of schema isn't expectedHow to initialize the object (table) using dict with some relationship with other table via sqlalchemy?SQLAlchemy “AttributeError: 'str' object has no attribute 'c'”How to count child table items with or without join to parent table using SQLAlchemy?
How does the Z80 determine which peripheral sent an interrupt?
Grabbing quick drinks
How do I reset passwords on multiple websites easily?
If the heap is zero-initialized for security, then why is the stack merely uninitialized?
Written every which way
How did people program for Consoles with multiple CPUs?
Why does standard notation not preserve intervals (visually)
Novel about a guy who is possessed by the divine essence and the world ends?
Is it professional to write unrelated content in an almost-empty email?
Is there a difference between "Fahrstuhl" and "Aufzug"
Preparing Indesign booklet with .psd graphics for print
Does it take more energy to get to Venus or to Mars?
Would a galaxy be visible from outside, but nearby?
Inappropriate reference requests from Journal reviewers
What can we do to stop prior company from asking us questions?
Are there any unintended negative consequences to allowing PCs to gain multiple levels at once in a short milestone-XP game?
How do scammers retract money, while you can’t?
Which tube will fit a -(700 x 25c) wheel?
Real integral using residue theorem - why doesn't this work?
How does the mv command work with external drives?
Complex fractions
Anatomically Correct Strange Women In Ponds Distributing Swords
What does convergence in distribution "in the Gromov–Hausdorff" sense mean?
Multiple labels for a single equation
'Table' object has no attribute 'id' on SQLAlchemy relation
The Next CEO of Stack OverflowHow to sort a list of objects based on an attribute of the objects?How to know if an object has an attribute in PythonDetermine the type of an object?Formalchemy - form for one to many relationHow to force aliases for columns in SQLAlchemy?Getting warning when 2 models inherit the same object in flask-sqlalchemyPython: sqlalchemy the result of schema isn't expectedHow to initialize the object (table) using dict with some relationship with other table via sqlalchemy?SQLAlchemy “AttributeError: 'str' object has no attribute 'c'”How to count child table items with or without join to parent table using SQLAlchemy?
I've got a relationship set up across three separate classes using SQLAlchemy, with an association table for a many-to-many relationship. Minimal example:
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import configure_mappers, relationship
Base = declarative_base()
teams_users = Table(
'teams_users', Base.metadata,
Column('team_id', ForeignKey('teams.id')),
Column('user_id', ForeignKey('users.id'))
)
class User(Base):
__tablename__ = 'users'
# No autoincrement, since we're using externally-generated UIDs
id = Column(Integer, primary_key=True, autoincrement=False)
teams = relationship('Team', secondary=teams_users, back_populates="users")
class Team(Base):
__tablename__ = 'teams'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
game_id = Column(Integer, ForeignKey('games.id'), nullable=False)
games = relationship("Game", foreign_keys='games.id')
users = relationship("User", secondary='teams_users', back_populates="teams")
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
team1_id = Column(Integer, ForeignKey('teams.id'))
team2_id = Column(Integer, ForeignKey('teams.id'))
team1 = relationship("Team", back_populates="games", foreign_keys=team1_id, uselist=False)
tean2 = relationship("Team", back_populates="games", foreign_keys=team2_id, uselist=False)
# done declaring, trigger the error
configure_mappers()
Attempting to query any of these relations returns a 'Table' object has no attribute 'id'
error:
Traceback (most recent call last):
File "...", line 35, in <module>
configure_mappers()
File "/.../sqlalchemy/orm/mapper.py", line 3033, in configure_mappers
mapper._post_configure_properties()
File "/.../sqlalchemy/orm/mapper.py", line 1832, in _post_configure_properties
prop.init()
File "/.../sqlalchemy/orm/interfaces.py", line 183, in init
self.do_init()
File "/.../sqlalchemy/orm/relationships.py", line 1655, in do_init
self._process_dependent_arguments()
File "/.../sqlalchemy/orm/relationships.py", line 1680, in _process_dependent_arguments
setattr(self, attr, attr_value())
File "/.../sqlalchemy/ext/declarative/clsregistry.py", line 281, in __call__
x = eval(self.arg, globals(), self._dict)
File "<string>", line 1, in <module>
AttributeError: 'Table' object has no attribute 'id'
My goal in constructing it this way is that I could easily check which Teams each User has ever been a part of.
Additionally, Game
has two foreign keys into Team
because the use case for this project supports arbitrarily sized teams, but only ever two teams. This allows me to have a result of "team1 won" and immediately have a reference to the winning users for stat tracking and historical reference.
What am I doing wrong here?
python sqlalchemy
add a comment |
I've got a relationship set up across three separate classes using SQLAlchemy, with an association table for a many-to-many relationship. Minimal example:
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import configure_mappers, relationship
Base = declarative_base()
teams_users = Table(
'teams_users', Base.metadata,
Column('team_id', ForeignKey('teams.id')),
Column('user_id', ForeignKey('users.id'))
)
class User(Base):
__tablename__ = 'users'
# No autoincrement, since we're using externally-generated UIDs
id = Column(Integer, primary_key=True, autoincrement=False)
teams = relationship('Team', secondary=teams_users, back_populates="users")
class Team(Base):
__tablename__ = 'teams'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
game_id = Column(Integer, ForeignKey('games.id'), nullable=False)
games = relationship("Game", foreign_keys='games.id')
users = relationship("User", secondary='teams_users', back_populates="teams")
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
team1_id = Column(Integer, ForeignKey('teams.id'))
team2_id = Column(Integer, ForeignKey('teams.id'))
team1 = relationship("Team", back_populates="games", foreign_keys=team1_id, uselist=False)
tean2 = relationship("Team", back_populates="games", foreign_keys=team2_id, uselist=False)
# done declaring, trigger the error
configure_mappers()
Attempting to query any of these relations returns a 'Table' object has no attribute 'id'
error:
Traceback (most recent call last):
File "...", line 35, in <module>
configure_mappers()
File "/.../sqlalchemy/orm/mapper.py", line 3033, in configure_mappers
mapper._post_configure_properties()
File "/.../sqlalchemy/orm/mapper.py", line 1832, in _post_configure_properties
prop.init()
File "/.../sqlalchemy/orm/interfaces.py", line 183, in init
self.do_init()
File "/.../sqlalchemy/orm/relationships.py", line 1655, in do_init
self._process_dependent_arguments()
File "/.../sqlalchemy/orm/relationships.py", line 1680, in _process_dependent_arguments
setattr(self, attr, attr_value())
File "/.../sqlalchemy/ext/declarative/clsregistry.py", line 281, in __call__
x = eval(self.arg, globals(), self._dict)
File "<string>", line 1, in <module>
AttributeError: 'Table' object has no attribute 'id'
My goal in constructing it this way is that I could easily check which Teams each User has ever been a part of.
Additionally, Game
has two foreign keys into Team
because the use case for this project supports arbitrarily sized teams, but only ever two teams. This allows me to have a result of "team1 won" and immediately have a reference to the winning users for stat tracking and historical reference.
What am I doing wrong here?
python sqlalchemy
add a comment |
I've got a relationship set up across three separate classes using SQLAlchemy, with an association table for a many-to-many relationship. Minimal example:
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import configure_mappers, relationship
Base = declarative_base()
teams_users = Table(
'teams_users', Base.metadata,
Column('team_id', ForeignKey('teams.id')),
Column('user_id', ForeignKey('users.id'))
)
class User(Base):
__tablename__ = 'users'
# No autoincrement, since we're using externally-generated UIDs
id = Column(Integer, primary_key=True, autoincrement=False)
teams = relationship('Team', secondary=teams_users, back_populates="users")
class Team(Base):
__tablename__ = 'teams'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
game_id = Column(Integer, ForeignKey('games.id'), nullable=False)
games = relationship("Game", foreign_keys='games.id')
users = relationship("User", secondary='teams_users', back_populates="teams")
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
team1_id = Column(Integer, ForeignKey('teams.id'))
team2_id = Column(Integer, ForeignKey('teams.id'))
team1 = relationship("Team", back_populates="games", foreign_keys=team1_id, uselist=False)
tean2 = relationship("Team", back_populates="games", foreign_keys=team2_id, uselist=False)
# done declaring, trigger the error
configure_mappers()
Attempting to query any of these relations returns a 'Table' object has no attribute 'id'
error:
Traceback (most recent call last):
File "...", line 35, in <module>
configure_mappers()
File "/.../sqlalchemy/orm/mapper.py", line 3033, in configure_mappers
mapper._post_configure_properties()
File "/.../sqlalchemy/orm/mapper.py", line 1832, in _post_configure_properties
prop.init()
File "/.../sqlalchemy/orm/interfaces.py", line 183, in init
self.do_init()
File "/.../sqlalchemy/orm/relationships.py", line 1655, in do_init
self._process_dependent_arguments()
File "/.../sqlalchemy/orm/relationships.py", line 1680, in _process_dependent_arguments
setattr(self, attr, attr_value())
File "/.../sqlalchemy/ext/declarative/clsregistry.py", line 281, in __call__
x = eval(self.arg, globals(), self._dict)
File "<string>", line 1, in <module>
AttributeError: 'Table' object has no attribute 'id'
My goal in constructing it this way is that I could easily check which Teams each User has ever been a part of.
Additionally, Game
has two foreign keys into Team
because the use case for this project supports arbitrarily sized teams, but only ever two teams. This allows me to have a result of "team1 won" and immediately have a reference to the winning users for stat tracking and historical reference.
What am I doing wrong here?
python sqlalchemy
I've got a relationship set up across three separate classes using SQLAlchemy, with an association table for a many-to-many relationship. Minimal example:
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import configure_mappers, relationship
Base = declarative_base()
teams_users = Table(
'teams_users', Base.metadata,
Column('team_id', ForeignKey('teams.id')),
Column('user_id', ForeignKey('users.id'))
)
class User(Base):
__tablename__ = 'users'
# No autoincrement, since we're using externally-generated UIDs
id = Column(Integer, primary_key=True, autoincrement=False)
teams = relationship('Team', secondary=teams_users, back_populates="users")
class Team(Base):
__tablename__ = 'teams'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
game_id = Column(Integer, ForeignKey('games.id'), nullable=False)
games = relationship("Game", foreign_keys='games.id')
users = relationship("User", secondary='teams_users', back_populates="teams")
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
team1_id = Column(Integer, ForeignKey('teams.id'))
team2_id = Column(Integer, ForeignKey('teams.id'))
team1 = relationship("Team", back_populates="games", foreign_keys=team1_id, uselist=False)
tean2 = relationship("Team", back_populates="games", foreign_keys=team2_id, uselist=False)
# done declaring, trigger the error
configure_mappers()
Attempting to query any of these relations returns a 'Table' object has no attribute 'id'
error:
Traceback (most recent call last):
File "...", line 35, in <module>
configure_mappers()
File "/.../sqlalchemy/orm/mapper.py", line 3033, in configure_mappers
mapper._post_configure_properties()
File "/.../sqlalchemy/orm/mapper.py", line 1832, in _post_configure_properties
prop.init()
File "/.../sqlalchemy/orm/interfaces.py", line 183, in init
self.do_init()
File "/.../sqlalchemy/orm/relationships.py", line 1655, in do_init
self._process_dependent_arguments()
File "/.../sqlalchemy/orm/relationships.py", line 1680, in _process_dependent_arguments
setattr(self, attr, attr_value())
File "/.../sqlalchemy/ext/declarative/clsregistry.py", line 281, in __call__
x = eval(self.arg, globals(), self._dict)
File "<string>", line 1, in <module>
AttributeError: 'Table' object has no attribute 'id'
My goal in constructing it this way is that I could easily check which Teams each User has ever been a part of.
Additionally, Game
has two foreign keys into Team
because the use case for this project supports arbitrarily sized teams, but only ever two teams. This allows me to have a result of "team1 won" and immediately have a reference to the winning users for stat tracking and historical reference.
What am I doing wrong here?
python sqlalchemy
python sqlalchemy
edited Mar 7 at 15:49
Martijn Pieters♦
723k14125392343
723k14125392343
asked Mar 7 at 15:34
Mikey T.K.Mikey T.K.
6111131
6111131
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
To define the relationship between a game and the two teams that play in it, you need to only give the games
table foreign keys; a team can play in multiple games, a one-to-many relationship; remove the games_id
column altogether. The exception you got is a bit of a red herring, but it fails to properly configure the foreign_keys='games.id'
argument in a relationship that doesn't need that foreign key.
The relationship configuration on the Team
class is a little tricky here as the Team.games
attribute would have to relate to either foreign key. This is covered in the documentation under Handling Multiple Join Paths; you were almost there but there is no uselist
parameter needed here:
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
team1_id = Column(Integer, ForeignKey('teams.id'))
team2_id = Column(Integer, ForeignKey('teams.id'))
team1 = relationship("Team", foreign_keys=team1_id)
team2 = relationship("Team", foreign_keys=team2_id)
Note that I omitted the back_populates
references here, because two relationships updating a single relationship on the other site leads one or the other of the two foreign keys being updated with the other value, leading to a game between the same team on either side!
The inverse relationship attribute, Team.games
, requires a custom primaryjoin
because you are looking for games where either team1_id
or team2_id
is the foreign key pointing back. Use the foreign()
annotation to help SQLAlchemy determine when to update the relationship (it'll watch for the foreign key changes), and use a lambda
to defer resolving the columns:
class Team(Base):
__tablename__ = 'teams'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
# game_id = Column(Integer, ForeignKey('games.c.id'), nullable=False)
games = relationship(
"Game",
primaryjoin=lambda: or_(
Team.id == foreign(Game.team1_id),
Team.id == foreign(Game.team2_id)
),
viewonly=True,
)
users = relationship("User", secondary='teams_users', back_populates="teams")
You could also make primaryjoin
a string containing the expression now being executed in a lambda
, so 'or_(Team.id == foreign(Game.team1_id), Team.id == foreign(Game.team2_id))'
.
Again, no back_populates
, this type of relationship can't automatically update relationships between loaded objects. If you need to see these relationships reflected before committing, you'll need to issue a session flush. I also added viewonly=True
, because you can't map mutations to the Team.games
list to updates in the database (what would adding a new game to the list mean, that this team is team 1 or team 2?).
You may want to add a custom constraint table to ensure that games never take place between the same team on both sides:
class Game(Base):
# ...
__table_args__ = (
CheckConstraint(team1_id != team2_id, name='different_teams'),
)
A quick demonstration of the relationships:
from itertools import combinations
engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()
teams = [Team() for _ in range(3)]
session.add_all(teams)
user = User(id=42, teams=teams)
session.add(user)
games = [Game(team1=t1, team2=t2) for t1, t2 in combinations(teams, 2)]
session.add_all(games)
session.commit()
for team in user.teams:
print('Team:', team.id, 'games:', [g.id for g in team.games])
for game in session.query(Game):
print(f'Game game.id: team game.team1.id vs game.team2.id')
which outputs:
Team: 2 games: [1, 3]
Team: 1 games: [1, 2]
Team: 3 games: [2, 3]
Game 1: team 1 vs 2
Game 2: team 1 vs 3
Game 3: team 2 vs 3
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55047510%2ftable-object-has-no-attribute-id-on-sqlalchemy-relation%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
To define the relationship between a game and the two teams that play in it, you need to only give the games
table foreign keys; a team can play in multiple games, a one-to-many relationship; remove the games_id
column altogether. The exception you got is a bit of a red herring, but it fails to properly configure the foreign_keys='games.id'
argument in a relationship that doesn't need that foreign key.
The relationship configuration on the Team
class is a little tricky here as the Team.games
attribute would have to relate to either foreign key. This is covered in the documentation under Handling Multiple Join Paths; you were almost there but there is no uselist
parameter needed here:
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
team1_id = Column(Integer, ForeignKey('teams.id'))
team2_id = Column(Integer, ForeignKey('teams.id'))
team1 = relationship("Team", foreign_keys=team1_id)
team2 = relationship("Team", foreign_keys=team2_id)
Note that I omitted the back_populates
references here, because two relationships updating a single relationship on the other site leads one or the other of the two foreign keys being updated with the other value, leading to a game between the same team on either side!
The inverse relationship attribute, Team.games
, requires a custom primaryjoin
because you are looking for games where either team1_id
or team2_id
is the foreign key pointing back. Use the foreign()
annotation to help SQLAlchemy determine when to update the relationship (it'll watch for the foreign key changes), and use a lambda
to defer resolving the columns:
class Team(Base):
__tablename__ = 'teams'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
# game_id = Column(Integer, ForeignKey('games.c.id'), nullable=False)
games = relationship(
"Game",
primaryjoin=lambda: or_(
Team.id == foreign(Game.team1_id),
Team.id == foreign(Game.team2_id)
),
viewonly=True,
)
users = relationship("User", secondary='teams_users', back_populates="teams")
You could also make primaryjoin
a string containing the expression now being executed in a lambda
, so 'or_(Team.id == foreign(Game.team1_id), Team.id == foreign(Game.team2_id))'
.
Again, no back_populates
, this type of relationship can't automatically update relationships between loaded objects. If you need to see these relationships reflected before committing, you'll need to issue a session flush. I also added viewonly=True
, because you can't map mutations to the Team.games
list to updates in the database (what would adding a new game to the list mean, that this team is team 1 or team 2?).
You may want to add a custom constraint table to ensure that games never take place between the same team on both sides:
class Game(Base):
# ...
__table_args__ = (
CheckConstraint(team1_id != team2_id, name='different_teams'),
)
A quick demonstration of the relationships:
from itertools import combinations
engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()
teams = [Team() for _ in range(3)]
session.add_all(teams)
user = User(id=42, teams=teams)
session.add(user)
games = [Game(team1=t1, team2=t2) for t1, t2 in combinations(teams, 2)]
session.add_all(games)
session.commit()
for team in user.teams:
print('Team:', team.id, 'games:', [g.id for g in team.games])
for game in session.query(Game):
print(f'Game game.id: team game.team1.id vs game.team2.id')
which outputs:
Team: 2 games: [1, 3]
Team: 1 games: [1, 2]
Team: 3 games: [2, 3]
Game 1: team 1 vs 2
Game 2: team 1 vs 3
Game 3: team 2 vs 3
add a comment |
To define the relationship between a game and the two teams that play in it, you need to only give the games
table foreign keys; a team can play in multiple games, a one-to-many relationship; remove the games_id
column altogether. The exception you got is a bit of a red herring, but it fails to properly configure the foreign_keys='games.id'
argument in a relationship that doesn't need that foreign key.
The relationship configuration on the Team
class is a little tricky here as the Team.games
attribute would have to relate to either foreign key. This is covered in the documentation under Handling Multiple Join Paths; you were almost there but there is no uselist
parameter needed here:
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
team1_id = Column(Integer, ForeignKey('teams.id'))
team2_id = Column(Integer, ForeignKey('teams.id'))
team1 = relationship("Team", foreign_keys=team1_id)
team2 = relationship("Team", foreign_keys=team2_id)
Note that I omitted the back_populates
references here, because two relationships updating a single relationship on the other site leads one or the other of the two foreign keys being updated with the other value, leading to a game between the same team on either side!
The inverse relationship attribute, Team.games
, requires a custom primaryjoin
because you are looking for games where either team1_id
or team2_id
is the foreign key pointing back. Use the foreign()
annotation to help SQLAlchemy determine when to update the relationship (it'll watch for the foreign key changes), and use a lambda
to defer resolving the columns:
class Team(Base):
__tablename__ = 'teams'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
# game_id = Column(Integer, ForeignKey('games.c.id'), nullable=False)
games = relationship(
"Game",
primaryjoin=lambda: or_(
Team.id == foreign(Game.team1_id),
Team.id == foreign(Game.team2_id)
),
viewonly=True,
)
users = relationship("User", secondary='teams_users', back_populates="teams")
You could also make primaryjoin
a string containing the expression now being executed in a lambda
, so 'or_(Team.id == foreign(Game.team1_id), Team.id == foreign(Game.team2_id))'
.
Again, no back_populates
, this type of relationship can't automatically update relationships between loaded objects. If you need to see these relationships reflected before committing, you'll need to issue a session flush. I also added viewonly=True
, because you can't map mutations to the Team.games
list to updates in the database (what would adding a new game to the list mean, that this team is team 1 or team 2?).
You may want to add a custom constraint table to ensure that games never take place between the same team on both sides:
class Game(Base):
# ...
__table_args__ = (
CheckConstraint(team1_id != team2_id, name='different_teams'),
)
A quick demonstration of the relationships:
from itertools import combinations
engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()
teams = [Team() for _ in range(3)]
session.add_all(teams)
user = User(id=42, teams=teams)
session.add(user)
games = [Game(team1=t1, team2=t2) for t1, t2 in combinations(teams, 2)]
session.add_all(games)
session.commit()
for team in user.teams:
print('Team:', team.id, 'games:', [g.id for g in team.games])
for game in session.query(Game):
print(f'Game game.id: team game.team1.id vs game.team2.id')
which outputs:
Team: 2 games: [1, 3]
Team: 1 games: [1, 2]
Team: 3 games: [2, 3]
Game 1: team 1 vs 2
Game 2: team 1 vs 3
Game 3: team 2 vs 3
add a comment |
To define the relationship between a game and the two teams that play in it, you need to only give the games
table foreign keys; a team can play in multiple games, a one-to-many relationship; remove the games_id
column altogether. The exception you got is a bit of a red herring, but it fails to properly configure the foreign_keys='games.id'
argument in a relationship that doesn't need that foreign key.
The relationship configuration on the Team
class is a little tricky here as the Team.games
attribute would have to relate to either foreign key. This is covered in the documentation under Handling Multiple Join Paths; you were almost there but there is no uselist
parameter needed here:
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
team1_id = Column(Integer, ForeignKey('teams.id'))
team2_id = Column(Integer, ForeignKey('teams.id'))
team1 = relationship("Team", foreign_keys=team1_id)
team2 = relationship("Team", foreign_keys=team2_id)
Note that I omitted the back_populates
references here, because two relationships updating a single relationship on the other site leads one or the other of the two foreign keys being updated with the other value, leading to a game between the same team on either side!
The inverse relationship attribute, Team.games
, requires a custom primaryjoin
because you are looking for games where either team1_id
or team2_id
is the foreign key pointing back. Use the foreign()
annotation to help SQLAlchemy determine when to update the relationship (it'll watch for the foreign key changes), and use a lambda
to defer resolving the columns:
class Team(Base):
__tablename__ = 'teams'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
# game_id = Column(Integer, ForeignKey('games.c.id'), nullable=False)
games = relationship(
"Game",
primaryjoin=lambda: or_(
Team.id == foreign(Game.team1_id),
Team.id == foreign(Game.team2_id)
),
viewonly=True,
)
users = relationship("User", secondary='teams_users', back_populates="teams")
You could also make primaryjoin
a string containing the expression now being executed in a lambda
, so 'or_(Team.id == foreign(Game.team1_id), Team.id == foreign(Game.team2_id))'
.
Again, no back_populates
, this type of relationship can't automatically update relationships between loaded objects. If you need to see these relationships reflected before committing, you'll need to issue a session flush. I also added viewonly=True
, because you can't map mutations to the Team.games
list to updates in the database (what would adding a new game to the list mean, that this team is team 1 or team 2?).
You may want to add a custom constraint table to ensure that games never take place between the same team on both sides:
class Game(Base):
# ...
__table_args__ = (
CheckConstraint(team1_id != team2_id, name='different_teams'),
)
A quick demonstration of the relationships:
from itertools import combinations
engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()
teams = [Team() for _ in range(3)]
session.add_all(teams)
user = User(id=42, teams=teams)
session.add(user)
games = [Game(team1=t1, team2=t2) for t1, t2 in combinations(teams, 2)]
session.add_all(games)
session.commit()
for team in user.teams:
print('Team:', team.id, 'games:', [g.id for g in team.games])
for game in session.query(Game):
print(f'Game game.id: team game.team1.id vs game.team2.id')
which outputs:
Team: 2 games: [1, 3]
Team: 1 games: [1, 2]
Team: 3 games: [2, 3]
Game 1: team 1 vs 2
Game 2: team 1 vs 3
Game 3: team 2 vs 3
To define the relationship between a game and the two teams that play in it, you need to only give the games
table foreign keys; a team can play in multiple games, a one-to-many relationship; remove the games_id
column altogether. The exception you got is a bit of a red herring, but it fails to properly configure the foreign_keys='games.id'
argument in a relationship that doesn't need that foreign key.
The relationship configuration on the Team
class is a little tricky here as the Team.games
attribute would have to relate to either foreign key. This is covered in the documentation under Handling Multiple Join Paths; you were almost there but there is no uselist
parameter needed here:
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
team1_id = Column(Integer, ForeignKey('teams.id'))
team2_id = Column(Integer, ForeignKey('teams.id'))
team1 = relationship("Team", foreign_keys=team1_id)
team2 = relationship("Team", foreign_keys=team2_id)
Note that I omitted the back_populates
references here, because two relationships updating a single relationship on the other site leads one or the other of the two foreign keys being updated with the other value, leading to a game between the same team on either side!
The inverse relationship attribute, Team.games
, requires a custom primaryjoin
because you are looking for games where either team1_id
or team2_id
is the foreign key pointing back. Use the foreign()
annotation to help SQLAlchemy determine when to update the relationship (it'll watch for the foreign key changes), and use a lambda
to defer resolving the columns:
class Team(Base):
__tablename__ = 'teams'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
# game_id = Column(Integer, ForeignKey('games.c.id'), nullable=False)
games = relationship(
"Game",
primaryjoin=lambda: or_(
Team.id == foreign(Game.team1_id),
Team.id == foreign(Game.team2_id)
),
viewonly=True,
)
users = relationship("User", secondary='teams_users', back_populates="teams")
You could also make primaryjoin
a string containing the expression now being executed in a lambda
, so 'or_(Team.id == foreign(Game.team1_id), Team.id == foreign(Game.team2_id))'
.
Again, no back_populates
, this type of relationship can't automatically update relationships between loaded objects. If you need to see these relationships reflected before committing, you'll need to issue a session flush. I also added viewonly=True
, because you can't map mutations to the Team.games
list to updates in the database (what would adding a new game to the list mean, that this team is team 1 or team 2?).
You may want to add a custom constraint table to ensure that games never take place between the same team on both sides:
class Game(Base):
# ...
__table_args__ = (
CheckConstraint(team1_id != team2_id, name='different_teams'),
)
A quick demonstration of the relationships:
from itertools import combinations
engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()
teams = [Team() for _ in range(3)]
session.add_all(teams)
user = User(id=42, teams=teams)
session.add(user)
games = [Game(team1=t1, team2=t2) for t1, t2 in combinations(teams, 2)]
session.add_all(games)
session.commit()
for team in user.teams:
print('Team:', team.id, 'games:', [g.id for g in team.games])
for game in session.query(Game):
print(f'Game game.id: team game.team1.id vs game.team2.id')
which outputs:
Team: 2 games: [1, 3]
Team: 1 games: [1, 2]
Team: 3 games: [2, 3]
Game 1: team 1 vs 2
Game 2: team 1 vs 3
Game 3: team 2 vs 3
edited Mar 7 at 17:51
answered Mar 7 at 15:55
Martijn Pieters♦Martijn Pieters
723k14125392343
723k14125392343
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55047510%2ftable-object-has-no-attribute-id-on-sqlalchemy-relation%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown