# The SQLAlchemy Session Class

_Why do we need the Session class, if we already have a perfectly fine create\_engine function? well, it all comes down to SQLAlchemy ORM vs SQLAlchemy Core._

By Yonatan Vega · September 24, 2025

> Source: https://fullstack.rocks/article/the-sqlalchemy-session-class

![a dj mixing a potion in a cauldron](https://storage.googleapis.com/fullstack-rocks-media/the-session-960-47d7d7a513b0.webp)

## What is the difference between SQLAlchemy ORM and SQLAlchemy Core?

In the first article in this series I introduced SQLAlchemy as one of the most complex and confusing ORMs that is on the market, that statement is only a half truth, mainly because SQLAlchemy is not simply an ORM.

SQLAlchemy is more of a pythonic database toolkit, it is in your hands (or your team lead’s hands) how you would like to use it.

SQLAlchemy `Core` is more of a hands-on kind of utility kit, it exposes more barebones methods for crafting your queries which often times can be used for query optimization and complex tasks that might be more straightforward to write and read for SQL veterans.

SQLAlchemy `ORM` on the other hand is great for creating reusable object to interact with when our queries and not as complex or when we have complex relationships in our database that we would like to simplify.

### Which should I use?

The neat thing is that using **both** of these methods usually goes hand-in-hand! you do not have to consider only one of these methods for your application, in-fact I think that with a little bit of planning it’s actually much easier to use both.

You should not be spending time trying to optimize complex and slow queries using the `ORM` that you can easily craft with `Core`, just as you should not be writing basic selects with `Core` that you can easily avoid by using the `ORM`.

In my experience, since the ORM covers most of what traditional web applications need, it is usually my go to choice, once a more difficult or complex tasks presents themselves I consider whether I should use the ORM or craft my queries by my self using Core.

## Lets dive into the Session

Now that we understand the difference between the ORM and Core we can dive into the Session class, which is very beneficial specifically to ORM usage.

Lets first take a second look at the Session class as code

```python
from sqlalchemy.orm import Session
from sqlalchemy import create_engine

# We still need to create an engine
engine = create_engine('sqlite:///:memory:')

# Create a session
with Session(engine) as session:
  pass
```

_[▶ Run this on fullstack.rocks](https://fullstack.rocks/article/the-sqlalchemy-session-class#code-6ac165385e218264632868f3)_

When using the Session we typically use it in the scope of a context manager, it might be declared once when we enter our code (such as an API endpoint), or many different times as our program runs.

### But what does the Session do?

Aside from establishing a connection to the database and managing it, the session does so much much, every-time we create a session (using the context manager), we are basically creating a container for the ORM, which tracks all of out usage of objects during the lifespan of the Session object.

One of the unique features of the Session, that might be a great cause for confusion if you are unaware of it, is the `identity map`.

### The `identity map`

In each session we might make many CRUD operations on many different tables, the role of the identity map is to track each object that you pull from your database and map it using its object type and primary key, the next time you might wish to access this object, SQLAlchemy will first consult with the identity map and check it’s existence, in the case that it already exists the result will be fetched from the identity map instead of from the database.

This feature can be real confusing the first time you encounter it, mainly because if you are unaware of the existence of the identity map you might get extra frustrated about why you are not getting the data you are expecting for a specific object.

> **Note:** 🧙‍♂️ If you are building an API, perhaps in a microservice architecture, you might want to make sure that not a single database field updates the in the case of a failure, in that case you might define a single Session that is created once you enter your API Endpoint, and roll it back in case of an error.
>
> In that case you might want to think deeply about how you write you code, making sure that your reusable code works in the way that you intended no matter where it is being called from in your code base.

### The Unit of Work pattern

The unit of work pattern is a behavioral pattern in software development, this pattern was defined by Martin Fowler as so:

> [The Unit of Work pattern] Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
>
> ...
>
> A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.
>
> — Martin Fowler, Author of "Patterns of Enterprise Application Architecture"

This interesting pattern is yet another puzzle piece in scalability for large applications, especially those with heavy load, its goal is to minimize roundtrips to the database, if you sprinkle database queries all over your codebase that get executed instantly when they are requested, you instantly make your applications slower.

SQLAlchemy aims to reduce these roundtrips by tracking changes and operations on objects within a session instance, querying local object directly instead of the database, at the end of or during the session when you commit your changes all of these CRUD operations will be persisted in the database all at once.

## Flush(ing) our changes

To some of you the term `flush` might sound as if we would like to dispose of our changes rather than keep them.

The `session.flush()` function is actually a powerful function that sends changes that exist in our session to the transaction, it does not commit but instead sends our operations to the transaction where other database operations such as triggers might be called as part of the changes that we made in the transaction so far.

> **Note:** 🧙‍♂️ Another useful use case of the `flush()` function is to receive primary keys for objects that have not been committed yet, if your database tables have columns that are populated based off of timestamps, UUID generation functions or generated columns, they will not exist until the objects go through the transaction.
>
> When you `flush()` SQLAlchemy will send these object creation operations to the transaction and update the objects tracked by the `identity map` with their new values.

The `flush()` function is always called as part of a `commit()` function, so you do not have to call both to persist your changes.

### An example using `flush()`

Lets show how flush may be used to ensure that our session creates multiple related objects.

First we will create a couple of table for this demonstration

```python
from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped
from sqlalchemy import ForeignKey, String

class BaseModel(DeclarativeBase):
  pass

class Wizards(BaseModel):
  __tablename__ = 'wizards'
  # Using autoincrement=True instructs the DB to increment the ID automatically
  id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
  name: Mapped[str] = mapped_column(String(45), nullable=False)
  age: Mapped[int]

class Familiars(BaseModel):
  __tablename__ = 'familiars'
  id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
  name: Mapped[str] = mapped_column(String(45), nullable=False)
  beast_type: Mapped[str] = mapped_column(String(150), nullable=False)
  alchemist_id: Mapped[int] = mapped_column(ForeignKey('wizards.id'), nullable=False)

BaseModel.metadata.create_all(engine)
```

_[▶ Run this on fullstack.rocks](https://fullstack.rocks/article/the-sqlalchemy-session-class#code-6ac165385e218264632868f7)_

Next lets write a simple Session where we can see `flush()` in action

```python
with Session(engine) as session:
  # Create a new alchemist
  # Since we have autoincrement=True, we do not pass an ID by ourselves
  albus = Wizards(name='Albus Dumbledore', age=115)
  session.add(albus)
  print("---")
  print('Albus Dumbledore\'s id BEFORE the flush: ' + str(albus.id))
  session.flush()
  print("---")
  print('Albus Dumbledore\'s id AFTER the flush: ' + str(albus.id))
  
  # Create a new familiar
  # Note that we are provding our new object the Id for our Wizard, which is still
  # not persisted in the database, only in the transaction
  fawkes = Familiars(name='Fawkes', beast_type='Phoenix', alchemist_id=albus.id)
  session.add(fawkes)
  session.flush()
  # Commit the changes
  print('---')
  print('Fawkes\'s id: ' + str(fawkes.id))
  print('Fawkes\'s alchemist_id: ' + str(fawkes.alchemist_id))
  session.commit()
```

_[▶ Run this on fullstack.rocks](https://fullstack.rocks/article/the-sqlalchemy-session-class#code-6ac165385e218264632868f8)_

In this example we can clearly see how the `flush()` function changes the way we write our code when working within a session instance, from the logs we can see that before the flush, Dumbledore’s id was `None` while after the flush it became  `1`.

We were then able to use that id when creating a `Familiar` object that requires a `Wizard` id as a foreign key in the `wizard_id` column.

> **Note:** 🧙‍♂️ If you turn on the `echo` flag in the `create_engine` function in the first code block, you will see that a flush actually does send SQL Queries to the transaction, but only calls `COMMIT` after all of our print statements.

### About `autoflush`

The curios amongst you may have already checked out the documentation page about the `Session` class, and might be wondering about a specific property that is available, specifically `autoflush` 

When checking out the documentation about flushing it starts with

> When the `Session` is used with its default configuration, the flush step is nearly always done transparently
>
> — SQLAlchemy, SQLAlchemy Documentation

That is an important sentence to read, as SQLAlchemy states that for the most part you probably do not need to flush explicitly, instead, when using the default configuration of the `Session` (or the `sessionmaker` that we will soon discuss) `autoflush` is set to `True` by default.

But what does it do?

`autoflush` makes sure that certain operations will always issue a `flush` before they occur, these operations are:

1. Legacy `Query` statement
2. Modern SQLAlchemy ORM v2.0 `session.execute` statements (discussed later in the series)
3. Before a `commit` statement
4. Before a `Session.begin_nested` statement which starts a “nested” transaction (issues a SAVEPOINT SQL statement)

In our use case we create multiple objects that rely on one another, and in this case the `flush` statement is relevant, later when we get to SQLAlchemy ORM `relationships` we will see how we might avoid using the `flush` statement directly and rely on `relationships` to deal with this for us.
