#36696: django.utils.inspect leads to NameError with Python 3.14
-------------------------------------+-------------------------------------
     Reporter:  Patrick Rauscher     |                    Owner:  (none)
         Type:  Bug                  |                   Status:  new
    Component:  Utilities            |                  Version:  5.2
     Severity:  Release blocker      |               Resolution:
     Keywords:                       |             Triage Stage:  Accepted
  typing,inspect,deferred            |
  annotations                        |
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

 * keywords:  typing,inspect => typing,inspect,deferred annotations
 * severity:  Normal => Release blocker
 * stage:  Unreviewed => Accepted
 * type:  Uncategorized => Bug


Old description:

> Starting with Python 3.14, deferred evaluation of annotations (PEP-649)
> is default now. This would suggest that using annotations in templatetags
> or signals should be as easy as:
>
> {{{
> from typing import TYPE_CHECKING
> from django.contrib.auth.signals import user_logged_in
> from django.dispatch import receiver
>
> if TYPE_CHECKING:
>     from django.http import HttpRequest
>
> @receiver(user_logged_in)
> def handle_successful_login(request: HttpRequest, *args, **kwargs) ->
> None:
>     print("Someone logged in successful - this would contain useful
> code")
> }}}
>
> Weirdly, as long as DEBUG is set to False, this code will run without
> problems. However, if DEBUG is turned to True, Signal.connect will use
> func_accepts_kwargs from django.utils.inspect to check if
> handle_successful_login accepts kwargs. func_accepts_kwargs will
> ultimatively use inspect.signature on the function, which triggers
> evaluation of the annotations and leads to NameError, as TYPE_CHECKING is
> False here.
>
> This can be resolved in various ways:
> 1. One can add from __future__ import annotations to switch back to
> Stringified annotations. However, this is called to be deprecated in the
> future
> 2. One can switch to always include HttpRequest. This would work, but
> code linters will fight this, argueing that HttpRequest is only used as
> an type annotation here (which is correct)
> 3. As django.utils.inspect does not actually care for the annotations,
> inspect.inspect(..., annotation_format=annotationlib.Format.STRING) can
> be used (sadly this works only in Python 3.14).
>
> My suggestion would be to something around
>
> {{{
> @functools.lru_cache(maxsize=512)
> def _get_func_parameters(func, remove_first):
>     if sys.version_info[0:2] >= (3, 14):
>         import annotationlib
>         signature = inspect.signature(func,
> annotation_format=annotationlib.Format.STRING)
>     else:
>         signature = inspect.signature(func)
>     parameters = tuple(signature.parameters.values())
>     if remove_first:
>         parameters = parameters[1:]
>     return parameters
> }}}
>
> But I agree that it might not be the nicest solution.

New description:

 Starting with Python 3.14, deferred evaluation of annotations (PEP-649) is
 default now. This would suggest that using annotations in templatetags or
 signals should be as easy as:

 {{{
 from typing import TYPE_CHECKING
 from django.contrib.auth.signals import user_logged_in
 from django.dispatch import receiver

 if TYPE_CHECKING:
     from django.http import HttpRequest

 @receiver(user_logged_in)
 def handle_successful_login(request: HttpRequest, *args, **kwargs) ->
 None:
     print("Someone logged in successful - this would contain useful code")
 }}}

 Weirdly, as long as DEBUG is set to False, this code will run without
 problems. However, if DEBUG is turned to True, Signal.connect will use
 func_accepts_kwargs from django.utils.inspect to check if
 handle_successful_login accepts kwargs. func_accepts_kwargs will
 ultimatively use inspect.signature on the function, which triggers
 evaluation of the annotations and leads to NameError, as TYPE_CHECKING is
 False here.

 This can be resolved in various ways:
 1. One can add from __future__ import annotations to switch back to
 Stringified annotations. However, this is called to be deprecated in the
 future
 2. One can switch to always import HttpRequest. This would work, but code
 linters will fight this, argueing that HttpRequest is only used as an type
 annotation here (which is correct)
 3. As django.utils.inspect does not actually care for the annotations,
 inspect.inspect(..., annotation_format=annotationlib.Format.STRING) can be
 used (sadly this works only in Python 3.14).

 My suggestion would be to something around

 {{{
 @functools.lru_cache(maxsize=512)
 def _get_func_parameters(func, remove_first):
     if sys.version_info[0:2] >= (3, 14):
         import annotationlib
         signature = inspect.signature(func,
 annotation_format=annotationlib.Format.STRING)
     else:
         signature = inspect.signature(func)
     parameters = tuple(signature.parameters.values())
     if remove_first:
         parameters = parameters[1:]
     return parameters
 }}}

 But I agree that it might not be the nicest solution.

--
Comment:

 Thanks for the report. I might opt for `FORWARDREF` instead of `STRING`,
 but otherwise I agree.

 Would you like to submit a PR targeting main?

 I think this should be backported as part of Python 3.14 support, as
 otherwise you need to know to watch out for this gotcha when using a major
 3.14 language feature.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36696#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/0107019a32194b09-2ba88d0e-8265-4a4b-810c-953c1067823a-000000%40eu-central-1.amazonses.com.

Reply via email to