typeddjango / django-stubs

PEP-484 stubs for Django
MIT License
1.58k stars 436 forks source link

Add type hints to builtin models' fields #2214

Open flaeppe opened 3 months ago

flaeppe commented 3 months ago

There are quite a bit of changes in here, but it shouldn't produce any different results really.

It mainly allows pyright to also pick up types from the builtin models.

One note about the types for the fields: they have been copied from what has been declared in _pyi_private_set_type and _pyi_private_get_type for each field, so there shouldn't be any difference for mypy.

Related issues

Viicos commented 3 months ago

Hopefully this won't play too bad with https://github.com/typeddjango/django-stubs/pull/1900

flaeppe commented 3 months ago

I'm hoping it should play along since #1900 is more about inferring and defaults?

This instead sets explicit and specific get/set types

intgr commented 3 months ago

This is quite ugly.

Something that I have been pondering about since mypy 1.10 implemented PEP 696 Type Defaults for Type Parameters is: we could add default= to the TypeVars used for model fields.

The downside is that we would need separate TypeVar definitions for most field classes.

# before
class IntegerField(Field[_ST, _GT]):
    _pyi_private_set_type: float | int | str | Combinable
    _pyi_private_get_type: int
    _pyi_lookup_exact_type: str | int

# after
_ST_IntegerField = TypeVar('_ST_IntegerField', default=float | int | str | Combinable)
_GT_IntegerField = TypeVar('_GT_IntegerField', default=int)

class IntegerField(Field[_ST_IntegerField, _GT_IntegerField]):
    _pyi_private_set_type: float | int | str | Combinable
    _pyi_private_get_type: int
    _pyi_lookup_exact_type: str | int

This should improve experience in general for users who don't have the mypy django plugin enabled, and would remove the need for these manual generic arguments:

    password: models.CharField[str | int | Combinable, str]
    last_login: models.DateTimeField[str | datetime | date | Combinable, datetime | None]
    is_active: bool | models.BooleanField[bool | Combinable, bool]
flaeppe commented 3 months ago

I don't mind the generic arguments per se, model fields are generic. And I find it great that we can declare them explicitly for models included in the stubs, because then it becomes very clear what types are allowed.

What I do find off is that e.g. CharField declares int as one of its set types. I know that CharField casts/coerces to a string but to me that looks a bit like support for shotgun parsing.

I wouldn't mind a setup that's using default= either. I looked at that initially but was unsure if we would run into any problems until https://github.com/python/mypy/issues/14851#issuecomment-1988786756 has been fixed. I'm happy to change to a default= approach if we don't see any troubles with it.