Skip to content

Conditional import of a class follows imports of all branches #19009

Closed
@ofek

Description

@ofek

Bug Report

When you have the following provider module:

if sys.platform == "win32":
    from my_pkg._pty.windows import PtySession
else:
    from my_pkg._pty.unix import PtySession

and attempt importing this symbol from somewhere else, Mypy does not respect the platform condition.

For example, if you call pty.openpty() inside my_pkg._pty.unix and run Mypy on Windows it will show:

Module has no attribute "openpty"  [attr-defined]

To Reproduce

Create the following structure:

mypy-issue
├── pyproject.toml
└── pkg
    ├── __init__.py
    ├── main.py
    └── _pty
        ├── __init__.py
        ├── interface.py
        ├── session.py
        ├── unix.py
        └── windows.py

with the following contents (__init__.py files are empty):

pyproject.toml
[tool.mypy]
pretty = true
pkg/main.py
from __future__ import annotations


class SubprocessRunner:
    def run(self) -> bool:
        from ._pty.session import PtySession

        return PtySession is not None
pkg/_pty/interface.py
from __future__ import annotations

from abc import ABC, abstractmethod


class PtySessionInterface(ABC):
    def __init__(self, command: list[str]) -> None:
        self.command = command

    @abstractmethod
    def get_exit_code(self) -> int | None: ...
pkg/_pty/session.py
from __future__ import annotations

import sys

if sys.platform == "win32":
    from .windows import PtySession
else:
    from .unix import PtySession

__all__ = ["PtySession"]
pkg/_pty/unix.py
from __future__ import annotations

import pty

from .interface import PtySessionInterface


class PtySession(PtySessionInterface):
    def __init__(self, command: list[str]) -> None:
        super().__init__(command)

        self._fd, self._child_fd = pty.openpty()

    def get_exit_code(self) -> int | None:
        return None
pkg/_pty/windows.py
from __future__ import annotations

from .interface import PtySessionInterface


class PtySession(PtySessionInterface):
    def __init__(self, command: list[str]) -> None:
        super().__init__(command)

        self._fd, self._child_fd = (0, 0)

    def get_exit_code(self) -> int | None:
        return None

Finally, enter the mypy-issue directory and run mypy pkg on Windows:

$ mypy pkg
pkg\_pty\unix.py:12: error: Module has no attribute "openpty"  [attr-defined]
            self._fd, self._child_fd = pty.openpty()
                                       ^~~~~~~~~~~
Found 1 error in 1 file (checked 7 source files)

Expected Behavior

I would expect that Mypy respects the platform condition like it does in other circumstances I've experienced and is documented.

Your Environment

  • Mypy version used: mypy 1.15.0 (compiled: yes)
  • Python version used: 3.12

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrongpendingIssues that may be closedtopic-reachabilityDetecting unreachable code

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions