youtux / types-factory-boy

Type stubs for factory-boy
MIT License
18 stars 4 forks source link

mypy complains that "Need type annotation" for Faker fields #7

Open anentropic opened 1 year ago

anentropic commented 1 year ago

I have factories like:

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User

    username = factory.Sequence(lambda n: f"{_faker.user_name()}_{n:08}")
    first_name = factory.Faker("first_name")
    last_name = factory.Faker("last_name")
    email = factory.Faker("ascii_safe_email")

with mypy 0.982 I get:

$ mypy --show-error-codes .
blog/factories.py:28: error: Need type annotation for "first_name"  [var-annotated]
blog/factories.py:29: error: Need type annotation for "last_name"  [var-annotated]
blog/factories.py:30: error: Need type annotation for "email"  [var-annotated]

so all the factory.Faker fields have a problem

I can guess this is probably due to the way factory.Faker itself is implemented - the type of the faker depends on the value of the string arg and there's no way for type info from faker itself to flow through that interface.

I guess the only way for that to work would be to tediously define an @overload with Literal[<method name>] for each faker method? Maybe that's not even possible either.

anentropic commented 1 year ago

I can annotate them like

first_name: factory.Faker[Any, str] = factory.Faker("first_name")

I think that's right? not sure what the first type param represents

anentropic commented 1 year ago

I have the same issue with SubFactory

user = factory.SubFactory(UserFactory)
blog/factories.py:38: error: Need type annotation for "user"  [var-annotated]
youtux commented 1 year ago

The thing is that Factory classes can't really be instantiated, so whatever we decide here doesn't really matter in this sense. That being said, it's indeed annoying that we have to type every single line.

Maybe these factory fields should be annotated with the same type as the model? If so, it's doable to write a mypy plugin that fills them in for you.

But I would need to see some use cases of how these fields annotation can actually be used for something.

For example, maybe we can tell the type checker that the argument of LazyAttribute is an instance of the factory class (although it isn't), and then we could access this class with correct types.

I'm not sure if I was clear, I'm not 100% sure either of how things should work in these cases.

anentropic commented 1 year ago

my main motivation is just to make the errors go away...

for now I have just excluded my factories.py from mypy 😀

jamesbraza commented 1 year ago

Hitting this today.

Maybe these factory fields should be annotated with the same type as the model?

When I do something like user: User = factory.SubFactory(UserFactory), I get:

file.py: error: Incompatible types in assignment
(expression has type "SubFactory[<nothing>, Any]", variable has type "User")
[assignment]
        user: User = factory.SubFactory(UserFactory)
                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

So that's not working out for me, mypy is throwing an assignment error. Anything I can do from there?

youtux commented 1 year ago

I'm not sure tbh

4c0n commented 1 year ago

Not really a solution to the actual problem, but as a workaround you could use LazyFunction with faker directly, for example:

import factory
import faker

fake = faker.Faker()

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User

    username = factory.Sequence(lambda n: f"{_faker.user_name()}_{n:08}")
    first_name = factory.LazyFunction(fake.first_name)
    last_name = factory.LazyFunction(fake.last_name)
    email = factory.LazyFunction(fake.ascii_safe_email)

That at least seems to keep mypy quiet for now :laughing: Faker itself is a typed package, so maybe that's why.