I wanted to dig into the Factory pattern a bit. I was fighting the thought that conceptually, it’s just a class with methods, but that’s not entirely right. I think what I’m settling into is thinking of it as an object that has purpose. It’s not modeling something. It’s creating. It’s using a model, and churning different objects out.
In the Naja project, I have a Roles table in my database. I have a simple CRUD written out, and I have tests for them. I want to be able to generate role data painlessly and get it into the database for testing. I’m using the Faker library in Python to generate generic data. So my first step is to build the Role factory.
def build(**kwargs):
defaults = {
'role_discord_id': str(fake.random_int(
min=10000000000000000,
max=99999999999999999)
), # 17-18 digits
'role_name': fake.word()[:30], # Ensure max 30 chars (under 32 limit)
'role_description': fake.text(max_nb_chars=200) # Keep under 255 limit
}
defaults.update(kwargs)
return defaults
This creates the role with faked but default attributes. The attributes can be overwritten. For example, here’s one of the tests where two roles are created:
def test_getAllRoles(db_session):
# Store some roles
role_1 = RoleFactory.create(db_session, role_name="RoleOne")
role_2 = RoleFactory.create(db_session, role_name="RoleTwo")
# Retrieve all roles
roles = getAllRoles()
assert len(roles) >= 2 # At least the two created should be present
assert any(role.role_name == "RoleOne" for role in roles)
assert any(role.role_name == "RoleTwo" for role in roles)
The name has been specified by passing in role_name, and the factory takes care of the rest of the data. You may notice that it’s calling .create() instead of .build().
def create(db_session, **kwargs):
role_data = RoleFactory.build(**kwargs)
role = Role(**role_data)
db_session.add(role)
db_session.commit()
return role
The RoleFactory builds the data and it’s stored in role_data which is a dictionary. Then it passes that information into role which is the actual role object. It then adds it to our database. Here’s the whole class:
class RoleFactory:
#####################
# Create a role data dictionary without saving to db
#####################
@staticmethod
def build(**kwargs):
defaults = {
'role_discord_id': str(fake.random_int(
min=10000000000000000,
max=99999999999999999)
), # 17-18 digits
'role_name': fake.word()[:30], # Ensure max 30 chars (under 32 limit)
'role_description': fake.text(max_nb_chars=200) # Keep under 255 limit
}
defaults.update(kwargs)
return defaults
@staticmethod
def create(db_session, **kwargs):
role_data = RoleFactory.build(**kwargs)
role = Role(**role_data)
db_session.add(role)
db_session.commit()
return role