From c70c02a0d5caa7676d364642418af22ee111fe24 Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 16 Dec 2022 19:56:47 -0500 Subject: [PATCH 1/5] Support non-type dependencies --- .github/workflows/daily.yml | 7 ++-- .github/workflows/stubtest_third_party.yml | 7 ++-- .github/workflows/tests.yml | 10 +++++ pyrightconfig.json | 7 +++- pyrightconfig.stricter.json | 6 +++ stubs/D3DShot/METADATA.toml | 1 + stubs/D3DShot/d3dshot/capture_output.pyi | 12 ++---- .../capture_outputs/numpy_capture_output.pyi | 16 ++++--- .../numpy_float_capture_output.pyi | 28 +++++++++++-- .../capture_outputs/pil_capture_output.pyi | 4 +- .../pytorch_capture_output.pyi | 15 +++---- stubs/D3DShot/d3dshot/dll/__init__.pyi | 6 +-- stubs/JACK-Client/METADATA.toml | 1 + stubs/JACK-Client/jack/__init__.pyi | 5 ++- stubs/pycocotools/METADATA.toml | 1 + stubs/pycocotools/pycocotools/coco.pyi | 17 +++----- stubs/pycocotools/pycocotools/cocoeval.pyi | 36 ++++++---------- stubs/pycocotools/pycocotools/mask.pyi | 18 ++++---- stubs/pyinstaller/METADATA.toml | 1 + .../PyInstaller/depend/analysis.pyi | 2 +- .../PyInstaller/depend/imphookapi.pyi | 2 +- .../pyinstaller/PyInstaller/lib/__init__.pyi | 0 .../PyInstaller/lib/modulegraph/__init__.pyi | 0 .../lib/modulegraph/modulegraph.pyi | 42 ------------------- tests/check_consistent.py | 1 + tests/external_pip_dependencies.py | 32 ++++++++++++++ tests/mypy_test.py | 6 ++- tests/pytype_test.py | 5 ++- tests/stubtest_third_party.py | 9 ++++ 29 files changed, 162 insertions(+), 135 deletions(-) delete mode 100644 stubs/pyinstaller/PyInstaller/lib/__init__.pyi delete mode 100644 stubs/pyinstaller/PyInstaller/lib/modulegraph/__init__.pyi delete mode 100644 stubs/pyinstaller/PyInstaller/lib/modulegraph/modulegraph.pyi create mode 100644 tests/external_pip_dependencies.py diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 059ae9908e4b..4f40d2633216 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -71,13 +71,14 @@ jobs: shell: bash run: | PACKAGES=$(python tests/get_packages.py) + PYTHON_EXECUTABLE="python" if [ "${{ runner.os }}" = "Linux" ]; then if [ -n "$PACKAGES" ]; then sudo apt update && sudo apt install -y $PACKAGES fi - xvfb-run python tests/stubtest_third_party.py --specified-stubs-only --num-shards 4 --shard-index ${{ matrix.shard-index }} + PYTHON_EXECUTABLE="xvfb-run" else if [ "${{ runner.os }}" = "macOS" ] && [ -n "$PACKAGES" ]; then NONINTERACTIVE=1 brew install $PACKAGES @@ -86,10 +87,10 @@ jobs: if [ "${{ runner.os }}" = "Windows" ] && [ -n "$PACKAGES" ]; then choco install -y $PACKAGES fi - - python tests/stubtest_third_party.py --specified-stubs-only --num-shards 4 --shard-index ${{ matrix.shard-index }} fi + $PYTHON_EXECUTABLE tests/stubtest_third_party.py --specified-stubs-only --num-shards 4 --shard-index ${{ matrix.shard-index }} + stub-uploader: name: Run the stub_uploader tests runs-on: ubuntu-latest diff --git a/.github/workflows/stubtest_third_party.yml b/.github/workflows/stubtest_third_party.yml index 762467055bda..0573d1814e1a 100644 --- a/.github/workflows/stubtest_third_party.yml +++ b/.github/workflows/stubtest_third_party.yml @@ -58,6 +58,7 @@ jobs: if [ -n "$STUBS" ]; then echo "Testing $STUBS..." PACKAGES=$(python tests/get_packages.py $STUBS) + PYTHON_EXECUTABLE="python" if [ "${{ runner.os }}" = "Linux" ]; then if [ -n "$PACKAGES" ]; then @@ -65,7 +66,7 @@ jobs: sudo apt update && sudo apt install -y $PACKAGES fi - xvfb-run python tests/stubtest_third_party.py --specified-stubs-only $STUBS + PYTHON_EXECUTABLE="xvfb-run" else if [ "${{ runner.os }}" = "macOS" ] && [ -n "$PACKAGES" ]; then echo "Installing Homebrew packages: $PACKAGES" @@ -76,9 +77,9 @@ jobs: echo "Installing Chocolatey packages: $PACKAGES" choco install -y $PACKAGES fi - - python tests/stubtest_third_party.py --specified-stubs-only $STUBS fi + + $PYTHON_EXECUTABLE tests/stubtest_third_party.py --specified-stubs-only $STUBS else echo "Nothing to test" fi diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 44f2c756c886..32f466dd57ce 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,6 +69,7 @@ jobs: cache: pip cache-dependency-path: requirements-tests.txt - run: pip install -r requirements-tests.txt + - run: pip install $(python tests/external_pip_dependencies.py) - run: ./tests/pytype_test.py --print-stderr mypy: @@ -87,6 +88,7 @@ jobs: cache: pip cache-dependency-path: requirements-tests.txt - run: pip install -r requirements-tests.txt + - run: pip install $(python tests/external_pip_dependencies.py) - run: python ./tests/mypy_test.py --platform=${{ matrix.platform }} --python-version=${{ matrix.python-version }} regression-tests: @@ -100,6 +102,7 @@ jobs: cache: pip cache-dependency-path: requirements-tests.txt - run: pip install -r requirements-tests.txt + # - run: pip install $(python tests/external_pip_dependencies.py) - run: python ./tests/regr_test.py --all --quiet pyright: @@ -112,6 +115,13 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: requirements-tests.txt + - run: pip install tomli + - run: pip install $(python tests/external_pip_dependencies.py) - name: Get pyright version uses: SebRollen/toml-action@v1.0.2 id: pyright_version diff --git a/pyrightconfig.json b/pyrightconfig.json index 50229b5aa177..d20ce1e06c15 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -14,7 +14,6 @@ "strictDictionaryInference": true, "strictSetInference": true, "reportFunctionMemberAccess": "error", - "reportMissingTypeStubs": "error", "reportUnusedImport": "error", "reportUnusedClass": "error", "reportUnusedFunction": "error", @@ -35,6 +34,12 @@ "reportInconsistentConstructor": "error", "reportTypeCommentUsage": "error", "reportUnnecessaryComparison": "error", + // Since we allow non-type dependencies, we may need to refer to a library + // that is not marked as "typed" and has no stubs. + // It's still useful to raise a warning to flag those dependencies. + "useLibraryCodeForTypes": true, + "reportMissingTypeStubs": "warning", + // Typeshed uses type-stubs, the source is not needed. "reportMissingModuleSource": "none", // Incompatible overrides and property type mismatches are out of typeshed's control // as they are inherited from the implementation. diff --git a/pyrightconfig.stricter.json b/pyrightconfig.stricter.json index 06d11f1a33b9..48e8feabf1b5 100644 --- a/pyrightconfig.stricter.json +++ b/pyrightconfig.stricter.json @@ -75,6 +75,12 @@ "typeCheckingMode": "strict", "reportPrivateUsage": "none", "reportIncompleteStub": "none", + // Since we allow non-type dependencies, we may need to refer to a library + // that is not marked as "typed" and has no stubs. + // It's still useful to raise a warning to flag those dependencies. + "useLibraryCodeForTypes": true, + "reportMissingTypeStubs": "warning", + // Typeshed uses type-stubs, the source is not needed. "reportMissingModuleSource": "none", // Incompatible overrides and property type mismatches are out of typeshed's control // as they are inherited from the implementation. diff --git a/stubs/D3DShot/METADATA.toml b/stubs/D3DShot/METADATA.toml index b66c346f9a8b..f64f40b0243c 100644 --- a/stubs/D3DShot/METADATA.toml +++ b/stubs/D3DShot/METADATA.toml @@ -1,5 +1,6 @@ version = "0.1.*" requires = ["types-Pillow"] +external_requires = ["numpy", "torch", "comtypes"] [tool.stubtest] # TODO: figure out how to run stubtest for this package diff --git a/stubs/D3DShot/d3dshot/capture_output.pyi b/stubs/D3DShot/d3dshot/capture_output.pyi index aa68b092eb20..a0f6a71ee440 100644 --- a/stubs/D3DShot/d3dshot/capture_output.pyi +++ b/stubs/D3DShot/d3dshot/capture_output.pyi @@ -1,18 +1,14 @@ import enum -from _typeshed import Incomplete from collections.abc import Sequence from ctypes import _CVoidConstPLike from typing_extensions import Literal, TypeAlias +import numpy as np +import numpy.typing as npt from PIL import Image +from torch import Tensor -_Frame: TypeAlias = Image.Image | Incomplete -# TODO: Complete types once we can import non-types dependencies -# See: #5768 -# from torch import Tensor -# from comtypes import IUnknown -# import numpy.typing as npt -# _Frame: TypeAlias = Image.Image | npt.NDArray[np.int32] | npt.NDArray[np.float32] | Tensor +_Frame: TypeAlias = Image.Image | npt.NDArray[np.int32] | npt.NDArray[np.float32] | Tensor class CaptureOutputs(enum.Enum): PIL: int diff --git a/stubs/D3DShot/d3dshot/capture_outputs/numpy_capture_output.pyi b/stubs/D3DShot/d3dshot/capture_outputs/numpy_capture_output.pyi index 083eeb798560..f0aa87748372 100644 --- a/stubs/D3DShot/d3dshot/capture_outputs/numpy_capture_output.pyi +++ b/stubs/D3DShot/d3dshot/capture_outputs/numpy_capture_output.pyi @@ -1,17 +1,13 @@ -from _typeshed import Incomplete from collections.abc import Sequence from ctypes import _CVoidConstPLike from typing_extensions import Literal, TypeAlias +import numpy as np +import numpy.typing as npt from d3dshot.capture_output import CaptureOutput from PIL import Image -# TODO: Complete types once we can import non-types dependencies -# See: #5768 -# import numpy as np -# import numpy.typing as npt -# _NDArray: TypeAlias = npt.NDArray[np.int32] -_NDArray: TypeAlias = Incomplete +_NDArray: TypeAlias = npt.NDArray[np.int32] class NumpyCaptureOutput(CaptureOutput): def __init__(self) -> None: ... @@ -25,5 +21,7 @@ class NumpyCaptureOutput(CaptureOutput): region: tuple[int, int, int, int], rotation: int, ) -> _NDArray: ... - def to_pil(self, frame: _NDArray) -> Image.Image: ... - def stack(self, frames: Sequence[_NDArray] | _NDArray, stack_dimension: Literal["first", "last"]) -> _NDArray: ... + def to_pil(self, frame: _NDArray) -> Image.Image: ... # type: ignore[override] + def stack( # type: ignore[override] + self, frames: Sequence[_NDArray] | _NDArray, stack_dimension: Literal["first", "last"] + ) -> _NDArray: ... diff --git a/stubs/D3DShot/d3dshot/capture_outputs/numpy_float_capture_output.pyi b/stubs/D3DShot/d3dshot/capture_outputs/numpy_float_capture_output.pyi index 49d4e5d7ea75..0ddb543efbc5 100644 --- a/stubs/D3DShot/d3dshot/capture_outputs/numpy_float_capture_output.pyi +++ b/stubs/D3DShot/d3dshot/capture_outputs/numpy_float_capture_output.pyi @@ -1,5 +1,27 @@ +from collections.abc import Sequence +from ctypes import _CVoidConstPLike +from typing_extensions import Literal, TypeAlias + +import numpy as np +import numpy.typing as npt from d3dshot.capture_outputs.numpy_capture_output import NumpyCaptureOutput +from PIL import Image + +_NDArray: TypeAlias = npt.NDArray[np.float64] -# TODO: Once we can import non-types dependencies, this CaptureOutput should be float based -# See: #5768 -class NumpyFloatCaptureOutput(NumpyCaptureOutput): ... +class NumpyFloatCaptureOutput(NumpyCaptureOutput): + def __init__(self) -> None: ... + def process( # type: ignore[override] + self, + pointer: _CVoidConstPLike, + pitch: float, + size: int, + width: float, + height: float, + region: tuple[float, float, float, float], + rotation: int, + ) -> _NDArray: ... + def to_pil(self, frame: _NDArray) -> Image.Image: ... # type: ignore[override] + def stack( # type: ignore[override] + self, frames: Sequence[_NDArray] | _NDArray, stack_dimension: Literal["first", "last"] + ) -> _NDArray: ... diff --git a/stubs/D3DShot/d3dshot/capture_outputs/pil_capture_output.pyi b/stubs/D3DShot/d3dshot/capture_outputs/pil_capture_output.pyi index 0a593d73357d..c5f934cbc24e 100644 --- a/stubs/D3DShot/d3dshot/capture_outputs/pil_capture_output.pyi +++ b/stubs/D3DShot/d3dshot/capture_outputs/pil_capture_output.pyi @@ -21,5 +21,5 @@ class PILCaptureOutput(CaptureOutput): region: tuple[int, int, int, int], rotation: int, ) -> Image.Image: ... - def to_pil(self, frame: _ImageT) -> _ImageT: ... - def stack(self, frames: Sequence[_ImageT], stack_dimension: _Unused) -> Sequence[_ImageT]: ... + def to_pil(self, frame: _ImageT) -> _ImageT: ... # type: ignore[override] + def stack(self, frames: Sequence[_ImageT], stack_dimension: _Unused) -> Sequence[_ImageT]: ... # type: ignore[override] diff --git a/stubs/D3DShot/d3dshot/capture_outputs/pytorch_capture_output.pyi b/stubs/D3DShot/d3dshot/capture_outputs/pytorch_capture_output.pyi index 4498bd52f01a..5064cffd6fce 100644 --- a/stubs/D3DShot/d3dshot/capture_outputs/pytorch_capture_output.pyi +++ b/stubs/D3DShot/d3dshot/capture_outputs/pytorch_capture_output.pyi @@ -1,15 +1,10 @@ -from _typeshed import Incomplete from collections.abc import Sequence from ctypes import _CVoidConstPLike -from typing_extensions import Literal, TypeAlias +from typing_extensions import Literal from d3dshot.capture_output import CaptureOutput from PIL import Image - -# TODO: Complete types once we can import non-types dependencies -# See: https://github.com/python/typeshed/issues/5768 -# from torch import Tensor -_Tensor: TypeAlias = Incomplete +from torch import Tensor class PytorchCaptureOutput(CaptureOutput): def __init__(self) -> None: ... @@ -22,6 +17,6 @@ class PytorchCaptureOutput(CaptureOutput): height: int, region: tuple[int, int, int, int], rotation: int, - ) -> _Tensor: ... - def to_pil(self, frame: _Tensor) -> Image.Image: ... - def stack(self, frames: Sequence[_Tensor], stack_dimension: Literal["first", "last"]) -> _Tensor: ... + ) -> Tensor: ... + def to_pil(self, frame: Tensor) -> Image.Image: ... # type: ignore[override] + def stack(self, frames: Sequence[Tensor], stack_dimension: Literal["first", "last"]) -> Tensor: ... # type: ignore[override] diff --git a/stubs/D3DShot/d3dshot/dll/__init__.pyi b/stubs/D3DShot/d3dshot/dll/__init__.pyi index 56e0e35b58ae..54afa35ff744 100644 --- a/stubs/D3DShot/d3dshot/dll/__init__.pyi +++ b/stubs/D3DShot/d3dshot/dll/__init__.pyi @@ -6,6 +6,7 @@ from ctypes.wintypes import PFLOAT from typing import TypeVar from typing_extensions import TypeAlias +from comtypes import IUnknown from d3dshot.capture_output import _Frame _ProcessFuncRegionArg = TypeVar("_ProcessFuncRegionArg", tuple[int, int, int, int], None) @@ -20,9 +21,8 @@ if sys.platform == "win32": else: _HRESULT: TypeAlias = Incomplete -# TODO: Use comtypes.IUnknown once we can import non-types dependencies -# See: #5768 -class _IUnknown(_CData): +# Type issue in comtypes: Type "IUnknown" cannot be assigned to type "_CData" +class _IUnknown(IUnknown, _CData): def QueryInterface(self, interface: type, iid: _CData | None = ...) -> _HRESULT: ... def AddRef(self) -> c_ulong: ... def Release(self) -> c_ulong: ... diff --git a/stubs/JACK-Client/METADATA.toml b/stubs/JACK-Client/METADATA.toml index 20062a169165..3be1c39bf3e9 100644 --- a/stubs/JACK-Client/METADATA.toml +++ b/stubs/JACK-Client/METADATA.toml @@ -1,4 +1,5 @@ version = "0.5.*" +external_requires = ["numpy"] [tool.stubtest] ignore_missing_stub = false diff --git a/stubs/JACK-Client/jack/__init__.pyi b/stubs/JACK-Client/jack/__init__.pyi index 19f30bde54ce..715725bf876a 100644 --- a/stubs/JACK-Client/jack/__init__.pyi +++ b/stubs/JACK-Client/jack/__init__.pyi @@ -4,7 +4,10 @@ from collections.abc import Callable, Generator, Iterable, Iterator, Sequence from typing import Any, overload from typing_extensions import Literal, TypeAlias -_NDArray: TypeAlias = Any # FIXME: no typings for numpy arrays +import numpy as np +import numpy.typing as npt + +_NDArray: TypeAlias = npt.NDArray[np.generic] # incomplete: np.generic may be too wide class _JackPositionT: ... diff --git a/stubs/pycocotools/METADATA.toml b/stubs/pycocotools/METADATA.toml index 424bb5bd8834..681ce35c5692 100644 --- a/stubs/pycocotools/METADATA.toml +++ b/stubs/pycocotools/METADATA.toml @@ -1,4 +1,5 @@ version = "2.0.*" +external_requires = ["numpy"] [tool.stubtest] ignore_missing_stub = false diff --git a/stubs/pycocotools/pycocotools/coco.pyi b/stubs/pycocotools/pycocotools/coco.pyi index 1cb93c63f2e9..e35c945ed06d 100644 --- a/stubs/pycocotools/pycocotools/coco.pyi +++ b/stubs/pycocotools/pycocotools/coco.pyi @@ -1,17 +1,14 @@ -from _typeshed import Incomplete from collections.abc import Collection, Sequence from pathlib import Path from typing import Generic, TypeVar, overload from typing_extensions import Literal, TypeAlias, TypedDict -from . import _EncodedRLE +import numpy as np +import numpy.typing as npt -# TODO: Use numpy types when #5768 is resolved. -# import numpy as np -# import numpy.typing as npt +from . import _EncodedRLE -PYTHON_VERSION: Incomplete -_NDArray: TypeAlias = Incomplete +PYTHON_VERSION: int class _Image(TypedDict): id: int @@ -82,13 +79,11 @@ class COCO: def showAnns(self, anns: Sequence[_Annotation], draw_bbox: bool = ...) -> None: ... def loadRes(self, resFile: str) -> COCO: ... def download(self, tarDir: str | None = ..., imgIds: Collection[int] = ...) -> Literal[-1] | None: ... - def loadNumpyAnnotations(self, data: _NDArray) -> list[_Annotation]: ... - # def loadNumpyAnnotations(self, data: npt.NDArray[np.float64]) -> list[_Annotation]: ... + def loadNumpyAnnotations(self, data: npt.NDArray[np.float64]) -> list[_Annotation]: ... @overload def annToRLE(self, ann: _AnnotationG[_RLE]) -> _RLE: ... @overload def annToRLE(self, ann: _AnnotationG[_EncodedRLE]) -> _EncodedRLE: ... @overload def annToRLE(self, ann: _AnnotationG[_TPolygonSegmentation]) -> _EncodedRLE: ... - def annToMask(self, ann: _Annotation) -> _NDArray: ... - # def annToMask(self, ann: _Annotation) -> npt.NDArray[np.uint8]: ... + def annToMask(self, ann: _Annotation) -> npt.NDArray[np.uint8]: ... diff --git a/stubs/pycocotools/pycocotools/cocoeval.pyi b/stubs/pycocotools/pycocotools/cocoeval.pyi index d9845c6cb712..41997897268f 100644 --- a/stubs/pycocotools/pycocotools/cocoeval.pyi +++ b/stubs/pycocotools/pycocotools/cocoeval.pyi @@ -1,13 +1,10 @@ -from _typeshed import Incomplete from typing_extensions import Literal, TypeAlias, TypedDict -from .coco import COCO +import numpy as np +import numpy.typing as npt -# TODO: Use numpy types when #5768 is resolved. -# import numpy as np -# import numpy.typing as npt +from .coco import COCO -_NDArray: TypeAlias = Incomplete _TIOU: TypeAlias = Literal["segm", "bbox", "keypoints"] class _EvaluationResult(TypedDict): @@ -17,15 +14,11 @@ class _EvaluationResult(TypedDict): maxDet: int dtIds: list[int] gtIds: list[int] - dtMatches: _NDArray - # dtMatches: npt.NDArray[np.float64] - gtMatches: _NDArray - # gtMatches: npt.NDArray[np.float64] + dtMatches: npt.NDArray[np.float64] + gtMatches: npt.NDArray[np.float64] dtScores: list[float] - gtIgnore: _NDArray - # gtIgnore: npt.NDArray[np.float64] - dtIgnore: _NDArray - # dtIgnore: npt.NDArray[np.float64] + gtIgnore: npt.NDArray[np.float64] + dtIgnore: npt.NDArray[np.float64] class COCOeval: cocoGt: COCO @@ -33,14 +26,12 @@ class COCOeval: evalImgs: list[_EvaluationResult] eval: _EvaluationResult params: Params - stats: _NDArray - # stats: npt.NDArray[np.float64] + stats: npt.NDArray[np.float64] ious: dict[tuple[int, int], list[float]] def __init__(self, cocoGt: COCO | None = ..., cocoDt: COCO | None = ..., iouType: _TIOU = ...) -> None: ... def evaluate(self) -> None: ... def computeIoU(self, imgId: int, catId: int) -> list[float]: ... - def computeOks(self, imgId: int, catId: int) -> _NDArray: ... - # def computeOks(self, imgId: int, catId: int) -> npt.NDArray[np.float64]: ... + def computeOks(self, imgId: int, catId: int) -> npt.NDArray[np.float64]: ... def evaluateImg(self, imgId: int, catId: int, aRng: list[int], maxDet: int) -> _EvaluationResult: ... def accumulate(self, p: Params | None = ...) -> None: ... def summarize(self) -> None: ... @@ -48,16 +39,13 @@ class COCOeval: class Params: imgIds: list[int] catIds: list[int] - iouThrs: _NDArray - # iouThrs: npt.NDArray[np.float64] - recThrs: _NDArray - # recThrs: npt.NDArray[np.float64] + iouThrs: npt.NDArray[np.float64] + recThrs: npt.NDArray[np.float64] maxDets: list[int] areaRng: list[int] areaRngLbl: list[str] useCats: int - kpt_oks_sigmas: _NDArray - # kpt_oks_sigmas: npt.NDArray[np.float64] + kpt_oks_sigmas: npt.NDArray[np.float64] iouType: _TIOU useSegm: int | None def __init__(self, iouType: _TIOU = ...) -> None: ... diff --git a/stubs/pycocotools/pycocotools/mask.pyi b/stubs/pycocotools/pycocotools/mask.pyi index 07d30d6624b9..1d66c58d18b7 100644 --- a/stubs/pycocotools/pycocotools/mask.pyi +++ b/stubs/pycocotools/pycocotools/mask.pyi @@ -1,17 +1,15 @@ -from _typeshed import Incomplete from typing import Any, overload from typing_extensions import TypeAlias -from . import _EncodedRLE +import numpy as np +import numpy.typing as npt -# TODO: Use numpy types when #5768 is resolved. -# import numpy as np -# import numpy.typing as npt +from . import _EncodedRLE -_NPUInt32: TypeAlias = Incomplete # np.uint32 -_NDArrayUInt8: TypeAlias = Incomplete # npt.NDArray[np.uint8] -_NDArrayUInt32: TypeAlias = Incomplete # npt.NDArray[np.uint32] -_NDArrayFloat64: TypeAlias = Incomplete # npt.NDArray[np.float64] +_NPUInt32: TypeAlias = np.uint32 +_NDArrayUInt8: TypeAlias = npt.NDArray[np.uint8] +_NDArrayUInt32: TypeAlias = npt.NDArray[np.uint32] +_NDArrayFloat64: TypeAlias = npt.NDArray[np.float64] def iou( dt: _NDArrayUInt32 | list[float] | list[_EncodedRLE], @@ -19,8 +17,6 @@ def iou( pyiscrowd: list[int] | _NDArrayUInt8, ) -> list[Any] | _NDArrayFloat64: ... def merge(rleObjs: list[_EncodedRLE], intersect: int = ...) -> _EncodedRLE: ... - -# ignore an "overlapping overloads" error due to _NDArrayInt32 being an alias for `Incomplete` for now @overload def frPyObjects(pyobj: _NDArrayUInt32 | list[list[int]] | list[_EncodedRLE], h: int, w: int) -> list[_EncodedRLE]: ... # type: ignore[misc] @overload diff --git a/stubs/pyinstaller/METADATA.toml b/stubs/pyinstaller/METADATA.toml index 19f97e644e90..450cf375fe1d 100644 --- a/stubs/pyinstaller/METADATA.toml +++ b/stubs/pyinstaller/METADATA.toml @@ -1,5 +1,6 @@ version = "5.7.*" requires = ["types-setuptools"] +external_requires = ["modulegraph"] [tool.stubtest] ignore_missing_stub = false diff --git a/stubs/pyinstaller/PyInstaller/depend/analysis.pyi b/stubs/pyinstaller/PyInstaller/depend/analysis.pyi index 4e1682dd393e..025077934db0 100644 --- a/stubs/pyinstaller/PyInstaller/depend/analysis.pyi +++ b/stubs/pyinstaller/PyInstaller/depend/analysis.pyi @@ -6,7 +6,7 @@ from _typeshed import Incomplete, StrPath, SupportsKeysAndGetItem from collections.abc import Iterable from typing_extensions import TypeAlias -from PyInstaller.lib.modulegraph.modulegraph import Alias, Node +from modulegraph.modulegraph import Alias, Node _LazyNode: TypeAlias = Iterable[Node] | Iterable[str] | Alias | None # from altgraph.Graph import Graph diff --git a/stubs/pyinstaller/PyInstaller/depend/imphookapi.pyi b/stubs/pyinstaller/PyInstaller/depend/imphookapi.pyi index 268ee3618eeb..ef709c6157b7 100644 --- a/stubs/pyinstaller/PyInstaller/depend/imphookapi.pyi +++ b/stubs/pyinstaller/PyInstaller/depend/imphookapi.pyi @@ -7,10 +7,10 @@ from collections.abc import Generator, Iterable from typing import Any from typing_extensions import Literal +from modulegraph.modulegraph import Package from PyInstaller.building.build_main import Analysis from PyInstaller.building.datastruct import TOC from PyInstaller.depend.analysis import PyiModuleGraph -from PyInstaller.lib.modulegraph.modulegraph import Package # https://pyinstaller.org/en/stable/hooks.html#the-pre-safe-import-module-psim-api-method class PreSafeImportModuleAPI: diff --git a/stubs/pyinstaller/PyInstaller/lib/__init__.pyi b/stubs/pyinstaller/PyInstaller/lib/__init__.pyi deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/stubs/pyinstaller/PyInstaller/lib/modulegraph/__init__.pyi b/stubs/pyinstaller/PyInstaller/lib/modulegraph/__init__.pyi deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/stubs/pyinstaller/PyInstaller/lib/modulegraph/modulegraph.pyi b/stubs/pyinstaller/PyInstaller/lib/modulegraph/modulegraph.pyi deleted file mode 100644 index ef9cfc5c97e6..000000000000 --- a/stubs/pyinstaller/PyInstaller/lib/modulegraph/modulegraph.pyi +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Any, Protocol - -class _SupportsGraphident(Protocol): - graphident: str - -# TODO: For typing purposes, once #5768 is complete, it'll be easier to use the modulegraph package directly. - -# code, filename and packagepath are always initialized to None. But they can be given a value later. -class Node: - # Compiled code. See stdlib.builtins.compile - code: Any | None - filename: str | None - graphident: str - identifier: str - packagepath: str | None - def __init__(self, identifier: str) -> None: ... - def is_global_attr(self, attr_name: str) -> bool: ... - def is_submodule(self, submodule_basename: str) -> bool: ... - def add_global_attr(self, attr_name: str) -> None: ... - def add_global_attrs_from_module(self, target_module: Node) -> None: ... - def add_submodule(self, submodule_basename: str, submodule_node: Node) -> None: ... - def get_submodule(self, submodule_basename: str) -> Node: ... - def get_submodule_or_none(self, submodule_basename: str) -> Node | None: ... - def remove_global_attr_if_found(self, attr_name: str) -> None: ... - def __eq__(self, other: object) -> bool: ... - def __ne__(self, other: object) -> bool: ... - def __lt__(self, other: _SupportsGraphident) -> bool: ... - def __le__(self, other: _SupportsGraphident) -> bool: ... - def __gt__(self, other: _SupportsGraphident) -> bool: ... - def __ge__(self, other: _SupportsGraphident) -> bool: ... - def infoTuple(self) -> tuple[str]: ... - -class Alias(str): ... - -class BaseModule(Node): - filename: str - packagepath: str - def __init__(self, name: str, filename: str | None = ..., path: str | None = ...) -> None: ... - # Returns a tuple of length 0, 1, 2, or 3 - def infoTuple(self) -> tuple[str, ...]: ... # type: ignore[override] - -class Package(BaseModule): ... diff --git a/tests/check_consistent.py b/tests/check_consistent.py index 237fffe0897b..a3d1c8b44545 100755 --- a/tests/check_consistent.py +++ b/tests/check_consistent.py @@ -27,6 +27,7 @@ metadata_keys = { "version", "requires", + "external_requires", "extra_description", "stub_distribution", "obsolete_since", diff --git a/tests/external_pip_dependencies.py b/tests/external_pip_dependencies.py new file mode 100644 index 000000000000..e036c537c328 --- /dev/null +++ b/tests/external_pip_dependencies.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import os +import subprocess +import sys +from collections.abc import Generator, Iterable + +import tomli + + +def install(distributions: Iterable[str] | None = None) -> None: + dependencies = " ".join(get(distributions)) + print("install:", distributions, dependencies) + subprocess.run(["pip", "install", dependencies], capture_output=True) + + +def get(distributions: Iterable[str] | None = None) -> Generator[str, None, None]: + if not distributions: + distributions = os.listdir("stubs") + + for distribution in distributions: + with open(f"stubs/{distribution}/METADATA.toml", "rb") as file: + for package in tomli.load(file).get("external_requires", []): + yield package + + +if __name__ == "__main__": + # print("main", sys.argv[1:]) + # install(sys.argv[1:]) + for dependency in get(sys.argv[1:]): + print(dependency) diff --git a/tests/mypy_test.py b/tests/mypy_test.py index 6eee3213a037..6c9f64585443 100644 --- a/tests/mypy_test.py +++ b/tests/mypy_test.py @@ -252,10 +252,14 @@ def get_mypy_flags(args: TestConfig, temp_name: str, *, testing_stdlib: bool) -> "--custom-typeshed-dir", str(Path(__file__).parent.parent), "--strict", + # Ignoring "module is installed, but missing library stubs or py.typed marker" + # because external dependencies may not be marked as typed. + # pyright already flags non-typed external dependencies as warnings. + # TODO: This mutes ALL import errors, can we narrow it down? + "--ignore-missing-imports", # Stub completion is checked by pyright (--allow-*-defs) "--allow-untyped-defs", "--allow-incomplete-defs", - "--allow-subclassing-any", # Needed until we can use non-types dependencies #5768 "--enable-error-code", "ignore-without-code", "--config-file", diff --git a/tests/pytype_test.py b/tests/pytype_test.py index d582cdb71efc..f65fa17e6f7e 100755 --- a/tests/pytype_test.py +++ b/tests/pytype_test.py @@ -59,7 +59,10 @@ def create_parser() -> argparse.ArgumentParser: def run_pytype(*, filename: str, python_version: str) -> str | None: """Runs pytype, returning the stderr if any.""" if python_version not in _LOADERS: - options = pytype_config.Options.create("", parse_pyi=True, python_version=python_version) + # Disabling pyi-error because external dependencies may not have stub files + # pyright already flags non-typed external dependencies as warnings + # TODO: Get pytype to work "pytype.load_pytd.BadDependencyError: Can't find pyi for 'torch', referenced from 'd3dshot.capture_output'" + options = pytype_config.Options.create("", parse_pyi=True, python_version=python_version, disable="pyi-error") loader = load_pytd.create_loader(options) _LOADERS[python_version] = (options, loader) options, loader = _LOADERS[python_version] diff --git a/tests/stubtest_third_party.py b/tests/stubtest_third_party.py index 61ba07e71777..c148b7e833f1 100755 --- a/tests/stubtest_third_party.py +++ b/tests/stubtest_third_party.py @@ -71,11 +71,14 @@ def run_stubtest(dist: Path, *, verbose: bool = False, specified_stubs_only: boo print_command_failure("Failed to install requirements", e) return False + external_requires = metadata.get("external_requires", []) + # We need stubtest to be able to import the package, so install mypy into the venv # Hopefully mypy continues to not need too many dependencies # TODO: Maybe find a way to cache these in CI dists_to_install = [dist_req, get_mypy_req()] dists_to_install.extend(metadata.get("requires", [])) + dists_to_install.extend(external_requires) pip_cmd = [pip_exe, "install"] + dists_to_install try: subprocess.run(pip_cmd, check=True, capture_output=True) @@ -84,6 +87,11 @@ def run_stubtest(dist: Path, *, verbose: bool = False, specified_stubs_only: boo return False ignore_missing_stub = ["--ignore-missing-stub"] if stubtest_meta.get("ignore_missing_stub", True) else [] + # Ignoring "module is installed, but missing library stubs or py.typed marker" + # because external dependencies may not be marked as typed. + # pyright already flags non-typed external dependencies as warnings. + # TODO: This mutes ALL import errors, can we narrow it down? + # ignore_missing_imports = ["--ignore-missing-imports"] if external_requires else [] packages_to_check = [d.name for d in dist.iterdir() if d.is_dir() and d.name.isidentifier()] modules_to_check = [d.stem for d in dist.iterdir() if d.is_file() and d.suffix == ".pyi"] stubtest_cmd = [ @@ -94,6 +102,7 @@ def run_stubtest(dist: Path, *, verbose: bool = False, specified_stubs_only: boo "--custom-typeshed-dir", str(dist.parent.parent), *ignore_missing_stub, + # *ignore_missing_imports, *packages_to_check, *modules_to_check, ] From 86d65e7b8b7f6f9cdc3743944ee15467ead667fa Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 16 Dec 2022 20:15:42 -0500 Subject: [PATCH 2/5] typo --- .github/workflows/daily.yml | 2 +- .github/workflows/stubtest_third_party.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 4f40d2633216..cb733586f1b3 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -78,7 +78,7 @@ jobs: sudo apt update && sudo apt install -y $PACKAGES fi - PYTHON_EXECUTABLE="xvfb-run" + PYTHON_EXECUTABLE="xvfb-run python" else if [ "${{ runner.os }}" = "macOS" ] && [ -n "$PACKAGES" ]; then NONINTERACTIVE=1 brew install $PACKAGES diff --git a/.github/workflows/stubtest_third_party.yml b/.github/workflows/stubtest_third_party.yml index 0573d1814e1a..a82c83c55703 100644 --- a/.github/workflows/stubtest_third_party.yml +++ b/.github/workflows/stubtest_third_party.yml @@ -66,7 +66,7 @@ jobs: sudo apt update && sudo apt install -y $PACKAGES fi - PYTHON_EXECUTABLE="xvfb-run" + PYTHON_EXECUTABLE="xvfb-run python" else if [ "${{ runner.os }}" = "macOS" ] && [ -n "$PACKAGES" ]; then echo "Installing Homebrew packages: $PACKAGES" From 8d7e141be1e534328e7a2eeed1d06cc93a001ea2 Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 16 Dec 2022 20:23:22 -0500 Subject: [PATCH 3/5] Revert --allow-subclassing-any (will tackle later) --- tests/mypy_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/mypy_test.py b/tests/mypy_test.py index 6c9f64585443..009956382591 100644 --- a/tests/mypy_test.py +++ b/tests/mypy_test.py @@ -260,6 +260,7 @@ def get_mypy_flags(args: TestConfig, temp_name: str, *, testing_stdlib: bool) -> # Stub completion is checked by pyright (--allow-*-defs) "--allow-untyped-defs", "--allow-incomplete-defs", + "--allow-subclassing-any", # Needed until we can use non-types dependencies #5768 "--enable-error-code", "ignore-without-code", "--config-file", From 6e05706bd239670c383cbc88d330b77a856c479f Mon Sep 17 00:00:00 2001 From: Avasam Date: Sat, 31 Dec 2022 12:51:32 -0500 Subject: [PATCH 4/5] Merge external_requires in requires --- .github/workflows/tests.yml | 1 - pyrightconfig.stricter.json | 2 +- stubs/D3DShot/METADATA.toml | 3 +-- stubs/JACK-Client/METADATA.toml | 2 +- stubs/pycocotools/METADATA.toml | 2 +- stubs/pyinstaller/METADATA.toml | 2 +- tests/check_consistent.py | 1 - tests/external_pip_dependencies.py | 7 +++---- tests/stubtest_third_party.py | 3 --- 9 files changed, 8 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 568ae6719e9a..8304bbfe1fda 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,7 +88,6 @@ jobs: cache: pip cache-dependency-path: requirements-tests.txt - run: pip install -r requirements-tests.txt - - run: pip install $(python tests/external_pip_dependencies.py) - run: python ./tests/mypy_test.py --platform=${{ matrix.platform }} --python-version=${{ matrix.python-version }} regression-tests: diff --git a/pyrightconfig.stricter.json b/pyrightconfig.stricter.json index 9ebd7a453185..73dc0031522f 100644 --- a/pyrightconfig.stricter.json +++ b/pyrightconfig.stricter.json @@ -86,7 +86,7 @@ // It's still useful to raise a warning to flag those dependencies. "useLibraryCodeForTypes": true, "reportMissingTypeStubs": "warning", - // Typeshed uses type-stubs, the source is not needed. + // Stubs don't need the actual modules to be installed "reportMissingModuleSource": "none", // Incompatible overrides and property type mismatches are out of typeshed's control // as they are inherited from the implementation. diff --git a/stubs/D3DShot/METADATA.toml b/stubs/D3DShot/METADATA.toml index f64f40b0243c..d4daff347ca3 100644 --- a/stubs/D3DShot/METADATA.toml +++ b/stubs/D3DShot/METADATA.toml @@ -1,6 +1,5 @@ version = "0.1.*" -requires = ["types-Pillow"] -external_requires = ["numpy", "torch", "comtypes"] +requires = ["types-Pillow", "numpy", "torch", "comtypes"] [tool.stubtest] # TODO: figure out how to run stubtest for this package diff --git a/stubs/JACK-Client/METADATA.toml b/stubs/JACK-Client/METADATA.toml index 3be1c39bf3e9..b4de4a0b2bf5 100644 --- a/stubs/JACK-Client/METADATA.toml +++ b/stubs/JACK-Client/METADATA.toml @@ -1,5 +1,5 @@ version = "0.5.*" -external_requires = ["numpy"] +requires = ["numpy"] [tool.stubtest] ignore_missing_stub = false diff --git a/stubs/pycocotools/METADATA.toml b/stubs/pycocotools/METADATA.toml index 681ce35c5692..d7aa2ab6ae9a 100644 --- a/stubs/pycocotools/METADATA.toml +++ b/stubs/pycocotools/METADATA.toml @@ -1,5 +1,5 @@ version = "2.0.*" -external_requires = ["numpy"] +requires = ["numpy"] [tool.stubtest] ignore_missing_stub = false diff --git a/stubs/pyinstaller/METADATA.toml b/stubs/pyinstaller/METADATA.toml index 450cf375fe1d..2a50d84a2653 100644 --- a/stubs/pyinstaller/METADATA.toml +++ b/stubs/pyinstaller/METADATA.toml @@ -1,6 +1,6 @@ version = "5.7.*" requires = ["types-setuptools"] -external_requires = ["modulegraph"] +requires = ["modulegraph"] [tool.stubtest] ignore_missing_stub = false diff --git a/tests/check_consistent.py b/tests/check_consistent.py index a3d1c8b44545..237fffe0897b 100755 --- a/tests/check_consistent.py +++ b/tests/check_consistent.py @@ -27,7 +27,6 @@ metadata_keys = { "version", "requires", - "external_requires", "extra_description", "stub_distribution", "obsolete_since", diff --git a/tests/external_pip_dependencies.py b/tests/external_pip_dependencies.py index e036c537c328..5d7c8cd4d95e 100644 --- a/tests/external_pip_dependencies.py +++ b/tests/external_pip_dependencies.py @@ -6,7 +6,7 @@ import sys from collections.abc import Generator, Iterable -import tomli +from utils import read_dependencies def install(distributions: Iterable[str] | None = None) -> None: @@ -20,9 +20,8 @@ def get(distributions: Iterable[str] | None = None) -> Generator[str, None, None distributions = os.listdir("stubs") for distribution in distributions: - with open(f"stubs/{distribution}/METADATA.toml", "rb") as file: - for package in tomli.load(file).get("external_requires", []): - yield package + for package in read_dependencies(distribution).external_pkgs: + yield package if __name__ == "__main__": diff --git a/tests/stubtest_third_party.py b/tests/stubtest_third_party.py index 0f88e602d553..e5ca1d6e37cd 100755 --- a/tests/stubtest_third_party.py +++ b/tests/stubtest_third_party.py @@ -57,14 +57,11 @@ def run_stubtest(dist: Path, *, verbose: bool = False, specified_stubs_only: boo print_command_failure("Failed to install requirements", e) return False - external_requires = metadata.get("external_requires", []) - # We need stubtest to be able to import the package, so install mypy into the venv # Hopefully mypy continues to not need too many dependencies # TODO: Maybe find a way to cache these in CI dists_to_install = [dist_req, get_mypy_req()] dists_to_install.extend(metadata.get("requires", [])) - dists_to_install.extend(external_requires) pip_cmd = [pip_exe, "install"] + dists_to_install try: subprocess.run(pip_cmd, check=True, capture_output=True) From f78f335beb3d667e68bd58a6f278c3f57934cc62 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sat, 31 Dec 2022 13:02:57 -0500 Subject: [PATCH 5/5] Revert to using vendored modulegraph in PyInstaller --- stubs/pyinstaller/METADATA.toml | 1 - .../PyInstaller/depend/analysis.pyi | 2 +- .../PyInstaller/depend/imphookapi.pyi | 2 +- .../pyinstaller/PyInstaller/lib/__init__.pyi | 0 .../PyInstaller/lib/modulegraph/__init__.pyi | 0 .../lib/modulegraph/modulegraph.pyi | 43 +++++++++++++++++++ 6 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 stubs/pyinstaller/PyInstaller/lib/__init__.pyi create mode 100644 stubs/pyinstaller/PyInstaller/lib/modulegraph/__init__.pyi create mode 100644 stubs/pyinstaller/PyInstaller/lib/modulegraph/modulegraph.pyi diff --git a/stubs/pyinstaller/METADATA.toml b/stubs/pyinstaller/METADATA.toml index 2a50d84a2653..19f97e644e90 100644 --- a/stubs/pyinstaller/METADATA.toml +++ b/stubs/pyinstaller/METADATA.toml @@ -1,6 +1,5 @@ version = "5.7.*" requires = ["types-setuptools"] -requires = ["modulegraph"] [tool.stubtest] ignore_missing_stub = false diff --git a/stubs/pyinstaller/PyInstaller/depend/analysis.pyi b/stubs/pyinstaller/PyInstaller/depend/analysis.pyi index 025077934db0..4e1682dd393e 100644 --- a/stubs/pyinstaller/PyInstaller/depend/analysis.pyi +++ b/stubs/pyinstaller/PyInstaller/depend/analysis.pyi @@ -6,7 +6,7 @@ from _typeshed import Incomplete, StrPath, SupportsKeysAndGetItem from collections.abc import Iterable from typing_extensions import TypeAlias -from modulegraph.modulegraph import Alias, Node +from PyInstaller.lib.modulegraph.modulegraph import Alias, Node _LazyNode: TypeAlias = Iterable[Node] | Iterable[str] | Alias | None # from altgraph.Graph import Graph diff --git a/stubs/pyinstaller/PyInstaller/depend/imphookapi.pyi b/stubs/pyinstaller/PyInstaller/depend/imphookapi.pyi index ef709c6157b7..268ee3618eeb 100644 --- a/stubs/pyinstaller/PyInstaller/depend/imphookapi.pyi +++ b/stubs/pyinstaller/PyInstaller/depend/imphookapi.pyi @@ -7,10 +7,10 @@ from collections.abc import Generator, Iterable from typing import Any from typing_extensions import Literal -from modulegraph.modulegraph import Package from PyInstaller.building.build_main import Analysis from PyInstaller.building.datastruct import TOC from PyInstaller.depend.analysis import PyiModuleGraph +from PyInstaller.lib.modulegraph.modulegraph import Package # https://pyinstaller.org/en/stable/hooks.html#the-pre-safe-import-module-psim-api-method class PreSafeImportModuleAPI: diff --git a/stubs/pyinstaller/PyInstaller/lib/__init__.pyi b/stubs/pyinstaller/PyInstaller/lib/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/stubs/pyinstaller/PyInstaller/lib/modulegraph/__init__.pyi b/stubs/pyinstaller/PyInstaller/lib/modulegraph/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/stubs/pyinstaller/PyInstaller/lib/modulegraph/modulegraph.pyi b/stubs/pyinstaller/PyInstaller/lib/modulegraph/modulegraph.pyi new file mode 100644 index 000000000000..b6984972b345 --- /dev/null +++ b/stubs/pyinstaller/PyInstaller/lib/modulegraph/modulegraph.pyi @@ -0,0 +1,43 @@ +# Note: modulegraph is vendored by PyInstaller. +# We're only typing the bare-minimum for PyInstaller itself to be complete. +# This also avoids having to whitelist modulegraph as a third-party dependency in stub-uploader +from typing import Any, Protocol + +class _SupportsGraphident(Protocol): + graphident: str + +# code, filename and packagepath are always initialized to None. But they can be given a value later. +class Node: + # Compiled code. See stdlib.builtins.compile + code: Any | None + filename: str | None + graphident: str + identifier: str + packagepath: str | None + def __init__(self, identifier: str) -> None: ... + def is_global_attr(self, attr_name: str) -> bool: ... + def is_submodule(self, submodule_basename: str) -> bool: ... + def add_global_attr(self, attr_name: str) -> None: ... + def add_global_attrs_from_module(self, target_module: Node) -> None: ... + def add_submodule(self, submodule_basename: str, submodule_node: Node) -> None: ... + def get_submodule(self, submodule_basename: str) -> Node: ... + def get_submodule_or_none(self, submodule_basename: str) -> Node | None: ... + def remove_global_attr_if_found(self, attr_name: str) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __lt__(self, other: _SupportsGraphident) -> bool: ... + def __le__(self, other: _SupportsGraphident) -> bool: ... + def __gt__(self, other: _SupportsGraphident) -> bool: ... + def __ge__(self, other: _SupportsGraphident) -> bool: ... + def infoTuple(self) -> tuple[str]: ... + +class Alias(str): ... + +class BaseModule(Node): + filename: str + packagepath: str + def __init__(self, name: str, filename: str | None = ..., path: str | None = ...) -> None: ... + # Returns a tuple of length 0, 1, 2, or 3 + def infoTuple(self) -> tuple[str, ...]: ... # type: ignore[override] + +class Package(BaseModule): ...