You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Structural subtype XX of X not recognized as subclass of X in class Z(Generic[TypeVar("XT", bound=X)]) and class ZZ(Z[XX]) #9560
It seems that whereas Python's structural subtyping works (using Python 3.7 and typing_extensions.Protocol) at runtime (see below), mypy doesn't recognize the subtype relationship in the following scenario:
There is a protocol class pep249.Connection (I'm creating this).
There is a class pymysql.connections.Connection that conforms to this protocol, as evidenced by issubclass(pymysql.connections.Connection, pep249.Connection) == True.
When defining a generic base class DatabaseAdapter that's generic in a ConnectionT type variable that has pep249.Connection as an upper bound (DatabaseAdapter(Generic[ConnectionT])), and defining a subclass MySQLAdapter
that parameterizes the generic base class with pymysql.connections.Connection (MySQLAdapter(DatabaseAdapter[pymysql.connections.Connection])), mypy complains that pymysql.connections.Connection is not a subtype of pep249.Connection.
changed the title [-]Structural subtype Y of X not recognized as subclass of X in `class Z(Generic[TypeVar("XT", bound=X)])`[/-][+]Structural subtype XX of X not recognized as subclass of X in `class Z(Generic[TypeVar("XT", bound=X)])` and `class ZZ(Z[X])`[/+]on Oct 8, 2020
changed the title [-]Structural subtype XX of X not recognized as subclass of X in `class Z(Generic[TypeVar("XT", bound=X)])` and `class ZZ(Z[X])`[/-][+]Structural subtype XX of X not recognized as subclass of X in `class Z(Generic[TypeVar("XT", bound=X)])` and `class ZZ(Z[XX])`[/+]on Oct 8, 2020
I'm happy to investigate, but what exactly would I need to look for? Also, what about the fact that issubclass (in conjunction with typing_extensions.runtime_checkable) thinks that pymysql.connections.Connectionis a subclass of pep249.Connection?
issubclass is actually pretty unsophisticated; it only checks presence of an attribute, whereas mypy checks a lot more stuff.
You could take a look at the pymysql stubs: https://github.com/python/typeshed/tree/master/third_party/2and3/pymysql If you can construct a self-contained repro, that would narrow down whether there's an issue with pymysql stubs or a mypy bug. I'd also confirm whether something simpler like: x: pep249.Connection = pymysql.connections.Connection(...) passes.
There are a couple other open issues for mypy to be more helpful about diagnosing why something isn't an instance of a Protocol — I think improving this is a pretty high priority.
@hauntsaninja, the advice to try a plain assignment was gold. It gets me some useful output:
main.py:7: error: Incompatible types in assignment (expression has type "pymysql.connections.Connection", variable has type "pep249.Connection")
main.py:7: note: Following member(s) of "Connection" have conflicts:
main.py:7: note: Expected:
main.py:7: note: def cursor(self, **Any) -> Any
main.py:7: note: Got:
main.py:7: note: def cursor(self, cursor: Optional[Type[Cursor]] = ...) -> Cursor
then it all works. But this puts me in a dilemma, because PEP 249 doesn't specify the signature of Connection.cursor(), and I can't assume there's a single argument (with a default), because other, non-pymysql DBI drivers have different arguments. Based on #5876 there's no way to type an arbitrary argument list. Evidently my first attempt doesn't match the actual signature of pymysql.connections.Connection.cursor.
Hot damn, that works. Thanks, @gvanrossum. I never, ever would've come up with that trick. So where does this leave us? I see at least a couple deficiencies:
When Type argument "XX" of "ZZ" must be a subtype of "X" is triggered, mypy doesn't give the same helpful information about whyXX isn't recognized as a subtype of X as it does when Incompatible types in assignment is triggered.
There is no way of specifying a loosely defined function/method signature (here: self required, all further arguments unspecified) in a protocol class that structural subtypes can satisfy (Make any callable compatible with (*args: Any, **kwargs: Any)? #5876). The property trick basically ignores all arguments, which happens to work well enough in this case.
Activity
[-]Structural subtype Y of X not recognized as subclass of X in `class Z(Generic[TypeVar("XT", bound=X)])`[/-][+]Structural subtype XX of X not recognized as subclass of X in `class Z(Generic[TypeVar("XT", bound=X)])` and `class ZZ(Z[X])`[/+][-]Structural subtype XX of X not recognized as subclass of X in `class Z(Generic[TypeVar("XT", bound=X)])` and `class ZZ(Z[X])`[/-][+]Structural subtype XX of X not recognized as subclass of X in `class Z(Generic[TypeVar("XT", bound=X)])` and `class ZZ(Z[XX])`[/+]gvanrossum commentedon Oct 8, 2020
jmehnle commentedon Oct 8, 2020
I'm happy to investigate, but what exactly would I need to look for? Also, what about the fact that
issubclass(in conjunction withtyping_extensions.runtime_checkable) thinks thatpymysql.connections.Connectionis a subclass ofpep249.Connection?hauntsaninja commentedon Oct 8, 2020
issubclass is actually pretty unsophisticated; it only checks presence of an attribute, whereas mypy checks a lot more stuff.
You could take a look at the pymysql stubs: https://github.com/python/typeshed/tree/master/third_party/2and3/pymysql If you can construct a self-contained repro, that would narrow down whether there's an issue with pymysql stubs or a mypy bug. I'd also confirm whether something simpler like:
x: pep249.Connection = pymysql.connections.Connection(...)passes.There are a couple other open issues for mypy to be more helpful about diagnosing why something isn't an instance of a Protocol — I think improving this is a pretty high priority.
jmehnle commentedon Oct 9, 2020
@hauntsaninja, the advice to try a plain assignment was gold. It gets me some useful output:
So if I change the method definition like so:
then it all works. But this puts me in a dilemma, because PEP 249 doesn't specify the signature of
Connection.cursor(), and I can't assume there's a single argument (with a default), because other, non-pymysqlDBI drivers have different arguments. Based on #5876 there's no way to type an arbitrary argument list. Evidently my first attempt doesn't match the actual signature ofpymysql.connections.Connection.cursor.gvanrossum commentedon Oct 9, 2020
Here's a horrible hack that might help: declare
cursoras a property (really!) that returns a Callable:No errors there.
jmehnle commentedon Oct 12, 2020
Hot damn, that works. Thanks, @gvanrossum. I never, ever would've come up with that trick. So where does this leave us? I see at least a couple deficiencies:
Type argument "XX" of "ZZ" must be a subtype of "X"is triggered,mypydoesn't give the same helpful information about whyXXisn't recognized as a subtype ofXas it does whenIncompatible types in assignmentis triggered.selfrequired, all further arguments unspecified) in a protocol class that structural subtypes can satisfy (Make any callable compatible with (*args: Any, **kwargs: Any)? #5876). Thepropertytrick basically ignores all arguments, which happens to work well enough in this case.hauntsaninja commentedon Feb 1, 2023
Closing, since #5876 has been fixed