sbdchd / django-types

:doughnut: Type stubs for Django
MIT License
188 stars 62 forks source link

Nullable model fields have type error when trying to set None as value #111

Open dbushy727 opened 2 years ago

dbushy727 commented 2 years ago

Thank you so much for this package. It really has helped bring so much more visibility into my codebase. 🙏

One thing I'm struggling with is setting the right type when I have a nullable Model field. I find that when i look at an instance of the model, it shows me the value or none, but when trying to set None, i get a type error.

Django version: 3.2.10 Python version: 3.10.1


class MyModel(model.Model):
    # hover on variable shows DecimalField[Decimal]
    total_cost: Optional[models.DecimalField[Decimal]] = models.DecimalField(max_digits=12, decimal_places=2, null=True) 

my_model_instance = MyModel.objects.first()
# hover shows Decimal | None (which is great!)
my_model_instance.total_cost 

# hover shows error Cannot assign member "total_cost" for type "MyModel"  
# "None" is incompatible with "Decimal | Combinable"
my_model.total_cost = None 

Ive tried playing with the annotation and I can't seem to get it to work. I think this may be a bug in how the generic handles None.

class MyModel(model.Model):
    # hover on expression shows Expression of type "DecimalField[Decimal]" cannot be assigned to declared type 
    # "DecimalField[Decimal | None]"
    #  TypeVar "_DEC@DecimalField" is invariant
    #    Type "Decimal" cannot be assigned to type "Decimal | None"
    #      Type cannot be assigned to type "None"
    total_cost: models.DecimalField[Optional[Decimal]] = models.DecimalField()

When looking at the stub file for DecimalField, I noticed that i see a type error for the null field.

Screen Shot 2022-04-22 at 2 45 12 PM

I think the Literal is the problem and if we use the one from typings it works for me, but im not sure if there are downstream effects.

I'll submit a PR to showcase what im talking about. Please let me know if this is something im doing wrong, or something we can address to fix. Any help would be super appreciated, thanks!

sbdchd commented 2 years ago

That's really weird pylance is erroring about Literal, I think we have some test cases that use that functionality

sminnee commented 2 years ago

Locally I've tried importing Literal from typing rather than typing_extensions (python 3.9) and that seems to improve things. I think maybe pyright isn't treating typing_extensions.Literal correctly any more?

sminnee commented 2 years ago

I've noted this on the pyright project here: https://github.com/microsoft/pyright/issues/3936

sminnee commented 2 years ago

Never mind, this was a bug in my environment.