django-stubs icon indicating copy to clipboard operation
django-stubs copied to clipboard

WithAnnotations[X, Y] gives runtime error (but typechecker is happy)

Open jordanbray opened this issue 2 years ago • 7 comments

Bug report

What's wrong

I am trying to use the WithAnnotations as documented with two parameters. The first is a django model, and the second is a typed dictionary. Here is a minified example:

from typing import TypedDict
from django_stubs_ext import WithAnnotations
from django.contrib.auth.models import User
  
class MinedData(TypedDict):
    """Use data science to get the information we need."""

    nose_hair_length: float

# Option 1: As Documented
def potential_customer(user: WithAnnotations[User, MinedData]) -> bool:
    return user.nose_hair_length > 2.0


# Option 2: As I tried to write it
PotentialRazorCustomer = WithAnnotations[User, MinedData]

def potential_customer2(user: PotentialRazorCustomer) -> bool:
    return user.nose_hair_length > 2.0
$ mypy --strict tester.py

Works as anticipated. However, when I try to run the program (in this case, with python manage.py shell < tester.py) the traceback for both Option 1 and Option 2 above is the same (save line numbers):

Traceback (most recent call last):
  File "/.../manage.py", line 22, in <module>
    execute_from_command_line(sys.argv)
  File "/.../lib/python3.9/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/.../lib/python3.9/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/.../lib/python3.9/site-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/.../lib/python3.9/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
  File "/.../lib/python3.9/site-packages/django/core/management/commands/shell.py", line 93, in handle
    exec(sys.stdin.read(), globals())
  File "<string>", line 12, in <module>
  File "/usr/lib/python3.9/typing.py", line 275, in inner
    return func(*args, **kwds)
  File "/usr/lib/python3.9/typing.py", line 758, in __getitem__
    _check_generic(self, params, len(self.__parameters__))
  File "/usr/lib/python3.9/typing.py", line 212, in _check_generic
    raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};"
TypeError: Too many parameters for typing.Annotated[~_T, django_stubs_ext.annotations.Annotations[+_Annotations]]; actual 2, expected 1

How is that should be

The above should typecheck correctly, and also run.

System information

  • OS: Linux (I use Arch, btw)
  • python version: 3.9.6
  • django version: 3.2
  • mypy version: 0.910
  • django-stubs version: 1.9.0
  • django-stubs-ext version: 0.3.1

jordanbray avatar Sep 24 '21 19:09 jordanbray

For people who find this via google, the above can be "fixed" by replacing:

PotentialRazorCustomer = WithAnnotations[User, MinedData]

with:

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    PotentialRazorCustomer = WithAnnotations[User, MinedData]
else:
    PotentialRazorCustomer = WithAnnotations[User]

This more-or-less gets you the best of both worlds.

jordanbray avatar Sep 27 '21 16:09 jordanbray

@jordanbray I would be happy to merge a PR with the fix! 👍

sobolevn avatar Sep 27 '21 17:09 sobolevn

When I try to do this, I get the error #1024 . I also have pyright complaigning about the same error, seemingly ignoring the TYPE_CHECKING If, as per https://github.com/typeddjango/django-stubs/issues/174#issuecomment-534210437 , I need to add # type: ignore, at every function defining my WithAnnotations types, this is a bit tedious.

AdrienLemaire avatar Jun 29 '22 07:06 AdrienLemaire

The above, sadly, does not work with VSCode and PyLance.

boatcoder avatar Oct 04 '22 20:10 boatcoder

From what I gathered it seems to be an error that occur with Python 3.8 ?

I managed to fix it in my code base by replacing by replacing WithAnnotation with Annotated from TypingExtension

eg

from typing_extensions import Annotated

class FormStatAnnotation(typing.TypedDict):
    form_stats: typing.Mapping[str, typing.Mapping]


OrgUnitWithFormStat = Annotated[OrgUnit, FormStatAnnotation]

olethanh avatar Mar 30 '23 09:03 olethanh

Any updates on this?

Briscoooe avatar Apr 05 '23 12:04 Briscoooe

From what I gathered it seems to be an error that occur with Python 3.8 ?

I managed to fix it in my code base by replacing by replacing WithAnnotation with Annotated from TypingExtension

eg

from typing_extensions import Annotated

class FormStatAnnotation(typing.TypedDict):
    form_stats: typing.Mapping[str, typing.Mapping]


OrgUnitWithFormStat = Annotated[OrgUnit, FormStatAnnotation]

@olethanh But is that doing the same thing? {typing,typing_extensions}.Annotated[T, X] is the type T with some metadata represented by X, e.g. to be interpreted at runtime. OTOH django_stubs_ext.WithAnnotations[T, X] is a new type, T with additional members specified by the typed dict X. I haven't checked, but I assume OrgUnitWithFormStat won't be considered as having an attribute form_stats in your example.

This line is not easy to parse 😅: https://github.com/typeddjango/django-stubs/blob/0a006f2dda989679b3d09fc876677bd90e1e5cb4/ext/django_stubs_ext/annotations.py#L18

Feuermurmel avatar Mar 22 '24 16:03 Feuermurmel