Repository: ilevkivskyi/typing_inspect

Issue:
Support Python 3.9
typing_inspect doesn't really work with Python 3.9 right now. There are issues like #60 and #64, but a variety of things need to be updated.
My initial survey (just guessing from poking at implementations; it would be better to have someone review/expand this list who is more intimately familiar with the code-level changes):
* The typing module in 3.9 introduces a new "_BaseGenericAlias" class that might more correctly correspond to typing_inspect's current uses of "_GenericAlias."
* Add support and tests for the new plain type hints (e.g. "list[int]" rather than "List[int]"). There seems to be a new "GenericAlias" class (that extends no other classes) for these kind of annotations.
* Get the current test suite to pass under 3.9.
* Add 3.9 to the travis config for regression testing.

Any volunteers willing to give it a try? @ilevkivskyi - any additional guidance? What's wrong/missing in my above list?

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index 1fb80e9..6cf3061 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@ matrix:
   fast_finish: true
   include:
     - python: 3.9
+    - python: 3.9.1
     - python: 3.8
     - python: 3.7
     - python: 3.6
diff --git a/typing_inspect.py b/typing_inspect.py
index d3a0604..c11b856 100644
--- a/typing_inspect.py
+++ b/typing_inspect.py
@@ -8,10 +8,14 @@ Example usage::
 # NOTE: This module must support Python 2.7 in addition to Python 3.x
 
 import sys
+import types
+
 from mypy_extensions import _TypedDictMeta as _TypedDictMeta_Mypy
-if sys.version_info[:3] >= (3, 4, 0) and sys.version_info[:3] < (3, 9, 0):
+
+# See comments in typing_extensions source on why the switch is at 3.9.2
+if (3, 4, 0) <= sys.version_info[:3] < (3, 9, 2):
     from typing_extensions import _TypedDictMeta as _TypedDictMeta_TE
-elif sys.version_info[:3] >= (3, 9, 0):
+elif sys.version_info[:3] >= (3, 9, 2):
     # typing_extensions.TypedDict is a re-export from typing.
     from typing import _TypedDictMeta as _TypedDictMeta_TE
 else:
@@ -35,7 +39,7 @@ if NEW_TYPING:
     from typing_extensions import Final, Literal
     if sys.version_info[:3] >= (3, 9, 0):
         from typing import _SpecialGenericAlias
-        typingGenericAlias = (_GenericAlias, _SpecialGenericAlias)
+        typingGenericAlias = (_GenericAlias, _SpecialGenericAlias, types.GenericAlias)
     else:
         typingGenericAlias = (_GenericAlias,)
 else:
@@ -463,7 +467,7 @@ def get_args(tp, evaluate=None):
     if NEW_TYPING:
         if evaluate is not None and not evaluate:
             raise ValueError('evaluate can only be True in Python >= 3.7')
-        if isinstance(tp, _GenericAlias):
+        if isinstance(tp, typingGenericAlias):
             res = tp.__args__
             if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
                 res = (list(res[:-1]), res[-1])
@@ -485,7 +489,7 @@ def get_args(tp, evaluate=None):
                 # backport of union's subs_tree
                 tree = _union_subs_tree(tp)
             elif is_generic_type(tp):
-                # backport of genericmeta's subs_tree
+                # backport of GenericMeta's subs_tree
                 tree = _generic_subs_tree(tp)
             elif is_tuple_type(tp):
                 # ad-hoc (inspired by union)


Test patch:
diff --git a/test_typing_inspect.py b/test_typing_inspect.py
index c5fc753..58a03ee 100644
--- a/test_typing_inspect.py
+++ b/test_typing_inspect.py
@@ -115,6 +115,7 @@ class Other(dict):
 """
 
 PY36 = sys.version_info[:3] >= (3, 6, 0)
+PY39 = sys.version_info[:3] >= (3, 9, 0)
 if PY36:
     exec(PY36_TESTS)
 
@@ -131,6 +132,8 @@ class IsUtilityTestCase(TestCase):
         T = TypeVar('T')
         samples = [Generic, Generic[T], Iterable[int], Mapping,
                    MutableMapping[T, List[int]], Sequence[Union[str, bytes]]]
+        if PY39:
+            samples.extend([list[int], dict[str, list[int]]])
         nonsamples = [int, Union[int, str], Union[int, T], Callable[..., T],
                       Optional, bytes, list] + CLASSVAR_GENERIC
         self.sample_test(is_generic_type, samples, nonsamples)
@@ -149,6 +152,8 @@ class IsUtilityTestCase(TestCase):
 
     def test_tuple(self):
         samples = [Tuple, Tuple[str, int], Tuple[Iterable, ...]]
+        if PY39:
+            samples.append(tuple[int, str])
         nonsamples = [int, tuple, 42, List[int], NamedTuple('N', [('x', int)])]
         self.sample_test(is_tuple_type, samples, nonsamples)
         if SUBCLASSABLE_TUPLES:
@@ -301,6 +306,8 @@ class GetUtilityTestCase(TestCase):
             self.assertEqual(get_origin(ClassVar[int]), None)
         self.assertEqual(get_origin(Generic), Generic)
         self.assertEqual(get_origin(Generic[T]), Generic)
+        if PY39:
+            self.assertEqual(get_origin(list[int]), list)
         if GENERIC_TUPLE_PARAMETRIZABLE:
             tp = List[Tuple[T, T]][int]
             self.assertEqual(get_origin(tp), list if NEW_TYPING else List)
@@ -323,6 +330,8 @@ class GetUtilityTestCase(TestCase):
         if EXISTING_UNIONS_SUBSCRIPTABLE:
             self.assertEqual(get_parameters(Union[S_co, Tuple[T, T]][int, U]), (U,))
         self.assertEqual(get_parameters(Mapping[T, Tuple[S_co, T]]), (T, S_co))
+        if PY39:
+            self.assertEqual(get_parameters(dict[int, T]), (T,))
 
     @skipIf(NEW_TYPING, "Not supported in Python 3.7")
     def test_last_args(self):
@@ -389,6 +398,11 @@ class GetUtilityTestCase(TestCase):
             self.assertEqual(get_args(Literal["value"], evaluate=True), ("value",))
             self.assertEqual(get_args(Literal[1, 2, 3], evaluate=True), (1, 2, 3))
 
+        if PY39:
+            self.assertEqual(get_args(list[int]), (int,))
+            self.assertEqual(get_args(tuple[int, str]), (int, str))
+            self.assertEqual(get_args(list[list[int]]), (list[int],))
+
     def test_bound(self):
         T = TypeVar('T')
         TB = TypeVar('TB', bound=int)