python / python/mypy

Differed Protocol / runtime_checkable import inconsistently causes incompatible type error

Open
#10,851 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Bug Report

An incompatible type error is raised for compatible types when Protocol & runtime_checkable are imported via a helper module (see # Option A: Via Helper), but not when they are imported within the module (see # Option B: Directly). Elsewhere in the codebase, importing via the helper module results in the expected behaviour.

Raises: error: Argument 1 to "handle" of "Interpreter" has incompatible type "ComposedAdapter"; expected "Adapter"

To Reproduce

# Option A: Via helper
from tickit.core.utils.compat.typing_compat import Protocol, runtime_checkable
# Option B: Directly
import sys

if sys.version_info >= (3, 8):
    from typing import Protocol, runtime_checkable
else:
    from typing_extensions import Protocol, runtime_checkable
@runtime_checkable
class Adapter(Protocol):
    interrupt: bool

    def __init__(self, device: Device) -> None:
        ...

    async def run_forever(self) -> None:
        ...

class ComposedAdapter:
    _interpreter: Interpreter
    _server: Server
    interrupt: bool = False

    def __init__(self, device: Device) -> None:
        assert isinstance(self._interpreter, Interpreter)
        assert isinstance(self._server, Server)
        self._device = device

    async def run_forever(self) -> None:
        async for interrupt in self._server.run_forever(
            lambda message: self._interpreter.handle(self, message) # Error raised here
        ):
            self.interrupt |= interrupt

Where tickit.utils.compat.typing_compat is implemented as:

import sys

if sys.version_info >= (3, 8):
    from typing import Protocol, runtime_checkable
else:
    from typing_extensions import Protocol, runtime_checkable

__all__ = ["Protocol", "runtime_checkable"]

Expected Behavior

No error is raised when importing via the helper module & behaviour is consistent across the codebase.

Actual Behavior

An error is raised when importing via the helper module.

Your Environment

  • Mypy version used: 0.910
  • Mypy command-line flags: N/A
  • Mypy configuration options from mypy.ini (and other config files): ignore_missing_imports = True
  • Python version used: 3.7.9, 3.9.5
  • Operating system and version: Red Hat Enterprise Linux release 8.4 (Ootpa)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the incompatible type error using the helper import from tickit.core.utils.compat.typing_compat and compare it with the direct typing or typing_extensions imports shown in the report. Trace how mypy handles Protocol and runtime_checkable through the helper module, then verify that both import styles produce consistent type-checking behavior for the Adapter and ComposedAdapter example.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.