From 28ce359917b5b4cec99d9586bcf2b401a2a014f4 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 01:29:32 +0100 Subject: [PATCH 01/15] Use conditional definitions for final and TypedDict as we do for other things --- .../src_py3/typing_extensions.py | 173 +++++++++--------- 1 file changed, 89 insertions(+), 84 deletions(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 1b6eb8e51..ea7f4de97 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -129,6 +129,7 @@ def _check_methods_in_mro(C, *methods): 'Counter', 'Deque', 'DefaultDict', + 'TypedDict', # One-off things. 'final', @@ -499,27 +500,30 @@ class FastConnector(Connection): __type__ = None -def final(f): - """This decorator can be used to indicate to type checkers that - the decorated method cannot be overridden, and decorated class - cannot be subclassed. For example: - - class Base: +if hasattr(typing, 'final'): + final = typing.final +else: + def final(f): + """This decorator can be used to indicate to type checkers that + the decorated method cannot be overridden, and decorated class + cannot be subclassed. For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... @final - def done(self) -> None: + class Leaf: ... - class Sub(Base): - def done(self) -> None: # Error reported by type checker + class Other(Leaf): # Error reported by type checker ... - @final - class Leaf: - ... - class Other(Leaf): # Error reported by type checker - ... - There is no runtime checking of these properties. - """ - return f + There is no runtime checking of these properties. + """ + return f def IntVar(name): @@ -1531,92 +1535,93 @@ def runtime(cls): return cls -def _check_fails(cls, other): - try: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools', 'typing']: - # Typed dicts are only for static structural subtyping. - raise TypeError('TypedDict does not support instance and class checks') - except (AttributeError, ValueError): - pass - return False +if hasattr(typing, 'TypedDict'): + def _check_fails(cls, other): + try: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools', 'typing']: + # Typed dicts are only for static structural subtyping. + raise TypeError('TypedDict does not support instance and class checks') + except (AttributeError, ValueError): + pass + return False -def _dict_new(cls, *args, **kwargs): - return dict(*args, **kwargs) + def _dict_new(cls, *args, **kwargs): + return dict(*args, **kwargs) -def _typeddict_new(cls, _typename, _fields=None, **kwargs): - total = kwargs.pop('total', True) - if _fields is None: - _fields = kwargs - elif kwargs: - raise TypeError("TypedDict takes either a dict or keyword arguments," - " but not both") + def _typeddict_new(cls, _typename, _fields=None, **kwargs): + total = kwargs.pop('total', True) + if _fields is None: + _fields = kwargs + elif kwargs: + raise TypeError("TypedDict takes either a dict or keyword arguments," + " but not both") - ns = {'__annotations__': dict(_fields), '__total__': total} - try: - # Setting correct module is necessary to make typed dict classes pickleable. - ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass + ns = {'__annotations__': dict(_fields), '__total__': total} + try: + # Setting correct module is necessary to make typed dict classes pickleable. + ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass - return _TypedDictMeta(_typename, (), ns) + return _TypedDictMeta(_typename, (), ns) -class _TypedDictMeta(type): - def __new__(cls, name, bases, ns, total=True): - # Create new typed dict class object. - # This method is called directly when TypedDict is subclassed, - # or via _typeddict_new when TypedDict is instantiated. This way - # TypedDict supports all three syntaxes described in its docstring. - # Subclasses and instances of TypedDict return actual dictionaries - # via _dict_new. - ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new - tp_dict = super(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns) + class _TypedDictMeta(type): + def __new__(cls, name, bases, ns, total=True): + # Create new typed dict class object. + # This method is called directly when TypedDict is subclassed, + # or via _typeddict_new when TypedDict is instantiated. This way + # TypedDict supports all three syntaxes described in its docstring. + # Subclasses and instances of TypedDict return actual dictionaries + # via _dict_new. + ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new + tp_dict = super(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns) - anns = ns.get('__annotations__', {}) - msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" - anns = {n: typing._type_check(tp, msg) for n, tp in anns.items()} - for base in bases: - anns.update(base.__dict__.get('__annotations__', {})) - tp_dict.__annotations__ = anns - if not hasattr(tp_dict, '__total__'): - tp_dict.__total__ = total - return tp_dict + anns = ns.get('__annotations__', {}) + msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" + anns = {n: typing._type_check(tp, msg) for n, tp in anns.items()} + for base in bases: + anns.update(base.__dict__.get('__annotations__', {})) + tp_dict.__annotations__ = anns + if not hasattr(tp_dict, '__total__'): + tp_dict.__total__ = total + return tp_dict - __instancecheck__ = __subclasscheck__ = _check_fails + __instancecheck__ = __subclasscheck__ = _check_fails -TypedDict = _TypedDictMeta('TypedDict', (dict,), {}) -TypedDict.__module__ = __name__ -TypedDict.__doc__ = \ - """A simple typed name space. At runtime it is equivalent to a plain dict. + TypedDict = _TypedDictMeta('TypedDict', (dict,), {}) + TypedDict.__module__ = __name__ + TypedDict.__doc__ = \ + """A simple typed name space. At runtime it is equivalent to a plain dict. - TypedDict creates a dictionary type that expects all of its - instances to have a certain set of keys, with each key - associated with a value of a consistent type. This expectation - is not checked at runtime but is only enforced by type checkers. - Usage:: + TypedDict creates a dictionary type that expects all of its + instances to have a certain set of keys, with each key + associated with a value of a consistent type. This expectation + is not checked at runtime but is only enforced by type checkers. + Usage:: - class Point2D(TypedDict): - x: int - y: int - label: str + class Point2D(TypedDict): + x: int + y: int + label: str - a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK - b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check + a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK + b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check - assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') - The type info could be accessed via Point2D.__annotations__. TypedDict - supports two additional equivalent forms:: + The type info could be accessed via Point2D.__annotations__. TypedDict + supports two additional equivalent forms:: - Point2D = TypedDict('Point2D', x=int, y=int, label=str) - Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + Point2D = TypedDict('Point2D', x=int, y=int, label=str) + Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) - The class syntax is only supported in Python 3.6+, while two other - syntax forms work for Python 2.7 and 3.2+ - """ + The class syntax is only supported in Python 3.6+, while two other + syntax forms work for Python 2.7 and 3.2+ + """ if PEP_560: From da223d388d1bac6713de9f93b4d227205abd21d6 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 01:34:36 +0100 Subject: [PATCH 02/15] Update typing_extensions tests --- .../src_py3/test_typing_extensions.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 57d77e65d..691fe2ddf 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -190,11 +190,15 @@ def test_basics(self): Final[int][str] def test_repr(self): - self.assertEqual(repr(Final), 'typing_extensions.Final') + if hasattr(typing, 'Final') and sys.version_info >= (3, 8, 0): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Final), mod_name + '.Final') cv = Final[int] - self.assertEqual(repr(cv), 'typing_extensions.Final[int]') + self.assertEqual(repr(cv), mod_name + '.Final[int]') cv = Final[Employee] - self.assertEqual(repr(cv), 'typing_extensions.Final[%s.Employee]' % __name__) + self.assertEqual(repr(cv), mod_name + '.Final[%s.Employee]' % __name__) @skipUnless(SUBCLASS_CHECK_FORBIDDEN, "Behavior added in typing 3.5.3") def test_cannot_subclass(self): @@ -242,7 +246,7 @@ def test_basics(self): def test_illegal_parameters_do_not_raise_runtime_errors(self): # Type checkers should reject these types, but we do not - # raise errors at runtime to maintain maximium flexibility + # raise errors at runtime to maintain maximum flexibility Literal[int] Literal[Literal[1, 2], Literal[4, 5]] Literal[3j + 2, ..., ()] @@ -255,11 +259,15 @@ def test_literals_inside_other_types(self): List[Literal[("foo", "bar", "baz")]] def test_repr(self): - self.assertEqual(repr(Literal[1]), "typing_extensions.Literal[1]") - self.assertEqual(repr(Literal[1, True, "foo"]), "typing_extensions.Literal[1, True, 'foo']") - self.assertEqual(repr(Literal[int]), "typing_extensions.Literal[int]") - self.assertEqual(repr(Literal), "typing_extensions.Literal") - self.assertEqual(repr(Literal[None]), "typing_extensions.Literal[None]") + if hasattr(typing, 'Literal'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Literal[1]), mod_name + ".Literal[1]") + self.assertEqual(repr(Literal[1, True, "foo"]), mod_name + ".Literal[1, True, 'foo']") + self.assertEqual(repr(Literal[int]), mod_name + ".Literal[int]") + self.assertEqual(repr(Literal), mod_name + ".Literal") + self.assertEqual(repr(Literal[None]), mod_name + ".Literal[None]") def test_cannot_init(self): with self.assertRaises(TypeError): From 2b5294f0362e526b943e948702158f1cc8da1c55 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 01:37:09 +0100 Subject: [PATCH 03/15] Update travis.yaml to include 3.8 --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 57eb842ff..27eb9d0dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: python +dist: xenial python: - - "nightly" - - "3.7-dev" + - "3.8-dev" + - "3.7" - "3.6.2" - "3.6.1" - "3.6.0" From 205e61f73befde540eb66c62663604ea2e5ea390 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 01:42:27 +0100 Subject: [PATCH 04/15] Fix a typo --- typing_extensions/src_py3/typing_extensions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index ea7f4de97..c0b6cfae4 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -1536,6 +1536,8 @@ def runtime(cls): if hasattr(typing, 'TypedDict'): + TypedDict = typing.TypedDict +else: def _check_fails(cls, other): try: if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools', 'typing']: From 9163600b0068d78f8ad7e05aa2a50a0f9aa9b2b1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 01:51:07 +0100 Subject: [PATCH 05/15] Try per-version distributon choioce in travis.yaml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 27eb9d0dc..66f65eb42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ language: python -dist: xenial python: - "3.8-dev" + dist: xenial - "3.7" + dist: xenial - "3.6.2" - "3.6.1" - "3.6.0" From 0567eaae4b1b27eb49390589b5d82d5aefe6dfe3 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 02:00:10 +0100 Subject: [PATCH 06/15] Try fancy yaml --- .travis.yml | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 66f65eb42..1ffe8b908 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,31 @@ language: python -python: - - "3.8-dev" - dist: xenial - - "3.7" - dist: xenial - - "3.6.2" - - "3.6.1" - - "3.6.0" - - "3.5.3" - - "3.5.2" - - "3.5.1" - - "3.5.0" - - "3.4" - - "2.7" + +jobs: + include: + - name: "3.8-dev" + dist: xenial + python: 3.8-dev + - name: "3.7" + dist: xenial + python: 3.7 + - name: "3.6.2" + python 3.6.2 + - name: "3.6.1" + python 3.6.1 + - name: "3.6.0" + python 3.6.0 + - name: "3.5.3" + python 3.5.3 + - name: "3.5.2" + python 3.5.2 + - name: "3.5.1" + python 3.5.1 + - name: "3.5.0" + python 3.5.0 + - name: "3.4" + python 3.4 + - name: "2.7" + python 2.7 install: - pip install -r test-requirements.txt From 2885ff645ea88490430b7aac2ac67413231cffa0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 02:02:20 +0100 Subject: [PATCH 07/15] Fix colons --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ffe8b908..7ab2a6b02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,23 +9,23 @@ jobs: dist: xenial python: 3.7 - name: "3.6.2" - python 3.6.2 + python: 3.6.2 - name: "3.6.1" - python 3.6.1 + python: 3.6.1 - name: "3.6.0" - python 3.6.0 + python: 3.6.0 - name: "3.5.3" - python 3.5.3 + python: 3.5.3 - name: "3.5.2" - python 3.5.2 + python: 3.5.2 - name: "3.5.1" - python 3.5.1 + python: 3.5.1 - name: "3.5.0" - python 3.5.0 + python: 3.5.0 - name: "3.4" - python 3.4 + python: 3.4 - name: "2.7" - python 2.7 + python: 2.7 install: - pip install -r test-requirements.txt From e717fc22fb692827501f84bbd030b93186d16060 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 02:07:51 +0100 Subject: [PATCH 08/15] Only test typing on supported versions (i.e. < 3.7) --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7ab2a6b02..566560f8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,8 @@ install: - pip install -r test-requirements.txt script: - - export PYTHONPATH=`python -c "import sys; print('python2' if sys.version.startswith('2') else 'src')"`; py.test $PYTHONPATH + - export PYTHONPATH=`python -c "import sys; print('python2' if sys.version.startswith('2') else 'src')"`; + if [[ $TRAVIS_PYTHON_VERSION < '3.7' ]]; then py.test $PYTHONPATH; fi - if [[ $TRAVIS_PYTHON_VERSION < '3.5' ]]; then python setup.py install; fi - export PYTHONPATH=`python -c "import sys; print('typing_extensions/src_py2' if sys.version.startswith('2') else 'typing_extensions/src_py3')"`; py.test $PYTHONPATH; From 208b05df11b568dbb2822dd8dd41aca68aa60771 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 02:15:26 +0100 Subject: [PATCH 09/15] Try fixing 3.8 tests --- typing_extensions/src_py3/test_typing_extensions.py | 7 +++++-- typing_extensions/src_py3/typing_extensions.py | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 691fe2ddf..082d76492 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -190,7 +190,7 @@ def test_basics(self): Final[int][str] def test_repr(self): - if hasattr(typing, 'Final') and sys.version_info >= (3, 8, 0): + if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): mod_name = 'typing' else: mod_name = 'typing_extensions' @@ -1427,7 +1427,10 @@ def test_basics_keywords_syntax(self): def test_typeddict_errors(self): Emp = TypedDict('Emp', {'name': str, 'id': int}) - self.assertEqual(TypedDict.__module__, 'typing_extensions') + if hasattr(typing, 'TypedDict'): + self.assertEqual(TypedDict.__module__, 'typing') + else: + self.assertEqual(TypedDict.__module__, 'typing_extensions') jim = Emp(name='Jim', id=1) with self.assertRaises(TypeError): isinstance({}, Emp) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index c0b6cfae4..fb1c63eef 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -1100,7 +1100,9 @@ def _is_callable_members_only(cls): return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls)) -if HAVE_PROTOCOLS and not PEP_560: +if hasattr(typing, 'Protocol'): + Protocol = typing.Protocol +elif HAVE_PROTOCOLS and not PEP_560: class _ProtocolMeta(GenericMeta): """Internal metaclass for Protocol. From 03f34962ca455270f4852095a1c0a19fc43c29f4 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 02:17:18 +0100 Subject: [PATCH 10/15] Add a bunch more versions --- .travis.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 566560f8f..c1da15c7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,20 @@ jobs: - name: "3.8-dev" dist: xenial python: 3.8-dev - - name: "3.7" + - name: "3.7.3" dist: xenial - python: 3.7 + python: 3.7.3 + - name: "3.7.2" + dist: xenial + python: 3.7.2 + - name: "3.7.1" + dist: xenial + python: 3.7.1 + - name: "3.7.0" + dist: xenial + python: 3.7.0 + - name: "3.6.3" + python: 3.6.3 - name: "3.6.2" python: 3.6.2 - name: "3.6.1" From 37fa77735a842f4c3d028fa5669f7ebb2326a831 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 02:41:21 +0100 Subject: [PATCH 11/15] Fix also Final; update runtime and runtime_checkable --- typing_extensions/src_py3/typing_extensions.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index fb1c63eef..aac7d0b6f 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -155,7 +155,7 @@ def _check_methods_in_mro(C, *methods): HAVE_PROTOCOLS = sys.version_info[:3] != (3, 5, 0) if HAVE_PROTOCOLS: - __all__.extend(['Protocol', 'runtime']) + __all__.extend(['Protocol', 'runtime', 'runtime_checkable']) # TODO @@ -349,7 +349,9 @@ class Starship: __type__ = None -if sys.version_info[:2] >= (3, 7): +if hasattr(typing, 'Final'): + Final = typing.Final +elif sys.version_info[:2] >= (3, 7): class _FinalForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -1521,8 +1523,10 @@ def _no_init(self, *args, **kwargs): cls.__init__ = _no_init +if hasattr(typing, 'runtime_checkable'): + runtime_checkable = typing.runtime_checkable if HAVE_PROTOCOLS: - def runtime(cls): + def runtime_checkable(cls): """Mark a protocol class as a runtime protocol, so that it can be used with isinstance() and issubclass(). Raise TypeError if applied to a non-protocol class. @@ -1531,12 +1535,16 @@ def runtime(cls): one-offs in collections.abc such as Hashable. """ if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol: - raise TypeError('@runtime can be only applied to protocol classes,' + raise TypeError('@runtime_checkable can be only applied to protocol classes,' ' got %r' % cls) cls._is_runtime_protocol = True return cls +# Exists for backwards compatibility. +runtime = runtime_checkable + + if hasattr(typing, 'TypedDict'): TypedDict = typing.TypedDict else: From 581e22dc56024cdc935574e3375b4dbf495695f8 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 02:46:54 +0100 Subject: [PATCH 12/15] Remove 3.6.3, pytest is broken there --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c1da15c7b..847addde4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,8 +17,6 @@ jobs: - name: "3.7.0" dist: xenial python: 3.7.0 - - name: "3.6.3" - python: 3.6.3 - name: "3.6.2" python: 3.6.2 - name: "3.6.1" From 7fb611427bf8b440d7f1ca4fd564a414fc1e2f95 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 02:48:09 +0100 Subject: [PATCH 13/15] Try fixing runtime_checkable --- typing_extensions/src_py3/typing_extensions.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index aac7d0b6f..f12d39959 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -1525,7 +1525,7 @@ def _no_init(self, *args, **kwargs): if hasattr(typing, 'runtime_checkable'): runtime_checkable = typing.runtime_checkable -if HAVE_PROTOCOLS: +elif HAVE_PROTOCOLS: def runtime_checkable(cls): """Mark a protocol class as a runtime protocol, so that it can be used with isinstance() and issubclass(). Raise TypeError @@ -1541,8 +1541,9 @@ def runtime_checkable(cls): return cls -# Exists for backwards compatibility. -runtime = runtime_checkable +if HAVE_PROTOCOLS: + # Exists for backwards compatibility. + runtime = runtime_checkable if hasattr(typing, 'TypedDict'): From ff8644ef246eb210a9a4ea14725a814d4975099a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 02:50:05 +0100 Subject: [PATCH 14/15] Fix definition of Final --- typing_extensions/src_py3/typing_extensions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index f12d39959..f8e8ac42c 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -349,7 +349,8 @@ class Starship: __type__ = None -if hasattr(typing, 'Final'): +# On older versions of typing there is an internal class named "Final". +if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): Final = typing.Final elif sys.version_info[:2] >= (3, 7): class _FinalForm(typing._SpecialForm, _root=True): From 998a2fc776ec41eeb9e0d8a15ad10b08ba21a8f5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Jun 2019 03:01:40 +0100 Subject: [PATCH 15/15] Try fixing protocol special attributes test --- typing_extensions/src_py3/test_typing_extensions.py | 5 ++++- typing_extensions/src_py3/typing_extensions.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 082d76492..5712c2970 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -1311,8 +1311,11 @@ def meth(self): self.assertTrue(P._is_protocol) self.assertTrue(PR._is_protocol) self.assertTrue(PG._is_protocol) - with self.assertRaises(AttributeError): + if hasattr(typing, 'Protocol'): self.assertFalse(P._is_runtime_protocol) + else: + with self.assertRaises(AttributeError): + self.assertFalse(P._is_runtime_protocol) self.assertTrue(PR._is_runtime_protocol) self.assertTrue(PG[int]._is_protocol) self.assertEqual(typing_extensions._get_protocol_attrs(P), {'meth'}) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index f8e8ac42c..f257d7d9c 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -359,8 +359,8 @@ def __repr__(self): return 'typing_extensions.' + self._name def __getitem__(self, parameters): - item = _type_check(parameters, - '{} accepts only single type'.format(self._name)) + item = typing._type_check(parameters, + '{} accepts only single type'.format(self._name)) return _GenericAlias(self, (item,)) Final = _FinalForm('Final', doc=