与 Factory Boy 的一对多关系
One to many relation with Factory Boy
我的 SQLAlchemy 模型中存在多对一关系。一份报告有很多样本(为简洁起见进行了简化):
class Sample(db.Model, CRUDMixin):
sample_id = Column(Integer, primary_key=True)
report_id = Column(Integer, ForeignKey('report.report_id', ondelete='CASCADE'), index=True, nullable=False)
report = relationship('Report', back_populates='samples')
class Report(db.Model, CRUDMixin):
report_id = Column(Integer, primary_key=True)
samples = relationship('Sample', back_populates='report')
现在在我的测试中,我希望能够生成一个 Sample
实例或一个 Report
实例,并填充缺失的关系。
class ReportFactory(BaseFactory):
class Meta:
model = models.Report
report_id = Faker('pyint')
samples = RelatedFactoryList('tests.factories.SampleFactory', size=3)
class SampleFactory(BaseFactory):
class Meta:
model = models.Sample
sample_id = Faker('pyint')
report = SubFactory(ReportFactory)
当我创建它们的实例时,工厂陷入无限循环:
RecursionError: maximum recursion depth exceeded in comparison
但是,如果我尝试使用 SelfAttribute
s 来停止无限循环,我最终会得到一个没有任何样本的报告:
class ReportFactory(BaseFactory):
samples = RelatedFactoryList('tests.factories.SampleFactory', size=3, report_id=SelfAttribute('..report_id'))
class SampleFactory(BaseFactory):
report = SubFactory(ReportFactory, samples=[])
report = factories.ReportFactory()
l = len(report.samples) # 0
但是,如果我用 SampleFactory()
生成 Sample
,它正确地有一个 Report
对象。
我应该如何正确设计我的工厂,以便 SampleFactory()
将生成具有关联 Report
的 Sample
,并且 ReportFactory()
将生成具有关联的 Report
2 个关联 Samples
,没有无限循环?
实例创建后,RelatedFactory
声明被评估:
- 实例化了
Report
- 对
SampleFactory
执行了 3 次调用
- 返回第1步实例化的
Report
为了填充 Report
个实例上的字段,您必须 link Sample
个实例到 Report
在第 2 步。
一个可能的实现是:
class SampleFactory(BaseFactory):
class Meta:
model = Sample
@classmethod
def _after_postgeneration(cls, instance, create, results=None):
if instance.report is not None and instance not in instance.report.samples:
instance.report.samples.append(instance)
id = factory.Faker('pyint')
# Enfore `post_samples = None` to prevent creating additional samples
report = factory.SubFactory('example.ReportFactory', samples=[], post_samples=None)
report_id = factory.SelfAttribute('report.id')
class ReportFactory(factory.Factory):
class Meta:
model = Report
id = factory.Faker('pyint')
# Set samples = [] if needed by `Report.__init__`
samples = []
# Named `post_samples` to mark that they are instantiated
# *after* the `Report` is ready (and never passed to the `samples` kwarg)
post_samples = factory.RelatedFactoryList(SampleFactory, 'report', size=3)
使用该代码,当您调用 ReportFactory
时,您:
- 生成
Report
没有任何样本
- 生成 3 个样本,向它们传递对刚刚生成的报告的引用
- 创建后,这些
Sample
个实例会附加到 Report.samples
我最终的解决方案实际上比我想象的要简单得多:
class ReportFactory(BaseFactory):
class Meta:
model = models.Report
samples = RelatedFactoryList('tests.factories.SampleFactory', 'report', size=3)
class SampleFactory(BaseFactory):
class Meta:
model = models.Sample
report = SubFactory(ReportFactory, samples=[])
关键是使用 RelatedFactoryList
的第二个参数,它必须对应于子 上的父 link,在这种情况下 'report'
。此外,我使用了 SubFactory(ReportFactory, samples=[])
,这确保了如果我构建单个样本,不会在父级上创建额外的样本。
使用此设置,我可以构建一个样本,该样本将关联一个 Report
,并且该报告只有 1 个子 Sample
。相反,我可以构造一个 Report
,它将自动填充 3 个子样本。
我认为没有必要生成实际的模型 ID,因为一旦模型实际插入到数据库中,SQLAlchemy 就会自动执行此操作。但是,如果您想在不使用数据库的情况下这样做,我认为@Xelnor 的 report_id = factory.SelfAttribute('report.id')
解决方案会起作用。
我遇到的唯一未解决的问题是覆盖报告中的示例列表(例如 ReportFactory(samples = [SampleFactory()])
),但我已经打开了一个记录此错误的问题:https://github.com/FactoryBoy/factory_boy/issues/636
我的 SQLAlchemy 模型中存在多对一关系。一份报告有很多样本(为简洁起见进行了简化):
class Sample(db.Model, CRUDMixin):
sample_id = Column(Integer, primary_key=True)
report_id = Column(Integer, ForeignKey('report.report_id', ondelete='CASCADE'), index=True, nullable=False)
report = relationship('Report', back_populates='samples')
class Report(db.Model, CRUDMixin):
report_id = Column(Integer, primary_key=True)
samples = relationship('Sample', back_populates='report')
现在在我的测试中,我希望能够生成一个 Sample
实例或一个 Report
实例,并填充缺失的关系。
class ReportFactory(BaseFactory):
class Meta:
model = models.Report
report_id = Faker('pyint')
samples = RelatedFactoryList('tests.factories.SampleFactory', size=3)
class SampleFactory(BaseFactory):
class Meta:
model = models.Sample
sample_id = Faker('pyint')
report = SubFactory(ReportFactory)
当我创建它们的实例时,工厂陷入无限循环:
RecursionError: maximum recursion depth exceeded in comparison
但是,如果我尝试使用 SelfAttribute
s 来停止无限循环,我最终会得到一个没有任何样本的报告:
class ReportFactory(BaseFactory):
samples = RelatedFactoryList('tests.factories.SampleFactory', size=3, report_id=SelfAttribute('..report_id'))
class SampleFactory(BaseFactory):
report = SubFactory(ReportFactory, samples=[])
report = factories.ReportFactory()
l = len(report.samples) # 0
但是,如果我用 SampleFactory()
生成 Sample
,它正确地有一个 Report
对象。
我应该如何正确设计我的工厂,以便 SampleFactory()
将生成具有关联 Report
的 Sample
,并且 ReportFactory()
将生成具有关联的 Report
2 个关联 Samples
,没有无限循环?
实例创建后,RelatedFactory
声明被评估:
- 实例化了
Report
- 对
SampleFactory
执行了 3 次调用 - 返回第1步实例化的
Report
为了填充 Report
个实例上的字段,您必须 link Sample
个实例到 Report
在第 2 步。
一个可能的实现是:
class SampleFactory(BaseFactory):
class Meta:
model = Sample
@classmethod
def _after_postgeneration(cls, instance, create, results=None):
if instance.report is not None and instance not in instance.report.samples:
instance.report.samples.append(instance)
id = factory.Faker('pyint')
# Enfore `post_samples = None` to prevent creating additional samples
report = factory.SubFactory('example.ReportFactory', samples=[], post_samples=None)
report_id = factory.SelfAttribute('report.id')
class ReportFactory(factory.Factory):
class Meta:
model = Report
id = factory.Faker('pyint')
# Set samples = [] if needed by `Report.__init__`
samples = []
# Named `post_samples` to mark that they are instantiated
# *after* the `Report` is ready (and never passed to the `samples` kwarg)
post_samples = factory.RelatedFactoryList(SampleFactory, 'report', size=3)
使用该代码,当您调用 ReportFactory
时,您:
- 生成
Report
没有任何样本 - 生成 3 个样本,向它们传递对刚刚生成的报告的引用
- 创建后,这些
Sample
个实例会附加到Report.samples
我最终的解决方案实际上比我想象的要简单得多:
class ReportFactory(BaseFactory):
class Meta:
model = models.Report
samples = RelatedFactoryList('tests.factories.SampleFactory', 'report', size=3)
class SampleFactory(BaseFactory):
class Meta:
model = models.Sample
report = SubFactory(ReportFactory, samples=[])
关键是使用 RelatedFactoryList
的第二个参数,它必须对应于子 上的父 link,在这种情况下 'report'
。此外,我使用了 SubFactory(ReportFactory, samples=[])
,这确保了如果我构建单个样本,不会在父级上创建额外的样本。
使用此设置,我可以构建一个样本,该样本将关联一个 Report
,并且该报告只有 1 个子 Sample
。相反,我可以构造一个 Report
,它将自动填充 3 个子样本。
我认为没有必要生成实际的模型 ID,因为一旦模型实际插入到数据库中,SQLAlchemy 就会自动执行此操作。但是,如果您想在不使用数据库的情况下这样做,我认为@Xelnor 的 report_id = factory.SelfAttribute('report.id')
解决方案会起作用。
我遇到的唯一未解决的问题是覆盖报告中的示例列表(例如 ReportFactory(samples = [SampleFactory()])
),但我已经打开了一个记录此错误的问题:https://github.com/FactoryBoy/factory_boy/issues/636