factory_boy
factory_boy copied to clipboard
post_generation method not auto-committing in SQLAlchemy
Description
I am using the post_generation method to set a many-to-many relationship. I have sqlalchemy_session_persistence = 'commit'
, however the many-to-many relationship is not being saved to the DB.
To Reproduce
Set sqlalchemy_session_persistence = 'commit'
Set up many-to-many relationship between two models
Create factory for both models
Create post_generation method to set many-to-many
Call factory.create()
Model / Factory code
class TaskTypeFactory(BaseCustomFactory):
class Meta:
model = models.TaskType
cms_slug = factory.Faker('word')
section = factory.LazyAttribute(lambda _: random.choice(list(constants.TaskTypeSections)).value)
@factory.post_generation
def journey_versions(self, create, extracted, **kwargs):
"""
Creates a JourneyVersion and associates it with the TaskType object after creation, or add a list of
JourneyVersion objects that are passed into the factory declaration with the `journey_versions` attribute.
Ex. TaskTypeFactory.create(journey_versions=(v1, v2, v3))
Ref: https://factoryboy.readthedocs.io/en/stable/recipes.html#simple-many-to-many-relationship
@param create: a boolean indicating which strategy was used -- .build() or .create()
@type create: bool
@param extracted: extracted is None unless a value was passed in for the PostGeneration declaration at Factory
declaration time
@type extracted: Any
@param kwargs: any extra parameters passed as attr__key=value when calling the Factory
@type kwargs: Any
"""
if not create:
# Simple build, do nothing.
return
if extracted:
if isinstance(extracted, Iterable):
# A list of JourneyVersion were passed in
for journey_version in extracted:
self.journey_versions.append(journey_version)
elif isinstance(extracted, models.JourneyVersion):
# A single JourneyVersion was passed in
self.journey_versions.append(extracted)
else:
# Nothing was passed in so we will just create a new JourneyVersion
journey_version = JourneyVersionFactory.create()
self.journey_versions.append(journey_version)
class TaskType(BaseIntPKModel):
__tablename__ = "task_types"
# Properties
cms_slug = Column(String, nullable=False)
section = Column(Enum(constants.TaskTypeSections), nullable=False, index=True)
# Relationships
journey_versions = relationship(
"JourneyVersion", secondary=journey_version_task_types_table, cascade="all", back_populates="task_types",
passive_deletes=True
)
triggers = relationship("Trigger", back_populates="task_type", cascade="all, delete-orphan", passive_deletes=True)
tasks = relationship("Task", back_populates="task_type", cascade="all, delete-orphan", passive_deletes=True)
class JourneyVersion(BaseIntPKModel):
__tablename__ = "journey_versions"
# Properties
name = Column(String, unique=True, nullable=False)
deal_type = Column(Enum(constants.DealTypes), nullable=False)
description = Column(String, nullable=True)
language = Column(Enum(constants.Languages), default=constants.Languages.en.value, nullable=False)
current = Column(Boolean, default=False, nullable=False)
# Relationships
journeys = relationship(
"Journey", back_populates="journey_version", cascade="all, delete-orphan", passive_deletes=True
)
task_types = relationship(
"TaskType", secondary=journey_version_task_types_table, cascade="all", back_populates="journey_versions",
passive_deletes=True
)
journey_version_task_types_table = Table('journey_version_task_types', Base.metadata,
Column('journey_version_id',
ForeignKey('journey_versions.id', ondelete="CASCADE"),
primary_key=True),
Column('task_type_id', ForeignKey('task_types.id', ondelete="CASCADE"),
primary_key=True)
)
The issue
Once the create method is called, it should save the model objects to the DB along with the many-to-many relationship. However, after running create, there is no DB entry in the association table between the two newly created objects.
factories.TaskTypeFactory.create() # after this executes, the association table entry is missing
session.commit() # after this executes, the association table entry is present
I think repeating the
if session_persistence == SESSION_PERSISTENCE_FLUSH:
session.flush()
elif session_persistence == SESSION_PERSISTENCE_COMMIT:
session.commit()
block in the use_postgeneration_results
/_after_postgeneration
functions would fix this, but the challenge is being aware of whether the instance was actually created or just fetched via the _get_or_create
feature. I doubt this would be possible at this height of the execution flow, but maybe unconditionally issuing the commit/flush would not be actually harmful? Do post-generation declarations run if the instance was fetched and not created? What is the outcome after a manual flush/commit in that case?