Skip to content

Commit 52738b6

Browse files
Mehdi DrissiPierre-Sassoulas
Mehdi Drissi
authored andcommitted
Add an option to disable sys.path patching.
update changelog + contributors Address comment to add default for option Add unit test for namespace package sys patching fix unit test to avoid global side effects from imports Update faq to explain namespace package issue with imports
1 parent fb8c751 commit 52738b6

15 files changed

+88
-9
lines changed

CONTRIBUTORS.txt

+1
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ contributors:
391391
- Michael Hudson-Doyle <[email protected]>
392392
- Michael Giuffrida <[email protected]>
393393
- Melvin Hazeleger <[email protected]> (melvio)
394+
- Mehdi Drissi: Added an option to disable sys path patching
394395
- Matěj Grabovský <[email protected]>
395396
- Matthijs Blom <[email protected]>
396397
- Matej Marušák <[email protected]>

ChangeLog

+6
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,12 @@ Release date: 2021-11-24
785785
longer relies on counting if statements anymore and uses known if statements locations instead.
786786
It should not crash on badly parsed if statements anymore.
787787

788+
* Add an option ``disable-path-patching`` to better support namespace packages.
789+
This option is disabled by default as it has a trade off with being able
790+
to lint uninstalled packages.
791+
792+
Closes #5266
793+
788794
* Fix ``simplify-boolean-expression`` when condition can be inferred as False.
789795

790796
Closes #5200

doc/faq.rst

+14-2
Original file line numberDiff line numberDiff line change
@@ -280,12 +280,24 @@ behavior. Likewise, since negative values are still technically supported,
280280
``evaluation`` can be set to a version of the above expression that does not
281281
enforce a floor of zero.
282282

283-
6.2 I think I found a bug in Pylint. What should I do?
283+
6.2 I have import errors working with a namespace package. How can I fix it?
284+
------------------------------------------------------------------------------
285+
286+
If you have an implicit namespace package (i.e. a package without ``__init__.py``)
287+
then pylint may not be able to determine base of the package. Normally when you run
288+
pylint it adjusts PYTHONPATH to include the parent directory of the package you are
289+
working in. For namespace packages you can disable path inference with
290+
``--disable-path-patching`` setting. For this to work you must have the namespace package
291+
installed in your environment or run pylint from the directory containing the package.
292+
293+
294+
295+
6.3 I think I found a bug in Pylint. What should I do?
284296
-------------------------------------------------------
285297

286298
Read :ref:`Bug reports, feedback`
287299

288-
6.3 I have a question about Pylint that isn't answered here.
300+
6.4 I have a question about Pylint that isn't answered here.
289301
------------------------------------------------------------
290302

291303
Read :ref:`Mailing lists`

doc/whatsnew/2.14.rst

+6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ Extensions
4242
Other Changes
4343
=============
4444

45+
* Add an option ``disable-path-patching`` to better support namespace packages.
46+
This option is disabled by default as it has a trade off with being able
47+
to lint uninstalled packages.
48+
49+
Closes #5266
50+
4551
* Only raise ``not-callable`` when all the inferred values of a property are not callable.
4652

4753
Closes #5931

pylint/lint/pylinter.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,21 @@ def make_options() -> Tuple[Tuple[str, OptionDict], ...]:
536536
),
537537
},
538538
),
539+
(
540+
"disable-path-patching",
541+
{
542+
"type": "yn",
543+
"metavar": "<y or n>",
544+
"default": False,
545+
"help": (
546+
"Disable the patching of sys.path when searching for "
547+
"modules. This is useful for implicit namespace packages where "
548+
"inferring base path of package is ambiguous. The downside is without "
549+
"patching, the files being linted must be importable from the location "
550+
"the script is run in interpreter."
551+
),
552+
},
553+
),
539554
)
540555

541556
base_option_groups = (
@@ -1058,13 +1073,13 @@ def check(self, files_or_modules: Union[Sequence[str], str]) -> None:
10581073
)
10591074

10601075
filepath = files_or_modules[0]
1061-
with fix_import_path(files_or_modules):
1076+
with fix_import_path(files_or_modules, self.config.disable_path_patching):
10621077
self._check_files(
10631078
functools.partial(self.get_ast, data=_read_stdin()),
10641079
[self._get_file_descr_from_stdin(filepath)],
10651080
)
10661081
elif self.config.jobs == 1:
1067-
with fix_import_path(files_or_modules):
1082+
with fix_import_path(files_or_modules, self.config.disable_path_patching):
10681083
self._check_files(
10691084
self.get_ast, self._iterate_file_descrs(files_or_modules)
10701085
)

pylint/lint/utils.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ def preprocess_options(args, search_for):
108108

109109

110110
def _patch_sys_path(args):
111-
original = list(sys.path)
112111
changes = []
113112
seen = set()
114113
for arg in args:
@@ -118,19 +117,25 @@ def _patch_sys_path(args):
118117
seen.add(path)
119118

120119
sys.path[:] = changes + sys.path
121-
return original
122120

123121

124122
@contextlib.contextmanager
125-
def fix_import_path(args):
123+
def fix_import_path(args, disable_path_patching: bool = False):
126124
"""Prepare 'sys.path' for running the linter checks.
127125
126+
'disable_path_patching' if True will make this context manager a no-op.
127+
The reason for disabling path patching is to allow running the linter
128+
using default path which can be safer then patching the path for implicit
129+
namespace packages.
130+
128131
Within this context, each of the given arguments is importable.
129132
Paths are added to 'sys.path' in corresponding order to the arguments.
130133
We avoid adding duplicate directories to sys.path.
131134
`sys.path` is reset to its original value upon exiting this context.
132135
"""
133-
original = _patch_sys_path(args)
136+
original = list(sys.path)
137+
if not disable_path_patching:
138+
_patch_sys_path(args)
134139
try:
135140
yield
136141
finally:

pylint/pyreverse/main.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,21 @@
191191
help="set the output directory path.",
192192
),
193193
),
194+
(
195+
"disable-path-patching",
196+
dict(
197+
type="yn",
198+
metavar="<y or n>",
199+
default=False,
200+
help=(
201+
"Disable the patching of sys.path when searching for "
202+
"modules. This is useful for implicit namespace packages where "
203+
"inferring base path of package is ambiguous. The downside is without "
204+
"patching, the files being linted must be importable from the location "
205+
"the script is run in interpreter."
206+
),
207+
),
208+
),
194209
)
195210

196211

@@ -217,7 +232,7 @@ def run(self, args):
217232
if not args:
218233
print(self.help())
219234
return 1
220-
with fix_import_path(args):
235+
with fix_import_path(args, self.config.disable_path_patching):
221236
project = project_from_files(
222237
args,
223238
project_name=self.config.project,

pylint/testutils/_fake.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2+
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
3+
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
4+
5+
"""This file is intended to only be used for testing namespace package importing.
6+
7+
Importing a file has a global side effect of adding a module to sys.modules.
8+
As namespace packages are sensitive to sys.path, to test them we check
9+
an intentional import error and a successful import. To avoid the imports
10+
messing up the test environment, we need to make the import error use
11+
a file that is never imported by any other test. This serves as that file.
12+
"""

tests/functional/n/namespace_package/pylint/__init__.py

Whitespace-only changes.

tests/functional/n/namespace_package/pylint/testutils/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# pylint: disable=missing-docstring,unused-import
2+
from pylint.testutils import _fake # [no-name-in-module]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
no-name-in-module:2:0::No name '_fake' in module 'pylint.testutils':HIGH
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# pylint: disable=missing-docstring,unused-import
2+
from pylint import lint
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[master]
2+
disable-path-patching=yes

tests/functional/n/namespace_package/pylint/testutils/namespace_package_disable_path_patching.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)