Travis Gockel
Travis Gockel

Reputation: 27673

MyPy checking typing.Protocol with Python 3.7 Support

I have a project which needs to support Python 3.7, but I would like to use typing.Protocol, which was added in 3.8. To support 3.7, I have a minor bit of fallback code which just uses object:

import typing

_Protocol = getattr(typing, 'Protocol', object)

class Foo(_Protocol):
    def bar(self) -> int:
        pass

This all functions as I would expect. The issue is, when running MyPy, I get the following error:

test.py:5: error: Variable "test._Protocol" is not valid as a type
test.py:5: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
test.py:5: error: Invalid base class "_Protocol"

The linked "Variables vs type aliases" section in that error message indicates that I should annotate _Protocol with : typing.Type[object] (which does not work) or use typing.TypeAlias (which isn't available until Python 3.9).

How can I indicate to MyPy that _Protocol is valid as a type?


Another workaround I tried was in "Python version and system platform checks":

if sys.version_info >= (3, 8):
    _Protocol = typing.Protocol
else:
    _Protocol = object

However, this ends with the same error.

Upvotes: 3

Views: 2126

Answers (2)

Travis Gockel
Travis Gockel

Reputation: 27673

It seems that MyPy can properly understand Protocol as part of an import statement:

if sys.version_info >= (3, 8):
    from typing import Protocol as _Protocol
else:
    _Protocol = object

class Foo(_Protocol):
    def bar(self) -> int: ...

MyPy will still flag usage of Foo as a type specification in Python 3.7, so you need a similar workaround for using it:

if sys.version_info >= (3, 8):
    _Foo = Foo
else:
    _Foo = typing.Any

def bar(x: _Foo):
    pass

It's worth noting that MyPy does not understand more clever things like _Foo = Foo if sys.version_info >= (3, 8) else typing.Any -- you have to use the simple format.

Upvotes: 0

Paweł Rubin
Paweł Rubin

Reputation: 3420

Use typing_extensions and typing.TYPE_CHECKING to import typing_extensions only when type-checking the code.

import typing

if typing.TYPE_CHECKING:
    from typing_extensions import Protocol
else:
    Protocol = object


class Foo(Protocol):
    def bar(self) -> int:
        ...

typing_extensions checks the Python version and uses typing.Protocol for versions >=3.8:

# 3.8+
if hasattr(typing, 'Protocol'):
    Protocol = typing.Protocol
# 3.7
else:
    ...

Upvotes: 3

Related Questions