summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhippo91 <guillaume.peillex@gmail.com>2021-08-29 20:57:34 +0200
committerGitHub <noreply@github.com>2021-08-29 20:57:34 +0200
commit8049834c518adae06f6848f01bcba33f49e5cdf5 (patch)
tree99cc737e324758219dc0fcfa5eb608b5f58760da
parente90016f8baf7de815249ee60dc82636b3d7380b0 (diff)
downloadastroid-git-8049834c518adae06f6848f01bcba33f49e5cdf5.tar.gz
Bug pylint 3342 (#1148)
* Adds some type hints * Adds a logger module * Remove this useless module * It is probably not a good idea to apply transforms on module which have been authorized to be directly imported * Adds a function named is_module_name_part_of_extension_package_whitelist which returns True if the beginning of a module dotted name is part of the package whitelist in argument * Adds the extension_package_whitelist variable to the brainless manager in order unittest dealing with this managert to be ok * Adds two tests that check the behavior of the is_module_name_part_of_extension_package_whitelist function * Updating Changelog * Adds explanation to the ChangeLog entry Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r--ChangeLog7
-rw-r--r--astroid/builder.py11
-rw-r--r--astroid/manager.py12
-rw-r--r--astroid/modutils.py19
-rw-r--r--astroid/raw_building.py6
-rw-r--r--astroid/test_utils.py1
-rw-r--r--tests/unittest_modutils.py38
7 files changed, 83 insertions, 11 deletions
diff --git a/ChangeLog b/ChangeLog
index 16e64238..1a6a9a33 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,13 @@ What's New in astroid 2.8.0?
============================
Release date: TBA
+* The transforms related to a module are applied only if this module has not been explicitly authorized to be imported
+ (i.e is not in AstroidManager.extension_package_whitelist). Solves the following issues if numpy is authorized to be imported
+ through the `extension-pkg-allow-list` option.
+
+ Closes PyCQA/pylint#3342
+ Closes PyCQA/pylint#4326
+
* Fixed bug in attribute inference from inside method calls.
Closes PyCQA/pylint#400
diff --git a/astroid/builder.py b/astroid/builder.py
index 19570055..d88d20cf 100644
--- a/astroid/builder.py
+++ b/astroid/builder.py
@@ -22,6 +22,7 @@ at the same time.
"""
import os
import textwrap
+import types
from tokenize import detect_encoding
from typing import List, Union
@@ -79,7 +80,9 @@ class AstroidBuilder(raw_building.InspectBuilder):
super().__init__(manager)
self._apply_transforms = apply_transforms
- def module_build(self, module, modname=None):
+ def module_build(
+ self, module: types.ModuleType, modname: str = None
+ ) -> nodes.Module:
"""Build an astroid from a living module instance."""
node = None
path = getattr(module, "__file__", None)
@@ -157,6 +160,10 @@ class AstroidBuilder(raw_building.InspectBuilder):
# Visit the transforms
if self._apply_transforms:
+ if modutils.is_module_name_part_of_extension_package_whitelist(
+ module.name, self._manager.extension_package_whitelist
+ ):
+ return module
module = self._manager.visit_transforms(module)
return module
@@ -260,7 +267,7 @@ class AstroidBuilder(raw_building.InspectBuilder):
pass
-def build_namespace_package_module(name, path):
+def build_namespace_package_module(name, path: str) -> nodes.Module:
return nodes.Module(name, doc="", path=path, package=True)
diff --git a/astroid/manager.py b/astroid/manager.py
index 306189db..5575151e 100644
--- a/astroid/manager.py
+++ b/astroid/manager.py
@@ -28,6 +28,7 @@ from various source and using a cache of built modules)
"""
import os
+import types
import zipimport
from typing import ClassVar
@@ -37,6 +38,7 @@ from astroid.modutils import (
NoSourceFile,
file_info_from_modpath,
get_source_file,
+ is_module_name_part_of_extension_package_whitelist,
is_python_source,
is_standard_module,
load_module_from_name,
@@ -138,15 +140,13 @@ class AstroidManager:
return build_namespace_package_module(modname, path)
- def _can_load_extension(self, modname):
+ def _can_load_extension(self, modname: str) -> bool:
if self.always_load_extensions:
return True
if is_standard_module(modname):
return True
- parts = modname.split(".")
- return any(
- ".".join(parts[:x]) in self.extension_package_whitelist
- for x in range(1, len(parts) + 1)
+ return is_module_name_part_of_extension_package_whitelist(
+ modname, self.extension_package_whitelist
)
def ast_from_module_name(self, modname, context_file=None):
@@ -263,7 +263,7 @@ class AstroidManager:
raise value.with_traceback(None)
return value
- def ast_from_module(self, module, modname=None):
+ def ast_from_module(self, module: types.ModuleType, modname: str = None):
"""given an imported module, return the astroid object"""
modname = modname or module.__name__
if modname in self.astroid_cache:
diff --git a/astroid/modutils.py b/astroid/modutils.py
index 2604f9b4..65a6253e 100644
--- a/astroid/modutils.py
+++ b/astroid/modutils.py
@@ -46,8 +46,10 @@ import itertools
import os
import platform
import sys
+import types
from distutils.errors import DistutilsPlatformError # pylint: disable=import-error
from distutils.sysconfig import get_python_lib # pylint: disable=import-error
+from typing import Set
from astroid.interpreter._import import spec, util
@@ -197,7 +199,7 @@ def _cache_normalize_path(path):
return result
-def load_module_from_name(dotted_name):
+def load_module_from_name(dotted_name: str) -> types.ModuleType:
"""Load a Python module from its name.
:type dotted_name: str
@@ -648,3 +650,18 @@ def is_namespace(specobj):
def is_directory(specobj):
return specobj.type == spec.ModuleType.PKG_DIRECTORY
+
+
+def is_module_name_part_of_extension_package_whitelist(
+ module_name: str, package_whitelist: Set[str]
+) -> bool:
+ """
+ Returns True if one part of the module name is in the package whitelist
+
+ >>> is_module_name_part_of_extension_package_whitelist('numpy.core.umath', {'numpy'})
+ True
+ """
+ parts = module_name.split(".")
+ return any(
+ ".".join(parts[:x]) in package_whitelist for x in range(1, len(parts) + 1)
+ )
diff --git a/astroid/raw_building.py b/astroid/raw_building.py
index 8aadd0e7..31ff22f7 100644
--- a/astroid/raw_building.py
+++ b/astroid/raw_building.py
@@ -106,7 +106,7 @@ def attach_import_node(node, modname, membername):
_attach_local_node(node, from_node, membername)
-def build_module(name, doc=None):
+def build_module(name: str, doc: str = None) -> nodes.Module:
"""create and initialize an astroid Module node"""
node = nodes.Module(name, doc, pure_python=False)
node.package = False
@@ -304,7 +304,9 @@ class InspectBuilder:
self._done = {}
self._module = None
- def inspect_build(self, module, modname=None, path=None):
+ def inspect_build(
+ self, module: types.ModuleType, modname: str = None, path: str = None
+ ) -> nodes.Module:
"""build astroid from a living module (i.e. using inspect)
this is used when there is no python source code available (either
because it's a built-in module or because the .py is not available)
diff --git a/astroid/test_utils.py b/astroid/test_utils.py
index d0ab92eb..7450a1f9 100644
--- a/astroid/test_utils.py
+++ b/astroid/test_utils.py
@@ -82,4 +82,5 @@ def brainless_manager():
m.astroid_cache = {}
m._mod_file_cache = {}
m._transform = transforms.TransformVisitor()
+ m.extension_package_whitelist = {}
return m
diff --git a/tests/unittest_modutils.py b/tests/unittest_modutils.py
index 5f8d285c..6edc94d1 100644
--- a/tests/unittest_modutils.py
+++ b/tests/unittest_modutils.py
@@ -361,5 +361,43 @@ class GetModuleFilesTest(unittest.TestCase):
self.assertTrue(m is xml.etree.ElementTree)
+class ExtensionPackageWhitelistTest(unittest.TestCase):
+ def test_is_module_name_part_of_extension_package_whitelist_true(self):
+ """Test that the is_module_name_part_of_extension_package_whitelist function returns True when needed"""
+ self.assertTrue(
+ modutils.is_module_name_part_of_extension_package_whitelist(
+ "numpy", {"numpy"}
+ )
+ )
+ self.assertTrue(
+ modutils.is_module_name_part_of_extension_package_whitelist(
+ "numpy.core", {"numpy"}
+ )
+ )
+ self.assertTrue(
+ modutils.is_module_name_part_of_extension_package_whitelist(
+ "numpy.core.umath", {"numpy"}
+ )
+ )
+
+ def test_is_module_name_part_of_extension_package_whitelist_success(self):
+ """Test that the is_module_name_part_of_extension_package_whitelist function returns False when needed"""
+ self.assertFalse(
+ modutils.is_module_name_part_of_extension_package_whitelist(
+ "numpy", {"numpy.core"}
+ )
+ )
+ self.assertFalse(
+ modutils.is_module_name_part_of_extension_package_whitelist(
+ "numpy.core", {"numpy.core.umath"}
+ )
+ )
+ self.assertFalse(
+ modutils.is_module_name_part_of_extension_package_whitelist(
+ "core.umath", {"numpy"}
+ )
+ )
+
+
if __name__ == "__main__":
unittest.main()