Saving objects in the database

Normally you don’t need to bother of saving your entity instances in the database manually - Pony automatically commits all changes to the database on leaving the db_session() context. It is very convenient. In the same time, in some cases you might want to flush() or commit() data in the database before leaving the current database session.

If you need to get the primary key value of a newly created object, you can do flush() manually within the db_session() in order to get this value:

  1. class Customer(db.Entity):
  2. id = PrimaryKey(int, auto=True)
  3. email = Required(str)
  4. @db_session
  5. def handler(email):
  6. c = Customer(email=email)
  7. # c.id is equal to None
  8. # because it is not assigned by the database yet
  9. c.flush()
  10. # c is saved as a table row to the database.
  11. # c.id has the value now
  12. print(c.id)

When flush() is called, the object is saved only inside the current session. It means it will be persisted to the database after calling commit() manually (not necessary in most cases) or automatically before leaving the current database session.

Order of saving objects

Usually Pony saves objects in the database in the same order as they are created or modified. In some cases Pony can reorder SQL INSERT statements if this is required for saving objects. Let’s consider the following example:

  1. from pony.orm import *
  2. db = Database()
  3. class TeamMember(db.Entity):
  4. name = Required(str)
  5. team = Optional('Team')
  6. class Team(db.Entity):
  7. name = Required(str)
  8. team_members = Set(TeamMember)
  9. db.bind('sqlite', ':memory:')
  10. db.generate_mapping(create_tables=True)
  11. set_sql_debug(True)
  12. with db_session:
  13. john = TeamMember(name='John')
  14. mary = TeamMember(name='Mary')
  15. team = Team(name='Tenacity', team_members=[john, mary])

In the example above we create two team members and then a team object, assigning the team members to the team. The relationship between TeamMember and Team objects is represented by a column in the TeamMember table:

  1. CREATE TABLE "Team" (
  2. "id" INTEGER PRIMARY KEY AUTOINCREMENT,
  3. "name" TEXT NOT NULL
  4. )
  5. CREATE TABLE "TeamMember" (
  6. "id" INTEGER PRIMARY KEY AUTOINCREMENT,
  7. "name" TEXT NOT NULL,
  8. "team" INTEGER REFERENCES "Team" ("id")
  9. )

When Pony creates john, mary and team objects, it understands that it should reorder SQL INSERT statements and create an instance of the Team object in the database first, because it will allow using the team id for saving TeamMember rows:

  1. INSERT INTO "Team" ("name") VALUES (?)
  2. [u'Tenacity']
  3. INSERT INTO "TeamMember" ("name", "team") VALUES (?, ?)
  4. [u'John', 1]
  5. INSERT INTO "TeamMember" ("name", "team") VALUES (?, ?)
  6. [u'Mary', 1]

Cyclic chains during saving objects

Now let’s say we want to have an ability to assign a captain to a team. For this purpose we need to add a couple of attributes to our entities: Team.captain and reverse attribute TeamMember.captain_of

  1. class TeamMember(db.Entity):
  2. name = Required(str)
  3. team = Optional('Team')
  4. captain_of = Optional('Team')
  5. class Team(db.Entity):
  6. name = Required(str)
  7. team_members = Set(TeamMember)
  8. captain = Optional(TeamMember, reverse='captain_of')

And here is the code for creating entity instances with a captain assigned to the team:

  1. with db_session:
  2. john = TeamMember(name='John')
  3. mary = TeamMember(name='Mary')
  4. team = Team(name='Tenacity', team_members=[john, mary], captain=mary)

When Pony tries to execute the code above it raises the following exception:

  1. pony.orm.core.CommitException: Cannot save cyclic chain: TeamMember -> Team -> TeamMember

Why did it happen? Let’s see. Pony sees that for saving the john and mary objects in the database it needs to know the id of the team, and tries to reorder the insert statements. But for saving the team object with the captain attribute assigned, it needs to know the id of mary object. In this case Pony cannot resolve this cyclic chain and raises an exception.

In order to save such a cyclic chain, you have to help Pony by adding the flush() command:

  1. with db_session:
  2. john = TeamMember(name='John')
  3. mary = TeamMember(name='Mary')
  4. flush() # saves objects created by this moment in the database
  5. team = Team(name='Tenacity', team_members=[john, mary], captain=mary)

In this case, Pony will save the john and mary objects in the database first and then will issue SQL UPDATE statement for building the relationship with the team object:

  1. INSERT INTO "TeamMember" ("name") VALUES (?)
  2. [u'John']
  3. INSERT INTO "TeamMember" ("name") VALUES (?)
  4. [u'Mary']
  5. INSERT INTO "Team" ("name", "captain") VALUES (?, ?)
  6. [u'Tenacity', 2]
  7. UPDATE "TeamMember"
  8. SET "team" = ?
  9. WHERE "id" = ?
  10. [1, 2]
  11. UPDATE "TeamMember"
  12. SET "team" = ?
  13. WHERE "id" = ?
  14. [1, 1]