The source code for this tutorial can be found on my GitHub: https://github.com/jmgraff/sqlalchemy-postgres-crud-example
This article will go over a simple example of how to use SQLAlchemy with PostgreSQL. You’re encouraged to clone the repository and run the example yourself. Once you’re familiar with how everything works, try modifying the main.py
file by making your own tables and queries.
Table of Contents
The Code
Below is the full code for the tutorial to follow:
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import Session, declarative_base
Base = declarative_base()
class Thing(Base):
__tablename__ = "thing"
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
engine = create_engine("postgresql://postgres:postgres@postgresql/postgres")
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
if __name__ == "__main__":
with Session(engine) as session:
# Add some things
for ii in range(5):
thing = Thing(name=f"Thing {ii}")
session.add(thing)
session.commit()
# Get everything
things = session.query(Thing).order_by(Thing.name).all()
for thing in things:
print(thing.name)
# Change something
print("Updating Thing 2...")
thing = session.query(Thing).where(Thing.name == "Thing 2").first()
thing.name = "Thing 2 (updated)"
session.commit()
# Get everything again
things = session.query(Thing).order_by(Thing.name).all()
for thing in things:
print(thing.name)
# Delete something
print("Deleting Thing 3...")
thing = session.query(Thing).where(Thing.name == "Thing 3").first()
session.delete(thing)
# Get everything again
things = session.query(Thing).order_by(Thing.name).all()
for thing in things:
print(thing.name)
main.pyWe’ll be going through this a section at a time to fully explain what’s going on here.
Expected Output
If you clone the repo and follow the directions to run it, you’ll see the following output:
Thing 0
Thing 1
Thing 2
Thing 3
Thing 4
Updating Thing 2...
Thing 0
Thing 1
Thing 2 (updated)
Thing 3
Thing 4
Deleting Thing 3...
Thing 0
Thing 1
Thing 2 (updated)
Thing 4
The Requirements
In order to use SQLAlchemy with PostgreSQL, you’ll need the following:
- A running PostgreSQL server (the GitHub repo comes with a Docker container for this)
- Python 3+
- The psycopg2 (PostgreSQL) Python package:
pip install psycopg2[binary]
- The sqlalchemy Python package:
pip install sqlalchemy
- A working knowledge of relational database concepts
- Intermediate Python skills
Importing the packages
Before we can use SQLAlchemy, we have to import a few things first:
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import Session, declarative_base
Base = declarative_base()
main.pyIn the above code, we’re importing:
sqlalchemy.Column
: this class is used to make an attribute of our ORM class (next section) a column in the tablesqlalchemy.Integer
: this is a data type used for columns that need to store integerssqlalchemy.String
: this is a data type used for columns that need to store stringssqlalchemy.create_engine
: this function is used to create the engine, which is used to connect to the databasesqlalchemy.orm.Session
: this is used with the engine we create to interact with our databasesqlalchemy.orm.declarative_base
: this function will create a class we can use as the base class for our ORM class (next section)
Creating a Schema
Here is where we actually define our ORM class, as well as the Base
class that it will inherit from:
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import Session, declarative_base
Base = declarative_base()
class Thing(Base):
__tablename__ = "thing"
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
engine = create_engine("postgresql://postgres:postgres@postgresql/postgres")
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
main.pyIn this snippet, we’re creating our base class for our ORM class and calling it Base
. Then, we define our actual ORM class Thing
which inherits from that class with the following attributes:
__tablename__
: this is the actual name of the table in the PostgreSQL databaseid
: this will serve as the unique ID we will use to reference each row inthing
. It’s anInteger
type, since it’s a number, and it’s also theprimary_key
for this table. You don’t have to manually specify this when creating a newThing
, PostgreSQL will generate one for usname
: this is a simpleString
column. We setnullable
toFalse
because we don’t want to be able to create a newThing
without specifying thename
Creating the Engine
Next, we’ll create the engine that will connect to our PostgreSQL database:
class Thing(Base):
__tablename__ = "thing"
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
engine = create_engine("postgresql://postgres:postgres@postgresql/postgres")
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
main.pyThe create_engine
method is pretty simple in this example, we just give it a specially formatted URI with all the information it needs to connect to our database.
Here’s what each part of the URI means:
postgresql://postgres:postgres@postgresql/postgres
: this is the database plugin we want to use. Note that this has to bepostgresql
and notpostgres
– SQLAlchemy no longer supports that one.postgresql://postgres:postgres@postgresql/postgres
: this is our username for the databasepostgresql://postgres:postgres@postgresql/postgres
: this is our password for the databasepostgresql://postgres:postgres@postgresql/postgres
: this is the domain name or IP address of the server – here, we’re using the domain namepostgresql
since that’s how we get to it on our Docker network.postgresql://postgres:postgres@postgresql/postgres
: this is the name of the database we want to use. Sincepostgres
is created automatically in thepostgres
container image we’re using, we’ll use that one
Creating the Table
Next we actually create the table(s) based on our schema:
engine = create_engine("postgresql://postgres:postgres@postgresql/postgres")
Base.metadata.drop_all(engine) # testing only - don't do this in production!
Base.metadata.create_all(engine)
if __name__ == "__main__":
with Session(engine) as session:
main.pyWe drop all tables first in the 3rd line of this snippet so that the program executes the same way every time, i.e. so we don’t keep adding rows to the thing
table. You probably don’t want to drop all tables as soon as your app starts up!
The Base
base class that our Thing
ORM class inherits from functions as a sort of registry. It automatically tracks classes’ metadata that inherit from it. By calling Base.metadata.create_all
and passing it the engine
object we created, it’ll create the schema we have defined by inheriting from it with our ORM classes if it doesn’t already exist.
Creating a Session
In order to interact with our newly created SQLAlchemy schema in the PostgreSQL database, we need to create a session object. We can use the with
block syntax to make sure it gets closed when we’re done with it:
if __name__ == "__main__":
with Session(engine) as session:
# Add some things
for ii in range(5):
thing = Thing(name=f"Thing {ii}")
session.add(thing)
session.commit()
# Get something
main.pyNote that we have to pass in the engine
object we created, so it’ll have to be imported wherever you need to use a Session
if you move your database logic to another file.
Adding Rows
Within our with
block, we can now interact with the database:
if __name__ == "__main__":
with Session(engine) as session:
# Add some things
for ii in range(5):
thing = Thing(name=f"Thing {ii}")
session.add(thing)
session.commit()
# Get everything
main.pyWhat we’re doing here is creating 5 Thing
s and adding them to the database via the session.add
method. This isn’t enough to create them, though. For that, we have to call session.commit
. This will also be necessary when updating as you’ll see in a minute.
Fetching Rows
Now that we have some things added to the PostgreSQL database, we can query them:
session.commit()
# Get something
things = session.query(Thing).order_by(Thing.name).all()
for thing in things:
print(thing.name)
# Change something
print("Updating Thing 2...")
main.pyWe create a SELECT
query by using the session.query
method, and passing it the class of the ORM object we want returned to us. Notice that we’re chaining several methods here.
The second method in the chain is .order_by
, which takes the property of the class of ORM object we want to order the results by. This will default to alphabetical order since Thing.name
is a String
type column.
Finally, we call the .all()
method to return all rows of the table that match our query parameters. We can then loop through all of them with a for
loop and print them out.
Updating Rows
Updating rows is even easier than selecting them:
print(thing.name)
# Change something
print("Updating Thing 2...")
thing = session.query(Thing).where(Thing.name == "Thing 2").first()
thing.name = "Thing 2 (updated)"
session.commit()
# Get everything again
things = session.query(Thing).order_by(Thing.name).all()
for thing in things:
print(thing.name)
# Delete something
main.pyHere, we’re doing another SELECT
query with the .query
method, but we define a WHERE
clause. The syntax here is a bit tricky, since SQLAlchemy has done a great job at becoming a domain-specific language (DSL) of sorts. Putting Thing.name == "Thing 2"
doesn’t look like it should work, but it does. We actually get the row with the .first
method. Note that if there’s more than one row returned by our query, this will only get the first one. We only have one that will match, so we don’t have to worry about that.
Next, we simply update that ORM object’s name
property to whatever we want, then call session.commit
to save the changes in the database.
Deleting Rows
Deleting rows is also very simple:
print(thing.name)
# Delete something
print("Deleting Thing 3...")
thing = session.query(Thing).where(Thing.name == "Thing 3").first()
session.delete(thing)
# Get everything again
things = session.query(Thing).order_by(Thing.name).all()
for thing in things:
print(thing.name)
main.pyHere we want to delete the Thing
whose name
is “Thing 3”. So, we select it out of the database, and then just call session.delete
on it. Very simple.
Conclusion
In this tutorial, we covered a simple Create, Read, Update, Delete (CRUD) example for using SQLAlchemy with PostgreSQL. While we did use Postgres with this example, you can swap it out for any supported database you’d like, such as MySQL or even SQLite.
John is a professional software engineer who has been solving problems with code for 15+ years. He has experience with full stack web development, container orchestration, mobile development, DevOps, Windows and Linux kernel development, cybersecurity, and reverse engineering. In his spare time, he’s researching the potential business applications of AI.