Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

factory.SubFactory fixture collision when the model has the same name #218

Open
mcosti opened this issue Apr 30, 2024 · 6 comments
Open

Comments

@mcosti
Copy link

mcosti commented Apr 30, 2024

Hi everyone! (Long time no talk)

I just came into the following situation that I can't seem to be able to untangle.

I have a "Product" model that is part of our core business logic, and a "Product" model coming in from Dj-stripe

When trying to set up the stripe models and relationships via the factories, I am running into a name collision

import factory
from djstripe.enums import SubscriptionStatus
from factory.django import DjangoModelFactory
from djstripe.models import (
    Product as SubscriptionProduct,
    Plan as SubscriptionPlan,
    Customer as SubscriptionCustomer,
    Price as SubscriptionPrice,
    Subscription, SubscriptionItem
)


class SubscriptionProductFactory(DjangoModelFactory):
    class Meta:
        model = SubscriptionProduct

    name = "Pro"

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        product = super()._create(model_class, *args, **kwargs)
        return product


class SubscriptionPriceFactory(DjangoModelFactory):
    class Meta:
        model = SubscriptionPrice

    active = True
    currency = "EUR"
    unit_amount = 1000
    product_test = factory.SubFactory("factories.stripe.SubscriptionProductFactory")
    lookup_key = "pro_monthly"

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        price = super()._create(model_class, *args, **kwargs)
        return price

But when the SubscriptionProductFactory is used as a SubFactory, the inner factoryboy code is evaluating it's model.name

image

Which returns the fixture of the other Product model.

Since the original Product class is used everywhere, refactoring is not an option, and the model coming in from the external package is not possible to change.

Is there any solution for this?

Thanks

@youtux
Copy link
Contributor

youtux commented May 2, 2024

You have a few options:

  • Use pytest_factoryboy.named_model:
class SubscriptionProductFactory(DjangoModelFactory):
    class Meta:
        model = named_model("SubscriptionProduct", SubscriptionProduct)
    ...
  • register the parent fixture passing the correct fixture to use for product_test:
register(
    SubscriptionPriceFactory,
    product_test=LazyFixture("subscription_product)
)

@mcosti
Copy link
Author

mcosti commented May 6, 2024

Thank you for taking the time!

The first variant does not work. (the arguments are reversed, the class is first, but I did that and it didn't work anyway)

backend-tests-1  | ../../../factories/stripe.py:69: in <module>
backend-tests-1  |     class SubscriptionProductFactory(StripeBaseFactory):
backend-tests-1  | ../../../factories/stripe.py:70: in SubscriptionProductFactory
backend-tests-1  |     class Meta:
backend-tests-1  | ../../../factories/stripe.py:71: in Meta
backend-tests-1  |     model = named_model(SubscriptionProduct, "subscription_product")
backend-tests-1  | /usr/local/lib/python3.11/dist-packages/pytest_factoryboy/fixture.py:75: in named_model
backend-tests-1  |     return type(name, (model_cls,), {})
backend-tests-1  | /usr/local/lib/python3.11/dist-packages/django/db/models/base.py:105: in __new__
backend-tests-1  |     module = attrs.pop("__module__")
backend-tests-1  | E   KeyError: '__module__'

But the second option works. What do you think of my fix at the factory definition level?

Thanks

@youtux
Copy link
Contributor

youtux commented May 6, 2024

maybe the SubscriptionProduct class is not a django model? In that case you have to use ModelFactoty instead of DjangoModelFactory.

About the PR, I don’t think it’s needed since we have already 2 ways of solving this.
Maybe we should just improve the README.md to showcase this scenario.

@mcosti
Copy link
Author

mcosti commented May 6, 2024

The problem extends even further since in this specific case, all of my "Stripe" models have a SubFactory pointing to an "Account" model (also coming from Stripe, and also a namespace conflict), so the solution for overriding at on register(...) doesn't sound very pleasant,
unless I write a custom register method that appends account=LazyFixture("stripe_account") to each registration

@mcosti
Copy link
Author

mcosti commented May 6, 2024

SubscriptionProduct is actually an import alias. The model itself is called "Product", which is where this issue stems from

@youtux
Copy link
Contributor

youtux commented May 10, 2024

You can always use the first option I suggested, it should work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants