diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f96e65bbbf..0fdf6905a4 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,3 @@ # These are supported funding model platforms tidelift: "pypi/astroid" - diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index cf7f86b25e..9b755b8d21 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -10,4 +10,3 @@ ### ``python -c "from astroid import __pkginfo__; print(__pkginfo__.version)"`` output - diff --git a/.gitignore b/.gitignore index 4b0ef30852..7cefc56442 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ astroid.egg-info/ .cache/ .eggs/ .pytest_cache/ -.mypy_cache/ \ No newline at end of file +.mypy_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9ba53749d1..f084f44f52 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,14 @@ repos: - repo: https://github.com/ambv/black - rev: 18.6b4 + rev: 20.8b1 hooks: - id: black args: [--safe, --quiet] - exclude: tests/testdata - python_version: python3.6 + exclude: tests/testdata|doc/ - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v1.2.3 + rev: v3.4.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer + - id: trailing-whitespace + exclude: .github/|tests/testdata + - id: end-of-file-fixer + exclude: tests/testdata diff --git a/COPYING.LESSER b/COPYING.LESSER index 2d2d780e60..182e0fbee1 100644 --- a/COPYING.LESSER +++ b/COPYING.LESSER @@ -57,7 +57,7 @@ modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - + Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a @@ -113,7 +113,7 @@ modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - + GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION @@ -160,7 +160,7 @@ Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - + 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 @@ -218,7 +218,7 @@ instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - + Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. @@ -269,7 +269,7 @@ Library will still fall under Section 6.) distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - + 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work @@ -331,7 +331,7 @@ restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - + 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined @@ -372,7 +372,7 @@ subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - + 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or @@ -425,7 +425,7 @@ conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - + 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is @@ -459,7 +459,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS - + How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest @@ -506,5 +506,3 @@ if necessary. Here is a sample; alter the names: Ty Coon, President of Vice That's all there is to it! - - diff --git a/ChangeLog b/ChangeLog index 519fa249f3..f6a9ce9e4b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,11 +6,20 @@ What's New in astroid 2.5.1? ============================ Release Date: TBA -* The ``context.path`` is reverted to a set because otherwise it leds to false positives +* The ``context.path`` is reverted to a set because otherwise it leds to false positives for non `numpy` functions. Closes #895 #899 +* Fix the `Duplicates found in MROs` false positive. + + Closes #905 + Closes PyCQA/pylint#2717 + Closes PyCQA/pylint#3247 + Closes PyCQA/pylint#4093 + Closes PyCQA/pylint#4131 + Closes PyCQA/pylint#4145 + What's New in astroid 2.5? ============================ Release Date: 2021-02-15 diff --git a/astroid/as_string.py b/astroid/as_string.py index 3fc4096588..d379ecd823 100644 --- a/astroid/as_string.py +++ b/astroid/as_string.py @@ -61,8 +61,8 @@ def _precedence_parens(self, node, child, is_left=True): def _should_wrap(self, node, child, is_left): """Wrap child if: - - it has lower precedence - - same precedence with position opposite to associativity direction + - it has lower precedence + - same precedence with position opposite to associativity direction """ node_precedence = node.op_precedence() child_precedence = child.op_precedence() diff --git a/astroid/brain/brain_type.py b/astroid/brain/brain_type.py index f09bd16d7f..dada081b32 100644 --- a/astroid/brain/brain_type.py +++ b/astroid/brain/brain_type.py @@ -8,9 +8,9 @@ Guido Van Rossum proposed a hack to handle this in the interpreter: https://github.com/python/cpython/blob/master/Objects/abstract.c#L186-L189 -This brain follows the same logic. It is no wise to add permanently the __class_getitem__ method +This brain follows the same logic. It is no wise to add permanently the __class_getitem__ method to the type object. Instead we choose to add it only in the case of a subscript node -which inside name node is type. +which inside name node is type. Doing this type[int] is allowed whereas str[int] is not. Thanks to Lukasz Langa for fruitful discussion. diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py index 60557952c8..bbd129d9e2 100644 --- a/astroid/brain/brain_typing.py +++ b/astroid/brain/brain_typing.py @@ -4,6 +4,7 @@ # Copyright (c) 2018 Bryce Guinta """Astroid hooks for typing.py support.""" +import sys import typing from astroid import ( @@ -11,10 +12,14 @@ UseInferenceDefault, extract_node, inference_tip, + node_classes, nodes, + context, InferenceError, ) +import astroid +PY37 = sys.version_info[:2] >= (3, 7) TYPING_NAMEDTUPLE_BASENAMES = {"NamedTuple", "typing.NamedTuple"} TYPING_TYPEVARS = {"TypeVar", "NewType"} @@ -85,6 +90,50 @@ def infer_typing_attr(node, context=None): return node.infer(context=context) +GET_ITEM_TEMPLATE = """ +@classmethod +def __getitem__(cls, value): + return cls +""" + + +def _looks_like_typing_alias(node: nodes.Call) -> bool: + """ + Returns True if the node corresponds to a call to _alias function. + For example : + + MutableSet = _alias(collections.abc.MutableSet, T) + + :param node: call node + """ + return ( + isinstance(node, nodes.Call) + and isinstance(node.func, nodes.Name) + and node.func.name == "_alias" + and isinstance(node.args[0], nodes.Attribute) + ) + + +def infer_typing_alias( + node: nodes.Call, context: context.InferenceContext = None +) -> node_classes.NodeNG: + """ + Infers the call to _alias function + + :param node: call node + :param context: inference context + """ + if not isinstance(node, nodes.Call): + return + res = next(node.args[0].infer(context=context)) + #  Needs to mock the __getitem__ class method so that + #  MutableSet[T] is acceptable + func_to_add = extract_node(GET_ITEM_TEMPLATE) + if res.metaclass(): + res.metaclass().locals["__getitem__"] = [func_to_add] + return res + + MANAGER.register_transform( nodes.Call, inference_tip(infer_typing_typevar_or_newtype), @@ -93,3 +142,6 @@ def infer_typing_attr(node, context=None): MANAGER.register_transform( nodes.Subscript, inference_tip(infer_typing_attr), _looks_like_typing_subscript ) + +if PY37: + MANAGER.register_transform(nodes.Call, infer_typing_alias, _looks_like_typing_alias) diff --git a/astroid/decorators.py b/astroid/decorators.py index 061d74d95e..1f95dca0e8 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -38,7 +38,7 @@ def cached(func, instance, args, kwargs): class cachedproperty: - """ Provides a cached property equivalent to the stacking of + """Provides a cached property equivalent to the stacking of @cached and @property, but more efficient. After first usage, the becomes part of the object's diff --git a/astroid/inference.py b/astroid/inference.py index 24c051a5e4..8fd435ecd1 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -166,7 +166,7 @@ def _infer_map(node, context): def _higher_function_scope(node): - """ Search for the first function which encloses the given + """Search for the first function which encloses the given scope. This can be used for looking up in that function's scope, in case looking up in a lower scope for a particular name fails. diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index 8dd2a922fa..9984dd1555 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -274,7 +274,7 @@ def _cached_set_diff(left, right): def _precache_zipimporters(path=None): """ - For each path that has not been already cached + For each path that has not been already cached in the sys.path_importer_cache, create a new zipimporter instance and add it into the cache. Return a dict associating all paths, stored in the cache, to corresponding diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py index 9f106c9af3..569867d178 100644 --- a/astroid/interpreter/objectmodel.py +++ b/astroid/interpreter/objectmodel.py @@ -798,7 +798,7 @@ def attr_fset(self): def find_setter(func: objects.Property) -> Optional[astroid.FunctionDef]: """ - Given a property, find the corresponding setter function and returns it. + Given a property, find the corresponding setter function and returns it. :param func: property for which the setter has to be found :return: the setter function or None diff --git a/astroid/mixins.py b/astroid/mixins.py index 497a8400b3..052fa26493 100644 --- a/astroid/mixins.py +++ b/astroid/mixins.py @@ -80,8 +80,7 @@ def _infer_name(self, frame, name): return name def do_import_module(self, modname=None): - """return the ast for a module whose name is imported by - """ + """return the ast for a module whose name is imported by """ # handle special case where we are on a package node importing a module # using the same name as the package, which may end in an infinite loop # on relative imports diff --git a/astroid/node_classes.py b/astroid/node_classes.py index a6397c0fbc..290cadf401 100644 --- a/astroid/node_classes.py +++ b/astroid/node_classes.py @@ -259,7 +259,7 @@ def _container_getitem(instance, elts, index, context=None): class NodeNG: - """ A node of the new Abstract Syntax Tree (AST). + """A node of the new Abstract Syntax Tree (AST). This is the base class for all Astroid node classes. """ diff --git a/astroid/raw_building.py b/astroid/raw_building.py index 5b8c201d03..803fbacdb3 100644 --- a/astroid/raw_building.py +++ b/astroid/raw_building.py @@ -319,7 +319,7 @@ def inspect_build(self, module, modname=None, path=None): def object_build(self, node, obj): """recursive method which create a partial ast from real objects - (only function, class, and method are handled) + (only function, class, and method are handled) """ if obj in self._done: return self._done[obj] diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index 269b16373d..bfa0b61484 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -150,7 +150,7 @@ def builtin_lookup(name): # TODO move this Mixin to mixins.py; problem: 'FunctionDef' in _scope_lookup class LocalsDictNodeNG(node_classes.LookupMixIn, node_classes.NodeNG): - """ this class provides locals handling common to Module, FunctionDef + """this class provides locals handling common to Module, FunctionDef and ClassDef nodes, including a dict like interface for direct access to locals information """ @@ -1454,7 +1454,7 @@ def extra_decorators(self): # pylint: disable=invalid-overridden-method @decorators_mod.cachedproperty def type( - self + self, ): # pylint: disable=invalid-overridden-method,too-many-return-statements """The function type for this node. @@ -1791,7 +1791,7 @@ def _rec_get_names(args, names=None): def _is_metaclass(klass, seen=None): - """ Return if the given class can be + """Return if the given class can be used as a metaclass. """ if klass.name == "type": diff --git a/astroid/test_utils.py b/astroid/test_utils.py index e750445429..d1dc005413 100644 --- a/astroid/test_utils.py +++ b/astroid/test_utils.py @@ -21,7 +21,7 @@ def require_version(minver=None, maxver=None): - """ Compare version of python interpreter to the given one. Skip the test + """Compare version of python interpreter to the given one. Skip the test if older. """ diff --git a/debian.sid/control b/debian.sid/control index 581455eec8..a488f71bdb 100644 --- a/debian.sid/control +++ b/debian.sid/control @@ -38,5 +38,3 @@ Description: rebuild a new abstract syntax tree from Python's ast the AST and building an extended ast. The new node classes have additional methods and attributes for different usages. Furthermore, astroid builds partial trees by inspecting living objects. - - diff --git a/debian/changelog b/debian/changelog index bc01680ea5..f5ddc001bf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -27,4 +27,3 @@ astroid (1.0.0-1) unstable; urgency=low * new upstream release, project renamed to astroid instead of logilab-astng -- Sylvain Thénault Mon, 29 Jul 2013 16:32:51 +0200 - diff --git a/debian/control b/debian/control index 516f7ff031..bc22ab72c4 100644 --- a/debian/control +++ b/debian/control @@ -27,5 +27,3 @@ Description: rebuild a new abstract syntax tree from Python's ast AST and building an extended ast. The new node classes have additional methods and attributes for different usages. Furthermore, astroid builds partial trees by inspecting living objects. - - diff --git a/doc/api/general.rst b/doc/api/general.rst index 90373ae2e8..74f022e494 100644 --- a/doc/api/general.rst +++ b/doc/api/general.rst @@ -2,4 +2,3 @@ General API ------------ .. automodule:: astroid - diff --git a/doc/changelog.rst b/doc/changelog.rst index 14c95bee0e..e0f208533f 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1 +1 @@ -.. include:: ../ChangeLog \ No newline at end of file +.. include:: ../ChangeLog diff --git a/doc/release.txt b/doc/release.txt index 18f70ff078..b330c7c900 100644 --- a/doc/release.txt +++ b/doc/release.txt @@ -20,7 +20,7 @@ Release Process https://github.com/PyCQA/astroid 6. Run - + $ git clean -fd && find . -name '*.pyc' -delete $ rm dist/* $ python setup.py sdist --formats=gztar bdist_wheel @@ -28,4 +28,3 @@ Release Process to release a new version to PyPI. - diff --git a/doc/whatsnew.rst b/doc/whatsnew.rst index 99267830cc..e26a3c7b59 100644 --- a/doc/whatsnew.rst +++ b/doc/whatsnew.rst @@ -8,4 +8,4 @@ The "Changelog" contains *all* nontrivial changes to astroid for the current ver .. toctree:: :maxdepth: 2 - changelog \ No newline at end of file + changelog diff --git a/setup.py b/setup.py index 43cad80a46..8ea0bd22c5 100644 --- a/setup.py +++ b/setup.py @@ -22,8 +22,11 @@ from setuptools.command import easy_install # pylint: disable=unused-import from setuptools.command import install_lib # pylint: disable=unused-import -if sys.version_info.major == 3 and sys.version_info.minor <=5: - warnings.warn("You will soon need to upgrade to python 3.6 in order to use the latest version of Astroid.", DeprecationWarning) +if sys.version_info.major == 3 and sys.version_info.minor <= 5: + warnings.warn( + "You will soon need to upgrade to python 3.6 in order to use the latest version of Astroid.", + DeprecationWarning, + ) real_path = os.path.realpath(__file__) astroid_dir = os.path.dirname(real_path) diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py index dc3f25d430..b9698c2349 100644 --- a/tests/unittest_brain.py +++ b/tests/unittest_brain.py @@ -1188,6 +1188,42 @@ def test_typing_namedtuple_dont_crash_on_no_fields(self): inferred = next(node.infer()) self.assertIsInstance(inferred, astroid.Instance) + @test_utils.require_version("3.7") + def test_typing_alias_type(self): + """ + Test that the type aliased thanks to typing._alias function are + correctly inferred. + """ + node = builder.extract_node( + """ + from typing import TypeVar, MutableSet + + T = TypeVar("T") + MutableSet[T] + + class V(MutableSet[T]): + pass + """ + ) + inferred = next(node.infer()) + mro_entries = list(inferred.mro()) + self.assertIsInstance(mro_entries[0], astroid.ClassDef) + self.assertEqual(mro_entries[0].name, "V") + self.assertIsInstance(mro_entries[1], astroid.ClassDef) + self.assertEqual(mro_entries[1].name, "MutableSet") + self.assertIsInstance(mro_entries[2], astroid.ClassDef) + self.assertEqual(mro_entries[2].name, "Set") + self.assertIsInstance(mro_entries[3], astroid.ClassDef) + self.assertEqual(mro_entries[3].name, "Collection") + self.assertIsInstance(mro_entries[4], astroid.ClassDef) + self.assertEqual(mro_entries[4].name, "Sized") + self.assertIsInstance(mro_entries[5], astroid.ClassDef) + self.assertEqual(mro_entries[5].name, "Iterable") + self.assertIsInstance(mro_entries[6], astroid.ClassDef) + self.assertEqual(mro_entries[6].name, "Container") + self.assertIsInstance(mro_entries[7], astroid.ClassDef) + self.assertEqual(mro_entries[7].name, "object") + class ReBrainTest(unittest.TestCase): def test_regex_flags(self): diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py index c74b04b014..553d628b1e 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -1273,7 +1273,7 @@ def _(self): self.assertEqual(cdef.col_offset, orig_offset) def test_no_runtime_error_in_repeat_inference(self): - """ Stop repeat inference attempt causing a RuntimeError in Python3.7 + """Stop repeat inference attempt causing a RuntimeError in Python3.7 See https://github.com/PyCQA/pylint/issues/2317 """ @@ -5498,9 +5498,15 @@ def test(self, value): A.test.getter #@ A.test.deleter #@ """ - prop, prop_result, prop_fget_result, prop_fset_result, prop_setter, prop_getter, prop_deleter = extract_node( - code - ) + ( + prop, + prop_result, + prop_fget_result, + prop_fset_result, + prop_setter, + prop_getter, + prop_deleter, + ) = extract_node(code) inferred = next(prop.infer()) assert isinstance(inferred, objects.Property) @@ -5793,7 +5799,7 @@ def a(self): node = extract_node(code) # Infer the class cls = next(node.infer()) - prop, = cls.getattr("a") + (prop,) = cls.getattr("a") # Try to infer the property function *multiple* times. `A.locals` should be modified only once for _ in range(3): diff --git a/tests/unittest_modutils.py b/tests/unittest_modutils.py index 8bfc8dbae1..7629cd6751 100644 --- a/tests/unittest_modutils.py +++ b/tests/unittest_modutils.py @@ -330,8 +330,7 @@ def test_get_module_files_1(self): self.assertEqual(modules, {os.path.join(package, x) for x in expected}) def test_get_all_files(self): - """test that list_all returns all Python files from given location - """ + """test that list_all returns all Python files from given location""" non_package = resources.find("data/notamodule") modules = modutils.get_module_files(non_package, [], list_all=True) self.assertEqual(modules, [os.path.join(non_package, "file.py")]) diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 5455810eb1..aececf457c 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -115,15 +115,13 @@ def test_varargs_kwargs_as_string(self): self.assertEqual(ast.as_string(), "raise_string(*args, **kwargs)") def test_module_as_string(self): - """check as_string on a whole module prepared to be returned identically - """ + """check as_string on a whole module prepared to be returned identically""" module = resources.build_file("data/module.py", "data.module") with open(resources.find("data/module.py")) as fobj: self.assertMultiLineEqual(module.as_string(), fobj.read()) def test_module2_as_string(self): - """check as_string on a whole module prepared to be returned identically - """ + """check as_string on a whole module prepared to be returned identically""" module2 = resources.build_file("data/module2.py", "data.module2") with open(resources.find("data/module2.py")) as fobj: self.assertMultiLineEqual(module2.as_string(), fobj.read()) diff --git a/tox.ini b/tox.ini index f6067eea3b..13260b5626 100644 --- a/tox.ini +++ b/tox.ini @@ -39,7 +39,8 @@ commands = [testenv:formatting] basepython = python3 -deps = black==18.6b4 +deps = + black==20.8b1 commands = black --check --exclude "tests/testdata" astroid tests changedir = {toxinidir}