As you already know how these goes, I'll use the short version and get the data in a single Python statement.
And because we are now using select(), we also have to import it.
fromtypingimportList,OptionalfromsqlmodelimportField,Relationship,Session,SQLModel,create_engine,select# Some code here omitted πdefupdate_heroes():withSession(engine)assession:hero_spider_boy=session.exec(select(Hero).where(Hero.name=="Spider-Boy")).one()team_z_force=session.exec(select(Team).where(Team.name=="Z-Force")).one()# Code below omitted π
Because we are accessing an attribute in the models right after we commit, with hero_spider_boy.teams and team_z_force.heroes, the data is refreshed automatically.
So we don't have to call session.refresh().
We then commit the change, refresh, and print the updated Spider-Boy's heroes to confirm.
Notice that we only addZ-Force to the session, then we commit.
We never add Spider-Boy to the session, and we never even refresh it. But we still print his teams.
This still works correctly because we are using back_populates in the Relationship() in the models. That way, SQLModel (actually SQLAlchemy) can keep track of the changes and updates, and make sure they also happen on the relationships in the other related models. π
You can confirm it's all working by running the program in the command line:
fast βpython app.py π¬ Previous output omitted π π¬ Create the new many-to-many relationshipINFO Engine INSERT INTO heroteamlink (team_id, hero_id) VALUES (?, ?) INFO Engine [generated in 0.00020s] (1, 3) INFO Engine COMMIT
π¬ Start a new automatic transactionINFO Engine BEGIN (implicit)
π¬ Automatically refresh the data while accessing the attribute .teamsINFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00044s] (3,) INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters FROM team, heroteamlink WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id INFO Engine [cached since 0.1648s ago] (3,)
π¬ Automatically refresh the data while accessing the attribute .heoresINFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age FROM hero, heroteamlink WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id INFO Engine [cached since 0.1499s ago] (1,)
Now let's say that right after joining the team, Spider-Boy realized that their "life preserving policies" are much more relaxed than what he's used to. π
And their occupational safety and health is also not as great... π₯
So, Spider-Boy decides to leave Z-Force.
Let's update the relationships to remove team_z_force from hero_spider_boy.teams.
Because hero_spider_boy.teams is just a list (a special list managed by SQLAlchemy, but a list), we can use the standard list methods.
In this case, we use the method .remove(), that takes an item and removes it from the list.
And this time, just to show again that by using back_populatesSQLModel (actually SQLAlchemy) takes care of connecting the models by their relationships, even though we performed the operation from the hero_spider_boy object (modifying hero_spider_boy.teams), we are adding team_z_force to the session. And we commit that, without even add hero_spider_boy.
This still works because by updating the teams in hero_spider_boy, because they are synchronized with back_populates, the changes are also reflected in team_z_force, so it also has changes to be saved in the DB (that Spider-Boy was removed).
And then we add the team, and commit the changes, which updates the team_z_force object, and because it changed the table that also had a connection with the hero_spider_boy, it is also marked internally as updated, so it all works.
And then we just print them again to confirm that everything worked correctly.
To confirm that this last part worked, you can run the program again, it will output something like:
fast βpython app.py π¬ Previous output omitted π π¬ Delete the row in the link tableINFO Engine DELETE FROM heroteamlink WHERE heroteamlink.team_id = ? AND heroteamlink.hero_id = ? INFO Engine [generated in 0.00043s] (1, 3)π¬ Save the changesINFO Engine COMMIT
π¬ Automatically start a new transactionINFO Engine BEGIN (implicit)
π¬ Automatically refresh the data while accessing the attribute .heroesINFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters FROM team WHERE team.id = ? INFO Engine [generated in 0.00029s] (1,) INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age FROM hero, heroteamlink WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id INFO Engine [cached since 0.5625s ago] (1,)
π¬ Print the Z-Force heroes after reverting the changesReverted Z-Force's heroes: [ Hero(name='Deadpond', age=None, id=1, secret_name='Dive Wilson') ]
π¬ Automatically refresh the data while accessing the attribute .teamsINFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age FROM hero WHERE hero.id = ? INFO Engine [cached since 0.4209s ago] (3,) INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters FROM team, heroteamlink WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id INFO Engine [cached since 0.5842s ago] (3,)
π¬ Print Spider-Boy's teams after reverting the changesReverted Spider-Boy's teams: [ Team(id=2, name='Preventers', headquarters='Sharp Tower') ]
π¬ Automatically roll back any possible previously unsaved transactionINFO Engine ROLLBACK