From 9e0ac952f4ef8aa10908d2b05d715973a23d5db7 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 8 Apr 2022 19:50:34 -0400 Subject: [PATCH 1/8] Fix instance_attrs building error in Qt brain --- ChangeLog | 3 +++ astroid/brain/brain_qt.py | 12 ++++++------ requirements_test_brain.txt | 1 + tests/unittest_brain_qt.py | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 tests/unittest_brain_qt.py diff --git a/ChangeLog b/ChangeLog index b0ad29b96d..10a7e90857 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,9 @@ What's New in astroid 2.11.3? ============================= Release date: TBA +* Fixed an error in the Qt brain when building ``instance_attrs``. + + Closes PyCQA/pylint#6221 What's New in astroid 2.11.2? diff --git a/astroid/brain/brain_qt.py b/astroid/brain/brain_qt.py index a9be8f2760..a4c17dcdb5 100644 --- a/astroid/brain/brain_qt.py +++ b/astroid/brain/brain_qt.py @@ -34,9 +34,9 @@ def emit(self, *args): """ ) signal_cls = module["pyqtSignal"] - node.instance_attrs["emit"] = signal_cls["emit"] - node.instance_attrs["disconnect"] = signal_cls["disconnect"] - node.instance_attrs["connect"] = signal_cls["connect"] + node.instance_attrs["emit"] = [signal_cls["emit"]] + node.instance_attrs["disconnect"] = [signal_cls["disconnect"]] + node.instance_attrs["connect"] = [signal_cls["connect"]] def transform_pyside_signal(node): @@ -52,9 +52,9 @@ def emit(self, *args): """ ) signal_cls = module["NotPySideSignal"] - node.instance_attrs["connect"] = signal_cls["connect"] - node.instance_attrs["disconnect"] = signal_cls["disconnect"] - node.instance_attrs["emit"] = signal_cls["emit"] + node.instance_attrs["connect"] = [signal_cls["connect"]] + node.instance_attrs["disconnect"] = [signal_cls["disconnect"]] + node.instance_attrs["emit"] = [signal_cls["emit"]] def pyqt4_qtcore_transform(): diff --git a/requirements_test_brain.txt b/requirements_test_brain.txt index a45e3bf2d0..9f7e13411f 100644 --- a/requirements_test_brain.txt +++ b/requirements_test_brain.txt @@ -3,6 +3,7 @@ types-attrs nose numpy>=1.17.0 python-dateutil +PyQt6 types-python-dateutil six types-six diff --git a/tests/unittest_brain_qt.py b/tests/unittest_brain_qt.py new file mode 100644 index 0000000000..53ab55cab5 --- /dev/null +++ b/tests/unittest_brain_qt.py @@ -0,0 +1,36 @@ +# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html +# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt + +from importlib.util import find_spec + +import pytest + +from astroid import extract_node +from astroid.bases import UnboundMethod +from astroid.manager import AstroidManager +from astroid.nodes import FunctionDef + +HAS_PYQT6 = find_spec("PyQt6") + + +@pytest.mark.skipif(HAS_PYQT6 is None, reason="This test requires the PyQt6 library.") +class TestBrainQt: + @staticmethod + def test_value_of_lambda_instance_attrs_is_list(): + """Regression test for https://github.com/PyCQA/pylint/issues/6221 + + A crash occurred in pylint when a nodes.FunctionDef was iterated directly, + giving items like "self" instead of iterating a one-element list containing + the wanted nodes.FunctionDef. + """ + src = """ + from PyQt6 import QtPrintSupport as printsupport + printsupport.QPrintPreviewDialog.paintRequested #@ + """ + AstroidManager.brain["extension_package_whitelist"] = {"PyQt6.QtPrintSupport"} + node = extract_node(src) + attribute_node = node.inferred()[0] + # scoped_nodes.Lambda.instance_attrs is typed as Dict[str, List[NodeNG]] + assert isinstance(attribute_node, UnboundMethod) + assert isinstance(attribute_node.instance_attrs["connect"][0], FunctionDef) From 1431862785534cb7ea4c89c587c4acf8538d3542 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 8 Apr 2022 22:41:32 -0400 Subject: [PATCH 2/8] add skip --- tests/unittest_brain_qt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unittest_brain_qt.py b/tests/unittest_brain_qt.py index 53ab55cab5..350d8ba285 100644 --- a/tests/unittest_brain_qt.py +++ b/tests/unittest_brain_qt.py @@ -6,7 +6,7 @@ import pytest -from astroid import extract_node +from astroid import Uninferable, extract_node from astroid.bases import UnboundMethod from astroid.manager import AstroidManager from astroid.nodes import FunctionDef @@ -32,5 +32,7 @@ def test_value_of_lambda_instance_attrs_is_list(): node = extract_node(src) attribute_node = node.inferred()[0] # scoped_nodes.Lambda.instance_attrs is typed as Dict[str, List[NodeNG]] + if attribute_node is Uninferable: + pytest.skip("PyQt6 C bindings may not be installed?") assert isinstance(attribute_node, UnboundMethod) assert isinstance(attribute_node.instance_attrs["connect"][0], FunctionDef) From c31a0873405c32ae62c0695547856cd0cfafefc3 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 8 Apr 2022 22:52:18 -0400 Subject: [PATCH 3/8] Move comment down --- tests/unittest_brain_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unittest_brain_qt.py b/tests/unittest_brain_qt.py index 350d8ba285..18e0d6d3e8 100644 --- a/tests/unittest_brain_qt.py +++ b/tests/unittest_brain_qt.py @@ -31,8 +31,8 @@ def test_value_of_lambda_instance_attrs_is_list(): AstroidManager.brain["extension_package_whitelist"] = {"PyQt6.QtPrintSupport"} node = extract_node(src) attribute_node = node.inferred()[0] - # scoped_nodes.Lambda.instance_attrs is typed as Dict[str, List[NodeNG]] if attribute_node is Uninferable: pytest.skip("PyQt6 C bindings may not be installed?") assert isinstance(attribute_node, UnboundMethod) + # scoped_nodes.Lambda.instance_attrs is typed as Dict[str, List[NodeNG]] assert isinstance(attribute_node.instance_attrs["connect"][0], FunctionDef) From 6aeeb8e0b69421425a7c72b64856e65b686cfa69 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 9 Apr 2022 09:53:26 -0400 Subject: [PATCH 4/8] Add typing --- astroid/brain/brain_qt.py | 8 ++++---- astroid/nodes/scoped_nodes/mixin.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/astroid/brain/brain_qt.py b/astroid/brain/brain_qt.py index a4c17dcdb5..e2cfe3adbf 100644 --- a/astroid/brain/brain_qt.py +++ b/astroid/brain/brain_qt.py @@ -21,7 +21,7 @@ def _looks_like_signal(node, signal_name="pyqtSignal"): return False -def transform_pyqt_signal(node): +def transform_pyqt_signal(node: nodes.FunctionDef): module = parse( """ class pyqtSignal(object): @@ -33,13 +33,13 @@ def emit(self, *args): pass """ ) - signal_cls = module["pyqtSignal"] + signal_cls: nodes.ClassDef = module["pyqtSignal"] node.instance_attrs["emit"] = [signal_cls["emit"]] node.instance_attrs["disconnect"] = [signal_cls["disconnect"]] node.instance_attrs["connect"] = [signal_cls["connect"]] -def transform_pyside_signal(node): +def transform_pyside_signal(node: nodes.FunctionDef): module = parse( """ class NotPySideSignal(object): @@ -51,7 +51,7 @@ def emit(self, *args): pass """ ) - signal_cls = module["NotPySideSignal"] + signal_cls: nodes.ClassDef = module["NotPySideSignal"] node.instance_attrs["connect"] = [signal_cls["connect"]] node.instance_attrs["disconnect"] = [signal_cls["disconnect"]] node.instance_attrs["emit"] = [signal_cls["emit"]] diff --git a/astroid/nodes/scoped_nodes/mixin.py b/astroid/nodes/scoped_nodes/mixin.py index a6c23b5c07..d248c9fa36 100644 --- a/astroid/nodes/scoped_nodes/mixin.py +++ b/astroid/nodes/scoped_nodes/mixin.py @@ -108,7 +108,7 @@ def add_local_node(self, child_node, name=None): self._append_node(child_node) self.set_local(name or child_node.name, child_node) - def __getitem__(self, item): + def __getitem__(self, item) -> "nodes.NodeNG": """The first node the defines the given local. :param item: The name of the locally defined object. From 7f6d70d4d8303ba668acb2acf9b6cbdaf9946ecd Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 10 Apr 2022 12:50:13 -0400 Subject: [PATCH 5/8] Create environment with Qt6 in release tests workflow --- .github/workflows/release-tests.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml index afff63a449..feb75128b0 100644 --- a/.github/workflows/release-tests.yml +++ b/.github/workflows/release-tests.yml @@ -31,3 +31,31 @@ jobs: . venv2\scripts\activate echo "import distutils.util # pylint: disable=unused-import" > test.py pylint test.py + + qt6-test: + # Regression test added in https://github.com/PyCQA/astroid/pull/1505 + name: Regression test for Qt6 brain + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.0.0 + - name: Set up Python + id: python + uses: actions/setup-python@v3.1.0 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + - name: Install Qt + run: | + sudo apt-get install build-essential libgl1-mesa-dev + - name: Create Python virtual environment + run: | + python -m venv venv + . venv/bin/activate + python -m pip install -U pip setuptools wheel + pip install -U -r requirements_test.txt -r requirements_test_brain.txt + pip install -e . + - name: Run brain_qt tests + run: | + . venv/bin/activate + pytest tests/unittest_brain_qt.py From 59ac8676f5386de586036566b6f9b216aef1c932 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 11 Apr 2022 07:10:09 -0400 Subject: [PATCH 6/8] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> --- astroid/brain/brain_qt.py | 4 ++-- astroid/nodes/scoped_nodes/mixin.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/astroid/brain/brain_qt.py b/astroid/brain/brain_qt.py index e2cfe3adbf..c02508478f 100644 --- a/astroid/brain/brain_qt.py +++ b/astroid/brain/brain_qt.py @@ -21,7 +21,7 @@ def _looks_like_signal(node, signal_name="pyqtSignal"): return False -def transform_pyqt_signal(node: nodes.FunctionDef): +def transform_pyqt_signal(node: nodes.FunctionDef) -> None: module = parse( """ class pyqtSignal(object): @@ -39,7 +39,7 @@ def emit(self, *args): node.instance_attrs["connect"] = [signal_cls["connect"]] -def transform_pyside_signal(node: nodes.FunctionDef): +def transform_pyside_signal(node: nodes.FunctionDef) -> None: module = parse( """ class NotPySideSignal(object): diff --git a/astroid/nodes/scoped_nodes/mixin.py b/astroid/nodes/scoped_nodes/mixin.py index d248c9fa36..bedb9b7ed9 100644 --- a/astroid/nodes/scoped_nodes/mixin.py +++ b/astroid/nodes/scoped_nodes/mixin.py @@ -108,7 +108,7 @@ def add_local_node(self, child_node, name=None): self._append_node(child_node) self.set_local(name or child_node.name, child_node) - def __getitem__(self, item) -> "nodes.NodeNG": + def __getitem__(self, item: str) -> "nodes.NodeNG": """The first node the defines the given local. :param item: The name of the locally defined object. From de8ca67b1bf25bdcc3e252966fea00bca274997f Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 11 Apr 2022 07:16:31 -0400 Subject: [PATCH 7/8] Remove docstring typing --- astroid/nodes/scoped_nodes/mixin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/astroid/nodes/scoped_nodes/mixin.py b/astroid/nodes/scoped_nodes/mixin.py index bedb9b7ed9..bb1e76f14f 100644 --- a/astroid/nodes/scoped_nodes/mixin.py +++ b/astroid/nodes/scoped_nodes/mixin.py @@ -112,7 +112,6 @@ def __getitem__(self, item: str) -> "nodes.NodeNG": """The first node the defines the given local. :param item: The name of the locally defined object. - :type item: str :raises KeyError: If the name is not defined. """ From d4bde30fbc6fd3fb1fc349a766ad2788afa70b82 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 11 Apr 2022 08:28:34 -0400 Subject: [PATCH 8/8] Rename --- .github/workflows/release-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml index feb75128b0..07778c10b2 100644 --- a/.github/workflows/release-tests.yml +++ b/.github/workflows/release-tests.yml @@ -32,9 +32,8 @@ jobs: echo "import distutils.util # pylint: disable=unused-import" > test.py pylint test.py - qt6-test: - # Regression test added in https://github.com/PyCQA/astroid/pull/1505 - name: Regression test for Qt6 brain + additional-dependencies-linux-tests: + name: Regression tests w/ additional dependencies (Linux) runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -56,6 +55,7 @@ jobs: pip install -U -r requirements_test.txt -r requirements_test_brain.txt pip install -e . - name: Run brain_qt tests + # Regression test added in https://github.com/PyCQA/astroid/pull/1505 run: | . venv/bin/activate pytest tests/unittest_brain_qt.py