Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --prefer-stubs=y option #9632

Merged
merged 9 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/whatsnew/fragments/9139.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Update astroid version to 3.2.1. This solves some reports of ``RecursionError``
and also makes the *prefer .pyi stubs* feature in astroid 3.2.0 *opt-in*
with the aforementioned ``--prefer-stubs=y`` option.

Refs #9139
9 changes: 9 additions & 0 deletions doc/whatsnew/fragments/9626.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Add `--prefer-stubs=yes` option to opt-in to the astroid 3.2 feature
that prefers `.pyi` stubs over same-named `.py` files. This has the
potential to reduce `no-member` errors but at the cost of more errors
such as `not-an-iterable` from function bodies appearing as `...`.

Defaults to `no`.

Closes #9626
Closes #9623
4 changes: 4 additions & 0 deletions examples/pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ load-plugins=
# Pickle collected data for later comparisons.
persistent=yes

# Resolve imports to .pyi stubs if available. May reduce no-member messages
# and increase not-an-iterable messages.
prefer-stubs=no

# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.10
Expand Down
4 changes: 4 additions & 0 deletions examples/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ limit-inference-results = 100
# Pickle collected data for later comparisons.
persistent = true

# Resolve imports to .pyi stubs if available. May reduce no-member messages
# and increase not-an-iterable messages.
prefer-stubs = false

# Minimum Python version to use for version dependent checks. Will default to the
# version used to run pylint.
py-version = "3.10"
Expand Down
11 changes: 11 additions & 0 deletions pylint/lint/base_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,17 @@ def _make_linter_options(linter: PyLinter) -> Options:
"Useful if running pylint in a server-like mode.",
},
),
(
"prefer-stubs",
{
"default": False,
"type": "yn",
"metavar": "<y or n>",
"help": "Resolve imports to .pyi stubs if available. May "
"reduce no-member messages and increase not-an-iterable "
"messages.",
},
),
)


Expand Down
1 change: 1 addition & 0 deletions pylint/lint/pylinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,7 @@ def open(self) -> None:
MANAGER.max_inferable_values = self.config.limit_inference_results
MANAGER.extension_package_whitelist.update(self.config.extension_pkg_allow_list)
MANAGER.module_denylist.update(self.config.ignored_modules)
MANAGER.prefer_stubs = self.config.prefer_stubs
if self.config.extension_pkg_whitelist:
MANAGER.extension_package_whitelist.update(
self.config.extension_pkg_whitelist
Expand Down
1 change: 1 addition & 0 deletions pylint/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"suggestion-mode",
"analyse-fallback-blocks",
"allow-global-unused-variables",
"prefer-stubs",
]
GLOBAL_OPTION_INT = Literal["max-line-length", "docstring-min-length"]
GLOBAL_OPTION_LIST = Literal["ignored-modules"]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ dependencies = [
# Also upgrade requirements_test_min.txt.
# Pinned to dev of second minor update to allow editable installs and fix primer issues,
# see https://github.com/pylint-dev/astroid/issues/1341
"astroid>=3.2.0,<=3.3.0-dev0",
"astroid>=3.2.1,<=3.3.0-dev0",
"isort>=4.2.5,<6,!=5.13.0",
"mccabe>=0.6,<0.8",
"tomli>=1.1.0;python_version<'3.11'",
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_min.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.[testutils,spelling]
# astroid dependency is also defined in pyproject.toml
astroid==3.2.0 # Pinned to a specific version for tests
astroid==3.2.1 # Pinned to a specific version for tests
typing-extensions~=4.11
py~=1.11.0
pytest~=7.4
Expand Down
9 changes: 9 additions & 0 deletions tests/lint/test_pylinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,12 @@ def test_open_pylinter_denied_modules(linter: PyLinter) -> None:
assert MANAGER.module_denylist == {"mod1", "mod2", "mod3"}
finally:
MANAGER.module_denylist = set()


def test_open_pylinter_prefer_stubs(linter: PyLinter) -> None:
try:
linter.config.prefer_stubs = True
linter.open()
assert MANAGER.prefer_stubs
finally:
MANAGER.prefer_stubs = False
18 changes: 17 additions & 1 deletion tests/lint/unittest_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,7 @@ def test_by_module_statement_value(initialized_linter: PyLinter) -> None:

def test_finds_pyi_file() -> None:
run = Run(
[join(REGRTEST_DATA_DIR, "pyi")],
["--prefer-stubs=y", join(REGRTEST_DATA_DIR, "pyi")],
exit=False,
)
assert run.linter.current_file is not None
Expand All @@ -1061,6 +1061,8 @@ def test_recursive_finds_pyi_file() -> None:
[
"--recursive",
"y",
"--prefer-stubs",
"y",
join(REGRTEST_DATA_DIR, "pyi"),
],
exit=False,
Expand All @@ -1069,6 +1071,20 @@ def test_recursive_finds_pyi_file() -> None:
assert run.linter.current_file.endswith("foo.pyi")


def test_no_false_positive_from_pyi_stub() -> None:
run = Run(
[
"--recursive",
"y",
"--prefer-stubs",
"n",
join(REGRTEST_DATA_DIR, "pyi"),
],
exit=False,
)
assert run._output is None


@pytest.mark.parametrize(
"ignore_parameter,ignore_parameter_value",
[
Expand Down
2 changes: 2 additions & 0 deletions tests/regrtest_data/pyi/foo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def three_item_iterable():
return [1, 2, 3]
3 changes: 3 additions & 0 deletions tests/regrtest_data/pyi/foo.pyi
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
foo = 1

def three_item_iterable():
...
4 changes: 4 additions & 0 deletions tests/regrtest_data/pyi/uses_foo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .foo import three_item_iterable

for val in three_item_iterable():
print(val)
1 change: 1 addition & 0 deletions tests/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def revert_stateful_config_changes(linter: PyLinter) -> Iterator[PyLinter]:
yield linter
# Revert any stateful configuration changes.
MANAGER.brain["module_denylist"] = set()
MANAGER.brain["prefer_stubs"] = False


@pytest.mark.usefixtures("revert_stateful_config_changes")
Expand Down
Loading