Repository: python/cpython (Python)

Issue:
Deprecate typing.no_type_check_decorator
# Feature or enhancement

We should deprecate `typing.no_type_check_decorator`, with a view to removing it in Python 3.15.

# Pitch

The typing documentation describes `typing.no_type_check_decorator` like this:

> Decorator to give another decorator the [no_type_check()](https://docs.python.org/3/library/typing.html#typing.no_type_check) effect.
> 
> This wraps the decorator with something that wraps the decorated function in [no_type_check()](https://docs.python.org/3/library/typing.html#typing.no_type_check).

In 2023, this is frankly misleading for users of the `typing` module. Unlike its twin `@no_type_check`, no major type checker has yet added support for `no_type_check_decorator`, despite the fact that it was added to the typing module in Python 3.5. If it's been 8 years and it still hasn't had support added by any major type checker, it's unlikely to ever be usable or useful. Meanwhile, the decorator is fairly obscure, and you can achieve the same effect using other tools such as `@typing.no_type_check` and `typing.Annotated`. There have been a few feature requests to type checkers over the years asking them to add support, but none of them have a significant number of upvotes. 

We should simply deprecate `@no_type_check_decorator`, with a view to removing it in Python 3.15. (Note that I **do not** propose deprecating `@no_type_check`, which is supported by mypy and reasonably popular among mypy users.)

# Previous discussion

- https://github.com/python/mypy/issues/6583
- https://github.com/google/pytype/issues/1452
- https://github.com/microsoft/pyright/issues/1448 (this issue is about `no_type_check` rather than `no_type_check_decorator`, but it shows that pyright is unlikely to ever support either decorator, unlike mypy which supports the former but not the latter)

<!-- gh-linked-prs -->
### Linked PRs
* gh-106312
<!-- /gh-linked-prs -->



Discussion:
@agronholm, I see that typeguard is one of the very few libraries that makes use of this decorator: https://github.com/agronholm/typeguard/blob/f377be389765ed0db104b41d78fce3c45e72e149/tests/dummymodule.py#L46-L48.

Does typeguard have special handling for `@no_type_check_decorator`? Would you oppose us removing it? Do you know if it's used by users of typeguard?

Typeguard's a fairly high-profile runtime type-checking library, so this gives me pause.
Oh, I see it says [here](https://typeguard.readthedocs.io/en/latest/userguide.html#permanently-suppressing-type-checks-for-selected-functions) in the docs for typeguard:

> Warning
> 
> The [@no_type_check_decorator](https://docs.python.org/3/library/typing.html#typing.no_type_check_decorator) decorator is not currently recognized by Typeguard.

I suppose that answers my question :)
Yep, as of right now, typeguard doesn't have the facilities to support it.
I just learned of this because I'm subscribed to tickets on mypy and pytype for this.

I think this is worthwhile functionality and the only reason that it's not being used is because it's not implemented in mypy which leads what the other checkers tend to do -- a chicken and egg problem.

Without this feature, there is no ergonomic way to use type annotations for data other than types, in a code base that is statically typed. Typically the way that you would use this data is with a class decorator, and without this feature a class decorator can't declare that it has the same power as `@no_type_check`, so you must manually put `@no_type_check` on every class where you use annotations for data.

The closest solution is `Annotated` which is very verbose compared to just putting the data you want in the annotation position, especially if you don't intend to provide real types at all in which case you are writing `Annotated(unused, actual_data)` instead of just `actual_data`.

You can try and encode data within types so that static checkers will allow your annotations, but this is severely hindered by how limited the python static type system is. A completely reasonable thing to do would be something like this:

```
@generate_cpp_binding
class MyClass:
    x: pick_based_on_version(u32, u64, version)
```

However all static checkers currently either forbid having a function call in the annotation position, or [have indicated their intention to do so](https://github.com/google/pytype/issues/1451#issuecomment-1610246977). This might seem odd for someone used to Python, but C++/D/Rust all allow compile time execution of code that can be used to influence what types are used for function-arguments/members/etc.

In the absence of this feature the easier thing for users to do if they want to use the annotation syntax for their own data is to just not use static checking at all. CPython has no problem with letting you put arbitrary expressions in annotation position and `inspect`ing the results later.
@jgarvin are you sure you are talking about `no_type_check_decorator` and not `@no_type_check`? Only the former is being deprecated.
@jgarvin I think it's really unlikely any type checker is going to add support for this feature any time soon, unfortunately. The [mypy issue requesting that mypy add support](https://github.com/python/mypy/issues/6583) has been open for four years, and has 0 upvotes -- and mypy doesn't even support adding _`@no_type_check`_ on classes yet (https://github.com/python/mypy/issues/607). Speaking as a mypy triager, I think adding support for `@no_type_check_decorator` would be very far from the top of the agenda of the mypy core team, even if we reversed the deprecation.

As a practical matter, I honestly think it's time to pull the plug and say that this feature has failed, at this point. I'm sorry that this isn't the result that you want.

I _would_ advise you to manually add `@no_type_check` to all classes you're decorating with `generate_cpp_binding`:

```py
@generate_cpp_binding
@no_type_check
class MyClass:
    x: pick_based_on_version(u32, u64, version)
```

Or perhaps do something like this:

```py
if TYPE_CHECKING:
    from typing import no_type_check as generate_cpp_binding
else:
    from my_library import generate_cpp_binding

@generate_cpp_binding
class MyClass:
    x: pick_based_on_version(u32, u64, version)
```

But, as previously mentioned, mypy doesn't even support `@no_type_check` on classes currently.
> the only reason that it's not being used is because it's not implemented in mypy which leads what the other checkers tend to do -- a chicken and egg problem.

For what it's worth, this isn't true, by the way — pyre and pyright frequently implement support for experimental features long before mypy does, and the pyright and pyre teams have brought forward several proposals for substantially enhancing the type system that have been accepted. If any other type checker wanted to add support for this feature before mypy, I don't think they would hold off from doing so.



Patch:
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 7ac1062eb26d7c..114515158f8338 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -2844,6 +2844,9 @@ Functions and decorators
    This wraps the decorator with something that wraps the decorated
    function in :func:`no_type_check`.
 
+   .. deprecated-removed:: 3.13 3.15
+      No type checker ever added support for ``@no_type_check_decorator``. It
+      is therefore deprecated, and will be removed in Python 3.15.
 
 .. decorator:: override
 
@@ -3628,18 +3631,20 @@ Certain features in ``typing`` are deprecated and may be removed in a future
 version of Python. The following table summarizes major deprecations for your
 convenience. This is subject to change, and not all deprecations are listed.
 
-+----------------------------------+---------------+-------------------+----------------+
-|  Feature                         | Deprecated in | Projected removal | PEP/issue      |
-+==================================+===============+===================+================+
-|  ``typing`` versions of standard | 3.9           | Undecided         | :pep:`585`     |
-|  collections                     |               |                   |                |
-+----------------------------------+---------------+-------------------+----------------+
-|  ``typing.ByteString``           | 3.9           | 3.14              | :gh:`91896`    |
-+----------------------------------+---------------+-------------------+----------------+
-|  ``typing.Text``                 | 3.11          | Undecided         | :gh:`92332`    |
-+----------------------------------+---------------+-------------------+----------------+
-|  ``typing.Hashable`` and         | 3.12          | Undecided         | :gh:`94309`    |
-|  ``typing.Sized``                |               |                   |                |
-+----------------------------------+---------------+-------------------+----------------+
-|  ``typing.TypeAlias``            | 3.12          | Undecided         | :pep:`695`     |
-+----------------------------------+---------------+-------------------+----------------+
++-------------------------------------+---------------+-------------------+----------------+
+|  Feature                            | Deprecated in | Projected removal | PEP/issue      |
++=====================================+===============+===================+================+
+|  ``typing`` versions of standard    | 3.9           | Undecided         | :pep:`585`     |
+|  collections                        |               |                   |                |
++-------------------------------------+---------------+-------------------+----------------+
+|  ``typing.ByteString``              | 3.9           | 3.14              | :gh:`91896`    |
++-------------------------------------+---------------+-------------------+----------------+
+|  ``typing.Text``                    | 3.11          | Undecided         | :gh:`92332`    |
++-------------------------------------+---------------+-------------------+----------------+
+|  ``typing.Hashable`` and            | 3.12          | Undecided         | :gh:`94309`    |
+|  ``typing.Sized``                   |               |                   |                |
++-------------------------------------+---------------+-------------------+----------------+
+|  ``typing.TypeAlias``               | 3.12          | Undecided         | :pep:`695`     |
++-------------------------------------+---------------+-------------------+----------------+
+|  ``typing.no_type_check_decorator`` | 3.13          | 3.15              | :gh:`106309`   |
++-------------------------------------+---------------+-------------------+----------------+
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 9696dd4ff0b700..d0dbe9a78d2fca 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -160,6 +160,10 @@ Deprecated
   ``NT = NamedTuple("NT", [])``. To create a TypedDict class with 0 fields, use
   ``class TD(TypedDict): pass`` or ``TD = TypedDict("TD", {})``.
   (Contributed by Alex Waygood in :gh:`105566` and :gh:`105570`.)
+* :func:`typing.no_type_check_decorator` is deprecated, and scheduled for
+  removal in Python 3.15. After eight years in the :mod:`typing` module, it
+  has yet to be supported by any major type checkers.
+  (Contributed by Alex Waygood in :gh:`106309`.)
 
 * :mod:`array`'s ``'u'`` format code, deprecated in docs since Python 3.3,
   emits :exc:`DeprecationWarning` since 3.13
diff --git a/Lib/typing.py b/Lib/typing.py
index 9187b74b0e2e1f..387b4c5ad5284b 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -2395,6 +2395,8 @@ def no_type_check_decorator(decorator):
     This wraps the decorator with something that wraps the decorated
     function in @no_type_check.
     """
+    import warnings
+    warnings._deprecated("typing.no_type_check_decorator", remove=(3, 15))
     @functools.wraps(decorator)
     def wrapped_decorator(*args, **kwds):
         func = decorator(*args, **kwds)
diff --git a/Misc/NEWS.d/next/Library/2023-07-01-16-51-55.gh-issue-106309.hSlB17.rst b/Misc/NEWS.d/next/Library/2023-07-01-16-51-55.gh-issue-106309.hSlB17.rst
new file mode 100644
index 00000000000000..5bd3880520871f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-07-01-16-51-55.gh-issue-106309.hSlB17.rst
@@ -0,0 +1,2 @@
+Deprecate :func:`typing.no_type_check_decorator`. No major type checker ever
+added support for this decorator. Patch by Alex Waygood.


Test patch:
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 1df21926d1f67e..0450a87577ecea 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -5794,10 +5794,14 @@ class F:
                 get_type_hints(clazz)
 
     def test_meta_no_type_check(self):
-
-        @no_type_check_decorator
-        def magic_decorator(func):
-            return func
+        depr_msg = (
+            "'typing.no_type_check_decorator' is deprecated "
+            "and slated for removal in Python 3.15"
+        )
+        with self.assertWarnsRegex(DeprecationWarning, depr_msg):
+            @no_type_check_decorator
+            def magic_decorator(func):
+                return func
 
         self.assertEqual(magic_decorator.__name__, 'magic_decorator')