types-factory-boy icon indicating copy to clipboard operation
types-factory-boy copied to clipboard

mypy complains that "Need type annotation" for Faker fields

Open anentropic opened this issue 2 years ago • 7 comments

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 avatar Dec 04 '22 21:12 anentropic

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 avatar Dec 04 '22 22:12 anentropic

I have the same issue with SubFactory

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

anentropic avatar Dec 04 '22 22:12 anentropic

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.

youtux avatar Dec 07 '22 22:12 youtux

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

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

anentropic avatar Dec 08 '22 10:12 anentropic

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?

jamesbraza avatar Aug 05 '23 00:08 jamesbraza

I'm not sure tbh

youtux avatar Aug 12 '23 07:08 youtux

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.

4c0n avatar Oct 18 '23 15:10 4c0n