diff options
author | hippo91 <guillaume.peillex@gmail.com> | 2021-08-29 20:57:34 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-29 20:57:34 +0200 |
commit | 8049834c518adae06f6848f01bcba33f49e5cdf5 (patch) | |
tree | 99cc737e324758219dc0fcfa5eb608b5f58760da | |
parent | e90016f8baf7de815249ee60dc82636b3d7380b0 (diff) | |
download | astroid-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-- | ChangeLog | 7 | ||||
-rw-r--r-- | astroid/builder.py | 11 | ||||
-rw-r--r-- | astroid/manager.py | 12 | ||||
-rw-r--r-- | astroid/modutils.py | 19 | ||||
-rw-r--r-- | astroid/raw_building.py | 6 | ||||
-rw-r--r-- | astroid/test_utils.py | 1 | ||||
-rw-r--r-- | tests/unittest_modutils.py | 38 |
7 files changed, 83 insertions, 11 deletions
@@ -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() |