Inverting dependencies between an ORM and model classes using SQL Alchemy in Python-Design Patterns

Python Code Nemesis
Technology Hits
Published in
5 min readJun 30, 2023
Photo by Kevin Ku on Unsplash

Why is inverting dependencies important?

Inverting dependencies between software components is important because it helps to reduce coupling and increase modularity, which in turn can lead to better maintainability, extensibility, and testability of the software.

When components are tightly coupled, changes to one component can have unintended effects on other components, leading to a domino effect of changes throughout the system. Inverted dependencies help to mitigate this problem by ensuring that components only depend on abstractions, rather than concrete implementations.

In the case of an ORM and model classes, inverting the dependencies can help to make the code more modular and maintainable by allowing the ORM and model classes to evolve independently of each other. For example, if the database schema changes, we can update the ORM to reflect the new schema without having to modify the model classes. Similarly, if we want to switch to a different ORM, we can do so without having to modify the model classes.

Furthermore, inverting dependencies can also make the code more testable by allowing us to test each component in isolation. For example, we can write unit tests for the model classes without having to set up a database connection, since the model classes do not depend on the ORM. Similarly, we can write integration tests for the ORM without having to instantiate the model classes, since the ORM only depends on abstractions of the model classes.

Code Example

Here’s an example of inverting dependencies between an ORM and model classes using SQLAlchemy in Python. We’ll define the schema first using metadata, then define the model classes, and finally use a mapper and relationships to set up the ORM.

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship, mapper
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.schema import MetaData

# Define the schema using metadata
metadata = MetaData()

class UserTable:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower() + 's'

id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String, unique=True)

class PostTable:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower() + 's'

id = Column(Integer, primary_key=True)
title = Column(String)
content = Column(String)
user_id = Column(Integer, ForeignKey('users.id'))

# Define the model classes
class User:
def __init__(self, name, email):
self.name = name
self.email = email

class Post:
def __init__(self, title, content, author):
self.title = title
self.content = content
self.author = author

# Set up the ORM using a mapper and relationships
engine = create_engine('sqlite:///example.db')
Session = sessionmaker(bind=engine)
Base = declarative_base()

class UserORM(Base, UserTable):
@property
def model(self):
return User(self.name, self.email)

class PostORM(Base, PostTable):
@property
def model(self):
return Post(self.title, self.content, self.author.model)

author = relationship(UserORM, backref='posts')

Base.metadata.create_all(engine)

# Use the ORM and model classes in our application
session = Session()


# create some users and posts
user1 = UserORM(name='Alice', email='alice@example.com')
user2 = UserORM(name='Bob', email='bob@example.com')

post1 = PostORM(title='My first post', content='Hello, world!', author=user1)
post2 = PostORM(title='Another post', content='This is a test', author=user2)

# save the users and posts to the database
session.add(user1)
session.add(user2)

session.add(post1)
session.add(post2)

session.commit()

# retrieve a user by their email address
user = session.query(UserORM).filter_by(email='alice@example.com').one()
print(user.model.name) # prints "Alice"

# retrieve a user's posts
for post in user.posts:
print(post.model.title) # prints "My first post"

In this code, we first define the schema using metadata, with separate classes for each table in our database. We then define our model classes, User and Post, which have the same attributes as their corresponding database tables.

Next, we use a mapper to map our model classes to the database tables. We also define a relationship between the User and Post models, with the User model having a posts attribute that is a list of their posts.

Finally, we use the ORM and model classes in our application by creating some users and posts and saving them to the database. We then retrieve a user by their email address and print their name, as well as retrieve a user’s posts and print their titles.

By inverting the dependencies between the ORM and model classes in this way, we have made our code more decoupled and easier to maintain. We can change our database schema or ORM implementation without having to modify our model classes, and vice versa. This also allows us to easily test our model classes without having to set up a database connection.

Additionally, by using the model property in our ORM classes to return an instance of the corresponding model class, we have further inverted the dependencies, as the ORM classes are now dependent on the model classes, rather than the other way around.

Inverting dependencies between an ORM and model classes can be achieved by defining the schema using metadata, defining the model classes, and using a mapper and relationships to set up the ORM. By doing so, we can make our code more decoupled and easier to maintain.

Inversion of Dependancies

In the above code, we achieved inversion of dependencies between the ORM and model classes by:

  1. Defining the database schema using metadata: We created a metadata object and used it to define the database schema using SQLAlchemy's table construct.
  2. Defining the model classes: We defined the User and Post model classes, which represent the entities in our system, and which define the attributes and behavior of those entities.
  3. Using a mapper and relationships to set up the ORM: We used SQLAlchemy’s mapper function to map the ORM classes to the database schema defined in step 1. We also used SQLAlchemy's relationship function to define the relationships between the ORM classes, such as the one-to-many relationship between User and Post.

By doing so, we decoupled the ORM classes from the database schema and the model classes, and instead made them depend on abstractions provided by SQLAlchemy, such as the metadata and mapper functions. This makes it easier to modify the database schema or switch to a different ORM implementation, without having to modify the model classes. It also makes the model classes more testable, since they can be tested in isolation without needing to instantiate the ORM classes or connect to a database.

That’s it for this article! Feel free to leave feedback or questions in the comments. If you found this an exciting read, leave some claps and follow! I love coffee, so feel free to buy me a coffee,XD. Cheers!

--

--

Python Code Nemesis
Technology Hits

Everything python, DSA, open source libraries and more!