Repository: python/typing_extensions

Issue:
Third-party tests failed on Thu Apr 17 2025
Run listed here: https://github.com/python/typing_extensions/actions/runs/14506991058

Discussion:
@Viicos: Old dirty code from Pydantic v1, I'll fix it.@JelleZijlstra: Thanks! This would still mean that future releases of typing-extensions will be incompatible with any currently released version of Pydantic, right? It might be worth re-adding `_AnnotatedAlias` (maybe just re-export it from typing) to avoid disrupting users who don't want to immediately upgrade Pydantic.@Viicos: > This would still mean that future releases of typing-extensions will be incompatible with any currently released version of Pydantic, right?

Only Pydantic V1, so the impact is limited (and while we still rely on a couple private `typing` members such as `_GenericAlias`, it was a bad idea to rely on `_AnnotatedAlias` as it could be easily avoided). I'll make a new Pydantic V1 release today and while users could be using an old V1 version, upgrading is really straightforward as only compatibility fixes were released for a while now.

 @JelleZijlstra: Ah right, it would only be if users explicitly import `pydantic.v1`.

Looks like litestar also relies on `_AnnotatedAlias` (https://github.com/python/typing_extensions/actions/runs/14506991058/job/40698118697), cc @provinzkraut. 

I feel like that's enough reason to re-add the private symbol to typing-extensions (though I'd still encourage you to avoid using it).@provinzkraut: > I feel like that's enough reason to re-add the private symbol to typing-extensions (though I'd still encourage you to avoid using it).

Mh. We use it to check *if* something is `Annotated`. The typing docs only say 

> At runtime, the metadata associated with an `Annotated` type can be retrieved via the `__metadata__` attribute

but that is brittle. Many things can have a `__metadata__` attribute, and we'd like to avoid false positives here. If we shouldn't use this (or its stdlib equivalent), what would be the canonical way to check if something is `Annotated` and extract its associated metadata in a safe way?

I'd also prefer not to use undocumented private types, but currently don't know of a better alternative. @JelleZijlstra: Like this:

```
>>> import typing
>>> typing.get_origin(typing.Annotated[int, 1])
typing.Annotated
>>> typing.get_args(typing.Annotated[int, 1])
(<class 'int'>, 1)
```

`get_origin()` and `get_args()` are the answer to most introspection needs in my experience.@Viicos: @provinzkraut you can also rely on our `typing-inspection` library as discussed earlier in one of litestar's PR. It does not use anything private from `typing`, and avoids many common issues (checking against both typing and typing_extensions variants, working around CPython bugs, etc).@AlexWaygood: @provinzkraut we also have some docs on robust strategies to introspect typing constructs here: https://typing-extensions.readthedocs.io/en/latest/#runtime-use-of-types@provinzkraut: > Like this:
> 
> ```
> >>> import typing
> >>> typing.get_origin(typing.Annotated[int, 1])
> typing.Annotated
> >>> typing.get_args(typing.Annotated[int, 1])
> (<class 'int'>, 1)
> ```
> 
> `get_origin()` and `get_args()` are the answer to most introspection needs in my experience.

Heh, thanks for the reminder. This comment made me re-visit our code again, and it turns out the util that has failed in the run above isn't actually used anymore, and we've migrated away from using `_AnnotatedAlias` everywhere else some time ago. I had just forgotten about this :)

We should be able to just remove the offending code, so there will be no compatibility issue from our side anymore.@JelleZijlstra: Great, thanks! I think it still makes sense to keep the name defined in typing-extensions for some time so users can safely upgrade typing-extensions without having to worry about upgrading other libraries, and because there are surely more people using `_AnnotatedAlias` than the two of you.

Solution patch:
diff --git a/src/typing_extensions.py b/src/typing_extensions.py
index f8b2f76..1c968f7 100644
--- a/src/typing_extensions.py
+++ b/src/typing_extensions.py
@@ -4095,7 +4095,7 @@ else:
         )
 
 
-# Aliases for items that have always been in typing.
+# Aliases for items that are in typing in all supported versions.
 # Explicitly assign these (rather than using `from typing import *` at the top),
 # so that we get a CI error if one of these is deleted from typing.py
 # in a future version of Python
@@ -4136,3 +4136,6 @@ ValuesView = typing.ValuesView
 cast = typing.cast
 no_type_check = typing.no_type_check
 no_type_check_decorator = typing.no_type_check_decorator
+# This is private, but it was defined by typing_extensions for a long time
+# and some users rely on it.
+_AnnotatedAlias = typing._AnnotatedAlias


Test patch:
diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py
index a694895..095505a 100644
--- a/src/test_typing_extensions.py
+++ b/src/test_typing_extensions.py
@@ -5251,6 +5251,11 @@ class AnnotatedTests(BaseTestCase):
         self.assertEqual(X.__origin__, List[Annotated[str, {"unhashable_metadata"}]])
         self.assertEqual(X.__metadata__, ("metadata",))
 
+    def test_compatibility(self):
+        # Test that the _AnnotatedAlias compatibility alias works
+        self.assertTrue(hasattr(typing_extensions, "_AnnotatedAlias"))
+        self.assertIs(typing_extensions._AnnotatedAlias, typing._AnnotatedAlias)
+
 
 class GetTypeHintsTests(BaseTestCase):
     def test_get_type_hints(self):