Skip to content

Commit a03ceae

Browse files
Add --prefer-stubs=y option (#9632)
* Bump astroid to 3.2.1 (cherry picked from commit ce47a62)
1 parent b2ea316 commit a03ceae

File tree

16 files changed

+83
-3
lines changed

16 files changed

+83
-3
lines changed

doc/user_guide/configuration/all-options.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ Standard Checkers
167167
**Default:** ``True``
168168

169169

170+
--prefer-stubs
171+
""""""""""""""
172+
*Resolve imports to .pyi stubs if available. May reduce no-member messages and increase not-an-iterable messages.*
173+
174+
**Default:** ``False``
175+
176+
170177
--py-version
171178
""""""""""""
172179
*Minimum Python version to use for version dependent checks. Will default to the version used to run pylint.*
@@ -271,6 +278,8 @@ Standard Checkers
271278
272279
persistent = true
273280
281+
prefer-stubs = false
282+
274283
py-version = "sys.version_info[:2]"
275284
276285
recursive = false

doc/whatsnew/fragments/9139.internal

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Update astroid version to 3.2.1. This solves some reports of ``RecursionError``
2+
and also makes the *prefer .pyi stubs* feature in astroid 3.2.0 *opt-in*
3+
with the aforementioned ``--prefer-stubs=y`` option.
4+
5+
Refs #9139

doc/whatsnew/fragments/9626.bugfix

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Add `--prefer-stubs=yes` option to opt-in to the astroid 3.2 feature
2+
that prefers `.pyi` stubs over same-named `.py` files. This has the
3+
potential to reduce `no-member` errors but at the cost of more errors
4+
such as `not-an-iterable` from function bodies appearing as `...`.
5+
6+
Defaults to `no`.
7+
8+
Closes #9626
9+
Closes #9623

examples/pylintrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ load-plugins=
8787
# Pickle collected data for later comparisons.
8888
persistent=yes
8989

90+
# Resolve imports to .pyi stubs if available. May reduce no-member messages
91+
# and increase not-an-iterable messages.
92+
prefer-stubs=no
93+
9094
# Minimum Python version to use for version dependent checks. Will default to
9195
# the version used to run pylint.
9296
py-version=3.10

examples/pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ limit-inference-results = 100
7777
# Pickle collected data for later comparisons.
7878
persistent = true
7979

80+
# Resolve imports to .pyi stubs if available. May reduce no-member messages
81+
# and increase not-an-iterable messages.
82+
prefer-stubs = false
83+
8084
# Minimum Python version to use for version dependent checks. Will default to the
8185
# version used to run pylint.
8286
py-version = "3.10"

pylint/lint/base_options.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,17 @@ def _make_linter_options(linter: PyLinter) -> Options:
415415
"Useful if running pylint in a server-like mode.",
416416
},
417417
),
418+
(
419+
"prefer-stubs",
420+
{
421+
"default": False,
422+
"type": "yn",
423+
"metavar": "<y or n>",
424+
"help": "Resolve imports to .pyi stubs if available. May "
425+
"reduce no-member messages and increase not-an-iterable "
426+
"messages.",
427+
},
428+
),
418429
)
419430

420431

pylint/lint/pylinter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,7 @@ def open(self) -> None:
10741074
MANAGER.max_inferable_values = self.config.limit_inference_results
10751075
MANAGER.extension_package_whitelist.update(self.config.extension_pkg_allow_list)
10761076
MANAGER.module_denylist.update(self.config.ignored_modules)
1077+
MANAGER.prefer_stubs = self.config.prefer_stubs
10771078
if self.config.extension_pkg_whitelist:
10781079
MANAGER.extension_package_whitelist.update(
10791080
self.config.extension_pkg_whitelist

pylint/utils/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"suggestion-mode",
5353
"analyse-fallback-blocks",
5454
"allow-global-unused-variables",
55+
"prefer-stubs",
5556
]
5657
GLOBAL_OPTION_INT = Literal["max-line-length", "docstring-min-length"]
5758
GLOBAL_OPTION_LIST = Literal["ignored-modules"]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ dependencies = [
4141
# Also upgrade requirements_test_min.txt.
4242
# Pinned to dev of second minor update to allow editable installs and fix primer issues,
4343
# see https://github.com/pylint-dev/astroid/issues/1341
44-
"astroid>=3.2.0,<=3.3.0-dev0",
44+
"astroid>=3.2.1,<=3.3.0-dev0",
4545
"isort>=4.2.5,<6,!=5.13.0",
4646
"mccabe>=0.6,<0.8",
4747
"tomli>=1.1.0;python_version<'3.11'",

requirements_test_min.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.[testutils,spelling]
22
# astroid dependency is also defined in pyproject.toml
3-
astroid==3.2.0 # Pinned to a specific version for tests
3+
astroid==3.2.1 # Pinned to a specific version for tests
44
typing-extensions~=4.11
55
py~=1.11.0
66
pytest~=7.4

tests/lint/test_pylinter.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,12 @@ def test_open_pylinter_denied_modules(linter: PyLinter) -> None:
5959
assert MANAGER.module_denylist == {"mod1", "mod2", "mod3"}
6060
finally:
6161
MANAGER.module_denylist = set()
62+
63+
64+
def test_open_pylinter_prefer_stubs(linter: PyLinter) -> None:
65+
try:
66+
linter.config.prefer_stubs = True
67+
linter.open()
68+
assert MANAGER.prefer_stubs
69+
finally:
70+
MANAGER.prefer_stubs = False

tests/lint/unittest_lint.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1049,7 +1049,7 @@ def test_by_module_statement_value(initialized_linter: PyLinter) -> None:
10491049

10501050
def test_finds_pyi_file() -> None:
10511051
run = Run(
1052-
[join(REGRTEST_DATA_DIR, "pyi")],
1052+
["--prefer-stubs=y", join(REGRTEST_DATA_DIR, "pyi")],
10531053
exit=False,
10541054
)
10551055
assert run.linter.current_file is not None
@@ -1061,6 +1061,8 @@ def test_recursive_finds_pyi_file() -> None:
10611061
[
10621062
"--recursive",
10631063
"y",
1064+
"--prefer-stubs",
1065+
"y",
10641066
join(REGRTEST_DATA_DIR, "pyi"),
10651067
],
10661068
exit=False,
@@ -1069,6 +1071,20 @@ def test_recursive_finds_pyi_file() -> None:
10691071
assert run.linter.current_file.endswith("foo.pyi")
10701072

10711073

1074+
def test_no_false_positive_from_pyi_stub() -> None:
1075+
run = Run(
1076+
[
1077+
"--recursive",
1078+
"y",
1079+
"--prefer-stubs",
1080+
"n",
1081+
join(REGRTEST_DATA_DIR, "uses_module_with_stub.py"),
1082+
],
1083+
exit=False,
1084+
)
1085+
assert not run.linter.stats.by_msg
1086+
1087+
10721088
@pytest.mark.parametrize(
10731089
"ignore_parameter,ignore_parameter_value",
10741090
[

tests/regrtest_data/pyi/foo.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def three_item_iterable():
2+
return [1, 2, 3]

tests/regrtest_data/pyi/foo.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
foo = 1
2+
3+
def three_item_iterable():
4+
...
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""If the stub is preferred over the .py, this might emit not-an-iterable"""
2+
from pyi.foo import three_item_iterable
3+
4+
for val in three_item_iterable():
5+
print(val)

tests/test_functional.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def revert_stateful_config_changes(linter: PyLinter) -> Iterator[PyLinter]:
5151
yield linter
5252
# Revert any stateful configuration changes.
5353
MANAGER.brain["module_denylist"] = set()
54+
MANAGER.brain["prefer_stubs"] = False
5455

5556

5657
@pytest.mark.usefixtures("revert_stateful_config_changes")

0 commit comments

Comments
 (0)