Python 与工厂男孩的一对多关系

Python 与工厂男孩的一对多关系,python,sqlalchemy,factory-boy,Python,Sqlalchemy,Factory Boy,在我的SQLAlchemy模型中,我有一个多对一的关系。一份报告包含多个示例(为简洁起见,进行了简化): 类示例(db.Model,CRUDMixin): 样本id=列(整数,主键=真) report\u id=Column(整数,ForeignKey('report.report\u id',ondelete='CASCADE'),index=True,nullable=False) 报告=关系('report',back_populates='samples') 课堂报告(db.Model,

在我的SQLAlchemy模型中,我有一个多对一的关系。一份报告包含多个示例(为简洁起见,进行了简化):

类示例(db.Model,CRUDMixin):
样本id=列(整数,主键=真)
report\u id=Column(整数,ForeignKey('report.report\u id',ondelete='CASCADE'),index=True,nullable=False)
报告=关系('report',back_populates='samples')
课堂报告(db.Model,CRUDMixin):
报告id=列(整数,主键=真)
样本=关系('Sample',back_populates='report')
现在在我的测试中,我希望能够生成一个
示例
实例,或者一个
报告
实例,并填充缺少的关系

类报告工厂(BaseFactory):
类元:
模型=模型。报告
报告\u id=Faker('pyint'))
samples=RelatedFactoryList('tests.factories.SampleFactory',size=3)
类别样本工厂(BaseFactory):
类元:
model=models.Sample
样本_id=Faker('pyint'))
报表=子工厂(报表工厂)
当我去创建这些实例时,工厂陷入无限循环:

RecursionError: maximum recursion depth exceeded in comparison
但是,如果我尝试使用
SelfAttribute
s来停止无限循环,我最终会得到一个没有任何示例的报告:

类报告工厂(BaseFactory):
samples=RelatedFactoryList('tests.factories.SampleFactory',size=3,report\u id=SelfAttribute(“…report\u id”))
类别样本工厂(BaseFactory):
报告=子工厂(报告工厂,样本=[])
但是,如果我使用
SampleFactory()
生成
Sample
,则它正确地具有一个
报告
对象


我应该如何正确地设计我的工厂,以便
SampleFactory()
将生成带有关联
报告的
样本
,而
ReportFactory()
将生成带有两个关联
样本的
报告
,没有无限循环?

创建实例后,将对
RelatedFactory
声明进行评估:

  • 将实例化
    报告
  • 执行3次调用
    SampleFactory
  • 返回步骤1中实例化的
    报告
  • 为了填充
    报告
    实例上的字段,必须在步骤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
    时,您:

  • 生成不带任何样本的
    报告
  • 生成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
    的第二个参数,该参数必须对应于子对象上的父链接,在本例中为
    'report'
    。此外,我还使用了
    子工厂(ReportFactory,samples=[])
    ,这确保了在构建单个样本时不会在父级上创建额外的样本

    通过此设置,我可以构造一个示例,该示例将有一个与之关联的
    报告
    ,并且该报告只有一个子
    示例
    。相反,我可以构建一个
    报告
    ,该报告将自动填充3个子样本

    我认为不需要生成实际的模型ID,因为一旦模型实际插入到数据库中,SQLAlchemy就会自动生成模型ID。但是,如果您想在不使用数据库的情况下实现这一点,我认为@Xelnor的
    report\u id=factory.SelfAttribute('report.id')
    解决方案是可行的


    我唯一未解决的问题是覆盖报表上的样本列表(例如,
    ReportFactory(samples=[SampleFactory()])
    ),但我已经打开了一个记录此错误的问题:

    但是自从您命名了
    RelatedFactoryList
    发布样本之后,生成的
    报表
    中不会有
    示例
    吗?不会,因为我们在创建报表时,会在
    \u postgeneration
    钩子中手动将每个
    示例
    附加到其
    报表
    中。这也很有效,但仅是因为ORM的基本功能:当您阅读
    报表时。示例
    ,SQLAlchemy将动态获取数据库(或会话)中指向特定报告的
    样本
    对象列表。如果您没有使用ORM,您必须手动链接它们。感谢您的澄清。不过,我在问题中确实提到了炼金术。
    
    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=[])