Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQLAlchemy: __init__() takes 1 positional argument but 2 were given (many to many)

SQLAlchemy is undoubtedly very powerful, but the documentation implicitly assumes lots of prior knowledge and on the subject of relationships, mixes between the backref and the newly-preferred back_populates() methods, which I find very confusing.

The following model design is pretty much an exact mirror of the guide in the documentation that deals with the Association Objects for many-to-many relationships. You can see that the comments are still identical to those in the original article, and I've only changed the actual code.

class MatchTeams(db.Model):
    match_id = db.Column(db.String, db.ForeignKey('match.id'), primary_key=True)
    team_id = db.Column(db.String, db.ForeignKey('team.id'), primary_key=True)
    team_score = db.Column(db.Integer, nullable="True")

    # bidirectional attribute/collection of "user"/"user_keywords"
    match = db.relationship("Match",
                            backref=db.backref("match_teams",
                                            cascade="all, delete-orphan")
                            )
    # reference to the "Keyword" object
    team = db.relationship("Team")


class Match(db.Model):
    id = db.Column(db.String, primary_key=True)

    # Many side of many to one with Round
    round_id = db.Column(db.Integer, ForeignKey('round.id'))
    round = db.relationship("Round", back_populates="matches")
    # Start of M2M

    # association proxy of "match_teams" collection
    # to "team" attribute
    teams = association_proxy('match_teams', 'team')

    def __repr__(self):
        return '<Match: %r>' % (self.id)


class Team(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)
    goals_for = db.Column(db.Integer)
    goals_against = db.Column(db.Integer)
    wins = db.Column(db.Integer)
    losses = db.Column(db.Integer)
    points = db.Column(db.Integer)
    matches_played = db.Column(db.Integer)

    def __repr__(self):
        return '<Team %r with ID: %r>' % (self.name, self.id)

But this snippet, which is supposed to associate the team instance find_liverpool with the match instance find_match (both boilerplate objects), doesn't work:

find_liverpool = Team.query.filter(Team.id==1).first()
print(find_liverpool)
find_match = Match.query.filter(Match.id=="123").first()
print(find_match)

find_match.teams.append(find_liverpool)

And outputs the following:

Traceback (most recent call last):
File "/REDACT/temp.py", line 12, in <module>
find_match.teams.append(find_liverpool)
File "/REDACT/lib/python3.4/site-packages/sqlalchemy/ext/associationproxy.py", line 609, in append
item = self._create(value)
File "/REDACT/lib/python3.4/site-packages/sqlalchemy/ext/associationproxy.py", line 532, in _create
 return self.creator(value)
TypeError: __init__() takes 1 positional argument but 2 were given    
<Team 'Liverpool' with ID: 1>
<Match: '123'>
like image 376
zerohedge Avatar asked Dec 19 '16 12:12

zerohedge


1 Answers

The call to append is trying to create a new instance of MatchTeams, as can be seen from the documentation. This is also noted under "Simplifying Association Objects" that you linked to:

Where above, each .keywords.append() operation is equivalent to:

>>> user.user_keywords.append(UserKeyword(Keyword('its_heavy')))

Hence your

find_match.teams.append(find_liverpool)

is equivalent to

find_match.match_teams.append(MatchTeams(find_liverpool))

Since MatchTeams has no explicitly defined __init__, it's using the _default_constructor() as constructor (unless you've overridden it), which accepts only keyword arguments in addition to self, the only positional argument.

To remedy this either pass a creator factory to your association proxy:

class Match(db.Model):

    teams = association_proxy('match_teams', 'team',
                              creator=lambda team: MatchTeams(team=team))

or define __init__ on MatchTeams to suit your needs, for example:

class MatchTeams(db.Model):

    # Accepts as positional arguments as well
    def __init__(self, team=None, match=None):
        self.team = team
        self.match = match

or create the association object explicitly:

db.session.add(MatchTeams(match=find_match, team=find_liverpool))
# etc.
like image 176
Ilja Everilä Avatar answered Oct 16 '22 22:10

Ilja Everilä