Self-paced

Explore our extensive collection of courses designed to help you master various subjects and skills. Whether you're a beginner or an advanced learner, there's something here for everyone.

Bootcamp

Learn live

Join us for our free workshops, webinars, and other events to learn more about our programs and get started on your journey to becoming a developer.

Upcoming live events

Learning library

For all the self-taught geeks out there, here is our content library with most of the learning materials we have produced throughout the years.

It makes sense to start learning by reading and watching videos about fundamentals and how things work.

Search from all Lessons


LoginGet Started
← Back to Lessons
Edit on Github

Build and operate database with SQL Alchemy

What are ORMs and what are they useful for?
Relationships between tables
  • How is the relationship defined?

What are ORMs and what are they useful for?

ORM stands for Object-Relational Mapping. It is a programming technique that connects the object-oriented programming (OOP) world of code with the relational world of databases. In simpler terms, it acts as a translator between the way you represent data in your code using objects and the way data is stored in relational databases using tables and relationships.

ORMs simplify the process of accessing and manipulating data in databases from code. They provide a layer of abstraction that protects you from the complexities of SQL queries and allows you to work with data using objects and their relationships, just as you would in any object-oriented programming application.

Among its benefits are:

  • Simplifies data access: Works with objects instead of complex SQL queries.

  • Improves productivity: Reduces development and code maintenance time.

  • Promotes modularity: Separates business logic from data management.

  • Increases portability: Facilitates database change without modifying the code.

What is SQLAlchemy?

  • Object-Relational Mapping (ORM): SQL Alchemy's ORM capabilities allow you to work with database data using Python objects, simplifying data access and manipulation.

  • Basic SQL query API: SQL Alchemy provides a powerful and flexible API for building and executing SQL queries, including support for both raw SQL and a declarative query builder.

  • Database abstraction layer: SQL Alchemy abstracts from the underlying database engine, allowing you to write database-independent code that can be easily ported to different databases.

  • Event system: SQL Alchemy has an event system that allows you to connect to various stages of database operations, such as before and after query execution.

  • Broad ecosystem: SQL Alchemy has support for integration with various complementary tools, such as flask-sqlalchemy for integration with apis Flask, or alembic that allows you to manage migrations in a simple way.

Overall, SQL Alchemy is a powerful and versatile tool that has become the de facto standard for Python database development. Its rich feature set, flexibility and ease of use make it an invaluable asset for creating robust and scalable data-driven applications.

Learn how to build an api with Flask in this article.

Build your database model

The first step in database creation is modeling, but if you want to know how to build a good model based on your application requirements we recommend that you review our data modeling article. For this article we are going to assume that you already know what tables you need, what columns they should have and how they all relate to each other.

To take the tables from the model to the database you have to start with a Base class, which is created from another one called DeclarativeBase from SQLAlchemy.

1from sqlalchemy.orm import DeclarativeBase 2 3 4class Base(DeclarativeBase): 5 pass 6

This base class will be used to create the tables and later generate the connection with which we will do the operations.

How to define a table?

From the base class we can create other classes that will be converted into tables of our database, and that you will be able to use from the code of your application to consult or to manipulate information. Let's analyze how a class for a films table looks like:

1class Films(Base): 2 __tablename__ = "films" 3 id: Mapped[int] = mapped_column(primary_key=True) 4 title: Mapped[str] = mapped_column(String(100)) 5 release_date: Mapped[date] = mapped_column(Date)
  1. The __tablename__ property corresponds to the final name that the table will have in the database. It is recommended to use lower case and avoid the use of special characters.

  2. The id property is mapped as an integer (int) and is indicated as corresponding to a primary key column. SQLAlchemy interprets this and creates a numeric sequence that is incremented with each row or record inserted.

  3. The title column is mapped as a string field (str) and is indicated to correspond to a String(100) column where the number represents the maximum text length that can be stored in that field.

  4. Finally the release_date column is mapped as the previous ones, but with a date data type to store dates.

With this we have already defined a class that later will be converted into a database table. Additionally we can also add other functions that can help us to represent or manipulate the data of the model, as well as more columns of any of the data types supported by SQLAlchemy

Relationships between tables

The main purpose of relational databases is precisely the association of different entities through their relationships, and SQLAlchemy has all the tools to define these relationships from Python code without having to touch SQL. Suppose that, in addition to the Films table defined in the previous section, you also have an Actors table with the information of the actors in the Hollywood industry.

1class Actors(Base): 2 __tablename__ = "actors" 3 id: Mapped[int] = mapped_column(primary_key=True) 4 name: Mapped[str] = mapped_column(String(100)) 5 date_of_birth: Mapped[date] = mapped_column(Date)

If you want to make a database of movies including the actors appearing in them (IMDB style) it is necessary to define this relationship in the model. Since an actor can appear in many movies, and in turn a movie can have many actors, we have a many to many relationship. For this transient relationship we need what is known as a pivot table, using a name to identify the relationship, this table will be called Cast.

1class Cast(Base): 2 __tablename__ = "cast" 3 id: Mapped[int] = mapped_column(primary_key=True) 4 # Relationship with actors 5 actor_id = mapped_column(ForeignKey("actors.id")) 6 actor: Mapped["Actors"] = relationship(back_populates="movies") 7 8 # Relationship with films 9 film_id = mapped_column(ForeignKey("films.id")) 10 film: Mapped["Films"] = relationship(back_populates="characters")

How is the relationship defined?

  1. Foreign key: This refers to the foreign key to which the entity is related. It should be noted that in a one-to-many relationship the foreign key is placed at the many end of the relationship. In this case, since one actor can be in many casts, the foreign key is placed in the cast table.

  2. Relationship mapping: In addition to the foreign key, SQLAlchemy allows us to map the entity with which we have a relationship thanks to the relationship properties. This generates a direct reference to the object to which it is related and allows access to it from the code without the need for an additional query.

  3. Filling backwards: In addition to the mapping of the relationship in the entity, it is also possible to generate the same reference in the opposite direction towards the foreign entity. In this case, it is possible to create a property in the Actors entity that makes reference to all the casts where it appears. To do so, the back_populates parameter is used, specifying the name of the relationship in the foreign entity that will make the reference.

1class Actors(Base): 2 __tablename__ = "actors" 3 id: Mapped[int] = mapped_column(primary_key=True) 4 name: Mapped[str] = mapped_column(String(100)) 5 date_of_birth: Mapped[date] = mapped_column(Date) 6 movies: Mapped[List["Cast"]] = relationship(back_populates="actor") 7 8 9class Cast(Base): 10 __tablename__ = "cast" 11 id: Mapped[int] = mapped_column(primary_key=True) 12 # Relationship with actors 13 actor_id = mapped_column(ForeignKey("actors.id")) 14 actor: Mapped["Actors"] = relationship(back_populates="movies") 15 # Relationship with Films 16 film_id = mapped_column(ForeignKey("films.id")) 17 film: Mapped["Films"] = relationship(back_populates="characters") 18 19class Films(Base): 20 __tablename__ = "films" 21 id: Mapped[int] = mapped_column(primary_key=True) 22 title: Mapped[str] = mapped_column(String(100)) 23 release_date: Mapped[date] = mapped_column(Date) 24 characters: Mapped[List["Cast"]] = relationship(back_populates="film") 25

Here we highlight the following:

  • We see how 2 entities relate to each other using relationship properties where each points to the relationship attribute of the other, and being mapped to the entity type of their relationship.

  • Although entities have relationship properties only Cast has ForeignKey since it corresponds to it because it is the many end of the relationships: both an Actor and a movie have many Casts.

  • Because an Actor can appear in many Cast the movies relationship maps as a list of Casts, while at the other end of the relationship an actor is directly of type Actors. The same happens in the Film relationship where characters is a list of Cast.

  • When mapping relationships to other entities, the name of the entity is specified in quotes and respecting capitalization, for example actor: Mapped["Actors"]. This is so that the relationships are not affected by the order in which the classes appear in the code, which allows in this case to have mapped movies: Mapped[List["Cast"]] being that Cast is defined further down in the code.

Finally let us remember that many-to-many relationships, such as the case of movies with actors, are in practice two one-to-many relationships of both entities with a pivot table. In this case the Cast entity is the pivot and just as its relationship with the Actors was defined, the same must be done with the Films to complete the relationship.

CRUD Operations

In order to perform operations with the database and our models it is necessary to generate a session with which to perform the operations.

1from sqlalchemy import create_engine 2from sqlalchemy.orm import sessionmaker 3 4# You can add the 'echo=True' parameter to the following line to see how the SQL code of the library works 5engine = create_engine("postgresql://user:password@server.com:5432/example", echo=False) 6 7Session = sessionmaker(bind=engine) 8session = Session()

The session object will be the one used to perform the database operations.

Create

To create a new Films record, you can instantiate a Movies object and set its attributes, then add it to the session and commit the changes:

1# Create a new film record 2new_film = Films(title="The Shawshank Redemption", release_date=datetime.date(1994, 9, 23)) 3 4# Add the new film to the session 5session.add(new_film) 6 7# Commit the changes to the database 8session.commit() 9

Read

To retrieve existing movie records, you can use queries created with the SQLAlchemy query API. This is an example of searching for all movies:

1# Fetch all films 2query = select(Planets) 3films = self.session.scalars(query).all() 4 5for film in films: 6 print(f"Film: {film.title}, Release Date: {film.release_date}")

You can also use filters to retrieve specific movies based on certain criteria:

1query = select(Films).filter_by(id=id) 2film = session.scalars(query).first() 3 4if film: 5 print(f"Film found: {film.title}, Release Date: {film.release_date}") 6else: 7 print("Film not found")

Update

To update an existing movie record, you must obtain the Flims object to be updated, modify its attributes and commit the changes:

1# Search for the movie with id 1 2query = select(Films).filter_by(id=1) 3film = self.session.scalars(query).first() 4if (film is None): 5 print("Film not found") 6 return None 7# Update the title and confirm the changes 8film.title="New title" 9session.add(film) 10session.commit()

Delete

To delete an existing movie record, you must get the Films object and delete it from the session, similar to the update case but changing the last step:

1# Search for the movie with id 1 2query = select(Films).filter_by(id=1) 3film = self.session.scalars(query).first() 4if (film is None): 5 print("Film not found") 6 return None 7session.delete(film) 8session.commit()

Registering relationships

Once the relationships have been defined, you can access the related objects using the defined relationship attributes. Returning to the example of the relationship of actors to the casts of each movie, you can obtain the actors of a given movie by first querying the movie object:

1query = select(Films).filter_by(id=1) 2film = session.scalars(query).first() 3 4for character in film.characters: 5 print(f"Actor: {character.actor.name}") 6
1# The film is obtained 2query = select(Films).filter_by(id=1) 3film = session.scalars(query).first() 4 5# Actor is obtained 6query = select(Actor).filter_by(id=1) 7actor = session.scalars(query).first() 8 9# Cast is added 10cast=Cast(actor_id=people_id,film_id=film_id) 11session.add(cast) 12session.commit() 13

To manipulate relationships you just edit the ForeignKey fields like any other field. The values of the ForeignKey fields must previously exist in the table where the relationship points to.

Test yourself

You already have all the information you need to start implementing SQLAlchemy databases in your Python applications. Remember that you are not limited to just APIs, you can also use SQLAlchemy in Data Science, Machine Learning, Electronics, Internet of Things (IOT) applications and much more.

Continue learning about SQL Alchemy, build projects and complete interactive tutorials.