summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhippo91 <guillaume.peillex@gmail.com>2020-06-20 11:37:37 +0200
committerhippo91 <guillaume.peillex@gmail.com>2020-06-20 11:37:37 +0200
commit1322e5d7790b7e3aa629a4e86e3b9f9b5846886b (patch)
treea6041c102f12f5cbb2a9f9f9e57edfb573d4a37f
parent14de71c7838e4bae329292035f1a225aa5218317 (diff)
parent92f556842c84a2b3cc33a1638fc625b4f67d0d1f (diff)
downloadastroid-git-1322e5d7790b7e3aa629a4e86e3b9f9b5846886b.tar.gz
Merge branch 'master' into sum_and_multiply
-rw-r--r--.travis.yml10
-rw-r--r--ChangeLog176
-rw-r--r--MANIFEST.in2
-rw-r--r--README.rst3
-rw-r--r--astroid/__init__.py1
-rw-r--r--astroid/__pkginfo__.py11
-rw-r--r--astroid/_ast.py123
-rw-r--r--astroid/arguments.py43
-rw-r--r--astroid/as_string.py191
-rw-r--r--astroid/bases.py21
-rw-r--r--astroid/brain/brain_argparse.py2
-rw-r--r--astroid/brain/brain_boto3.py28
-rw-r--r--astroid/brain/brain_builtin_inference.py35
-rw-r--r--astroid/brain/brain_collections.py1
-rw-r--r--astroid/brain/brain_dateutil.py2
-rw-r--r--astroid/brain/brain_fstrings.py2
-rw-r--r--astroid/brain/brain_functools.py5
-rw-r--r--astroid/brain/brain_gi.py5
-rw-r--r--astroid/brain/brain_hashlib.py2
-rw-r--r--astroid/brain/brain_http.py2
-rw-r--r--astroid/brain/brain_io.py2
-rw-r--r--astroid/brain/brain_mechanize.py61
-rw-r--r--astroid/brain/brain_multiprocessing.py3
-rw-r--r--astroid/brain/brain_namedtuple_enum.py10
-rw-r--r--astroid/brain/brain_nose.py2
-rw-r--r--astroid/brain/brain_numpy_core_fromnumeric.py2
-rw-r--r--astroid/brain/brain_numpy_core_function_base.py2
-rw-r--r--astroid/brain/brain_numpy_core_multiarray.py7
-rw-r--r--astroid/brain/brain_numpy_core_numeric.py2
-rw-r--r--astroid/brain/brain_numpy_core_numerictypes.py8
-rw-r--r--astroid/brain/brain_numpy_core_umath.py2
-rw-r--r--astroid/brain/brain_numpy_ndarray.py16
-rw-r--r--astroid/brain/brain_numpy_random_mtrand.py4
-rw-r--r--astroid/brain/brain_numpy_utils.py15
-rw-r--r--astroid/brain/brain_pytest.py2
-rw-r--r--astroid/brain/brain_qt.py3
-rwxr-xr-xastroid/brain/brain_scipy_signal.py2
-rw-r--r--astroid/brain/brain_six.py5
-rw-r--r--astroid/brain/brain_sqlalchemy.py35
-rw-r--r--astroid/brain/brain_ssl.py3
-rw-r--r--astroid/brain/brain_subprocess.py16
-rw-r--r--astroid/brain/brain_threading.py2
-rw-r--r--astroid/brain/brain_uuid.py2
-rw-r--r--astroid/builder.py25
-rw-r--r--astroid/context.py2
-rw-r--r--astroid/decorators.py1
-rw-r--r--astroid/exceptions.py14
-rw-r--r--astroid/helpers.py23
-rw-r--r--astroid/inference.py45
-rw-r--r--astroid/interpreter/_import/spec.py4
-rw-r--r--astroid/interpreter/objectmodel.py44
-rw-r--r--astroid/manager.py14
-rw-r--r--astroid/modutils.py68
-rw-r--r--astroid/node_classes.py175
-rw-r--r--astroid/nodes.py3
-rw-r--r--astroid/objects.py12
-rw-r--r--astroid/protocols.py17
-rw-r--r--astroid/raw_building.py17
-rw-r--r--astroid/rebuilder.py392
-rw-r--r--astroid/scoped_nodes.py126
-rw-r--r--astroid/test_utils.py2
-rw-r--r--setup.py14
-rw-r--r--tests/resources.py5
-rw-r--r--tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.eggbin1222 -> 0 bytes
-rw-r--r--tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.zipbin1222 -> 0 bytes
-rw-r--r--tests/testdata/python2/data/SSL1/Connection1.py5
-rw-r--r--tests/testdata/python2/data/SSL1/__init__.py1
-rw-r--r--tests/testdata/python2/data/__init__.py1
-rw-r--r--tests/testdata/python2/data/absimp/__init__.py5
-rw-r--r--tests/testdata/python2/data/absimp/sidepackage/__init__.py3
-rw-r--r--tests/testdata/python2/data/absimp/string.py3
-rw-r--r--tests/testdata/python2/data/absimport.py3
-rw-r--r--tests/testdata/python2/data/all.py9
-rw-r--r--tests/testdata/python2/data/appl/__init__.py3
-rw-r--r--tests/testdata/python2/data/appl/myConnection.py12
-rw-r--r--tests/testdata/python2/data/contribute_to_namespace/namespace_pep_420/submodule.py1
-rw-r--r--tests/testdata/python2/data/descriptor_crash.py11
-rw-r--r--tests/testdata/python2/data/email.py1
-rw-r--r--tests/testdata/python2/data/find_test/module.py0
-rw-r--r--tests/testdata/python2/data/find_test/module2.py0
-rw-r--r--tests/testdata/python2/data/find_test/noendingnewline.py0
-rw-r--r--tests/testdata/python2/data/find_test/nonregr.py0
-rw-r--r--tests/testdata/python2/data/foogle/fax/__init__.py0
-rw-r--r--tests/testdata/python2/data/foogle/fax/a.py1
-rw-r--r--tests/testdata/python2/data/foogle_fax-0.12.5-py2.7-nspkg.pth2
-rw-r--r--tests/testdata/python2/data/format.py34
-rw-r--r--tests/testdata/python2/data/invalid_encoding.py1
-rw-r--r--tests/testdata/python2/data/lmfp/__init__.py2
-rw-r--r--tests/testdata/python2/data/lmfp/foo.py6
-rw-r--r--tests/testdata/python2/data/module.py90
-rw-r--r--tests/testdata/python2/data/module1abs/__init__.py4
-rw-r--r--tests/testdata/python2/data/module1abs/core.py1
-rw-r--r--tests/testdata/python2/data/module2.py143
-rw-r--r--tests/testdata/python2/data/namespace_pep_420/module.py1
-rw-r--r--tests/testdata/python2/data/noendingnewline.py36
-rw-r--r--tests/testdata/python2/data/nonregr.py57
-rw-r--r--tests/testdata/python2/data/notall.py7
-rw-r--r--tests/testdata/python2/data/notamodule/file.py0
-rw-r--r--tests/testdata/python2/data/operator_precedence.py27
-rw-r--r--tests/testdata/python2/data/package/__init__.py4
-rw-r--r--tests/testdata/python2/data/package/absimport.py6
-rw-r--r--tests/testdata/python2/data/package/hello.py2
-rw-r--r--tests/testdata/python2/data/package/import_package_subpackage_module.py49
-rw-r--r--tests/testdata/python2/data/package/subpackage/__init__.py1
-rw-r--r--tests/testdata/python2/data/package/subpackage/module.py1
-rw-r--r--tests/testdata/python2/data/path_pkg_resources_1/package/__init__.py1
-rw-r--r--tests/testdata/python2/data/path_pkg_resources_1/package/foo.py0
-rw-r--r--tests/testdata/python2/data/path_pkg_resources_2/package/__init__.py1
-rw-r--r--tests/testdata/python2/data/path_pkg_resources_2/package/bar.py0
-rw-r--r--tests/testdata/python2/data/path_pkg_resources_3/package/__init__.py1
-rw-r--r--tests/testdata/python2/data/path_pkg_resources_3/package/baz.py0
-rw-r--r--tests/testdata/python2/data/path_pkgutil_1/package/__init__.py2
-rw-r--r--tests/testdata/python2/data/path_pkgutil_1/package/foo.py0
-rw-r--r--tests/testdata/python2/data/path_pkgutil_2/package/__init__.py2
-rw-r--r--tests/testdata/python2/data/path_pkgutil_2/package/bar.py0
-rw-r--r--tests/testdata/python2/data/path_pkgutil_3/package/__init__.py2
-rw-r--r--tests/testdata/python2/data/path_pkgutil_3/package/baz.py0
-rw-r--r--tests/testdata/python2/data/recursion.py3
-rw-r--r--tests/testdata/python2/data/tmp__init__.py0
-rw-r--r--tests/testdata/python2/data/unicode_package/__init__.py1
-rw-r--r--tests/testdata/python2/data/unicode_package/core/__init__.py0
-rw-r--r--tests/testdata/python3/data/metaclass_recursion/__init__.py (renamed from tests/testdata/python2/data/find_test/__init__.py)0
-rw-r--r--tests/testdata/python3/data/metaclass_recursion/monkeypatch.py17
-rw-r--r--tests/testdata/python3/data/metaclass_recursion/parent.py3
-rw-r--r--tests/unittest_brain.py52
-rw-r--r--tests/unittest_brain_numpy_core_fromnumeric.py3
-rw-r--r--tests/unittest_brain_numpy_core_function_base.py3
-rw-r--r--tests/unittest_brain_numpy_core_multiarray.py3
-rw-r--r--tests/unittest_brain_numpy_core_numeric.py3
-rw-r--r--tests/unittest_brain_numpy_core_numerictypes.py33
-rw-r--r--tests/unittest_brain_numpy_core_umath.py3
-rw-r--r--tests/unittest_brain_numpy_ndarray.py36
-rw-r--r--tests/unittest_brain_numpy_random_mtrand.py5
-rw-r--r--tests/unittest_builder.py5
-rw-r--r--tests/unittest_helpers.py18
-rw-r--r--tests/unittest_inference.py417
-rw-r--r--tests/unittest_lookup.py4
-rw-r--r--tests/unittest_manager.py18
-rw-r--r--tests/unittest_modutils.py13
-rw-r--r--tests/unittest_nodes.py93
-rw-r--r--tests/unittest_object_model.py73
-rw-r--r--tests/unittest_objects.py25
-rw-r--r--tests/unittest_protocols.py3
-rw-r--r--tests/unittest_python3.py3
-rw-r--r--tests/unittest_raw_building.py3
-rw-r--r--tests/unittest_regrtest.py45
-rw-r--r--tests/unittest_scoped_nodes.py249
-rw-r--r--tests/unittest_transforms.py3
-rw-r--r--tests/unittest_utils.py3
-rw-r--r--tox.ini2
150 files changed, 1959 insertions, 1599 deletions
diff --git a/.travis.yml b/.travis.yml
index 49b37e3d..638720d3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,25 +11,21 @@ jobs:
env: TOXENV=formatting
- python: 3.5
env: TOXENV=py35
- - python: pypy3.5
+ - python: pypy3
env: TOXENV=pypy
- python: 3.6
env: TOXENV=py36
- python: 3.7
env: TOXENV=py37
- dist: xenial
- sudo: true
- - python: 3.8-dev
+ - python: 3.8
env: TOXENV=py38
- dist: xenial
- sudo: true
before_install:
- python --version
- uname -a
- lsb_release -a
install:
- python -m pip install pip -U
-- python -m pip install tox coverage coveralls
+- python -m pip install tox "coverage<5" coveralls
- python -m virtualenv --version
- python -m easy_install --version
- python -m pip --version
diff --git a/ChangeLog b/ChangeLog
index 2fca581d..1e7d2a77 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,15 +2,171 @@
astroid's ChangeLog
===================
-What's New in astroid 2.4.0?
-============================
-Release Date: TBA
-
* The ``context.path`` is now a ``dict`` and the ``context.push`` method
returns ``True`` if the node has been visited a certain amount of times.
Close #669
+What's New in astroid 2.5.0?
+============================
+Release Date: TBA
+
+* Added a brain for ``sqlalchemy.orm.session``
+
+* Added missing methods to the brain for ``mechanize``, to fix pylint false positives
+
+ Close #793
+
+* Added more supported parameters to ``subprocess.check_output``
+
+* Added exception inference for `UnicodeDecodeError`
+
+ Close PyCQA/pylint#3639
+
+* `FunctionDef.is_generator` properly handles `yield` nodes in `If` tests
+
+ Close PyCQA/pylint#3583
+
+
+What's New in astroid 2.4.3?
+============================
+Release Date: TBA
+
+* Fix a crash caused by a lookup of a monkey-patched method
+
+ Close PyCQA/pylint#3686
+
+
+What's New in astroid 2.4.2?
+============================
+Release Date: 2020-06-08
+
+* `FunctionDef.is_generator` properly handles `yield` nodes in `While` tests
+
+ Close PyCQA/pylint#3519
+
+* Properly construct the arguments of infered property descriptors
+
+ Close PyCQA/pylint#3648
+
+
+What's New in astroid 2.4.1?
+============================
+Release Date: 2020-05-05
+
+* Handle the case where the raw builder fails to retrieve the ``__all__`` attribute
+
+ Close #772
+
+* Restructure the AST parsing heuristic to always pick the same module
+
+ Close PyCQA/pylint#3540
+ Close #773
+
+* Changed setup.py to work with [distlib](https://pypi.org/project/distlib)
+
+ Close #779
+
+* Do not crash with SyntaxError when parsing namedtuples with invalid label
+
+ Close PyCQA/pylint#3549
+
+* Protect against ``infer_call_result`` failing with `InferenceError` in `Super.getattr()`
+
+ Close PyCQA/pylint#3529
+
+
+What's New in astroid 2.4.0?
+============================
+Release Date: 2020-04-27
+
+* Expose a ast_from_string method in AstroidManager, which will accept
+ source code as a string and return the corresponding astroid object
+
+ Closes PyCQA/astroid#725
+
+* ``BoundMethod.implicit_parameters`` returns a proper value for ``__new__``
+
+ Close PyCQA/pylint#2335
+
+* Allow slots added dynamically to a class to still be inferred
+
+ Close PyCQA/pylint#2334
+
+* Allow `FunctionDef.getattr` to look into both instance attrs and special attributes
+
+ Close PyCQA/pylint#1078
+
+* Infer qualified ``classmethod`` as a classmethod.
+
+ Close PyCQA/pylint#3417
+
+* Prevent a recursion error to happen when inferring the declared metaclass of a class
+
+ Close #749
+
+* Raise ``AttributeInferenceError`` when ``getattr()`` receives an empty name
+
+ Close PyCQA/pylint#2991
+
+* Prevent a recursion error for self reference variables and `type()` calls.
+
+ Close #199
+
+* Do not infer the first argument of a staticmethod in a metaclass as the class itself
+
+ Close PyCQA/pylint#3032
+
+* ``NodeNG.bool_value()`` gained an optional ``context`` parameter
+
+ We need to pass an inference context downstream when inferring the boolean
+ value of a node in order to prevent recursion errors and double inference.
+
+ This fix prevents a recursion error with dask library.
+
+ Close PyCQA/pylint#2985
+
+* Pass a context argument to ``astroid.Arguments`` to prevent recursion errors
+
+ Close PyCQA/pylint#3414
+
+* Better inference of class and static methods decorated with custom methods
+
+ Close PyCQA/pylint#3209
+
+* Reverse the order of decorators for `infer_subscript`
+
+ `path_wrapper` needs to come first, followed by `raise_if_nothing_inferred`,
+ otherwise we won't handle `StopIteration` correctly.
+
+ Close #762
+
+* Prevent a recursion error when inferring self-referential variables without definition
+
+ Close PyCQA/pylint#1285
+
+* Numpy `datetime64.astype` return value is inferred as a `ndarray`.
+
+ Close PyCQA/pylint#3332
+
+* Skip non ``Assign`` and ``AnnAssign`` nodes from enum reinterpretation
+
+ Closes PyCQA/pylint#3365
+
+* Numpy ``ndarray`` attributes ``imag`` and ``real`` are now inferred as ``ndarray``.
+
+ Close PyCQA/pylint#3322
+
+* Added a call to ``register_transform`` for all functions of the ``brain_numpy_core_multiarray``
+ module in case the current node is an instance of ``astroid.Name``
+
+ Close #666
+
+* Use the parent of the node when inferring aug assign nodes instead of the statement
+
+ Close PyCQA/pylint#2911
+ Close PyCQA/pylint#3214
+
* Added some functions to the ``brain_numpy_core_umath`` module
Close PyCQA/pylint#3319
@@ -100,6 +256,18 @@ Release Date: TBA
Close PyCQA/pylint#3274
+* Can access per argument type comments for positional only and keyword only arguments.
+
+ The comments are accessed through through the new
+ ``Arguments.type_comment_posonlyargs`` and
+ ``Arguments.type_comment_kwonlyargs`` attributes respectively.
+
+* Relax upper bound on `wrapt`
+
+ Close #755
+
+* Properly analyze CFFI compiled extensions.
+
What's New in astroid 2.3.2?
============================
Release Date: TBA
diff --git a/MANIFEST.in b/MANIFEST.in
index 23286ffc..8cf73ba0 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -5,3 +5,5 @@ include COPYING.LESSER
include pytest.ini
recursive-include tests *.py *.zip *.egg *.pth
recursive-include astroid/brain *.py
+graft tests
+recursive-exclude tests *.pyc
diff --git a/README.rst b/README.rst
index 3ba31f6d..b02fe9ae 100644
--- a/README.rst
+++ b/README.rst
@@ -79,7 +79,8 @@ Python Versions
---------------
astroid 2.0 is currently available for Python 3 only. If you want Python 2
-support, older versions of astroid will still supported until 2020.
+support, use an older version of astroid (though note that these versions
+are no longer supported).
Test
----
diff --git a/astroid/__init__.py b/astroid/__init__.py
index 9feac683..e869274e 100644
--- a/astroid/__init__.py
+++ b/astroid/__init__.py
@@ -6,6 +6,7 @@
# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
# Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Nick Drozd <nicholasdrozd@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py
index 4647a6eb..aa48c537 100644
--- a/astroid/__pkginfo__.py
+++ b/astroid/__pkginfo__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2015-2017 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
@@ -9,22 +9,26 @@
# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017 Calen Pennington <cale@edx.org>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Uilian Ries <uilianries@gmail.com>
+# Copyright (c) 2019 Thomas Hisch <t.hisch@gmail.com>
+# Copyright (c) 2020 Michael <michael-k@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
"""astroid packaging information"""
-version = "2.4.0"
+version = "2.5.0"
numversion = tuple(int(elem) for elem in version.split(".") if elem.isdigit())
extras_require = {}
install_requires = [
"lazy_object_proxy==1.4.*",
"six~=1.12",
- "wrapt==1.11.*",
+ "wrapt~=1.11",
'typed-ast>=1.4.0,<1.5;implementation_name== "cpython" and python_version<"3.8"',
]
@@ -46,6 +50,7 @@ classifiers = [
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
diff --git a/astroid/_ast.py b/astroid/_ast.py
index 66c5cf25..34b74c5f 100644
--- a/astroid/_ast.py
+++ b/astroid/_ast.py
@@ -4,10 +4,11 @@ from functools import partial
from typing import Optional
import sys
-_ast_py2 = _ast_py3 = None
+import astroid
+
+_ast_py3 = None
try:
import typed_ast.ast3 as _ast_py3
- import typed_ast.ast27 as _ast_py2
except ImportError:
pass
@@ -21,28 +22,30 @@ if PY38:
FunctionType = namedtuple("FunctionType", ["argtypes", "returns"])
-def _get_parser_module(parse_python_two=False, type_comments_support=True):
- if not type_comments_support:
- return ast
-
- if parse_python_two:
- parser_module = _ast_py2
- else:
- parser_module = _ast_py3
- return parser_module or ast
-
-
-def _parse(string: str, parse_python_two=False, type_comments=True):
- parse_module = _get_parser_module(
- parse_python_two=parse_python_two, type_comments_support=type_comments
+class ParserModule(
+ namedtuple(
+ "ParserModule",
+ [
+ "module",
+ "unary_op_classes",
+ "cmp_op_classes",
+ "bool_op_classes",
+ "bin_op_classes",
+ "context_classes",
+ ],
)
- parse_func = parse_module.parse
- if parse_module is _ast_py3:
- if PY38:
- parse_func = partial(parse_func, type_comments=type_comments)
- if not parse_python_two:
- parse_func = partial(parse_func, feature_version=sys.version_info.minor)
- return parse_func(string)
+):
+ def parse(self, string: str, type_comments=True):
+ if self.module is _ast_py3:
+ if PY38:
+ parse_func = partial(self.module.parse, type_comments=type_comments)
+ else:
+ parse_func = partial(
+ self.module.parse, feature_version=sys.version_info.minor
+ )
+ else:
+ parse_func = self.module.parse
+ return parse_func(string)
def parse_function_type_comment(type_comment: str) -> Optional[FunctionType]:
@@ -52,3 +55,77 @@ def parse_function_type_comment(type_comment: str) -> Optional[FunctionType]:
func_type = _ast_py3.parse(type_comment, "<type_comment>", "func_type")
return FunctionType(argtypes=func_type.argtypes, returns=func_type.returns)
+
+
+def get_parser_module(type_comments=True) -> ParserModule:
+ if not type_comments:
+ parser_module = ast
+ else:
+ parser_module = _ast_py3
+ parser_module = parser_module or ast
+
+ unary_op_classes = _unary_operators_from_module(parser_module)
+ cmp_op_classes = _compare_operators_from_module(parser_module)
+ bool_op_classes = _bool_operators_from_module(parser_module)
+ bin_op_classes = _binary_operators_from_module(parser_module)
+ context_classes = _contexts_from_module(parser_module)
+
+ return ParserModule(
+ parser_module,
+ unary_op_classes,
+ cmp_op_classes,
+ bool_op_classes,
+ bin_op_classes,
+ context_classes,
+ )
+
+
+def _unary_operators_from_module(module):
+ return {module.UAdd: "+", module.USub: "-", module.Not: "not", module.Invert: "~"}
+
+
+def _binary_operators_from_module(module):
+ binary_operators = {
+ module.Add: "+",
+ module.BitAnd: "&",
+ module.BitOr: "|",
+ module.BitXor: "^",
+ module.Div: "/",
+ module.FloorDiv: "//",
+ module.MatMult: "@",
+ module.Mod: "%",
+ module.Mult: "*",
+ module.Pow: "**",
+ module.Sub: "-",
+ module.LShift: "<<",
+ module.RShift: ">>",
+ }
+ return binary_operators
+
+
+def _bool_operators_from_module(module):
+ return {module.And: "and", module.Or: "or"}
+
+
+def _compare_operators_from_module(module):
+ return {
+ module.Eq: "==",
+ module.Gt: ">",
+ module.GtE: ">=",
+ module.In: "in",
+ module.Is: "is",
+ module.IsNot: "is not",
+ module.Lt: "<",
+ module.LtE: "<=",
+ module.NotEq: "!=",
+ module.NotIn: "not in",
+ }
+
+
+def _contexts_from_module(module):
+ return {
+ module.Load: astroid.Load,
+ module.Store: astroid.Store,
+ module.Del: astroid.Del,
+ module.Param: astroid.Store,
+ }
diff --git a/astroid/arguments.py b/astroid/arguments.py
index 0a853605..5f4d9092 100644
--- a/astroid/arguments.py
+++ b/astroid/arguments.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
@@ -20,20 +20,27 @@ class CallSite:
It needs a call context, which contains the arguments and the
keyword arguments that were passed into a given call site.
- In order to infer what an argument represents, call
- :meth:`infer_argument` with the corresponding function node
- and the argument name.
+ In order to infer what an argument represents, call :meth:`infer_argument`
+ with the corresponding function node and the argument name.
+
+ :param callcontext:
+ An instance of :class:`astroid.context.CallContext`, that holds
+ the arguments for the call site.
+ :param argument_context_map:
+ Additional contexts per node, passed in from :attr:`astroid.context.Context.extra_context`
+ :param context:
+ An instance of :class:`astroid.context.Context`.
"""
- def __init__(self, callcontext, argument_context_map=None):
+ def __init__(self, callcontext, argument_context_map=None, context=None):
if argument_context_map is None:
argument_context_map = {}
self.argument_context_map = argument_context_map
args = callcontext.args
keywords = callcontext.keywords
self.duplicated_keywords = set()
- self._unpacked_args = self._unpack_args(args)
- self._unpacked_kwargs = self._unpack_keywords(keywords)
+ self._unpacked_args = self._unpack_args(args, context=context)
+ self._unpacked_kwargs = self._unpack_keywords(keywords, context=context)
self.positional_arguments = [
arg for arg in self._unpacked_args if arg is not util.Uninferable
@@ -45,10 +52,18 @@ class CallSite:
}
@classmethod
- def from_call(cls, call_node):
- """Get a CallSite object from the given Call node."""
+ def from_call(cls, call_node, context=None):
+ """Get a CallSite object from the given Call node.
+
+ :param context:
+ An instance of :class:`astroid.context.Context` that will be used
+ to force a single inference path.
+ """
+
+ # Determine the callcontext from the given `context` object if any.
+ context = context or contextmod.InferenceContext()
callcontext = contextmod.CallContext(call_node.args, call_node.keywords)
- return cls(callcontext)
+ return cls(callcontext, context=context)
def has_invalid_arguments(self):
"""Check if in the current CallSite were passed *invalid* arguments
@@ -70,9 +85,9 @@ class CallSite:
"""
return len(self.keyword_arguments) != len(self._unpacked_kwargs)
- def _unpack_keywords(self, keywords):
+ def _unpack_keywords(self, keywords, context=None):
values = {}
- context = contextmod.InferenceContext()
+ context = context or contextmod.InferenceContext()
context.extra_context = self.argument_context_map
for name, value in keywords:
if name is None:
@@ -110,9 +125,9 @@ class CallSite:
values[name] = value
return values
- def _unpack_args(self, args):
+ def _unpack_args(self, args, context=None):
values = []
- context = contextmod.InferenceContext()
+ context = context or contextmod.InferenceContext()
context.extra_context = self.argument_context_map
for arg in args:
if isinstance(arg, nodes.Starred):
diff --git a/astroid/as_string.py b/astroid/as_string.py
index 57049141..653411f4 100644
--- a/astroid/as_string.py
+++ b/astroid/as_string.py
@@ -1,15 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
-# Copyright (c) 2013-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2013-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2013-2014 Google, Inc.
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
+# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017 rr- <rr-@sakuya.pl>
+# Copyright (c) 2018 Serhiy Storchaka <storchaka@gmail.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2019 Alex Hall <alex.mojaki@gmail.com>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -80,6 +84,15 @@ class AsStringVisitor:
## visit_<node> methods ###########################################
+ def visit_await(self, node):
+ return "await %s" % node.value.accept(self)
+
+ def visit_asyncwith(self, node):
+ return "async %s" % self.visit_with(node)
+
+ def visit_asyncfor(self, node):
+ return "async %s" % self.visit_for(node)
+
def visit_arguments(self, node):
"""return an astroid.Function node as string"""
return node.format_args()
@@ -180,11 +193,12 @@ class AsStringVisitor:
def visit_comprehension(self, node):
"""return an astroid.Comprehension node as string"""
ifs = "".join(" if %s" % n.accept(self) for n in node.ifs)
- return "for %s in %s%s" % (
+ generated = "for %s in %s%s" % (
node.target.accept(self),
node.iter.accept(self),
ifs,
)
+ return "%s%s" % ("async " if node.is_async else "", generated)
def visit_const(self, node):
"""return an astroid.Const node as string"""
@@ -248,7 +262,7 @@ class AsStringVisitor:
def visit_excepthandler(self, node):
if node.type:
if node.name:
- excs = "except %s, %s" % (
+ excs = "except %s as %s" % (
node.type.accept(self),
node.name.accept(self),
)
@@ -300,6 +314,41 @@ class AsStringVisitor:
_import_string(node.names),
)
+ def visit_joinedstr(self, node):
+ string = "".join(
+ # Use repr on the string literal parts
+ # to get proper escapes, e.g. \n, \\, \"
+ # But strip the quotes off the ends
+ # (they will always be one character: ' or ")
+ repr(value.value)[1:-1]
+ # Literal braces must be doubled to escape them
+ .replace("{", "{{").replace("}", "}}")
+ # Each value in values is either a string literal (Const)
+ # or a FormattedValue
+ if type(value).__name__ == "Const" else value.accept(self)
+ for value in node.values
+ )
+
+ # Try to find surrounding quotes that don't appear at all in the string.
+ # Because the formatted values inside {} can't contain backslash (\)
+ # using a triple quote is sometimes necessary
+ for quote in ["'", '"', '"""', "'''"]:
+ if quote not in string:
+ break
+
+ return "f" + quote + string + quote
+
+ def visit_formattedvalue(self, node):
+ result = node.value.accept(self)
+ if node.conversion and node.conversion >= 0:
+ # e.g. if node.conversion == 114: result += "!r"
+ result += "!" + chr(node.conversion)
+ if node.format_spec:
+ # The format spec is itself a JoinedString, i.e. an f-string
+ # We strip the f and quotes of the ends
+ result += ":" + node.format_spec.accept(self)[2:-1]
+ return "{%s}" % result
+
def handle_functiondef(self, node, keyword):
"""return a (possibly async) function definition node as string"""
decorate = node.decorators.accept(self) if node.decorators else ""
@@ -401,6 +450,16 @@ class AsStringVisitor:
"""return an astroid.Name node as string"""
return node.name
+ def visit_namedexpr(self, node):
+ """Return an assignment expression node as string"""
+ target = node.target.accept(self)
+ value = node.value.accept(self)
+ return "%s := %s" % (target, value)
+
+ def visit_nonlocal(self, node):
+ """return an astroid.Nonlocal node as string"""
+ return "nonlocal %s" % ", ".join(node.names)
+
def visit_pass(self, node):
"""return an astroid.Pass node as string"""
return "pass"
@@ -417,14 +476,11 @@ class AsStringVisitor:
def visit_raise(self, node):
"""return an astroid.Raise node as string"""
if node.exc:
- if node.inst:
- if node.tback:
- return "raise %s, %s, %s" % (
- node.exc.accept(self),
- node.inst.accept(self),
- node.tback.accept(self),
- )
- return "raise %s, %s" % (node.exc.accept(self), node.inst.accept(self))
+ if node.cause:
+ return "raise %s from %s" % (
+ node.exc.accept(self),
+ node.cause.accept(self),
+ )
return "raise %s" % node.exc.accept(self)
return "raise"
@@ -529,6 +585,15 @@ class AsStringVisitor:
return "(%s)" % (expr,)
+ def visit_yieldfrom(self, node):
+ """ Return an astroid.YieldFrom node as string. """
+ yi_val = (" " + node.value.accept(self)) if node.value else ""
+ expr = "yield from" + yi_val
+ if node.parent.is_statement:
+ return expr
+
+ return "(%s)" % (expr,)
+
def visit_starred(self, node):
"""return Starred node as string"""
return "*" + node.value.accept(self)
@@ -544,103 +609,11 @@ class AsStringVisitor:
def visit_uninferable(self, node):
return str(node)
+ def visit_property(self, node):
+ return node.function.accept(self)
-class AsStringVisitor3(AsStringVisitor):
- """AsStringVisitor3 overwrites some AsStringVisitor methods"""
-
- def visit_excepthandler(self, node):
- if node.type:
- if node.name:
- excs = "except %s as %s" % (
- node.type.accept(self),
- node.name.accept(self),
- )
- else:
- excs = "except %s" % node.type.accept(self)
- else:
- excs = "except"
- return "%s:\n%s" % (excs, self._stmt_list(node.body))
-
- def visit_nonlocal(self, node):
- """return an astroid.Nonlocal node as string"""
- return "nonlocal %s" % ", ".join(node.names)
-
- def visit_raise(self, node):
- """return an astroid.Raise node as string"""
- if node.exc:
- if node.cause:
- return "raise %s from %s" % (
- node.exc.accept(self),
- node.cause.accept(self),
- )
- return "raise %s" % node.exc.accept(self)
- return "raise"
-
- def visit_yieldfrom(self, node):
- """ Return an astroid.YieldFrom node as string. """
- yi_val = (" " + node.value.accept(self)) if node.value else ""
- expr = "yield from" + yi_val
- if node.parent.is_statement:
- return expr
-
- return "(%s)" % (expr,)
-
- def visit_await(self, node):
- return "await %s" % node.value.accept(self)
-
- def visit_asyncwith(self, node):
- return "async %s" % self.visit_with(node)
-
- def visit_asyncfor(self, node):
- return "async %s" % self.visit_for(node)
-
- def visit_joinedstr(self, node):
- string = "".join(
- # Use repr on the string literal parts
- # to get proper escapes, e.g. \n, \\, \"
- # But strip the quotes off the ends
- # (they will always be one character: ' or ")
- repr(value.value)[1:-1]
- # Literal braces must be doubled to escape them
- .replace("{", "{{").replace("}", "}}")
- # Each value in values is either a string literal (Const)
- # or a FormattedValue
- if type(value).__name__ == "Const" else value.accept(self)
- for value in node.values
- )
-
- # Try to find surrounding quotes that don't appear at all in the string.
- # Because the formatted values inside {} can't contain backslash (\)
- # using a triple quote is sometimes necessary
- for quote in ["'", '"', '"""', "'''"]:
- if quote not in string:
- break
-
- return "f" + quote + string + quote
-
- def visit_formattedvalue(self, node):
- result = node.value.accept(self)
- if node.conversion and node.conversion >= 0:
- # e.g. if node.conversion == 114: result += "!r"
- result += "!" + chr(node.conversion)
- if node.format_spec:
- # The format spec is itself a JoinedString, i.e. an f-string
- # We strip the f and quotes of the ends
- result += ":" + node.format_spec.accept(self)[2:-1]
- return "{%s}" % result
-
- def visit_comprehension(self, node):
- """return an astroid.Comprehension node as string"""
- return "%s%s" % (
- "async " if node.is_async else "",
- super(AsStringVisitor3, self).visit_comprehension(node),
- )
-
- def visit_namedexpr(self, node):
- """Return an assignment expression node as string"""
- target = node.target.accept(self)
- value = node.value.accept(self)
- return "%s := %s" % (target, value)
+ def visit_evaluatedobject(self, node):
+ return node.original.accept(self)
def _import_string(names):
@@ -654,7 +627,5 @@ def _import_string(names):
return ", ".join(_names)
-AsStringVisitor = AsStringVisitor3
-
# This sets the default indent to 4 spaces.
to_code = AsStringVisitor(" ")
diff --git a/astroid/bases.py b/astroid/bases.py
index 0c6cb9b2..9c743031 100644
--- a/astroid/bases.py
+++ b/astroid/bases.py
@@ -1,15 +1,19 @@
+# -*- coding: utf-8 -*-
# Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
# Copyright (c) 2016-2017 Derek Gustafson <degustaf@gmail.com>
# Copyright (c) 2017 Calen Pennington <calen.pennington@gmail.com>
+# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
# Copyright (c) 2018 Daniel Colascione <dancol@dancol.org>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -288,7 +292,7 @@ class Instance(BaseInstance):
def display_type(self):
return "Instance of"
- def bool_value(self):
+ def bool_value(self, context=None):
"""Infer the truth value for an Instance
The truth value of an instance is determined by these conditions:
@@ -301,7 +305,7 @@ class Instance(BaseInstance):
nonzero. If a class defines neither __len__() nor __bool__(),
all its instances are considered true.
"""
- context = contextmod.InferenceContext()
+ context = context or contextmod.InferenceContext()
context.callcontext = contextmod.CallContext(args=[])
context.boundnode = self
@@ -376,7 +380,7 @@ class UnboundMethod(Proxy):
return (Instance(x) if x is not util.Uninferable else x for x in infer)
return self._proxied.infer_call_result(caller, context)
- def bool_value(self):
+ def bool_value(self, context=None):
return True
@@ -391,6 +395,9 @@ class BoundMethod(UnboundMethod):
self.bound = bound
def implicit_parameters(self):
+ if self.name == "__new__":
+ # __new__ acts as a classmethod but the class argument is not implicit.
+ return 0
return 1
def is_bound(self):
@@ -479,9 +486,9 @@ class BoundMethod(UnboundMethod):
if new_cls:
return iter((new_cls,))
- return super(BoundMethod, self).infer_call_result(caller, context)
+ return super().infer_call_result(caller, context)
- def bool_value(self):
+ def bool_value(self, context=None):
return True
@@ -507,7 +514,7 @@ class Generator(BaseInstance):
def display_type(self):
return "Generator"
- def bool_value(self):
+ def bool_value(self, context=None):
return True
def __repr__(self):
diff --git a/astroid/brain/brain_argparse.py b/astroid/brain/brain_argparse.py
index d4899117..6a7556f6 100644
--- a/astroid/brain/brain_argparse.py
+++ b/astroid/brain/brain_argparse.py
@@ -2,7 +2,7 @@ from astroid import MANAGER, arguments, nodes, inference_tip, UseInferenceDefaul
def infer_namespace(node, context=None):
- callsite = arguments.CallSite.from_call(node)
+ callsite = arguments.CallSite.from_call(node, context=context)
if not callsite.keyword_arguments:
# Cannot make sense of it.
raise UseInferenceDefault()
diff --git a/astroid/brain/brain_boto3.py b/astroid/brain/brain_boto3.py
new file mode 100644
index 00000000..342ca571
--- /dev/null
+++ b/astroid/brain/brain_boto3.py
@@ -0,0 +1,28 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
+
+"""Astroid hooks for understanding boto3.ServiceRequest()"""
+import astroid
+from astroid import MANAGER, extract_node
+
+BOTO_SERVICE_FACTORY_QUALIFIED_NAME = "boto3.resources.base.ServiceResource"
+
+
+def service_request_transform(node):
+ """Transform ServiceResource to look like dynamic classes"""
+ code = """
+ def __getattr__(self, attr):
+ return 0
+ """
+ func_getattr = extract_node(code)
+ node.locals["__getattr__"] = [func_getattr]
+ return node
+
+
+def _looks_like_boto3_service_request(node):
+ return node.qname() == BOTO_SERVICE_FACTORY_QUALIFIED_NAME
+
+
+MANAGER.register_transform(
+ astroid.ClassDef, service_request_transform, _looks_like_boto3_service_request
+)
diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py
index 3cbe48f7..4b07ac5c 100644
--- a/astroid/brain/brain_builtin_inference.py
+++ b/astroid/brain/brain_builtin_inference.py
@@ -1,8 +1,14 @@
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 Rene Zhang <rz99@cornell.edu>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
+# Copyright (c) 2019 Stanislav Levin <slev@altlinux.org>
+# Copyright (c) 2019 David Liu <david@cs.toronto.edu>
+# Copyright (c) 2019 Bryce Guinta <bryce.guinta@protonmail.com>
+# Copyright (c) 2019 Frédéric Chapoton <fchapoton2@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -177,9 +183,14 @@ def _container_generic_transform(arg, context, klass, iterables, build_elts):
elts = [elt.value for elt in arg.elts]
else:
# TODO: Does not handle deduplication for sets.
- elts = filter(
- None, map(partial(helpers.safe_infer, context=context), arg.elts)
- )
+ elts = []
+ for element in arg.elts:
+ inferred = helpers.safe_infer(element, context=context)
+ if inferred:
+ evaluated_object = nodes.EvaluatedObject(
+ original=element, value=inferred
+ )
+ elts.append(evaluated_object)
elif isinstance(arg, nodes.Dict):
# Dicts need to have consts as strings already.
if not all(isinstance(elt[0], nodes.Const) for elt in arg.items):
@@ -293,7 +304,7 @@ def infer_dict(node, context=None):
If a case can't be inferred, we'll fallback to default inference.
"""
- call = arguments.CallSite.from_call(node)
+ call = arguments.CallSite.from_call(node, context=context)
if call.has_invalid_arguments() or call.has_invalid_keywords():
raise UseInferenceDefault
@@ -527,7 +538,7 @@ def infer_bool(node, context=None):
if inferred is util.Uninferable:
return util.Uninferable
- bool_value = inferred.bool_value()
+ bool_value = inferred.bool_value(context=context)
if bool_value is util.Uninferable:
return util.Uninferable
return nodes.Const(bool_value)
@@ -597,7 +608,7 @@ def infer_issubclass(callnode, context=None):
:rtype nodes.Const: Boolean Const value of the `issubclass` call
:raises UseInferenceDefault: If the node cannot be inferred
"""
- call = arguments.CallSite.from_call(callnode)
+ call = arguments.CallSite.from_call(callnode, context=context)
if call.keyword_arguments:
# issubclass doesn't support keyword arguments
raise UseInferenceDefault("TypeError: issubclass() takes no keyword arguments")
@@ -644,7 +655,7 @@ def infer_isinstance(callnode, context=None):
:raises UseInferenceDefault: If the node cannot be inferred
"""
- call = arguments.CallSite.from_call(callnode)
+ call = arguments.CallSite.from_call(callnode, context=context)
if call.keyword_arguments:
# isinstance doesn't support keyword arguments
raise UseInferenceDefault("TypeError: isinstance() takes no keyword arguments")
@@ -701,7 +712,7 @@ def infer_len(node, context=None):
:param context.InferenceContext: node context
:rtype nodes.Const: a Const node with the inferred length, if possible
"""
- call = arguments.CallSite.from_call(node)
+ call = arguments.CallSite.from_call(node, context=context)
if call.keyword_arguments:
raise UseInferenceDefault("TypeError: len() must take no keyword arguments")
if len(call.positional_arguments) != 1:
@@ -723,7 +734,7 @@ def infer_str(node, context=None):
:param context.InferenceContext: node context
:rtype nodes.Const: a Const containing an empty string
"""
- call = arguments.CallSite.from_call(node)
+ call = arguments.CallSite.from_call(node, context=context)
if call.keyword_arguments:
raise UseInferenceDefault("TypeError: str() must take no keyword arguments")
try:
@@ -739,7 +750,7 @@ def infer_int(node, context=None):
:param context.InferenceContext: node context
:rtype nodes.Const: a Const containing the integer value of the int() call
"""
- call = arguments.CallSite.from_call(node)
+ call = arguments.CallSite.from_call(node, context=context)
if call.keyword_arguments:
raise UseInferenceDefault("TypeError: int() must take no keyword arguments")
@@ -782,7 +793,7 @@ def infer_dict_fromkeys(node, context=None):
new_node.postinit(elements)
return new_node
- call = arguments.CallSite.from_call(node)
+ call = arguments.CallSite.from_call(node, context=context)
if call.keyword_arguments:
raise UseInferenceDefault("TypeError: int() must take no keyword arguments")
if len(call.positional_arguments) not in {1, 2}:
diff --git a/astroid/brain/brain_collections.py b/astroid/brain/brain_collections.py
index e5b09ec8..669c6ca4 100644
--- a/astroid/brain/brain_collections.py
+++ b/astroid/brain/brain_collections.py
@@ -3,6 +3,7 @@
# Copyright (c) 2016-2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
# Copyright (c) 2018 Ioana Tagirta <ioana.tagirta@gmail.com>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_dateutil.py b/astroid/brain/brain_dateutil.py
index a1c270fe..9fdb9fde 100644
--- a/astroid/brain/brain_dateutil.py
+++ b/astroid/brain/brain_dateutil.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015 raylu <lurayl@gmail.com>
# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
diff --git a/astroid/brain/brain_fstrings.py b/astroid/brain/brain_fstrings.py
index 7d8c7b68..298d58af 100644
--- a/astroid/brain/brain_fstrings.py
+++ b/astroid/brain/brain_fstrings.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2017-2018 Claudiu Popa <pcmanticore@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_functools.py b/astroid/brain/brain_functools.py
index 8b594efe..d6c60691 100644
--- a/astroid/brain/brain_functools.py
+++ b/astroid/brain/brain_functools.py
@@ -1,4 +1,5 @@
-# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2018 hippo91 <guillaume.peillex@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
"""Astroid hooks for understanding functools library module."""
@@ -62,7 +63,7 @@ def _transform_lru_cache(node, context=None):
def _functools_partial_inference(node, context=None):
- call = arguments.CallSite.from_call(node)
+ call = arguments.CallSite.from_call(node, context=context)
number_of_positional = len(call.positional_arguments)
if number_of_positional < 1:
raise astroid.UseInferenceDefault(
diff --git a/astroid/brain/brain_gi.py b/astroid/brain/brain_gi.py
index d8e47d22..e49f3a22 100644
--- a/astroid/brain/brain_gi.py
+++ b/astroid/brain/brain_gi.py
@@ -1,11 +1,14 @@
+# -*- coding: utf-8 -*-
# Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2014 Cole Robinson <crobinso@redhat.com>
-# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 David Shea <dshea@redhat.com>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
# Copyright (c) 2016 Giuseppe Scrivano <gscrivan@redhat.com>
+# Copyright (c) 2018 Christoph Reiter <reiter.christoph@gmail.com>
+# Copyright (c) 2019 Philipp Hörist <philipp@hoerist.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_hashlib.py b/astroid/brain/brain_hashlib.py
index 98ae774d..eb34e159 100644
--- a/astroid/brain/brain_hashlib.py
+++ b/astroid/brain/brain_hashlib.py
@@ -1,4 +1,6 @@
# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2018 David Poirier <david-poirier-csn@users.noreply.github.com>
+# Copyright (c) 2018 wgehalo <wgehalo@gmail.com>
# Copyright (c) 2018 Ioana Tagirta <ioana.tagirta@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
diff --git a/astroid/brain/brain_http.py b/astroid/brain/brain_http.py
index 6d7fb7a5..b16464e8 100644
--- a/astroid/brain/brain_http.py
+++ b/astroid/brain/brain_http.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2018-2019 Claudiu Popa <pcmanticore@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_io.py b/astroid/brain/brain_io.py
index 4c689225..c7453112 100644
--- a/astroid/brain/brain_io.py
+++ b/astroid/brain/brain_io.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_mechanize.py b/astroid/brain/brain_mechanize.py
index 93f282ec..88623a82 100644
--- a/astroid/brain/brain_mechanize.py
+++ b/astroid/brain/brain_mechanize.py
@@ -1,6 +1,6 @@
# Copyright (c) 2012-2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
@@ -15,13 +15,70 @@ def mechanize_transform():
"""
class Browser(object):
+ def __getattr__(self, name):
+ return None
+ def __getitem__(self, name):
+ return None
+ def __setitem__(self, name, val):
+ return None
+ def back(self, n=1):
+ return None
+ def clear_history(self):
+ return None
+ def click(self, *args, **kwds):
+ return None
+ def click_link(self, link=None, **kwds):
+ return None
+ def close(self):
+ return None
+ def encoding(self):
+ return None
+ def find_link(self, text=None, text_regex=None, name=None, name_regex=None, url=None, url_regex=None, tag=None, predicate=None, nr=0):
+ return None
+ def follow_link(self, link=None, **kwds):
+ return None
+ def forms(self):
+ return None
+ def geturl(self):
+ return None
+ def global_form(self):
+ return None
+ def links(self, **kwds):
+ return None
+ def open_local_file(self, filename):
+ return None
def open(self, url, data=None, timeout=None):
return None
def open_novisit(self, url, data=None, timeout=None):
return None
def open_local_file(self, filename):
return None
-
+ def reload(self):
+ return None
+ def response(self):
+ return None
+ def select_form(self, name=None, predicate=None, nr=None, **attrs):
+ return None
+ def set_cookie(self, cookie_string):
+ return None
+ def set_handle_referer(self, handle):
+ return None
+ def set_header(self, header, value=None):
+ return None
+ def set_html(self, html, url="http://example.com/"):
+ return None
+ def set_response(self, response):
+ return None
+ def set_simple_cookie(self, name, value, domain, path='/'):
+ return None
+ def submit(self, *args, **kwds):
+ return None
+ def title(self):
+ return None
+ def viewing_html(self):
+ return None
+ def visit_response(self, response, request=None):
+ return None
"""
)
diff --git a/astroid/brain/brain_multiprocessing.py b/astroid/brain/brain_multiprocessing.py
index 71256ee3..3629b032 100644
--- a/astroid/brain/brain_multiprocessing.py
+++ b/astroid/brain/brain_multiprocessing.py
@@ -1,4 +1,5 @@
-# Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py
index de240671..13fcf793 100644
--- a/astroid/brain/brain_namedtuple_enum.py
+++ b/astroid/brain/brain_namedtuple_enum.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2012-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru>
@@ -11,6 +11,8 @@
# Copyright (c) 2016 Mateusz Bysiek <mb@mbdev.pl>
# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -121,6 +123,8 @@ def infer_func_form(node, base_type, context=None, enum=False):
except (AttributeError, exceptions.InferenceError):
raise UseInferenceDefault()
+ attributes = [attr for attr in attributes if " " not in attr]
+
# If we can't infer the name of the class, don't crash, up to this point
# we know it is a namedtuple anyway.
name = name or "Uninferable"
@@ -167,7 +171,7 @@ def infer_named_tuple(node, context=None):
class_node, name, attributes = infer_func_form(
node, tuple_base_name, context=context
)
- call_site = arguments.CallSite.from_call(node)
+ call_site = arguments.CallSite.from_call(node, context=context)
func = next(extract_node("import collections; collections.namedtuple").infer())
try:
rename = next(call_site.infer_argument(func, "rename", context)).bool_value()
@@ -317,6 +321,8 @@ def infer_enum_class(node):
targets = stmt.targets
elif isinstance(stmt, nodes.AnnAssign):
targets = [stmt.target]
+ else:
+ continue
inferred_return_value = None
if isinstance(stmt, nodes.Assign):
diff --git a/astroid/brain/brain_nose.py b/astroid/brain/brain_nose.py
index 7b12d760..d0280a3f 100644
--- a/astroid/brain/brain_nose.py
+++ b/astroid/brain/brain_nose.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
diff --git a/astroid/brain/brain_numpy_core_fromnumeric.py b/astroid/brain/brain_numpy_core_fromnumeric.py
index 43b30e4d..62dfe991 100644
--- a/astroid/brain/brain_numpy_core_fromnumeric.py
+++ b/astroid/brain/brain_numpy_core_fromnumeric.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_numpy_core_function_base.py b/astroid/brain/brain_numpy_core_function_base.py
index 05a73d96..58aa0a98 100644
--- a/astroid/brain/brain_numpy_core_function_base.py
+++ b/astroid/brain/brain_numpy_core_function_base.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_numpy_core_multiarray.py b/astroid/brain/brain_numpy_core_multiarray.py
index 18218436..b2e32bc8 100644
--- a/astroid/brain/brain_numpy_core_multiarray.py
+++ b/astroid/brain/brain_numpy_core_multiarray.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019-2020 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -85,3 +85,8 @@ for method_name, function_src in METHODS_TO_BE_INFERRED.items():
astroid.inference_tip(inference_function),
functools.partial(looks_like_numpy_member, method_name),
)
+ astroid.MANAGER.register_transform(
+ astroid.Name,
+ astroid.inference_tip(inference_function),
+ functools.partial(looks_like_numpy_member, method_name),
+ )
diff --git a/astroid/brain/brain_numpy_core_numeric.py b/astroid/brain/brain_numpy_core_numeric.py
index ba43c941..2a6f37e3 100644
--- a/astroid/brain/brain_numpy_core_numeric.py
+++ b/astroid/brain/brain_numpy_core_numeric.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_numpy_core_numerictypes.py b/astroid/brain/brain_numpy_core_numerictypes.py
index 42021fae..6ac4a146 100644
--- a/astroid/brain/brain_numpy_core_numerictypes.py
+++ b/astroid/brain/brain_numpy_core_numerictypes.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019-2020 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -11,6 +11,10 @@ import astroid
def numpy_core_numerictypes_transform():
+ # TODO: Uniformize the generic API with the ndarray one.
+ # According to numpy doc the generic object should expose
+ # the same API than ndarray. This has been done here partially
+ # through the astype method.
return astroid.parse(
"""
# different types defined in numerictypes.py
@@ -35,7 +39,7 @@ def numpy_core_numerictypes_transform():
def argmax(self): return uninferable
def argmin(self): return uninferable
def argsort(self): return uninferable
- def astype(self): return uninferable
+ def astype(self, dtype, order='K', casting='unsafe', subok=True, copy=True): return np.ndarray([0, 0])
def base(self): return uninferable
def byteswap(self): return uninferable
def choose(self): return uninferable
diff --git a/astroid/brain/brain_numpy_core_umath.py b/astroid/brain/brain_numpy_core_umath.py
index 69a3a90d..d76161d0 100644
--- a/astroid/brain/brain_numpy_core_umath.py
+++ b/astroid/brain/brain_numpy_core_umath.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_numpy_ndarray.py b/astroid/brain/brain_numpy_ndarray.py
index 302ee4a4..d40a7dd0 100644
--- a/astroid/brain/brain_numpy_ndarray.py
+++ b/astroid/brain/brain_numpy_ndarray.py
@@ -1,6 +1,6 @@
-# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2017-2020 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -24,20 +24,20 @@ def infer_numpy_ndarray(node, context=None):
self.dtype = None
self.flags = None
self.flat = None
- self.imag = None
+ self.imag = np.ndarray([0, 0])
self.itemsize = None
self.nbytes = None
self.ndim = None
- self.real = None
+ self.real = np.ndarray([0, 0])
self.shape = numpy.ndarray([0, 0])
self.size = None
self.strides = None
def __abs__(self): return numpy.ndarray([0, 0])
- def __add__(self, value): return numpy.ndarray([0, 0])
- def __and__(self, value): return numpy.ndarray([0, 0])
- def __array__(self, dtype=None): return numpy.ndarray([0, 0])
- def __array_wrap__(self, obj): return numpy.ndarray([0, 0])
+ def __add__(self, value): return numpy.ndarray([0, 0])
+ def __and__(self, value): return numpy.ndarray([0, 0])
+ def __array__(self, dtype=None): return numpy.ndarray([0, 0])
+ def __array_wrap__(self, obj): return numpy.ndarray([0, 0])
def __contains__(self, key): return True
def __copy__(self): return numpy.ndarray([0, 0])
def __deepcopy__(self, memo): return numpy.ndarray([0, 0])
diff --git a/astroid/brain/brain_numpy_random_mtrand.py b/astroid/brain/brain_numpy_random_mtrand.py
index 772bfc4e..cffdceef 100644
--- a/astroid/brain/brain_numpy_random_mtrand.py
+++ b/astroid/brain/brain_numpy_random_mtrand.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -40,7 +40,7 @@ def numpy_random_mtrand_transform():
def poisson(lam=1.0, size=None): return uninferable
def power(a, size=None): return uninferable
def rand(*args): return uninferable
- def randint(low, high=None, size=None, dtype='l'):
+ def randint(low, high=None, size=None, dtype='l'):
import numpy
return numpy.ndarray((1,1))
def randn(*args): return uninferable
diff --git a/astroid/brain/brain_numpy_utils.py b/astroid/brain/brain_numpy_utils.py
index 2bad01ee..b29d2719 100644
--- a/astroid/brain/brain_numpy_utils.py
+++ b/astroid/brain/brain_numpy_utils.py
@@ -1,4 +1,5 @@
-# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019-2020 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 Claudiu Popa <pcmanticore@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -48,9 +49,17 @@ def looks_like_numpy_member(
:param node: node to test
:return: True if the node is a member of numpy
"""
- return (
+ if (
isinstance(node, astroid.Attribute)
and node.attrname == member_name
and isinstance(node.expr, astroid.Name)
and _is_a_numpy_module(node.expr)
- )
+ ):
+ return True
+ if (
+ isinstance(node, astroid.Name)
+ and node.name == member_name
+ and node.root().name.startswith("numpy")
+ ):
+ return True
+ return False
diff --git a/astroid/brain/brain_pytest.py b/astroid/brain/brain_pytest.py
index d7e3ac8a..56202ab8 100644
--- a/astroid/brain/brain_pytest.py
+++ b/astroid/brain/brain_pytest.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2014-2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Jeff Quast <contact@jeffquast.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2016 Florian Bruhin <me@the-compiler.org>
diff --git a/astroid/brain/brain_qt.py b/astroid/brain/brain_qt.py
index 8679d140..b703b373 100644
--- a/astroid/brain/brain_qt.py
+++ b/astroid/brain/brain_qt.py
@@ -1,7 +1,8 @@
-# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2017 Roy Wright <roy@wright.org>
# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Antoine Boellinger <aboellinger@hotmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_scipy_signal.py b/astroid/brain/brain_scipy_signal.py
index 3da59737..996300d4 100755
--- a/astroid/brain/brain_scipy_signal.py
+++ b/astroid/brain/brain_scipy_signal.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2019 European Synchrotron Radiation Facility
+# Copyright (c) 2019 Valentin Valls <valentin.valls@esrf.fr>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_six.py b/astroid/brain/brain_six.py
index b342fbf5..46d9fa32 100644
--- a/astroid/brain/brain_six.py
+++ b/astroid/brain/brain_six.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
@@ -50,7 +50,8 @@ input = input
from sys import intern
map = map
range = range
-from imp import reload as reload_module
+from importlib import reload
+reload_module = lambda module: reload(module)
from functools import reduce
from shlex import quote as shlex_quote
from io import StringIO
diff --git a/astroid/brain/brain_sqlalchemy.py b/astroid/brain/brain_sqlalchemy.py
new file mode 100644
index 00000000..c80eee1e
--- /dev/null
+++ b/astroid/brain/brain_sqlalchemy.py
@@ -0,0 +1,35 @@
+import astroid
+
+
+def _session_transform():
+ return astroid.parse(
+ """
+ from sqlalchemy.orm.session import Session
+
+ class sessionmaker:
+ def __init__(
+ self,
+ bind=None,
+ class_=Session,
+ autoflush=True,
+ autocommit=False,
+ expire_on_commit=True,
+ info=None,
+ **kw
+ ):
+ return
+
+ def __call__(self, **local_kw):
+ return Session()
+
+ def configure(self, **new_kw):
+ return
+
+ return Session()
+ """
+ )
+
+
+astroid.register_module_extender(
+ astroid.MANAGER, "sqlalchemy.orm.session", _session_transform
+)
diff --git a/astroid/brain/brain_ssl.py b/astroid/brain/brain_ssl.py
index 893d8a2f..2ae21c35 100644
--- a/astroid/brain/brain_ssl.py
+++ b/astroid/brain/brain_ssl.py
@@ -1,5 +1,6 @@
-# Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
+# Copyright (c) 2019 Benjamin Elven <25181435+S3ntinelX@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_subprocess.py b/astroid/brain/brain_subprocess.py
index 72f4b461..2769a039 100644
--- a/astroid/brain/brain_subprocess.py
+++ b/astroid/brain/brain_subprocess.py
@@ -1,6 +1,8 @@
-# Copyright (c) 2016-2017 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2016-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
+# Copyright (c) 2018 Peter Talley <peterctalley@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -74,6 +76,12 @@ def _subprocess_transform():
restore_signals=True,
preexec_fn=None,
pass_fds=(),
+ input=None,
+ bufsize=0,
+ executable=None,
+ close_fds=False,
+ startupinfo=None,
+ creationflags=0,
start_new_session=False
):
""".strip()
@@ -93,6 +101,12 @@ def _subprocess_transform():
restore_signals=True,
preexec_fn=None,
pass_fds=(),
+ input=None,
+ bufsize=0,
+ executable=None,
+ close_fds=False,
+ startupinfo=None,
+ creationflags=0,
start_new_session=False
):
""".strip()
diff --git a/astroid/brain/brain_threading.py b/astroid/brain/brain_threading.py
index db32ae79..ba3085b5 100644
--- a/astroid/brain/brain_threading.py
+++ b/astroid/brain/brain_threading.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
diff --git a/astroid/brain/brain_uuid.py b/astroid/brain/brain_uuid.py
index 8bda631d..5a33fc25 100644
--- a/astroid/brain/brain_uuid.py
+++ b/astroid/brain/brain_uuid.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2017-2018 Claudiu Popa <pcmanticore@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/builder.py b/astroid/builder.py
index 7f30aaeb..142764b1 100644
--- a/astroid/builder.py
+++ b/astroid/builder.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2013 Phil Schaf <flying-sheep@web.de>
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014-2015 Google, Inc.
# Copyright (c) 2014 Alexander Presnyakov <flagist0@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
@@ -22,7 +22,7 @@ import os
import textwrap
from tokenize import detect_encoding
-from astroid._ast import _parse
+from astroid._ast import get_parser_module
from astroid import bases
from astroid import exceptions
from astroid import manager
@@ -42,7 +42,7 @@ _TRANSIENT_FUNCTION = "__"
# The comment used to select a statement to be extracted
# when calling extract_node.
_STATEMENT_SELECTOR = "#@"
-
+MISPLACED_TYPE_ANNOTATION_ERROR = "misplaced type annotation"
MANAGER = manager.AstroidManager()
@@ -77,7 +77,7 @@ class AstroidBuilder(raw_building.InspectBuilder):
# pylint: disable=redefined-outer-name
def __init__(self, manager=None, apply_transforms=True):
- super(AstroidBuilder, self).__init__()
+ super().__init__()
self._manager = manager or MANAGER
self._apply_transforms = apply_transforms
@@ -165,7 +165,7 @@ class AstroidBuilder(raw_building.InspectBuilder):
def _data_build(self, data, modname, path):
"""Build tree node from data and add some informations"""
try:
- node = _parse_string(data)
+ node, parser_module = _parse_string(data, type_comments=True)
except (TypeError, ValueError, SyntaxError) as exc:
raise exceptions.AstroidSyntaxError(
"Parsing Python code failed:\n{error}",
@@ -174,6 +174,7 @@ class AstroidBuilder(raw_building.InspectBuilder):
path=path,
error=exc,
) from exc
+
if path is not None:
node_file = os.path.abspath(path)
else:
@@ -186,7 +187,7 @@ class AstroidBuilder(raw_building.InspectBuilder):
path is not None
and os.path.splitext(os.path.basename(path))[0] == "__init__"
)
- builder = rebuilder.TreeRebuilder(self._manager)
+ builder = rebuilder.TreeRebuilder(self._manager, parser_module)
module = builder.visit_module(node, modname, node_file, package)
module._import_from_nodes = builder._import_from_nodes
module._delayed_assattr = builder._delayed_assattr
@@ -438,17 +439,17 @@ def extract_node(code, module_name=""):
return extracted
-MISPLACED_TYPE_ANNOTATION_ERROR = "misplaced type annotation"
-
-
def _parse_string(data, type_comments=True):
+ parser_module = get_parser_module(type_comments=type_comments)
try:
- node = _parse(data + "\n", type_comments=type_comments)
+ parsed = parser_module.parse(data + "\n", type_comments=type_comments)
except SyntaxError as exc:
# If the type annotations are misplaced for some reason, we do not want
# to fail the entire parsing of the file, so we need to retry the parsing without
# type comment support.
if exc.args[0] != MISPLACED_TYPE_ANNOTATION_ERROR or not type_comments:
raise
- node = _parse(data + "\n", type_comments=False)
- return node
+
+ parser_module = get_parser_module(type_comments=False)
+ parsed = parser_module.parse(data + "\n", type_comments=False)
+ return parsed, parser_module
diff --git a/astroid/context.py b/astroid/context.py
index 113cac6b..82a80a06 100644
--- a/astroid/context.py
+++ b/astroid/context.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
diff --git a/astroid/decorators.py b/astroid/decorators.py
index 14487570..0f3632c4 100644
--- a/astroid/decorators.py
+++ b/astroid/decorators.py
@@ -3,6 +3,7 @@
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2018 Tomas Gavenciak <gavento@ucw.cz>
# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
diff --git a/astroid/exceptions.py b/astroid/exceptions.py
index 7e9d655e..08e72c13 100644
--- a/astroid/exceptions.py
+++ b/astroid/exceptions.py
@@ -28,7 +28,7 @@ class AstroidError(Exception):
"""
def __init__(self, message="", **kws):
- super(AstroidError, self).__init__(message)
+ super().__init__(message)
self.message = message
for key, value in kws.items():
setattr(self, key, value)
@@ -46,7 +46,7 @@ class AstroidBuildingError(AstroidError):
"""
def __init__(self, message="Failed to import module {modname}.", **kws):
- super(AstroidBuildingError, self).__init__(message, **kws)
+ super().__init__(message, **kws)
class AstroidImportError(AstroidBuildingError):
@@ -69,7 +69,7 @@ class TooManyLevelsError(AstroidImportError):
message="Relative import with too many levels " "({level}) for module {name!r}",
**kws
):
- super(TooManyLevelsError, self).__init__(message, **kws)
+ super().__init__(message, **kws)
class AstroidSyntaxError(AstroidBuildingError):
@@ -89,7 +89,7 @@ class NoDefault(AstroidError):
name = None
def __init__(self, message="{func!r} has no default for {name!r}.", **kws):
- super(NoDefault, self).__init__(message, **kws)
+ super().__init__(message, **kws)
class ResolveError(AstroidError):
@@ -157,7 +157,7 @@ class InferenceError(ResolveError):
context = None
def __init__(self, message="Inference failed for {node!r}.", **kws):
- super(InferenceError, self).__init__(message, **kws)
+ super().__init__(message, **kws)
# Why does this inherit from InferenceError rather than ResolveError?
@@ -175,7 +175,7 @@ class NameInferenceError(InferenceError):
scope = None
def __init__(self, message="{name!r} not found in {scope!r}.", **kws):
- super(NameInferenceError, self).__init__(message, **kws)
+ super().__init__(message, **kws)
class AttributeInferenceError(ResolveError):
@@ -191,7 +191,7 @@ class AttributeInferenceError(ResolveError):
attribute = None
def __init__(self, message="{attribute!r} not found on {target!r}.", **kws):
- super(AttributeInferenceError, self).__init__(message, **kws)
+ super().__init__(message, **kws)
class UseInferenceDefault(Exception):
diff --git a/astroid/helpers.py b/astroid/helpers.py
index be133b38..8ab68799 100644
--- a/astroid/helpers.py
+++ b/astroid/helpers.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
@@ -190,9 +190,9 @@ def _type_check(type1, type2):
return False
try:
return type1 in type2.mro()[:-1]
- except exceptions.MroError:
+ except exceptions.MroError as e:
# The MRO is invalid.
- raise exceptions._NonDeducibleTypeHierarchy
+ raise exceptions._NonDeducibleTypeHierarchy from e
def is_subtype(type1, type2):
@@ -254,13 +254,17 @@ def object_len(node, context=None):
return len(inferred_node.elts)
if isinstance(inferred_node, nodes.Dict):
return len(inferred_node.items)
+
+ node_type = object_type(inferred_node, context=context)
+ if not node_type:
+ raise exceptions.InferenceError(node=node)
+
try:
- node_type = object_type(inferred_node, context=context)
len_call = next(node_type.igetattr("__len__", context=context))
- except exceptions.AttributeInferenceError:
+ except exceptions.AttributeInferenceError as e:
raise exceptions.AstroidTypeError(
- "object of type '{}' has no len()".format(len_call.pytype())
- )
+ "object of type '{}' has no len()".format(node_type.pytype())
+ ) from e
result_of_len = next(len_call.infer_call_result(node, context))
if (
@@ -268,6 +272,11 @@ def object_len(node, context=None):
and result_of_len.pytype() == "builtins.int"
):
return result_of_len.value
+ if isinstance(result_of_len, bases.Instance) and result_of_len.is_subtype_of(
+ "builtins.int"
+ ):
+ # Fake a result as we don't know the arguments of the instance call.
+ return 0
raise exceptions.AstroidTypeError(
"'{}' object cannot be interpreted as an integer".format(result_of_len)
)
diff --git a/astroid/inference.py b/astroid/inference.py
index a563287d..bc3e1f97 100644
--- a/astroid/inference.py
+++ b/astroid/inference.py
@@ -2,7 +2,7 @@
# Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru>
@@ -10,10 +10,13 @@
# Copyright (c) 2017 Michał Masłowski <m.maslowski@clearcode.cc>
# Copyright (c) 2017 Calen Pennington <cale@edx.org>
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
+# Copyright (c) 2018-2019 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2018 Daniel Martin <daniel.martin@crowdstrike.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
+# Copyright (c) 2020 Leandro T. C. Melo <ltcmelo@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -25,6 +28,7 @@ import functools
import itertools
import operator
+import wrapt
from astroid import bases
from astroid import context as contextmod
from astroid import exceptions
@@ -307,6 +311,8 @@ def infer_attribute(self, context=None):
except exceptions._NonDeducibleTypeHierarchy:
# Can't determine anything useful.
pass
+ elif not context:
+ context = contextmod.InferenceContext()
try:
context.boundnode = owner
@@ -348,7 +354,6 @@ nodes.Global._infer = infer_global
_SUBSCRIPT_SENTINEL = object()
-@decorators.raise_if_nothing_inferred
def infer_subscript(self, context=None):
"""Inference for subscripts
@@ -405,8 +410,10 @@ def infer_subscript(self, context=None):
return None
-nodes.Subscript._infer = decorators.path_wrapper(infer_subscript)
-nodes.Subscript.infer_lhs = infer_subscript
+nodes.Subscript._infer = decorators.raise_if_nothing_inferred(
+ decorators.path_wrapper(infer_subscript)
+)
+nodes.Subscript.infer_lhs = decorators.raise_if_nothing_inferred(infer_subscript)
@decorators.raise_if_nothing_inferred
@@ -837,9 +844,8 @@ def infer_assign(self, context=None):
"""infer a AssignName/AssignAttr: need to inspect the RHS part of the
assign node
"""
- stmt = self.statement()
- if isinstance(stmt, nodes.AugAssign):
- return stmt.infer(context)
+ if isinstance(self.parent, nodes.AugAssign):
+ return self.parent.infer(context)
stmts = list(self.assigned_stmts(context=context))
return bases._infer_stmts(stmts, context)
@@ -947,10 +953,30 @@ def infer_ifexp(self, context=None):
nodes.IfExp._infer = infer_ifexp
+# pylint: disable=dangerous-default-value
+@wrapt.decorator
+def _cached_generator(func, instance, args, kwargs, _cache={}):
+ node = args[0]
+ try:
+ return iter(_cache[func, id(node)])
+ except KeyError:
+ result = func(*args, **kwargs)
+ # Need to keep an iterator around
+ original, copy = itertools.tee(result)
+ _cache[func, id(node)] = list(copy)
+ return original
+
+
+# When inferring a property, we instantiate a new `objects.Property` object,
+# which in turn, because it inherits from `FunctionDef`, sets itself in the locals
+# of the wrapping frame. This means that everytime we infer a property, the locals
+# are mutated with a new instance of the property. This is why we cache the result
+# of the function's inference.
+@_cached_generator
def infer_functiondef(self, context=None):
if not self.decorators or not bases._is_property(self):
yield self
- return
+ return dict(node=self, context=context)
prop_func = objects.Property(
function=self,
@@ -962,6 +988,7 @@ def infer_functiondef(self, context=None):
)
prop_func.postinit(body=[], args=self.args)
yield prop_func
+ return dict(node=self, context=context)
nodes.FunctionDef._infer = infer_functiondef
diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py
index 84e093bb..3cf5fea5 100644
--- a/astroid/interpreter/_import/spec.py
+++ b/astroid/interpreter/_import/spec.py
@@ -5,6 +5,8 @@
# Copyright (c) 2017 ioanatia <ioanatia@users.noreply.github.com>
# Copyright (c) 2017 Calen Pennington <cale@edx.org>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
import abc
import collections
@@ -182,7 +184,7 @@ class ZipFinder(Finder):
"""Finder that knows how to find a module inside zip files."""
def __init__(self, path):
- super(ZipFinder, self).__init__(path)
+ super().__init__(path)
self._zipimporters = _precache_zipimporters(path)
def find_module(self, modname, module_parts, processed, submodule_path):
diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py
index 57d9da09..10c659f6 100644
--- a/astroid/interpreter/objectmodel.py
+++ b/astroid/interpreter/objectmodel.py
@@ -1,8 +1,10 @@
-# Copyright (c) 2016-2018 Claudiu Popa <pcmanticore@gmail.com>
+# -*- coding: utf-8 -*-
+# Copyright (c) 2016-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
# Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2017 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2017 Calen Pennington <cale@edx.org>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -657,9 +659,16 @@ class ImportErrorInstanceModel(ExceptionInstanceModel):
return node_classes.Const("")
+class UnicodeDecodeErrorInstanceModel(ExceptionInstanceModel):
+ @property
+ def attr_object(self):
+ return node_classes.Const("")
+
+
BUILTIN_EXCEPTIONS = {
"builtins.SyntaxError": SyntaxErrorInstanceModel,
"builtins.ImportError": ImportErrorInstanceModel,
+ "builtins.UnicodeDecodeError": UnicodeDecodeErrorInstanceModel,
# These are all similar to OSError in terms of attributes
"builtins.OSError": OSErrorInstanceModel,
"builtins.BlockingIOError": OSErrorInstanceModel,
@@ -741,6 +750,27 @@ class PropertyModel(ObjectModel):
"""Model for a builtin property"""
# pylint: disable=import-outside-toplevel
+ def _init_function(self, name):
+ from astroid.node_classes import Arguments
+ from astroid.scoped_nodes import FunctionDef
+
+ args = Arguments()
+ args.postinit(
+ args=[],
+ defaults=[],
+ kwonlyargs=[],
+ kw_defaults=[],
+ annotations=[],
+ posonlyargs=[],
+ posonlyargs_annotations=[],
+ kwonlyargs_annotations=[],
+ )
+
+ function = FunctionDef(name=name, parent=self._instance)
+
+ function.postinit(args=args, body=[])
+ return function
+
@property
def attr_fget(self):
from astroid.scoped_nodes import FunctionDef
@@ -765,20 +795,14 @@ class PropertyModel(ObjectModel):
@property
def attr_setter(self):
- from astroid.scoped_nodes import FunctionDef
-
- return FunctionDef(name="setter", parent=self._instance)
+ return self._init_function("setter")
@property
def attr_deleter(self):
- from astroid.scoped_nodes import FunctionDef
-
- return FunctionDef(name="deleter", parent=self._instance)
+ return self._init_function("deleter")
@property
def attr_getter(self):
- from astroid.scoped_nodes import FunctionDef
-
- return FunctionDef(name="getter", parent=self._instance)
+ return self._init_function("getter")
# pylint: enable=import-outside-toplevel
diff --git a/astroid/manager.py b/astroid/manager.py
index 4dad678a..82208adf 100644
--- a/astroid/manager.py
+++ b/astroid/manager.py
@@ -1,5 +1,5 @@
# Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 BioGeek <jeroen.vangoey@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
@@ -8,6 +8,9 @@
# Copyright (c) 2017 Iva Miholic <ivamiho@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2019 Raphael Gaschignard <raphael@makeleaps.com>
+# Copyright (c) 2020 Anubhav <35621759+anubh-v@users.noreply.github.com>
+# Copyright (c) 2020 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -99,6 +102,13 @@ class AstroidManager:
"Unable to build an AST for {path}.", path=filepath
)
+ def ast_from_string(self, data, modname="", filepath=None):
+ """ Given some source code as a string, return its corresponding astroid object"""
+ # pylint: disable=import-outside-toplevel; circular import
+ from astroid.builder import AstroidBuilder
+
+ return AstroidBuilder(self).string_build(data, modname, filepath)
+
def _build_stub_module(self, modname):
# pylint: disable=import-outside-toplevel; circular import
from astroid.builder import AstroidBuilder
@@ -168,6 +178,8 @@ class AstroidManager:
return self._build_namespace_module(
modname, found_spec.submodule_search_locations
)
+ elif found_spec.type == spec.ModuleType.PY_FROZEN:
+ return self._build_stub_module(modname)
if found_spec.location is None:
raise exceptions.AstroidImportError(
diff --git a/astroid/modutils.py b/astroid/modutils.py
index 0c009b13..4e6ed86b 100644
--- a/astroid/modutils.py
+++ b/astroid/modutils.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2014 Denis Laxalde <denis.laxalde@logilab.fr>
# Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
@@ -9,9 +9,13 @@
# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Mario Corchero <mcorcherojim@bloomberg.net>
# Copyright (c) 2018 Mario Corchero <mariocj89@gmail.com>
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
+# Copyright (c) 2019 markmcclain <markmcclain@users.noreply.github.com>
+# Copyright (c) 2019 BasPH <BasPH@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -40,6 +44,7 @@ from distutils.errors import DistutilsPlatformError
# distutils is replaced by virtualenv with a module that does
# weird path manipulations in order to get to the
# real distutils module.
+from typing import Optional, List
from .interpreter._import import spec
from .interpreter._import import util
@@ -198,16 +203,15 @@ def load_module_from_name(dotted_name, path=None, use_sys=True):
return load_module_from_modpath(dotted_name.split("."), path, use_sys)
-def load_module_from_modpath(parts, path=None, use_sys=1):
+def load_module_from_modpath(parts, path: Optional[List[str]] = None, use_sys=1):
"""Load a python module from its split name.
:type parts: list(str) or tuple(str)
:param parts:
python name of a module or package split on '.'
- :type path: list or None
:param path:
- optional list of path where the module or package should be
+ Optional list of path where the module or package should be
searched (use sys.path if nothing or None is given)
:type use_sys: bool
@@ -254,15 +258,16 @@ def load_module_from_modpath(parts, path=None, use_sys=1):
return module
-def load_module_from_file(filepath, path=None, use_sys=True, extrapath=None):
+def load_module_from_file(
+ filepath: str, path: Optional[List[str]] = None, use_sys=True
+):
"""Load a Python module from it's path.
:type filepath: str
:param filepath: path to the python module or package
- :type path: list or None
- :param path:
- optional list of path where the module or package should be
+ :param Optional[List[str]] path:
+ Optional list of path where the module or package should be
searched (use sys.path if nothing or None is given)
:type use_sys: bool
@@ -270,13 +275,12 @@ def load_module_from_file(filepath, path=None, use_sys=True, extrapath=None):
boolean indicating whether the sys.modules dictionary should be
used or not
-
:raise ImportError: if the module or package is not found
:rtype: module
:return: the loaded module
"""
- modpath = modpath_from_file(filepath, extrapath)
+ modpath = modpath_from_file(filepath)
return load_module_from_modpath(modpath, path, use_sys)
@@ -327,28 +331,18 @@ def _get_relative_base_path(filename, path_to_check):
return None
-def modpath_from_file_with_callback(filename, extrapath=None, is_package_cb=None):
+def modpath_from_file_with_callback(filename, path=None, is_package_cb=None):
filename = os.path.expanduser(_path_from_filename(filename))
-
- if extrapath is not None:
- for path_ in itertools.chain(map(_canonicalize_path, extrapath), extrapath):
- path = os.path.abspath(path_)
- if not path:
- continue
- submodpath = _get_relative_base_path(filename, path)
- if not submodpath:
- continue
- if is_package_cb(path, submodpath[:-1]):
- return extrapath[path_].split(".") + submodpath
-
- for path in itertools.chain(map(_canonicalize_path, sys.path), sys.path):
- path = _cache_normalize_path(path)
- if not path:
+ for pathname in itertools.chain(
+ path or [], map(_canonicalize_path, sys.path), sys.path
+ ):
+ pathname = _cache_normalize_path(pathname)
+ if not pathname:
continue
- modpath = _get_relative_base_path(filename, path)
+ modpath = _get_relative_base_path(filename, pathname)
if not modpath:
continue
- if is_package_cb(path, modpath[:-1]):
+ if is_package_cb(pathname, modpath[:-1]):
return modpath
raise ImportError(
@@ -356,19 +350,17 @@ def modpath_from_file_with_callback(filename, extrapath=None, is_package_cb=None
)
-def modpath_from_file(filename, extrapath=None):
- """given a file path return the corresponding split module's name
- (i.e name of a module or package split on '.')
+def modpath_from_file(filename, path=None):
+ """Get the corresponding split module's name from a filename
+
+ This function will return the name of a module or package split on `.`.
:type filename: str
:param filename: file's path for which we want the module's name
- :type extrapath: dict
- :param extrapath:
- optional extra search path, with path as key and package name for the path
- as value. This is usually useful to handle package split in multiple
- directories using __path__ trick.
-
+ :type Optional[List[str]] path:
+ Optional list of path where the module or package should be
+ searched (use sys.path if nothing or None is given)
:raise ImportError:
if the corresponding module's name has not been found
@@ -376,7 +368,7 @@ def modpath_from_file(filename, extrapath=None):
:rtype: list(str)
:return: the corresponding split module's name
"""
- return modpath_from_file_with_callback(filename, extrapath, check_modpath_has_init)
+ return modpath_from_file_with_callback(filename, path, check_modpath_has_init)
def file_from_modpath(modpath, path=None, context_file=None):
diff --git a/astroid/node_classes.py b/astroid/node_classes.py
index b1344723..59b1e31c 100644
--- a/astroid/node_classes.py
+++ b/astroid/node_classes.py
@@ -3,7 +3,7 @@
# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
@@ -11,13 +11,17 @@
# Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
# Copyright (c) 2016 Dave Baum <dbaum@google.com>
-# Copyright (c) 2017-2018 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
+# Copyright (c) 2017-2020 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017 rr- <rr-@sakuya.pl>
+# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
+# Copyright (c) 2019 kavins14 <kavin.singh@mail.utoronto.ca>
+# Copyright (c) 2019 kavins14 <kavinsingh@hotmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -897,7 +901,7 @@ class NodeNG:
_repr_tree(self, result, set())
return "".join(result)
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
The boolean value of a node can have three
@@ -986,7 +990,7 @@ class _BaseContainer(
:type: list(NodeNG)
"""
- super(_BaseContainer, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, elts):
"""Do some setup after initialisation.
@@ -1021,7 +1025,7 @@ class _BaseContainer(
"""
return self.elts
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -1145,10 +1149,9 @@ class LookupMixIn:
_stmts = []
_stmt_parents = []
statements = self._get_filtered_node_statements(stmts)
-
for node, stmt in statements:
# line filtering is on and we have reached our location, break
- if stmt.fromlineno > mylineno > 0:
+ if stmt.fromlineno and stmt.fromlineno > mylineno > 0:
break
# Ignore decorators with the same name as the
# decorated function
@@ -1282,7 +1285,7 @@ class AssignName(
:type: str or None
"""
- super(AssignName, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
class DelName(
@@ -1322,7 +1325,7 @@ class DelName(
:type: str or None
"""
- super(DelName, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
class Name(mixins.NoChildrenMixin, LookupMixIn, NodeNG):
@@ -1363,7 +1366,7 @@ class Name(mixins.NoChildrenMixin, LookupMixIn, NodeNG):
:type: str or None
"""
- super(Name, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def _get_name_nodes(self):
yield self
@@ -1395,18 +1398,21 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
# - we expose 'annotation', a list with annotations for
# for each normal argument. If an argument doesn't have an
# annotation, its value will be None.
-
+ # pylint: disable=too-many-instance-attributes
_astroid_fields = (
"args",
"defaults",
"kwonlyargs",
"posonlyargs",
+ "posonlyargs_annotations",
"kw_defaults",
"annotations",
"varargannotation",
"kwargannotation",
"kwonlyargs_annotations",
"type_comment_args",
+ "type_comment_kwonlyargs",
+ "type_comment_posonlyargs",
)
varargannotation = None
"""The type annotation for the variable length arguments.
@@ -1432,7 +1438,7 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
:param parent: The parent node in the syntax tree.
:type parent: NodeNG or None
"""
- super(Arguments, self).__init__(parent=parent)
+ super().__init__(parent=parent)
self.vararg = vararg
"""The name of the variable length arguments.
@@ -1502,6 +1508,24 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
:type: list(NodeNG or None)
"""
+ self.type_comment_kwonlyargs = []
+ """The type annotation, passed by a type comment, of each keyword only argument.
+
+ If an argument does not have a type comment,
+ the value for that argument will be None.
+
+ :type: list(NodeNG or None)
+ """
+
+ self.type_comment_posonlyargs = []
+ """The type annotation, passed by a type comment, of each positional argument.
+
+ If an argument does not have a type comment,
+ the value for that argument will be None.
+
+ :type: list(NodeNG or None)
+ """
+
# pylint: disable=too-many-arguments
def postinit(
self,
@@ -1516,6 +1540,8 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
varargannotation=None,
kwargannotation=None,
type_comment_args=None,
+ type_comment_kwonlyargs=None,
+ type_comment_posonlyargs=None,
):
"""Do some setup after initialisation.
@@ -1563,6 +1589,14 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
:param type_comment_args: The type annotation,
passed by a type comment, of each argument.
:type type_comment_args: list(NodeNG or None)
+
+ :param type_comment_args: The type annotation,
+ passed by a type comment, of each keyword only argument.
+ :type type_comment_args: list(NodeNG or None)
+
+ :param type_comment_args: The type annotation,
+ passed by a type comment, of each positional argument.
+ :type type_comment_args: list(NodeNG or None)
"""
self.args = args
self.defaults = defaults
@@ -1575,6 +1609,8 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
self.varargannotation = varargannotation
self.kwargannotation = kwargannotation
self.type_comment_args = type_comment_args
+ self.type_comment_kwonlyargs = type_comment_kwonlyargs
+ self.type_comment_posonlyargs = type_comment_posonlyargs
# pylint: disable=too-many-arguments
@@ -1589,7 +1625,7 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
:type: int or None
"""
- lineno = super(Arguments, self).fromlineno
+ lineno = super().fromlineno
return max(lineno, self.parent.fromlineno or 0)
@decorators.cachedproperty
@@ -1612,7 +1648,13 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
positional_only_defaults = self.defaults[: len(self.defaults) - len(args)]
if self.posonlyargs:
- result.append(_format_args(self.posonlyargs, positional_only_defaults))
+ result.append(
+ _format_args(
+ self.posonlyargs,
+ positional_only_defaults,
+ self.posonlyargs_annotations,
+ )
+ )
result.append("/")
if self.args:
result.append(
@@ -1695,6 +1737,11 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
def get_children(self):
yield from self.posonlyargs or ()
+
+ for elt in self.posonlyargs_annotations:
+ if elt is not None:
+ yield elt
+
yield from self.args or ()
yield from self.defaults
@@ -1798,7 +1845,7 @@ class AssignAttr(mixins.ParentAssignTypeMixin, NodeNG):
:type: str or None
"""
- super(AssignAttr, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, expr=None):
"""Do some setup after initialisation.
@@ -2017,7 +2064,7 @@ class AugAssign(mixins.AssignTypeMixin, Statement):
:type: str or None
"""
- super(AugAssign, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, target=None, value=None):
"""Do some setup after initialisation.
@@ -2130,7 +2177,7 @@ class BinOp(NodeNG):
:type: str or None
"""
- super(BinOp, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, left=None, right=None):
"""Do some setup after initialisation.
@@ -2218,7 +2265,7 @@ class BoolOp(NodeNG):
:type: str or None
"""
- super(BoolOp, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, values=None):
"""Do some setup after initialisation.
@@ -2416,7 +2463,7 @@ class Comprehension(NodeNG):
:param parent: The parent node in the syntax tree.
:type parent: NodeNG or None
"""
- super(Comprehension, self).__init__()
+ super().__init__()
self.parent = parent
# pylint: disable=redefined-builtin; same name as builtin ast module.
@@ -2513,7 +2560,7 @@ class Const(mixins.NoChildrenMixin, NodeNG, bases.Instance):
:type: object
"""
- super(Const, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def __getattr__(self, name):
# This is needed because of Proxy's __getattr__ method.
@@ -2592,7 +2639,7 @@ class Const(mixins.NoChildrenMixin, NodeNG, bases.Instance):
"""
return self._proxied.qname()
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -2694,7 +2741,7 @@ class DelAttr(mixins.ParentAssignTypeMixin, NodeNG):
:type: str or None
"""
- super(DelAttr, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, expr=None):
"""Do some setup after initialisation.
@@ -2767,7 +2814,7 @@ class Dict(NodeNG, bases.Instance):
:type: list(tuple(NodeNG, NodeNG))
"""
- super(Dict, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, items):
"""Do some setup after initialisation.
@@ -2865,7 +2912,7 @@ class Dict(NodeNG, bases.Instance):
raise exceptions.AstroidIndexError(index)
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -2920,7 +2967,7 @@ class Ellipsis(mixins.NoChildrenMixin, NodeNG): # pylint: disable=redefined-bui
<Ellipsis l.1 at 0x7f23b2e35160>
"""
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -3299,7 +3346,7 @@ class ImportFrom(mixins.NoChildrenMixin, mixins.ImportFromMixin, Statement):
:type: int
"""
- super(ImportFrom, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
class Attribute(NodeNG):
@@ -3334,7 +3381,7 @@ class Attribute(NodeNG):
:type: str or None
"""
- super(Attribute, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, expr=None):
"""Do some setup after initialisation.
@@ -3379,7 +3426,7 @@ class Global(mixins.NoChildrenMixin, Statement):
:type: list(str)
"""
- super(Global, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def _infer_name(self, frame, name):
return name
@@ -3460,6 +3507,11 @@ class If(mixins.MultiLineBlockMixin, mixins.BlockRangeMixIn, Statement):
def has_elif_block(self):
return len(self.orelse) == 1 and isinstance(self.orelse[0], If)
+ def _get_yield_nodes_skip_lambdas(self):
+ """An If node can contain a Yield node in the test"""
+ yield from self.test._get_yield_nodes_skip_lambdas()
+ yield from super()._get_yield_nodes_skip_lambdas()
+
class IfExp(NodeNG):
"""Class representing an :class:`ast.IfExp` node.
@@ -3547,7 +3599,7 @@ class Import(mixins.NoChildrenMixin, mixins.ImportFromMixin, Statement):
:type: list(tuple(str, str or None)) or None
"""
- super(Import, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
class Index(NodeNG):
@@ -3620,7 +3672,7 @@ class Keyword(NodeNG):
:type: Name or None
"""
- super(Keyword, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, value=None):
"""Do some setup after initialisation.
@@ -3665,7 +3717,7 @@ class List(_BaseContainer):
:type: Context or None
"""
- super(List, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def pytype(self):
"""Get the name of the type that this node represents.
@@ -3720,7 +3772,7 @@ class Nonlocal(mixins.NoChildrenMixin, Statement):
:type: list(str)
"""
- super(Nonlocal, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def _infer_name(self, frame, name):
return name
@@ -3776,7 +3828,7 @@ class Print(Statement):
:type: bool or None
"""
- super(Print, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, dest=None, values=None):
"""Do some setup after initialisation.
@@ -4029,9 +4081,7 @@ class Starred(mixins.ParentAssignTypeMixin, NodeNG):
:type: Context or None
"""
- super(Starred, self).__init__(
- lineno=lineno, col_offset=col_offset, parent=parent
- )
+ super().__init__(lineno=lineno, col_offset=col_offset, parent=parent)
def postinit(self, value=None):
"""Do some setup after initialisation.
@@ -4087,9 +4137,7 @@ class Subscript(NodeNG):
:type: Context or None
"""
- super(Subscript, self).__init__(
- lineno=lineno, col_offset=col_offset, parent=parent
- )
+ super().__init__(lineno=lineno, col_offset=col_offset, parent=parent)
# pylint: disable=redefined-builtin; had to use the same name as builtin ast module.
def postinit(self, value=None, slice=None):
@@ -4282,7 +4330,7 @@ class Tuple(_BaseContainer):
:type: Context or None
"""
- super(Tuple, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def pytype(self):
"""Get the name of the type that this node represents.
@@ -4338,7 +4386,7 @@ class UnaryOp(NodeNG):
:type: str or None
"""
- super(UnaryOp, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, operand=None):
"""Do some setup after initialisation.
@@ -4452,6 +4500,11 @@ class While(mixins.MultiLineBlockMixin, mixins.BlockRangeMixIn, Statement):
yield from self.body
yield from self.orelse
+ def _get_yield_nodes_skip_lambdas(self):
+ """A While node can contain a Yield node in the test"""
+ yield from self.test._get_yield_nodes_skip_lambdas()
+ yield from super()._get_yield_nodes_skip_lambdas()
+
class With(
mixins.MultiLineBlockMixin,
@@ -4694,6 +4747,42 @@ class Unknown(mixins.AssignTypeMixin, NodeNG):
yield util.Uninferable
+class EvaluatedObject(NodeNG):
+ """Contains an object that has already been inferred
+
+ This class is useful to pre-evaluate a particular node,
+ with the resulting class acting as the non-evaluated node.
+ """
+
+ name = "EvaluatedObject"
+ _astroid_fields = ("original",)
+ _other_fields = ("value",)
+
+ original = None
+ """The original node that has already been evaluated
+
+ :type: NodeNG
+ """
+
+ value = None
+ """The inferred value
+
+ :type: Union[Uninferable, NodeNG]
+ """
+
+ def __init__(self, original, value):
+ self.original = original
+ self.value = value
+ super().__init__(
+ lineno=self.original.lineno,
+ col_offset=self.original.col_offset,
+ parent=self.original.parent,
+ )
+
+ def infer(self, context=None, **kwargs):
+ yield self.value
+
+
# constants ##############################################################
CONST_CLS = {
diff --git a/astroid/nodes.py b/astroid/nodes.py
index bf6911ac..4ce4ebe2 100644
--- a/astroid/nodes.py
+++ b/astroid/nodes.py
@@ -1,6 +1,6 @@
# Copyright (c) 2006-2011, 2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com>
@@ -86,6 +86,7 @@ from astroid.node_classes import (
# Node not present in the builtin ast module.
DictUnpack,
Unknown,
+ EvaluatedObject,
)
from astroid.scoped_nodes import (
Module,
diff --git a/astroid/objects.py b/astroid/objects.py
index 93c5f406..fb782e6d 100644
--- a/astroid/objects.py
+++ b/astroid/objects.py
@@ -1,7 +1,8 @@
-# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
+# Copyright (c) 2018 hippo91 <guillaume.peillex@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
@@ -186,7 +187,12 @@ class Super(node_classes.NodeNG):
yield inferred
elif isinstance(inferred, Property):
function = inferred.function
- yield from function.infer_call_result(caller=self, context=context)
+ try:
+ yield from function.infer_call_result(
+ caller=self, context=context
+ )
+ except exceptions.InferenceError:
+ yield util.Uninferable
elif bases._is_property(inferred):
# TODO: support other descriptors as well.
try:
@@ -291,8 +297,8 @@ class Property(scoped_nodes.FunctionDef):
def __init__(
self, function, name=None, doc=None, lineno=None, col_offset=None, parent=None
):
- super().__init__(name, doc, lineno, col_offset, parent)
self.function = function
+ super().__init__(name, doc, lineno, col_offset, parent)
# pylint: disable=unnecessary-lambda
special_attributes = util.lazy_descriptor(lambda: objectmodel.PropertyModel())
diff --git a/astroid/protocols.py b/astroid/protocols.py
index 33d90ea3..2cdf5548 100644
--- a/astroid/protocols.py
+++ b/astroid/protocols.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
@@ -9,9 +9,11 @@
# Copyright (c) 2017-2018 Ashley Whetter <ashley@awhetter.co.uk>
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017 rr- <rr-@sakuya.pl>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
+# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -317,9 +319,14 @@ def _arguments_infer_argname(self, name, context):
if not (self.arguments or self.vararg or self.kwarg):
yield util.Uninferable
return
+
+ functype = self.parent.type
# first argument of instance/class method
- if self.arguments and getattr(self.arguments[0], "name", None) == name:
- functype = self.parent.type
+ if (
+ self.arguments
+ and getattr(self.arguments[0], "name", None) == name
+ and functype != "staticmethod"
+ ):
cls = self.parent.parent.scope()
is_metaclass = isinstance(cls, nodes.ClassDef) and cls.type == "metaclass"
# If this is a metaclass, then the first argument will always
@@ -367,7 +374,7 @@ def arguments_assigned_stmts(self, node=None, context=None, assign_path=None):
callcontext = context.callcontext
context = contextmod.copy_context(context)
context.callcontext = None
- args = arguments.CallSite(callcontext)
+ args = arguments.CallSite(callcontext, context=context)
return args.infer_argument(self.parent, node.name, context)
return _arguments_infer_argname(self, node.name, context)
diff --git a/astroid/raw_building.py b/astroid/raw_building.py
index d94f9240..b2612778 100644
--- a/astroid/raw_building.py
+++ b/astroid/raw_building.py
@@ -1,14 +1,17 @@
+# -*- coding: utf-8 -*-
# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
# Copyright (c) 2015 Ovidiu Sabou <ovidiu@sabou.org>
# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2020 Robin Jarry <robin.jarry@6wind.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -273,6 +276,13 @@ def _build_from_function(node, name, member, module):
object_build_function(node, member, name)
+def _safe_has_attribute(obj, member):
+ try:
+ return hasattr(obj, member)
+ except Exception: # pylint: disable=broad-except
+ return False
+
+
class InspectBuilder:
"""class for building nodes from living object
@@ -354,6 +364,11 @@ class InspectBuilder:
# This should be called for Jython, where some builtin
# methods aren't caught by isbuiltin branch.
_build_from_function(node, name, member, self._module)
+ elif _safe_has_attribute(member, "__all__"):
+ module = build_module(name)
+ _attach_local_node(node, module, name)
+ # recursion
+ self.object_build(module, member)
else:
# create an empty node so that the name is actually defined
attach_dummy_node(node, name, member)
diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py
index 10fd5900..3fc1a83f 100644
--- a/astroid/rebuilder.py
+++ b/astroid/rebuilder.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2009-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2013-2014 Google, Inc.
# Copyright (c) 2014 Alexander Presnyakov <flagist0@gmail.com>
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
@@ -10,8 +10,14 @@
# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017 rr- <rr-@sakuya.pl>
+# Copyright (c) 2018-2019 Ville Skyttä <ville.skytta@iki.fi>
+# Copyright (c) 2018 Tomas Gavenciak <gavento@ucw.cz>
+# Copyright (c) 2018 Serhiy Storchaka <storchaka@gmail.com>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019-2020 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
+# Copyright (c) 2019 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -21,9 +27,10 @@ order to get a single Astroid representation
"""
import sys
+from typing import Optional
import astroid
-from astroid._ast import _parse, _get_parser_module, parse_function_type_comment
+from astroid._ast import parse_function_type_comment, get_parser_module, ParserModule
from astroid import nodes
@@ -41,57 +48,6 @@ PY37 = sys.version_info >= (3, 7)
PY38 = sys.version_info >= (3, 8)
-def _binary_operators_from_module(module):
- binary_operators = {
- module.Add: "+",
- module.BitAnd: "&",
- module.BitOr: "|",
- module.BitXor: "^",
- module.Div: "/",
- module.FloorDiv: "//",
- module.MatMult: "@",
- module.Mod: "%",
- module.Mult: "*",
- module.Pow: "**",
- module.Sub: "-",
- module.LShift: "<<",
- module.RShift: ">>",
- }
- return binary_operators
-
-
-def _bool_operators_from_module(module):
- return {module.And: "and", module.Or: "or"}
-
-
-def _unary_operators_from_module(module):
- return {module.UAdd: "+", module.USub: "-", module.Not: "not", module.Invert: "~"}
-
-
-def _compare_operators_from_module(module):
- return {
- module.Eq: "==",
- module.Gt: ">",
- module.GtE: ">=",
- module.In: "in",
- module.Is: "is",
- module.IsNot: "is not",
- module.Lt: "<",
- module.LtE: "<=",
- module.NotEq: "!=",
- module.NotIn: "not in",
- }
-
-
-def _contexts_from_module(module):
- return {
- module.Load: astroid.Load,
- module.Store: astroid.Store,
- module.Del: astroid.Del,
- module.Param: astroid.Store,
- }
-
-
def _visit_or_none(node, attr, visitor, parent, visit="visit", **kws):
"""If the given node has an attribute, visits the attribute, and
otherwise returns None.
@@ -107,32 +63,30 @@ def _visit_or_none(node, attr, visitor, parent, visit="visit", **kws):
class TreeRebuilder:
"""Rebuilds the _ast tree to become an Astroid tree"""
- def __init__(self, manager, parse_python_two: bool = False):
+ def __init__(self, manager, parser_module: Optional[ParserModule] = None):
self._manager = manager
self._global_names = []
self._import_from_nodes = []
self._delayed_assattr = []
self._visit_meths = {}
- # Configure the right classes for the right module
- self._parser_module = _get_parser_module(parse_python_two=parse_python_two)
- self._unary_op_classes = _unary_operators_from_module(self._parser_module)
- self._cmp_op_classes = _compare_operators_from_module(self._parser_module)
- self._bool_op_classes = _bool_operators_from_module(self._parser_module)
- self._bin_op_classes = _binary_operators_from_module(self._parser_module)
- self._context_classes = _contexts_from_module(self._parser_module)
+ if parser_module is None:
+ self._parser_module = get_parser_module()
+ else:
+ self._parser_module = parser_module
+ self._module = self._parser_module.module
def _get_doc(self, node):
try:
if PY37 and hasattr(node, "docstring"):
doc = node.docstring
return node, doc
- if node.body and isinstance(node.body[0], self._parser_module.Expr):
+ if node.body and isinstance(node.body[0], self._module.Expr):
first_value = node.body[0].value
- if isinstance(first_value, self._parser_module.Str) or (
+ if isinstance(first_value, self._module.Str) or (
PY38
- and isinstance(first_value, self._parser_module.Constant)
+ and isinstance(first_value, self._module.Constant)
and isinstance(first_value.value, str)
):
doc = first_value.value if PY38 else first_value.s
@@ -143,7 +97,7 @@ class TreeRebuilder:
return node, None
def _get_context(self, node):
- return self._context_classes.get(type(node.ctx), astroid.Load)
+ return self._parser_module.context_classes.get(type(node.ctx), astroid.Load)
def visit_module(self, node, modname, modpath, package):
"""visit a Module node by returning a fresh instance of it"""
@@ -177,6 +131,10 @@ class TreeRebuilder:
else:
node.parent.set_local(node.name, node)
+ def visit_arg(self, node, parent):
+ """visit an arg node by returning a fresh AssName instance"""
+ return self.visit_assignname(node, parent, node.arg)
+
def visit_arguments(self, node, parent):
"""visit an Arguments node by returning a fresh instance of it"""
vararg, kwarg = node.vararg, node.kwarg
@@ -221,6 +179,15 @@ class TreeRebuilder:
type_comment_args = [
self.check_type_comment(child, parent=newnode) for child in node.args
]
+ type_comment_kwonlyargs = [
+ self.check_type_comment(child, parent=newnode) for child in node.kwonlyargs
+ ]
+ type_comment_posonlyargs = []
+ if PY38:
+ type_comment_posonlyargs = [
+ self.check_type_comment(child, parent=newnode)
+ for child in node.posonlyargs
+ ]
newnode.postinit(
args=args,
@@ -234,6 +201,8 @@ class TreeRebuilder:
varargannotation=varargannotation,
kwargannotation=kwargannotation,
type_comment_args=type_comment_args,
+ type_comment_kwonlyargs=type_comment_kwonlyargs,
+ type_comment_posonlyargs=type_comment_posonlyargs,
)
# save argument names in locals:
if vararg:
@@ -258,7 +227,7 @@ class TreeRebuilder:
return None
try:
- type_comment_ast = _parse(type_comment)
+ type_comment_ast = self._parser_module.parse(type_comment)
except SyntaxError:
# Invalid type comment, just skip it.
return None
@@ -289,6 +258,21 @@ class TreeRebuilder:
return returns, argtypes
+ # Async structs added in Python 3.5
+ def visit_asyncfunctiondef(self, node, parent):
+ return self._visit_functiondef(nodes.AsyncFunctionDef, node, parent)
+
+ def visit_asyncfor(self, node, parent):
+ return self._visit_for(nodes.AsyncFor, node, parent)
+
+ def visit_await(self, node, parent):
+ newnode = nodes.Await(node.lineno, node.col_offset, parent)
+ newnode.postinit(value=self.visit(node.value, newnode))
+ return newnode
+
+ def visit_asyncwith(self, node, parent):
+ return self._visit_with(nodes.AsyncWith, node, parent)
+
def visit_assign(self, node, parent):
"""visit a Assign node by returning a fresh instance of it"""
newnode = nodes.Assign(node.lineno, node.col_offset, parent)
@@ -300,6 +284,18 @@ class TreeRebuilder:
)
return newnode
+ def visit_annassign(self, node, parent):
+ """visit an AnnAssign node by returning a fresh instance of it"""
+ newnode = nodes.AnnAssign(node.lineno, node.col_offset, parent)
+ annotation = _visit_or_none(node, "annotation", self, newnode)
+ newnode.postinit(
+ target=self.visit(node.target, newnode),
+ annotation=annotation,
+ simple=node.simple,
+ value=_visit_or_none(node, "value", self, newnode),
+ )
+ return newnode
+
def visit_assignname(self, node, parent, node_name=None):
"""visit a node and return a AssignName node"""
newnode = nodes.AssignName(
@@ -314,7 +310,7 @@ class TreeRebuilder:
def visit_augassign(self, node, parent):
"""visit a AugAssign node by returning a fresh instance of it"""
newnode = nodes.AugAssign(
- self._bin_op_classes[type(node.op)] + "=",
+ self._parser_module.bin_op_classes[type(node.op)] + "=",
node.lineno,
node.col_offset,
parent,
@@ -333,7 +329,10 @@ class TreeRebuilder:
def visit_binop(self, node, parent):
"""visit a BinOp node by returning a fresh instance of it"""
newnode = nodes.BinOp(
- self._bin_op_classes[type(node.op)], node.lineno, node.col_offset, parent
+ self._parser_module.bin_op_classes[type(node.op)],
+ node.lineno,
+ node.col_offset,
+ parent,
)
newnode.postinit(
self.visit(node.left, newnode), self.visit(node.right, newnode)
@@ -343,7 +342,10 @@ class TreeRebuilder:
def visit_boolop(self, node, parent):
"""visit a BoolOp node by returning a fresh instance of it"""
newnode = nodes.BoolOp(
- self._bool_op_classes[type(node.op)], node.lineno, node.col_offset, parent
+ self._parser_module.bool_op_classes[type(node.op)],
+ node.lineno,
+ node.col_offset,
+ parent,
)
newnode.postinit([self.visit(child, newnode) for child in node.values])
return newnode
@@ -389,7 +391,7 @@ class TreeRebuilder:
newnode.postinit(self.visit(node.func, newnode), args, keywords)
return newnode
- def visit_classdef(self, node, parent, newstyle=None):
+ def visit_classdef(self, node, parent, newstyle=True):
"""visit a ClassDef node to become astroid"""
node, doc = self._get_doc(node)
newnode = nodes.ClassDef(node.name, doc, node.lineno, node.col_offset, parent)
@@ -437,7 +439,10 @@ class TreeRebuilder:
newnode.postinit(
self.visit(node.left, newnode),
[
- (self._cmp_op_classes[op.__class__], self.visit(expr, newnode))
+ (
+ self._parser_module.cmp_op_classes[op.__class__],
+ self.visit(expr, newnode),
+ )
for (op, expr) in zip(node.ops, node.comparators)
],
)
@@ -524,10 +529,13 @@ class TreeRebuilder:
def visit_excepthandler(self, node, parent):
"""visit an ExceptHandler node by returning a fresh instance of it"""
newnode = nodes.ExceptHandler(node.lineno, node.col_offset, parent)
- # /!\ node.name can be a tuple
+ if node.name:
+ name = self.visit_assignname(node, newnode, node.name)
+ else:
+ name = None
newnode.postinit(
_visit_or_none(node, "type", self, newnode),
- _visit_or_none(node, "name", self, newnode),
+ name,
[self.visit(child, newnode) for child in node.body],
)
return newnode
@@ -698,6 +706,27 @@ class TreeRebuilder:
parent.set_local(name.split(".")[0], newnode)
return newnode
+ def visit_joinedstr(self, node, parent):
+ newnode = nodes.JoinedStr(node.lineno, node.col_offset, parent)
+ newnode.postinit([self.visit(child, newnode) for child in node.values])
+ return newnode
+
+ def visit_formattedvalue(self, node, parent):
+ newnode = nodes.FormattedValue(node.lineno, node.col_offset, parent)
+ newnode.postinit(
+ self.visit(node.value, newnode),
+ node.conversion,
+ _visit_or_none(node, "format_spec", self, newnode),
+ )
+ return newnode
+
+ def visit_namedexpr(self, node, parent):
+ newnode = nodes.NamedExpr(node.lineno, node.col_offset, parent)
+ newnode.postinit(
+ self.visit(node.target, newnode), self.visit(node.value, newnode)
+ )
+ return newnode
+
# Not used in Python 3.8+.
def visit_index(self, node, parent):
"""visit a Index node by returning a fresh instance of it"""
@@ -759,6 +788,25 @@ class TreeRebuilder:
self._save_assignment(newnode)
return newnode
+ # Not used in Python 3.8+.
+ def visit_nameconstant(self, node, parent):
+ # in Python 3.4 we have NameConstant for True / False / None
+ return nodes.Const(
+ node.value,
+ getattr(node, "lineno", None),
+ getattr(node, "col_offset", None),
+ parent,
+ )
+
+ def visit_nonlocal(self, node, parent):
+ """visit a Nonlocal node and return a new instance of it"""
+ return nodes.Nonlocal(
+ node.names,
+ getattr(node, "lineno", None),
+ getattr(node, "col_offset", None),
+ parent,
+ )
+
def visit_constant(self, node, parent):
"""visit a Constant node by returning a fresh instance of Const"""
return nodes.Const(
@@ -806,11 +854,10 @@ class TreeRebuilder:
def visit_raise(self, node, parent):
"""visit a Raise node by returning a fresh instance of it"""
newnode = nodes.Raise(node.lineno, node.col_offset, parent)
- # pylint: disable=too-many-function-args
+ # no traceback; anyway it is not used in Pylint
newnode.postinit(
- _visit_or_none(node, "type", self, newnode),
- _visit_or_none(node, "inst", self, newnode),
- _visit_or_none(node, "tback", self, newnode),
+ _visit_or_none(node, "exc", self, newnode),
+ _visit_or_none(node, "cause", self, newnode),
)
return newnode
@@ -857,6 +904,15 @@ class TreeRebuilder:
)
return newnode
+ def visit_starred(self, node, parent):
+ """visit a Starred node and return a new instance of it"""
+ context = self._get_context(node)
+ newnode = nodes.Starred(
+ ctx=context, lineno=node.lineno, col_offset=node.col_offset, parent=parent
+ )
+ newnode.postinit(self.visit(node.value, newnode))
+ return newnode
+
def visit_tryexcept(self, node, parent):
"""visit a TryExcept node by returning a fresh instance of it"""
newnode = nodes.TryExcept(node.lineno, node.col_offset, parent)
@@ -867,6 +923,21 @@ class TreeRebuilder:
)
return newnode
+ def visit_try(self, node, parent):
+ # python 3.3 introduce a new Try node replacing
+ # TryFinally/TryExcept nodes
+ if node.finalbody:
+ newnode = nodes.TryFinally(node.lineno, node.col_offset, parent)
+ if node.handlers:
+ body = [self.visit_tryexcept(node, newnode)]
+ else:
+ body = [self.visit(child, newnode) for child in node.body]
+ newnode.postinit(body, [self.visit(n, newnode) for n in node.finalbody])
+ return newnode
+ if node.handlers:
+ return self.visit_tryexcept(node, parent)
+ return None
+
def visit_tryfinally(self, node, parent):
"""visit a TryFinally node by returning a fresh instance of it"""
newnode = nodes.TryFinally(node.lineno, node.col_offset, parent)
@@ -888,7 +959,7 @@ class TreeRebuilder:
def visit_unaryop(self, node, parent):
"""visit a UnaryOp node by returning a fresh instance of it"""
newnode = nodes.UnaryOp(
- self._unary_op_classes[node.op.__class__],
+ self._parser_module.unary_op_classes[node.op.__class__],
node.lineno,
node.col_offset,
parent,
@@ -906,121 +977,7 @@ class TreeRebuilder:
)
return newnode
- def visit_with(self, node, parent):
- newnode = nodes.With(node.lineno, node.col_offset, parent)
- expr = self.visit(node.context_expr, newnode)
- if node.optional_vars is not None:
- optional_vars = self.visit(node.optional_vars, newnode)
- else:
- optional_vars = None
-
- type_annotation = self.check_type_comment(node, parent=newnode)
- newnode.postinit(
- items=[(expr, optional_vars)],
- body=[self.visit(child, newnode) for child in node.body],
- type_annotation=type_annotation,
- )
- return newnode
-
- def visit_yield(self, node, parent):
- """visit a Yield node by returning a fresh instance of it"""
- newnode = nodes.Yield(node.lineno, node.col_offset, parent)
- if node.value is not None:
- newnode.postinit(self.visit(node.value, newnode))
- return newnode
-
-
-class TreeRebuilder3(TreeRebuilder):
- """extend and overwrite TreeRebuilder for python3k"""
-
- def visit_arg(self, node, parent):
- """visit an arg node by returning a fresh AssName instance"""
- return self.visit_assignname(node, parent, node.arg)
-
- # Not used in Python 3.8+.
- def visit_nameconstant(self, node, parent):
- # in Python 3.4 we have NameConstant for True / False / None
- return nodes.Const(
- node.value,
- getattr(node, "lineno", None),
- getattr(node, "col_offset", None),
- parent,
- )
-
- def visit_excepthandler(self, node, parent):
- """visit an ExceptHandler node by returning a fresh instance of it"""
- newnode = nodes.ExceptHandler(node.lineno, node.col_offset, parent)
- if node.name:
- name = self.visit_assignname(node, newnode, node.name)
- else:
- name = None
- newnode.postinit(
- _visit_or_none(node, "type", self, newnode),
- name,
- [self.visit(child, newnode) for child in node.body],
- )
- return newnode
-
- def visit_nonlocal(self, node, parent):
- """visit a Nonlocal node and return a new instance of it"""
- return nodes.Nonlocal(
- node.names,
- getattr(node, "lineno", None),
- getattr(node, "col_offset", None),
- parent,
- )
-
- def visit_raise(self, node, parent):
- """visit a Raise node by returning a fresh instance of it"""
- newnode = nodes.Raise(node.lineno, node.col_offset, parent)
- # no traceback; anyway it is not used in Pylint
- newnode.postinit(
- _visit_or_none(node, "exc", self, newnode),
- _visit_or_none(node, "cause", self, newnode),
- )
- return newnode
-
- def visit_starred(self, node, parent):
- """visit a Starred node and return a new instance of it"""
- context = self._get_context(node)
- newnode = nodes.Starred(
- ctx=context, lineno=node.lineno, col_offset=node.col_offset, parent=parent
- )
- newnode.postinit(self.visit(node.value, newnode))
- return newnode
-
- def visit_try(self, node, parent):
- # python 3.3 introduce a new Try node replacing
- # TryFinally/TryExcept nodes
- if node.finalbody:
- newnode = nodes.TryFinally(node.lineno, node.col_offset, parent)
- if node.handlers:
- body = [self.visit_tryexcept(node, newnode)]
- else:
- body = [self.visit(child, newnode) for child in node.body]
- newnode.postinit(body, [self.visit(n, newnode) for n in node.finalbody])
- return newnode
- if node.handlers:
- return self.visit_tryexcept(node, parent)
- return None
-
- def visit_annassign(self, node, parent):
- """visit an AnnAssign node by returning a fresh instance of it"""
- newnode = nodes.AnnAssign(node.lineno, node.col_offset, parent)
- annotation = _visit_or_none(node, "annotation", self, newnode)
- newnode.postinit(
- target=self.visit(node.target, newnode),
- annotation=annotation,
- simple=node.simple,
- value=_visit_or_none(node, "value", self, newnode),
- )
- return newnode
-
def _visit_with(self, cls, node, parent):
- if "items" not in node._fields:
- # python < 3.3
- return super(TreeRebuilder3, self).visit_with(node, parent)
-
newnode = cls(node.lineno, node.col_offset, parent)
def visit_child(child):
@@ -1039,52 +996,15 @@ class TreeRebuilder3(TreeRebuilder):
def visit_with(self, node, parent):
return self._visit_with(nodes.With, node, parent)
- def visit_yieldfrom(self, node, parent):
- newnode = nodes.YieldFrom(node.lineno, node.col_offset, parent)
+ def visit_yield(self, node, parent):
+ """visit a Yield node by returning a fresh instance of it"""
+ newnode = nodes.Yield(node.lineno, node.col_offset, parent)
if node.value is not None:
newnode.postinit(self.visit(node.value, newnode))
return newnode
- def visit_classdef(self, node, parent, newstyle=True):
- return super(TreeRebuilder3, self).visit_classdef(
- node, parent, newstyle=newstyle
- )
-
- # Async structs added in Python 3.5
- def visit_asyncfunctiondef(self, node, parent):
- return self._visit_functiondef(nodes.AsyncFunctionDef, node, parent)
-
- def visit_asyncfor(self, node, parent):
- return self._visit_for(nodes.AsyncFor, node, parent)
-
- def visit_await(self, node, parent):
- newnode = nodes.Await(node.lineno, node.col_offset, parent)
- newnode.postinit(value=self.visit(node.value, newnode))
- return newnode
-
- def visit_asyncwith(self, node, parent):
- return self._visit_with(nodes.AsyncWith, node, parent)
-
- def visit_joinedstr(self, node, parent):
- newnode = nodes.JoinedStr(node.lineno, node.col_offset, parent)
- newnode.postinit([self.visit(child, newnode) for child in node.values])
- return newnode
-
- def visit_formattedvalue(self, node, parent):
- newnode = nodes.FormattedValue(node.lineno, node.col_offset, parent)
- newnode.postinit(
- self.visit(node.value, newnode),
- node.conversion,
- _visit_or_none(node, "format_spec", self, newnode),
- )
- return newnode
-
- def visit_namedexpr(self, node, parent):
- newnode = nodes.NamedExpr(node.lineno, node.col_offset, parent)
- newnode.postinit(
- self.visit(node.target, newnode), self.visit(node.value, newnode)
- )
+ def visit_yieldfrom(self, node, parent):
+ newnode = nodes.YieldFrom(node.lineno, node.col_offset, parent)
+ if node.value is not None:
+ newnode.postinit(self.visit(node.value, newnode))
return newnode
-
-
-TreeRebuilder = TreeRebuilder3
diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py
index ad727b13..8561e745 100644
--- a/astroid/scoped_nodes.py
+++ b/astroid/scoped_nodes.py
@@ -2,7 +2,7 @@
# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
# Copyright (c) 2011, 2013-2015 Google, Inc.
-# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2013 Phil Schaf <flying-sheep@web.de>
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
# Copyright (c) 2015-2016 Florian Bruhin <me@the-compiler.org>
@@ -14,9 +14,12 @@
# Copyright (c) 2017-2018 Ashley Whetter <ashley@awhetter.co.uk>
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017 David Euresti <david@dropbox.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2018-2019 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
+# Copyright (c) 2019 Peter de Blanc <peter@standard.ai>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -50,6 +53,9 @@ BUILTINS = builtins.__name__
ITER_METHODS = ("__iter__", "__getitem__")
EXCEPTION_BASE_CLASSES = frozenset({"Exception", "BaseException"})
objects = util.lazy_import("objects")
+BUILTIN_DESCRIPTORS = frozenset(
+ {"classmethod", "staticmethod", "builtins.classmethod", "builtins.staticmethod"}
+)
def _c3_merge(sequences, cls, context):
@@ -524,6 +530,11 @@ class Module(LocalsDictNodeNG):
return "Module"
def getattr(self, name, context=None, ignore_locals=False):
+ if not name:
+ raise exceptions.AttributeInferenceError(
+ target=self, attribute=name, context=context
+ )
+
result = []
name_in_locals = name in self.locals
@@ -734,7 +745,7 @@ class Module(LocalsDictNodeNG):
"""
return [name for name in self.keys() if not name.startswith("_")]
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -803,7 +814,7 @@ class GeneratorExp(ComprehensionScope):
:type: dict(str, NodeNG)
"""
- super(GeneratorExp, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, elt=None, generators=None):
"""Do some setup after initialisation.
@@ -820,7 +831,7 @@ class GeneratorExp(ComprehensionScope):
else:
self.generators = generators
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -879,7 +890,7 @@ class DictComp(ComprehensionScope):
:type: dict(str, NodeNG)
"""
- super(DictComp, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, key=None, value=None, generators=None):
"""Do some setup after initialisation.
@@ -900,7 +911,7 @@ class DictComp(ComprehensionScope):
else:
self.generators = generators
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -955,7 +966,7 @@ class SetComp(ComprehensionScope):
:type: dict(str, NodeNG)
"""
- super(SetComp, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, elt=None, generators=None):
"""Do some setup after initialisation.
@@ -972,7 +983,7 @@ class SetComp(ComprehensionScope):
else:
self.generators = generators
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -1019,7 +1030,7 @@ class _ListComp(node_classes.NodeNG):
self.elt = elt
self.generators = generators
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -1051,7 +1062,7 @@ class ListComp(_ListComp, ComprehensionScope):
:type: dict(str, NodeNG)
"""
- super(ListComp, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def _infer_decorator_callchain(node):
@@ -1073,6 +1084,21 @@ def _infer_decorator_callchain(node):
return "classmethod"
if result.is_subtype_of("%s.staticmethod" % BUILTINS):
return "staticmethod"
+ if isinstance(result, FunctionDef):
+ if not result.decorators:
+ return None
+ # Determine if this function is decorated with one of the builtin descriptors we want.
+ for decorator in result.decorators.nodes:
+ if isinstance(decorator, node_classes.Name):
+ if decorator.name in BUILTIN_DESCRIPTORS:
+ return decorator.name
+ if (
+ isinstance(decorator, node_classes.Attribute)
+ and isinstance(decorator.expr, node_classes.Name)
+ and decorator.expr.name == BUILTINS
+ and decorator.attrname in BUILTIN_DESCRIPTORS
+ ):
+ return decorator.attrname
return None
@@ -1136,7 +1162,7 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
:type: list(NodeNG)
"""
- super(Lambda, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
def postinit(self, args, body):
"""Do some setup after initialisation.
@@ -1243,7 +1269,7 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
frame = self
return frame._scope_lookup(node, name, offset)
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -1341,7 +1367,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
"""
self.instance_attrs = {}
- super(FunctionDef, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
if parent:
frame = parent.frame()
frame.set_local(name, self)
@@ -1423,17 +1449,17 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
return decorators
@decorators_mod.cachedproperty
- def type(self): # pylint: disable=invalid-overridden-method
+ def type(
+ self
+ ): # pylint: disable=invalid-overridden-method,too-many-return-statements
"""The function type for this node.
Possible values are: method, function, staticmethod, classmethod.
:type: str
"""
- builtin_descriptors = {"classmethod", "staticmethod"}
-
for decorator in self.extra_decorators:
- if decorator.func.name in builtin_descriptors:
+ if decorator.func.name in BUILTIN_DESCRIPTORS:
return decorator.func.name
frame = self.parent.frame()
@@ -1451,8 +1477,15 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
for node in self.decorators.nodes:
if isinstance(node, node_classes.Name):
- if node.name in builtin_descriptors:
+ if node.name in BUILTIN_DESCRIPTORS:
return node.name
+ if (
+ isinstance(node, node_classes.Attribute)
+ and isinstance(node.expr, node_classes.Name)
+ and node.expr.name == BUILTINS
+ and node.attrname in BUILTIN_DESCRIPTORS
+ ):
+ return node.attrname
if isinstance(node, node_classes.Call):
# Handle the following case:
@@ -1526,10 +1559,18 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
"""this method doesn't look in the instance_attrs dictionary since it's
done by an Instance proxy at inference time.
"""
+ if not name:
+ raise exceptions.AttributeInferenceError(
+ target=self, attribute=name, context=context
+ )
+
+ found_attrs = []
if name in self.instance_attrs:
- return self.instance_attrs[name]
+ found_attrs = self.instance_attrs[name]
if name in self.special_attributes:
- return [self.special_attributes.lookup(name)]
+ found_attrs.append(self.special_attributes.lookup(name))
+ if found_attrs:
+ return found_attrs
raise exceptions.AttributeInferenceError(target=self, attribute=name)
def igetattr(self, name, context=None):
@@ -1620,7 +1661,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
:returns: True is this is a generator function, False otherwise.
:rtype: bool
"""
- return next(self._get_yield_nodes_skip_lambdas(), False)
+ return bool(next(self._get_yield_nodes_skip_lambdas(), False))
def infer_call_result(self, caller=None, context=None):
"""Infer what the function returns when called.
@@ -1682,7 +1723,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
except exceptions.InferenceError:
yield util.Uninferable
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
@@ -1933,7 +1974,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
:type doc: str or None
"""
- super(ClassDef, self).__init__(lineno, col_offset, parent)
+ super().__init__(lineno, col_offset, parent)
if parent is not None:
parent.frame().set_local(name, self)
@@ -2092,7 +2133,14 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
# Get the bases of the class.
class_bases = next(caller.args[1].infer(context))
if isinstance(class_bases, (node_classes.Tuple, node_classes.List)):
- result.bases = class_bases.itered()
+ bases = []
+ for base in class_bases.itered():
+ inferred = next(base.infer(context=context))
+ if inferred:
+ bases.append(
+ node_classes.EvaluatedObject(original=base, value=inferred)
+ )
+ result.bases = bases
else:
# There is currently no AST node that can represent an 'unknown'
# node (Uninferable is not an AST node), therefore we simply return Uninferable here
@@ -2381,6 +2429,11 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
:raises AttributeInferenceError: If the attribute cannot be inferred.
"""
+ if not name:
+ raise exceptions.AttributeInferenceError(
+ target=self, attribute=name, context=context
+ )
+
values = self.locals.get(name, [])
if name in self.special_attributes and class_context and not values:
result = [self.special_attributes.lookup(name)]
@@ -2417,7 +2470,8 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
"""Search the given name in the implicit and the explicit metaclass."""
attrs = set()
implicit_meta = self.implicit_metaclass()
- metaclass = self.metaclass()
+ context = contextmod.copy_context(context)
+ metaclass = self.metaclass(context=context)
for cls in {implicit_meta, metaclass}:
if cls and cls != self and isinstance(cls, ClassDef):
cls_attributes = self._get_attribute_from_metaclass(cls, name, context)
@@ -2467,8 +2521,20 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
metaclass = self.declared_metaclass(context=context)
try:
- attr = self.getattr(name, context, class_context=class_context)[0]
- for inferred in bases._infer_stmts([attr], context, frame=self):
+ attributes = self.getattr(name, context, class_context=class_context)
+ # If we have more than one attribute, make sure that those starting from
+ # the second one are from the same scope. This is to account for modifications
+ # to the attribute happening *after* the attribute's definition (e.g. AugAssigns on lists)
+ if len(attributes) > 1:
+ first_attr, attributes = attributes[0], attributes[1:]
+ first_scope = first_attr.scope()
+ attributes = [first_attr] + [
+ attr
+ for attr in attributes
+ if attr.parent and attr.parent.scope() == first_scope
+ ]
+
+ for inferred in bases._infer_stmts(attributes, context, frame=self):
# yield Uninferable object instead of descriptors when necessary
if not isinstance(inferred, node_classes.Const) and isinstance(
inferred, bases.Instance
@@ -2768,7 +2834,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
if not all(slot is not None for slot in slots):
return None
- return sorted(slots, key=lambda item: item.value)
+ return sorted(set(slots), key=lambda item: item.value)
def _inferred_bases(self, context=None):
# Similar with .ancestors, but the difference is when one base is inferred,
@@ -2837,7 +2903,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
"""
return self._compute_mro(context=context)
- def bool_value(self):
+ def bool_value(self, context=None):
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
diff --git a/astroid/test_utils.py b/astroid/test_utils.py
index 6c965efd..e22c7a4f 100644
--- a/astroid/test_utils.py
+++ b/astroid/test_utils.py
@@ -1,6 +1,6 @@
# Copyright (c) 2013-2014 Google, Inc.
# Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
diff --git a/setup.py b/setup.py
index ad2891c1..016fce18 100644
--- a/setup.py
+++ b/setup.py
@@ -1,10 +1,11 @@
#!/usr/bin/env python
# Copyright (c) 2006, 2009-2010, 2012-2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2010-2011 Julien Jehannet <julien.jehannet@logilab.fr>
-# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2018-2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Enji Cooper <yaneurabeya@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -28,8 +29,9 @@ with open(os.path.join(astroid_dir, "README.rst")) as fobj:
long_description = fobj.read()
-needs_pytest = set(['pytest', 'test', 'ptr']).intersection(sys.argv)
-pytest_runner = ['pytest-runner'] if needs_pytest else []
+needs_pytest = set(["pytest", "test", "ptr"]).intersection(sys.argv)
+pytest_runner = ["pytest-runner"] if needs_pytest else []
+
def install():
return setup(
@@ -42,10 +44,10 @@ def install():
author=author,
author_email=author_email,
url=web,
- python_requires=">=3.5.*",
+ python_requires=">=3.5",
install_requires=install_requires,
extras_require=extras_require,
- packages=find_packages() + ["astroid.brain"],
+ packages=find_packages(exclude=["tests"]) + ["astroid.brain"],
setup_requires=pytest_runner,
test_suite="test",
tests_require=["pytest"],
diff --git a/tests/resources.py b/tests/resources.py
index ee57b9fe..7113f2b1 100644
--- a/tests/resources.py
+++ b/tests/resources.py
@@ -1,7 +1,8 @@
# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -14,7 +15,7 @@ from astroid import MANAGER
from astroid.bases import BUILTINS
-DATA_DIR = os.path.join("testdata", "python{}".format(sys.version_info[0]))
+DATA_DIR = os.path.join("testdata", "python3")
RESOURCE_PATH = os.path.join(os.path.dirname(__file__), DATA_DIR, "data")
diff --git a/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.egg b/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.egg
deleted file mode 100644
index f62599c7..00000000
--- a/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.egg
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.zip b/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.zip
deleted file mode 100644
index f62599c7..00000000
--- a/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.zip
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/python2/data/SSL1/Connection1.py b/tests/testdata/python2/data/SSL1/Connection1.py
deleted file mode 100644
index 1307b239..00000000
--- a/tests/testdata/python2/data/SSL1/Connection1.py
+++ /dev/null
@@ -1,5 +0,0 @@
-
-class Connection:
-
- def __init__(self, ctx, sock=None):
- print('init Connection')
diff --git a/tests/testdata/python2/data/SSL1/__init__.py b/tests/testdata/python2/data/SSL1/__init__.py
deleted file mode 100644
index a007b049..00000000
--- a/tests/testdata/python2/data/SSL1/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from Connection1 import Connection
diff --git a/tests/testdata/python2/data/__init__.py b/tests/testdata/python2/data/__init__.py
deleted file mode 100644
index 332e2e72..00000000
--- a/tests/testdata/python2/data/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__revision__="$Id: __init__.py,v 1.1 2005-06-13 20:55:20 syt Exp $"
diff --git a/tests/testdata/python2/data/absimp/__init__.py b/tests/testdata/python2/data/absimp/__init__.py
deleted file mode 100644
index b98444df..00000000
--- a/tests/testdata/python2/data/absimp/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""a package with absolute import activated
-"""
-
-from __future__ import absolute_import
-
diff --git a/tests/testdata/python2/data/absimp/sidepackage/__init__.py b/tests/testdata/python2/data/absimp/sidepackage/__init__.py
deleted file mode 100644
index 239499a6..00000000
--- a/tests/testdata/python2/data/absimp/sidepackage/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""a side package with nothing in it
-"""
-
diff --git a/tests/testdata/python2/data/absimp/string.py b/tests/testdata/python2/data/absimp/string.py
deleted file mode 100644
index e68e7496..00000000
--- a/tests/testdata/python2/data/absimp/string.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from __future__ import absolute_import, print_function
-import string
-print(string)
diff --git a/tests/testdata/python2/data/absimport.py b/tests/testdata/python2/data/absimport.py
deleted file mode 100644
index f98effa6..00000000
--- a/tests/testdata/python2/data/absimport.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from __future__ import absolute_import
-import email
-from email import message
diff --git a/tests/testdata/python2/data/all.py b/tests/testdata/python2/data/all.py
deleted file mode 100644
index 23f7d2b6..00000000
--- a/tests/testdata/python2/data/all.py
+++ /dev/null
@@ -1,9 +0,0 @@
-
-name = 'a'
-_bla = 2
-other = 'o'
-class Aaa: pass
-
-def func(): print 'yo'
-
-__all__ = 'Aaa', '_bla', 'name'
diff --git a/tests/testdata/python2/data/appl/__init__.py b/tests/testdata/python2/data/appl/__init__.py
deleted file mode 100644
index d652ffd9..00000000
--- a/tests/testdata/python2/data/appl/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-Init
-"""
diff --git a/tests/testdata/python2/data/appl/myConnection.py b/tests/testdata/python2/data/appl/myConnection.py
deleted file mode 100644
index 5b24b259..00000000
--- a/tests/testdata/python2/data/appl/myConnection.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from __future__ import print_function
-from data import SSL1
-class MyConnection(SSL1.Connection):
-
- """An SSL connection."""
-
- def __init__(self, dummy):
- print('MyConnection init')
-
-if __name__ == '__main__':
- myConnection = MyConnection(' ')
- raw_input('Press Enter to continue...')
diff --git a/tests/testdata/python2/data/contribute_to_namespace/namespace_pep_420/submodule.py b/tests/testdata/python2/data/contribute_to_namespace/namespace_pep_420/submodule.py
deleted file mode 100644
index 6fbcff41..00000000
--- a/tests/testdata/python2/data/contribute_to_namespace/namespace_pep_420/submodule.py
+++ /dev/null
@@ -1 +0,0 @@
-var = 42 \ No newline at end of file
diff --git a/tests/testdata/python2/data/descriptor_crash.py b/tests/testdata/python2/data/descriptor_crash.py
deleted file mode 100644
index 11fbb4a2..00000000
--- a/tests/testdata/python2/data/descriptor_crash.py
+++ /dev/null
@@ -1,11 +0,0 @@
-
-import urllib
-
-class Page(object):
- _urlOpen = staticmethod(urllib.urlopen)
-
- def getPage(self, url):
- handle = self._urlOpen(url)
- data = handle.read()
- handle.close()
- return data
diff --git a/tests/testdata/python2/data/email.py b/tests/testdata/python2/data/email.py
deleted file mode 100644
index dc593564..00000000
--- a/tests/testdata/python2/data/email.py
+++ /dev/null
@@ -1 +0,0 @@
-"""fake email module to test absolute import doesn't grab this one"""
diff --git a/tests/testdata/python2/data/find_test/module.py b/tests/testdata/python2/data/find_test/module.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/find_test/module.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/find_test/module2.py b/tests/testdata/python2/data/find_test/module2.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/find_test/module2.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/find_test/noendingnewline.py b/tests/testdata/python2/data/find_test/noendingnewline.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/find_test/noendingnewline.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/find_test/nonregr.py b/tests/testdata/python2/data/find_test/nonregr.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/find_test/nonregr.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/foogle/fax/__init__.py b/tests/testdata/python2/data/foogle/fax/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/foogle/fax/__init__.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/foogle/fax/a.py b/tests/testdata/python2/data/foogle/fax/a.py
deleted file mode 100644
index 3d2b4b14..00000000
--- a/tests/testdata/python2/data/foogle/fax/a.py
+++ /dev/null
@@ -1 +0,0 @@
-x = 1 \ No newline at end of file
diff --git a/tests/testdata/python2/data/foogle_fax-0.12.5-py2.7-nspkg.pth b/tests/testdata/python2/data/foogle_fax-0.12.5-py2.7-nspkg.pth
deleted file mode 100644
index eeb7ecac..00000000
--- a/tests/testdata/python2/data/foogle_fax-0.12.5-py2.7-nspkg.pth
+++ /dev/null
@@ -1,2 +0,0 @@
-import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('foogle',));ie = os.path.exists(os.path.join(p,'__init__.py'));m = not ie and sys.modules.setdefault('foogle', types.ModuleType('foogle'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
-import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('foogle','crank'));ie = os.path.exists(os.path.join(p,'__init__.py'));m = not ie and sys.modules.setdefault('foogle.crank', types.ModuleType('foogle.crank'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
diff --git a/tests/testdata/python2/data/format.py b/tests/testdata/python2/data/format.py
deleted file mode 100644
index 73797061..00000000
--- a/tests/testdata/python2/data/format.py
+++ /dev/null
@@ -1,34 +0,0 @@
-"""A multiline string
-"""
-
-function('aeozrijz\
-earzer', hop)
-# XXX write test
-x = [i for i in range(5)
- if i % 4]
-
-fonction(1,
- 2,
- 3,
- 4)
-
-def definition(a,
- b,
- c):
- return a + b + c
-
-class debile(dict,
- object):
- pass
-
-if aaaa: pass
-else:
- aaaa,bbbb = 1,2
- aaaa,bbbb = bbbb,aaaa
-# XXX write test
-hop = \
- aaaa
-
-
-__revision__.lower();
-
diff --git a/tests/testdata/python2/data/invalid_encoding.py b/tests/testdata/python2/data/invalid_encoding.py
deleted file mode 100644
index dddd208e..00000000
--- a/tests/testdata/python2/data/invalid_encoding.py
+++ /dev/null
@@ -1 +0,0 @@
-# -*- coding: lala -*- \ No newline at end of file
diff --git a/tests/testdata/python2/data/lmfp/__init__.py b/tests/testdata/python2/data/lmfp/__init__.py
deleted file mode 100644
index 74b26b82..00000000
--- a/tests/testdata/python2/data/lmfp/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# force a "direct" python import
-from . import foo
diff --git a/tests/testdata/python2/data/lmfp/foo.py b/tests/testdata/python2/data/lmfp/foo.py
deleted file mode 100644
index 8f7de1e8..00000000
--- a/tests/testdata/python2/data/lmfp/foo.py
+++ /dev/null
@@ -1,6 +0,0 @@
-import sys
-if not getattr(sys, 'bar', None):
- sys.just_once = []
-# there used to be two numbers here because
-# of a load_module_from_path bug
-sys.just_once.append(42)
diff --git a/tests/testdata/python2/data/module.py b/tests/testdata/python2/data/module.py
deleted file mode 100644
index 1205c1dd..00000000
--- a/tests/testdata/python2/data/module.py
+++ /dev/null
@@ -1,90 +0,0 @@
-"""test module for astroid
-"""
-
-__revision__ = '$Id: module.py,v 1.2 2005-11-02 11:56:54 syt Exp $'
-from astroid.node_classes import Name as NameNode
-from astroid import modutils
-from astroid.utils import *
-import os.path
-MY_DICT = {}
-
-def global_access(key, val):
- """function test"""
- local = 1
- MY_DICT[key] = val
- for i in val:
- if i:
- del MY_DICT[i]
- continue
- else:
- break
- else:
- return local
-
-
-class YO:
- """hehe
- haha"""
- a = 1
-
- def __init__(self):
- try:
- self.yo = 1
- except ValueError, ex:
- pass
- except (NameError, TypeError):
- raise XXXError()
- except:
- raise
-
-
-
-class YOUPI(YO):
- class_attr = None
-
- def __init__(self):
- self.member = None
-
- def method(self):
- """method
- test"""
- global MY_DICT
- try:
- MY_DICT = {}
- local = None
- autre = [a for (a, b) in MY_DICT if b]
- if b in autre:
- return b
- elif a in autre:
- return a
- global_access(local, val=autre)
- finally:
- return local
-
- def static_method():
- """static method test"""
- assert MY_DICT, '???'
- static_method = staticmethod(static_method)
-
- def class_method(cls):
- """class method test"""
- exec a in b
- class_method = classmethod(class_method)
-
-
-def four_args(a, b, c, d):
- """four arguments (was nested_args)"""
- pass
- while 1:
- if a:
- break
- a += +1
- else:
- b += -2
- if c:
- d = a and (b or c)
- else:
- c = a and b or d
- map(lambda x, y: (y, x), a)
-redirect = four_args
-
diff --git a/tests/testdata/python2/data/module1abs/__init__.py b/tests/testdata/python2/data/module1abs/__init__.py
deleted file mode 100644
index 42949a44..00000000
--- a/tests/testdata/python2/data/module1abs/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from __future__ import absolute_import, print_function
-from . import core
-from .core import *
-print(sys.version)
diff --git a/tests/testdata/python2/data/module1abs/core.py b/tests/testdata/python2/data/module1abs/core.py
deleted file mode 100644
index de101117..00000000
--- a/tests/testdata/python2/data/module1abs/core.py
+++ /dev/null
@@ -1 +0,0 @@
-import sys
diff --git a/tests/testdata/python2/data/module2.py b/tests/testdata/python2/data/module2.py
deleted file mode 100644
index 7042a4c1..00000000
--- a/tests/testdata/python2/data/module2.py
+++ /dev/null
@@ -1,143 +0,0 @@
-from data.module import YO, YOUPI
-import data
-
-
-class Specialization(YOUPI, YO):
- pass
-
-
-
-class Metaclass(type):
- pass
-
-
-
-class Interface:
- pass
-
-
-
-class MyIFace(Interface):
- pass
-
-
-
-class AnotherIFace(Interface):
- pass
-
-
-
-class MyException(Exception):
- pass
-
-
-
-class MyError(MyException):
- pass
-
-
-
-class AbstractClass(object):
-
- def to_override(self, whatever):
- raise NotImplementedError()
-
- def return_something(self, param):
- if param:
- return 'toto'
- return
-
-
-
-class Concrete0:
- __implements__ = MyIFace
-
-
-
-class Concrete1:
- __implements__ = (MyIFace, AnotherIFace)
-
-
-
-class Concrete2:
- __implements__ = (MyIFace, AnotherIFace)
-
-
-
-class Concrete23(Concrete1):
- pass
-
-del YO.member
-del YO
-[SYN1, SYN2] = (Concrete0, Concrete1)
-assert '1'
-b = (1 | 2) & (3 ^ 8)
-bb = 1 | (two | 6)
-ccc = one & two & three
-dddd = x ^ (o ^ r)
-exec 'c = 3'
-exec 'c = 3' in {}, {}
-
-def raise_string(a=2, *args, **kwargs):
- raise Exception, 'yo'
- yield 'coucou'
- yield
-a = b + 2
-c = b * 2
-c = b / 2
-c = b // 2
-c = b - 2
-c = b % 2
-c = b**2
-c = b << 2
-c = b >> 2
-c = ~b
-c = not b
-d = [c]
-e = d[:]
-e = d[a:b:c]
-raise_string(*args, **kwargs)
-print >> stream, 'bonjour'
-print >> stream, 'salut',
-
-def make_class(any, base=data.module.YO, *args, **kwargs):
- """check base is correctly resolved to Concrete0"""
-
-
- class Aaaa(base):
- """dynamic class"""
-
-
- return Aaaa
-from os.path import abspath
-import os as myos
-
-
-class A:
- pass
-
-
-
-class A(A):
- pass
-
-
-def generator():
- """A generator."""
- yield
-
-def not_a_generator():
- """A function that contains generator, but is not one."""
-
- def generator():
- yield
- genl = lambda: (yield)
-
-def with_metaclass(meta, *bases):
- return meta('NewBase', bases, {})
-
-
-class NotMetaclass(with_metaclass(Metaclass)):
- pass
-
-
diff --git a/tests/testdata/python2/data/namespace_pep_420/module.py b/tests/testdata/python2/data/namespace_pep_420/module.py
deleted file mode 100644
index a4d111e6..00000000
--- a/tests/testdata/python2/data/namespace_pep_420/module.py
+++ /dev/null
@@ -1 +0,0 @@
-from namespace_pep_420.submodule import var \ No newline at end of file
diff --git a/tests/testdata/python2/data/noendingnewline.py b/tests/testdata/python2/data/noendingnewline.py
deleted file mode 100644
index e1d6e4a1..00000000
--- a/tests/testdata/python2/data/noendingnewline.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import unittest
-
-
-class TestCase(unittest.TestCase):
-
- def setUp(self):
- unittest.TestCase.setUp(self)
-
-
- def tearDown(self):
- unittest.TestCase.tearDown(self)
-
- def testIt(self):
- self.a = 10
- self.xxx()
-
-
- def xxx(self):
- if False:
- pass
- print 'a'
-
- if False:
- pass
- pass
-
- if False:
- pass
- print 'rara'
-
-
-if __name__ == '__main__':
- print 'test2'
- unittest.main()
-
-
diff --git a/tests/testdata/python2/data/nonregr.py b/tests/testdata/python2/data/nonregr.py
deleted file mode 100644
index 813469fe..00000000
--- a/tests/testdata/python2/data/nonregr.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from __future__ import generators, print_function
-
-try:
- enumerate = enumerate
-except NameError:
-
- def enumerate(iterable):
- """emulates the python2.3 enumerate() function"""
- i = 0
- for val in iterable:
- yield i, val
- i += 1
-
-def toto(value):
- for k, v in value:
- print(v.get('yo'))
-
-
-import imp
-fp, mpath, desc = imp.find_module('optparse',a)
-s_opt = imp.load_module('std_optparse', fp, mpath, desc)
-
-class OptionParser(s_opt.OptionParser):
-
- def parse_args(self, args=None, values=None, real_optparse=False):
- if real_optparse:
- pass
-## return super(OptionParser, self).parse_args()
- else:
- import optcomp
- optcomp.completion(self)
-
-
-class Aaa(object):
- """docstring"""
- def __init__(self):
- self.__setattr__('a','b')
- pass
-
- def one_public(self):
- """docstring"""
- pass
-
- def another_public(self):
- """docstring"""
- pass
-
-class Ccc(Aaa):
- """docstring"""
-
- class Ddd(Aaa):
- """docstring"""
- pass
-
- class Eee(Ddd):
- """docstring"""
- pass
diff --git a/tests/testdata/python2/data/notall.py b/tests/testdata/python2/data/notall.py
deleted file mode 100644
index 042491e0..00000000
--- a/tests/testdata/python2/data/notall.py
+++ /dev/null
@@ -1,7 +0,0 @@
-name = 'a'
-_bla = 2
-other = 'o'
-class Aaa: pass
-
-def func(): return 'yo'
-
diff --git a/tests/testdata/python2/data/notamodule/file.py b/tests/testdata/python2/data/notamodule/file.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/notamodule/file.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/operator_precedence.py b/tests/testdata/python2/data/operator_precedence.py
deleted file mode 100644
index 07a3c772..00000000
--- a/tests/testdata/python2/data/operator_precedence.py
+++ /dev/null
@@ -1,27 +0,0 @@
-assert not not True == True
-assert (not False or True) == True
-assert True or False and True
-assert (True or False) and True
-
-assert True is not (False is True) == False
-assert True is (not False is True == False)
-
-assert 1 + 2 + 3 == 6
-assert 5 - 4 + 3 == 4
-assert 4 - 5 - 6 == -7
-assert 7 - (8 - 9) == 8
-assert 2**3**4 == 2**81
-assert (2**3)**4 == 8**4
-
-assert 1 + 2 if (0.5 if True else 0.2) else 1 if True else 2 == 3
-assert (0 if True else 1) if False else 2 == 2
-assert lambda x: x if (0 if False else 0) else 0 if False else 0
-assert (lambda x: x) if (0 if True else 0.2) else 1 if True else 2 == 1
-
-assert ('1' + '2').replace('1', '3') == '32'
-assert (lambda x: x)(1) == 1
-assert ([0] + [1])[1] == 1
-assert (lambda x: lambda: x + 1)(2)() == 3
-
-f = lambda x, y, z: y(x, z)
-assert f(1, lambda x, y: x + y[1], (2, 3)) == 4
diff --git a/tests/testdata/python2/data/package/__init__.py b/tests/testdata/python2/data/package/__init__.py
deleted file mode 100644
index 575d18b1..00000000
--- a/tests/testdata/python2/data/package/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-"""package's __init__ file"""
-
-
-from . import subpackage
diff --git a/tests/testdata/python2/data/package/absimport.py b/tests/testdata/python2/data/package/absimport.py
deleted file mode 100644
index 33ed117c..00000000
--- a/tests/testdata/python2/data/package/absimport.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from __future__ import absolute_import, print_function
-import import_package_subpackage_module # fail
-print(import_package_subpackage_module)
-
-from . import hello as hola
-
diff --git a/tests/testdata/python2/data/package/hello.py b/tests/testdata/python2/data/package/hello.py
deleted file mode 100644
index b154c844..00000000
--- a/tests/testdata/python2/data/package/hello.py
+++ /dev/null
@@ -1,2 +0,0 @@
-"""hello module"""
-
diff --git a/tests/testdata/python2/data/package/import_package_subpackage_module.py b/tests/testdata/python2/data/package/import_package_subpackage_module.py
deleted file mode 100644
index ad442c16..00000000
--- a/tests/testdata/python2/data/package/import_package_subpackage_module.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# pylint: disable-msg=I0011,C0301,W0611
-"""I found some of my scripts trigger off an AttributeError in pylint
-0.8.1 (with common 0.12.0 and astroid 0.13.1).
-
-Traceback (most recent call last):
- File "/usr/bin/pylint", line 4, in ?
- lint.Run(sys.argv[1:])
- File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 729, in __init__
- linter.check(args)
- File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 412, in check
- self.check_file(filepath, modname, checkers)
- File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 426, in check_file
- astroid = self._check_file(filepath, modname, checkers)
- File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 450, in _check_file
- self.check_astroid_module(astroid, checkers)
- File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 494, in check_astroid_module
- self.astroid_events(astroid, [checker for checker in checkers
- File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events
- self.astroid_events(child, checkers, _reversed_checkers)
- File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events
- self.astroid_events(child, checkers, _reversed_checkers)
- File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 508, in astroid_events
- checker.visit(astroid)
- File "/usr/lib/python2.4/site-packages/logilab/astroid/utils.py", line 84, in visit
- method(node)
- File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 295, in visit_import
- self._check_module_attrs(node, module, name_parts[1:])
- File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 357, in _check_module_attrs
- self.add_message('E0611', args=(name, module.name),
-AttributeError: Import instance has no attribute 'name'
-
-
-You can reproduce it by:
-(1) create package structure like the following:
-
-package/
- __init__.py
- subpackage/
- __init__.py
- module.py
-
-(2) in package/__init__.py write:
-
-import subpackage
-
-(3) run pylint with a script importing package.subpackage.module.
-"""
-__revision__ = '$Id: import_package_subpackage_module.py,v 1.1 2005-11-10 15:59:32 syt Exp $'
-import package.subpackage.module
diff --git a/tests/testdata/python2/data/package/subpackage/__init__.py b/tests/testdata/python2/data/package/subpackage/__init__.py
deleted file mode 100644
index dc4782e6..00000000
--- a/tests/testdata/python2/data/package/subpackage/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""package.subpackage"""
diff --git a/tests/testdata/python2/data/package/subpackage/module.py b/tests/testdata/python2/data/package/subpackage/module.py
deleted file mode 100644
index 4b7244ba..00000000
--- a/tests/testdata/python2/data/package/subpackage/module.py
+++ /dev/null
@@ -1 +0,0 @@
-"""package.subpackage.module"""
diff --git a/tests/testdata/python2/data/path_pkg_resources_1/package/__init__.py b/tests/testdata/python2/data/path_pkg_resources_1/package/__init__.py
deleted file mode 100644
index b0d64337..00000000
--- a/tests/testdata/python2/data/path_pkg_resources_1/package/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file
diff --git a/tests/testdata/python2/data/path_pkg_resources_1/package/foo.py b/tests/testdata/python2/data/path_pkg_resources_1/package/foo.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/path_pkg_resources_1/package/foo.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/path_pkg_resources_2/package/__init__.py b/tests/testdata/python2/data/path_pkg_resources_2/package/__init__.py
deleted file mode 100644
index b0d64337..00000000
--- a/tests/testdata/python2/data/path_pkg_resources_2/package/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file
diff --git a/tests/testdata/python2/data/path_pkg_resources_2/package/bar.py b/tests/testdata/python2/data/path_pkg_resources_2/package/bar.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/path_pkg_resources_2/package/bar.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/path_pkg_resources_3/package/__init__.py b/tests/testdata/python2/data/path_pkg_resources_3/package/__init__.py
deleted file mode 100644
index b0d64337..00000000
--- a/tests/testdata/python2/data/path_pkg_resources_3/package/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file
diff --git a/tests/testdata/python2/data/path_pkg_resources_3/package/baz.py b/tests/testdata/python2/data/path_pkg_resources_3/package/baz.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/path_pkg_resources_3/package/baz.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/path_pkgutil_1/package/__init__.py b/tests/testdata/python2/data/path_pkgutil_1/package/__init__.py
deleted file mode 100644
index 0bfb5a62..00000000
--- a/tests/testdata/python2/data/path_pkgutil_1/package/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from pkgutil import extend_path
-__path__ = extend_path(__path__, __name__) \ No newline at end of file
diff --git a/tests/testdata/python2/data/path_pkgutil_1/package/foo.py b/tests/testdata/python2/data/path_pkgutil_1/package/foo.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/path_pkgutil_1/package/foo.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/path_pkgutil_2/package/__init__.py b/tests/testdata/python2/data/path_pkgutil_2/package/__init__.py
deleted file mode 100644
index 0bfb5a62..00000000
--- a/tests/testdata/python2/data/path_pkgutil_2/package/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from pkgutil import extend_path
-__path__ = extend_path(__path__, __name__) \ No newline at end of file
diff --git a/tests/testdata/python2/data/path_pkgutil_2/package/bar.py b/tests/testdata/python2/data/path_pkgutil_2/package/bar.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/path_pkgutil_2/package/bar.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/path_pkgutil_3/package/__init__.py b/tests/testdata/python2/data/path_pkgutil_3/package/__init__.py
deleted file mode 100644
index 0bfb5a62..00000000
--- a/tests/testdata/python2/data/path_pkgutil_3/package/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from pkgutil import extend_path
-__path__ = extend_path(__path__, __name__) \ No newline at end of file
diff --git a/tests/testdata/python2/data/path_pkgutil_3/package/baz.py b/tests/testdata/python2/data/path_pkgutil_3/package/baz.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/path_pkgutil_3/package/baz.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/recursion.py b/tests/testdata/python2/data/recursion.py
deleted file mode 100644
index a34dad32..00000000
--- a/tests/testdata/python2/data/recursion.py
+++ /dev/null
@@ -1,3 +0,0 @@
-""" For issue #25 """
-class Base(object):
- pass \ No newline at end of file
diff --git a/tests/testdata/python2/data/tmp__init__.py b/tests/testdata/python2/data/tmp__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/tmp__init__.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/unicode_package/__init__.py b/tests/testdata/python2/data/unicode_package/__init__.py
deleted file mode 100644
index 713e5591..00000000
--- a/tests/testdata/python2/data/unicode_package/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-x = "șțîâ" \ No newline at end of file
diff --git a/tests/testdata/python2/data/unicode_package/core/__init__.py b/tests/testdata/python2/data/unicode_package/core/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/tests/testdata/python2/data/unicode_package/core/__init__.py
+++ /dev/null
diff --git a/tests/testdata/python2/data/find_test/__init__.py b/tests/testdata/python3/data/metaclass_recursion/__init__.py
index e69de29b..e69de29b 100644
--- a/tests/testdata/python2/data/find_test/__init__.py
+++ b/tests/testdata/python3/data/metaclass_recursion/__init__.py
diff --git a/tests/testdata/python3/data/metaclass_recursion/monkeypatch.py b/tests/testdata/python3/data/metaclass_recursion/monkeypatch.py
new file mode 100644
index 00000000..757bb3f8
--- /dev/null
+++ b/tests/testdata/python3/data/metaclass_recursion/monkeypatch.py
@@ -0,0 +1,17 @@
+# https://github.com/PyCQA/astroid/issues/749
+# Not an actual module but allows us to reproduce the issue
+from tests.testdata.python3.data.metaclass_recursion import parent
+
+class MonkeyPatchClass(parent.OriginalClass):
+ _original_class = parent.OriginalClass
+
+ @classmethod
+ def patch(cls):
+ if parent.OriginalClass != MonkeyPatchClass:
+ cls._original_class = parent.OriginalClass
+ parent.OriginalClass = MonkeyPatchClass
+
+ @classmethod
+ def unpatch(cls):
+ if parent.OriginalClass == MonkeyPatchClass:
+ parent.OriginalClass = cls._original_class
diff --git a/tests/testdata/python3/data/metaclass_recursion/parent.py b/tests/testdata/python3/data/metaclass_recursion/parent.py
new file mode 100644
index 00000000..5cff73e0
--- /dev/null
+++ b/tests/testdata/python3/data/metaclass_recursion/parent.py
@@ -0,0 +1,3 @@
+# https://github.com/PyCQA/astroid/issues/749
+class OriginalClass:
+ pass
diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py
index 1a082d12..0a833664 100644
--- a/tests/unittest_brain.py
+++ b/tests/unittest_brain.py
@@ -1,20 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2015 raylu <lurayl@gmail.com>
# Copyright (c) 2015 Philip Lorenz <philip@bithub.de>
# Copyright (c) 2016 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2017 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017 David Euresti <github@euresti.com>
# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
+# Copyright (c) 2018 Tomas Gavenciak <gavento@ucw.cz>
+# Copyright (c) 2018 David Poirier <david-poirier-csn@users.noreply.github.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
# Copyright (c) 2018 Ioana Tagirta <ioana.tagirta@gmail.com>
# Copyright (c) 2018 Ahmed Azzaoui <ahmed.azzaoui@engie.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Tomas Novak <ext.Tomas.Novak@skoda-auto.cz>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
+# Copyright (c) 2019 Grygorii Iermolenko <gyermolenko@gmail.com>
+# Copyright (c) 2019 Bryce Guinta <bryce.guinta@protonmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -323,6 +331,18 @@ class NamedTupleTest(unittest.TestCase):
self.assertIsInstance(inferred.bases[0], astroid.Name)
self.assertEqual(inferred.bases[0].name, "tuple")
+ def test_invalid_label_does_not_crash_inference(self):
+ code = """
+ import collections
+ a = collections.namedtuple( 'a', ['b c'] )
+ a
+ """
+ node = builder.extract_node(code)
+ inferred = next(node.infer())
+ assert isinstance(inferred, astroid.ClassDef)
+ assert "b" not in inferred.locals
+ assert "c" not in inferred.locals
+
class DefaultDictTest(unittest.TestCase):
def test_1(self):
@@ -622,6 +642,21 @@ class EnumBrainTest(unittest.TestCase):
test = next(enumeration.igetattr("test"))
self.assertEqual(test.value, 42)
+ def test_ignores_with_nodes_from_body_of_enum(self):
+ code = """
+ import enum
+
+ class Error(enum.Enum):
+ Foo = "foo"
+ Bar = "bar"
+ with "error" as err:
+ pass
+ """
+ node = builder.extract_node(code)
+ inferred = next(node.infer())
+ assert "err" in inferred.locals
+ assert len(inferred.locals["err"]) == 1
+
def test_enum_multiple_base_classes(self):
module = builder.parse(
"""
@@ -1633,10 +1668,13 @@ class TestLenBuiltinInference:
)
assert next(node.infer()).as_string() == "3"
- @pytest.mark.xfail(reason="Can't retrieve subclassed type value ")
def test_int_subclass_result(self):
- """I am unable to figure out the value of an
- object which subclasses int"""
+ """Check that a subclass of an int can still be inferred
+
+ This test does not properly infer the value passed to the
+ int subclass (5) but still returns a proper integer as we
+ fake the result of the `len()` call.
+ """
node = astroid.extract_node(
"""
class IntSubclass(int):
@@ -1648,7 +1686,7 @@ class TestLenBuiltinInference:
len(F())
"""
)
- assert next(node.infer()).as_string() == "5"
+ assert next(node.infer()).as_string() == "0"
@pytest.mark.xfail(reason="Can't use list special astroid fields")
def test_int_subclass_argument(self):
diff --git a/tests/unittest_brain_numpy_core_fromnumeric.py b/tests/unittest_brain_numpy_core_fromnumeric.py
index 16d73e2e..fd571f30 100644
--- a/tests/unittest_brain_numpy_core_fromnumeric.py
+++ b/tests/unittest_brain_numpy_core_fromnumeric.py
@@ -1,5 +1,6 @@
# -*- encoding=utf-8 -*-
-# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_brain_numpy_core_function_base.py b/tests/unittest_brain_numpy_core_function_base.py
index 06a940c8..109238a2 100644
--- a/tests/unittest_brain_numpy_core_function_base.py
+++ b/tests/unittest_brain_numpy_core_function_base.py
@@ -1,5 +1,6 @@
# -*- encoding=utf-8 -*-
-# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_brain_numpy_core_multiarray.py b/tests/unittest_brain_numpy_core_multiarray.py
index e02db6cc..9e945d21 100644
--- a/tests/unittest_brain_numpy_core_multiarray.py
+++ b/tests/unittest_brain_numpy_core_multiarray.py
@@ -1,5 +1,6 @@
# -*- encoding=utf-8 -*-
-# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_brain_numpy_core_numeric.py b/tests/unittest_brain_numpy_core_numeric.py
index 56b7d0d6..a39fb19e 100644
--- a/tests/unittest_brain_numpy_core_numeric.py
+++ b/tests/unittest_brain_numpy_core_numeric.py
@@ -1,5 +1,6 @@
# -*- encoding=utf-8 -*-
-# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_brain_numpy_core_numerictypes.py b/tests/unittest_brain_numpy_core_numerictypes.py
index 768a5570..db4fdd23 100644
--- a/tests/unittest_brain_numpy_core_numerictypes.py
+++ b/tests/unittest_brain_numpy_core_numerictypes.py
@@ -1,7 +1,8 @@
# -*- encoding=utf-8 -*-
-# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2017-2020 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2017-2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -314,6 +315,34 @@ class NumpyBrainCoreNumericTypesTest(unittest.TestCase):
with self.subTest(attr=attr):
self.assertNotEqual(len(inferred.getattr(attr)), 0)
+ def test_datetime_astype_return(self):
+ """
+ Test that the return of astype method of the datetime object
+ is inferred as a ndarray.
+
+ PyCQA/pylint#3332
+ """
+ node = builder.extract_node(
+ """
+ import numpy as np
+ import datetime
+ test_array = np.datetime64(1, 'us')
+ test_array.astype(datetime.datetime)
+ """
+ )
+ licit_array_types = ".ndarray"
+ inferred_values = list(node.infer())
+ self.assertTrue(
+ len(inferred_values) == 1,
+ msg="Too much inferred value for {:s}".format("datetime64.astype"),
+ )
+ self.assertTrue(
+ inferred_values[-1].pytype() in licit_array_types,
+ msg="Illicit type for {:s} ({})".format(
+ "datetime64.astype", inferred_values[-1].pytype()
+ ),
+ )
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/unittest_brain_numpy_core_umath.py b/tests/unittest_brain_numpy_core_umath.py
index e6fb1c5e..3db3f149 100644
--- a/tests/unittest_brain_numpy_core_umath.py
+++ b/tests/unittest_brain_numpy_core_umath.py
@@ -1,5 +1,6 @@
# -*- encoding=utf-8 -*-
-# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_brain_numpy_ndarray.py b/tests/unittest_brain_numpy_ndarray.py
index d982f7f6..defce47d 100644
--- a/tests/unittest_brain_numpy_ndarray.py
+++ b/tests/unittest_brain_numpy_ndarray.py
@@ -1,7 +1,8 @@
# -*- encoding=utf-8 -*-
-# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2017-2020 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2017-2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -117,6 +118,18 @@ class NumpyBrainNdarrayTest(unittest.TestCase):
)
return node.infer()
+ def _inferred_ndarray_attribute(self, attr_name):
+ node = builder.extract_node(
+ """
+ import numpy as np
+ test_array = np.ndarray((2, 2))
+ test_array.{:s}
+ """.format(
+ attr_name
+ )
+ )
+ return node.infer()
+
def test_numpy_function_calls_inferred_as_ndarray(self):
"""
Test that some calls to numpy functions are inferred as numpy.ndarray
@@ -136,6 +149,25 @@ class NumpyBrainNdarrayTest(unittest.TestCase):
),
)
+ def test_numpy_ndarray_attribute_inferred_as_ndarray(self):
+ """
+ Test that some numpy ndarray attributes are inferred as numpy.ndarray
+ """
+ licit_array_types = ".ndarray"
+ for attr_ in ("real", "imag"):
+ with self.subTest(typ=attr_):
+ inferred_values = list(self._inferred_ndarray_attribute(attr_))
+ self.assertTrue(
+ len(inferred_values) == 1,
+ msg="Too much inferred value for {:s}".format(attr_),
+ )
+ self.assertTrue(
+ inferred_values[-1].pytype() in licit_array_types,
+ msg="Illicit type for {:s} ({})".format(
+ attr_, inferred_values[-1].pytype()
+ ),
+ )
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/unittest_brain_numpy_random_mtrand.py b/tests/unittest_brain_numpy_random_mtrand.py
index 4ca296f1..20a5d310 100644
--- a/tests/unittest_brain_numpy_random_mtrand.py
+++ b/tests/unittest_brain_numpy_random_mtrand.py
@@ -1,7 +1,6 @@
# -*- encoding=utf-8 -*-
-# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_builder.py b/tests/unittest_builder.py
index 9ea597c8..22e49e60 100644
--- a/tests/unittest_builder.py
+++ b/tests/unittest_builder.py
@@ -1,14 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014-2015 Google, Inc.
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
# Copyright (c) 2017 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com>
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_helpers.py b/tests/unittest_helpers.py
index 8ed07ede..ced5fc0a 100644
--- a/tests/unittest_helpers.py
+++ b/tests/unittest_helpers.py
@@ -1,5 +1,6 @@
-# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -192,21 +193,6 @@ class TestHelpers(unittest.TestCase):
self.assertFalse(helpers.is_subtype(cls_a, cls_b))
self.assertFalse(helpers.is_subtype(cls_a, cls_b))
- @test_utils.require_version(maxver="3.0")
- def test_is_subtype_supertype_old_style_classes(self):
- cls_a, cls_b = builder.extract_node(
- """
- class A: #@
- pass
- class B(A): #@
- pass
- """
- )
- self.assertFalse(helpers.is_subtype(cls_a, cls_b))
- self.assertFalse(helpers.is_subtype(cls_b, cls_a))
- self.assertFalse(helpers.is_supertype(cls_a, cls_b))
- self.assertFalse(helpers.is_supertype(cls_b, cls_a))
-
def test_is_subtype_supertype_mro_error(self):
cls_e, cls_f = builder.extract_node(
"""
diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py
index b541eb28..b3df5f6c 100644
--- a/tests/unittest_inference.py
+++ b/tests/unittest_inference.py
@@ -2,7 +2,7 @@
# Copyright (c) 2006-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2007 Marien Zwart <marienz@gentoo.org>
# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru>
@@ -14,8 +14,14 @@
# Copyright (c) 2017 Calen Pennington <calen.pennington@gmail.com>
# Copyright (c) 2017 David Euresti <david@dropbox.com>
# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
+# Copyright (c) 2018 Daniel Martin <daniel.martin@crowdstrike.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
+# Copyright (c) 2019 Stanislav Levin <slev@altlinux.org>
+# Copyright (c) 2019 David Liu <david@cs.toronto.edu>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -24,6 +30,7 @@
"""
# pylint: disable=too-many-lines
import platform
+import textwrap
from functools import partial
import unittest
from unittest.mock import patch
@@ -499,18 +506,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
self.assertEqual(inferred.name, "set")
self.assertIn("remove", inferred._proxied.locals)
- @test_utils.require_version(maxver="3.0")
- def test_unicode_type(self):
- code = '''u = u""'''
- ast = parse(code, __name__)
- n = ast["u"]
- inferred = next(n.infer())
- self.assertIsInstance(inferred, nodes.Const)
- self.assertIsInstance(inferred, Instance)
- self.assertEqual(inferred.name, "unicode")
- self.assertIn("lower", inferred._proxied.locals)
-
- @unittest.expectedFailure
+ @pytest.mark.xfail(reason="Descriptors are not properly inferred as callable")
def test_descriptor_are_callable(self):
code = """
class A:
@@ -1701,7 +1697,9 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
pass
"""
)
- self.assertIn("object", [base.name for base in klass.ancestors()])
+ ancestors = [base.name for base in klass.ancestors()]
+ expected_subset = ["datetime", "date"]
+ self.assertEqual(expected_subset, ancestors[:2])
def test_stop_iteration_leak(self):
code = """
@@ -3256,7 +3254,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "B")
- @unittest.expectedFailure
+ @pytest.mark.xfail(reason="String interpolation is incorrect for modulo formatting")
def test_string_interpolation(self):
ast_nodes = extract_node(
"""
@@ -3628,7 +3626,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
]
self.assertEqual(titles, ["Catch 22", "Ubik", "Grimus"])
- @unittest.expectedFailure
+ @pytest.mark.xfail(reason="Does not support function metaclasses")
def test_function_metaclasses(self):
# These are not supported right now, although
# they will be in the future.
@@ -3776,7 +3774,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
second = next(ast_nodes[1].infer())
self.assertIsInstance(second, Instance)
- @unittest.expectedFailure
+ @pytest.mark.xfail(reason="Metaclass arguments not inferred as classes")
def test_metaclass_arguments_are_classes_not_instances(self):
ast_node = extract_node(
"""
@@ -3859,19 +3857,6 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
inferred.getattr("teta")
inferred.getattr("a")
- @test_utils.require_version(maxver="3.0")
- def test_delayed_attributes_with_old_style_classes(self):
- ast_node = extract_node(
- """
- class A:
- __slots__ = ('a', )
- a = A()
- a.teta = 42
- a #@
- """
- )
- next(ast_node.infer()).getattr("teta")
-
def test_lambda_as_methods(self):
ast_node = extract_node(
"""
@@ -3906,7 +3891,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 25)
- @unittest.expectedFailure
+ @pytest.mark.xfail(reason="Cannot reuse inner value due to inference context reuse")
def test_inner_value_redefined_by_subclass_with_mro(self):
# This might work, but it currently doesn't due to not being able
# to reuse inference contexts.
@@ -5169,10 +5154,25 @@ def test_builtin_inference_list_of_exceptions():
inferred = next(node.infer())
assert isinstance(inferred, nodes.Tuple)
assert len(inferred.elts) == 2
- assert isinstance(inferred.elts[0], nodes.ClassDef)
- assert inferred.elts[0].name == "ValueError"
- assert isinstance(inferred.elts[1], nodes.ClassDef)
- assert inferred.elts[1].name == "TypeError"
+ assert isinstance(inferred.elts[0], nodes.EvaluatedObject)
+ assert isinstance(inferred.elts[0].value, nodes.ClassDef)
+ assert inferred.elts[0].value.name == "ValueError"
+ assert isinstance(inferred.elts[1], nodes.EvaluatedObject)
+ assert isinstance(inferred.elts[1].value, nodes.ClassDef)
+ assert inferred.elts[1].value.name == "TypeError"
+
+ # Test that inference of evaluated objects returns what is expected
+ first_elem = next(inferred.elts[0].infer())
+ assert isinstance(first_elem, nodes.ClassDef)
+ assert first_elem.name == "ValueError"
+
+ second_elem = next(inferred.elts[1].infer())
+ assert isinstance(second_elem, nodes.ClassDef)
+ assert second_elem.name == "TypeError"
+
+ # Test that as_string() also works
+ as_string = inferred.as_string()
+ assert as_string.strip() == "(ValueError, TypeError)"
@test_utils.require_version(minver="3.6")
@@ -5478,6 +5478,28 @@ def test_property_inference():
assert isinstance(inferred, nodes.FunctionDef)
+def test_property_as_string():
+ code = """
+ class A:
+ @property
+ def test(self):
+ return 42
+
+ A.test #@
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ assert isinstance(inferred, objects.Property)
+ property_body = textwrap.dedent(
+ """
+ @property
+ def test(self):
+ return 42
+ """
+ )
+ assert inferred.as_string().strip() == property_body.strip()
+
+
def test_property_callable_inference():
code = """
class A:
@@ -5517,5 +5539,330 @@ def test_recursion_error_inferring_builtin_containers():
helpers.safe_infer(node.targets[0])
+def test_inferaugassign_picking_parent_instead_of_stmt():
+ code = """
+ from collections import namedtuple
+ SomeClass = namedtuple('SomeClass', ['name'])
+ items = [SomeClass(name='some name')]
+
+ some_str = ''
+ some_str += ', '.join(__(item) for item in items)
+ """
+ # item needs to be inferrd as `SomeClass` but it was inferred
+ # as a string because the entire `AugAssign` node was inferred
+ # as a string.
+ node = extract_node(code)
+ inferred = next(node.infer())
+ assert isinstance(inferred, Instance)
+ assert inferred.name == "SomeClass"
+
+
+def test_classmethod_from_builtins_inferred_as_bound():
+ code = """
+ import builtins
+
+ class Foo():
+ @classmethod
+ def bar1(cls, text):
+ pass
+
+ @builtins.classmethod
+ def bar2(cls, text):
+ pass
+
+ Foo.bar1 #@
+ Foo.bar2 #@
+ """
+ first_node, second_node = extract_node(code)
+ assert isinstance(next(first_node.infer()), BoundMethod)
+ assert isinstance(next(second_node.infer()), BoundMethod)
+
+
+def test_infer_dict_passes_context():
+ code = """
+ k = {}
+ (_ for k in __(dict(**k)))
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ assert isinstance(inferred, Instance)
+ assert inferred.qname() == "builtins.dict"
+
+
+@pytest.mark.parametrize(
+ "code,obj,obj_type",
+ [
+ (
+ """
+ def klassmethod1(method):
+ @classmethod
+ def inner(cls):
+ return method(cls)
+ return inner
+
+ class X(object):
+ @klassmethod1
+ def x(cls):
+ return 'X'
+ X.x
+ """,
+ BoundMethod,
+ "classmethod",
+ ),
+ (
+ """
+ def staticmethod1(method):
+ @staticmethod
+ def inner(cls):
+ return method(cls)
+ return inner
+
+ class X(object):
+ @staticmethod1
+ def x(cls):
+ return 'X'
+ X.x
+ """,
+ nodes.FunctionDef,
+ "staticmethod",
+ ),
+ (
+ """
+ def klassmethod1(method):
+ def inner(cls):
+ return method(cls)
+ return classmethod(inner)
+
+ class X(object):
+ @klassmethod1
+ def x(cls):
+ return 'X'
+ X.x
+ """,
+ BoundMethod,
+ "classmethod",
+ ),
+ (
+ """
+ def staticmethod1(method):
+ def inner(cls):
+ return method(cls)
+ return staticmethod(inner)
+
+ class X(object):
+ @staticmethod1
+ def x(cls):
+ return 'X'
+ X.x
+ """,
+ nodes.FunctionDef,
+ "staticmethod",
+ ),
+ ],
+)
+def test_custom_decorators_for_classmethod_and_staticmethods(code, obj, obj_type):
+ node = extract_node(code)
+ inferred = next(node.infer())
+ assert isinstance(inferred, obj)
+ assert inferred.type == obj_type
+
+
+@pytest.mark.skipif(sys.version_info < (3, 8), reason="Needs dataclasses available")
+def test_dataclasses_subscript_inference_recursion_error():
+ code = """
+ from dataclasses import dataclass, replace
+
+ @dataclass
+ class ProxyConfig:
+ auth: str = "/auth"
+
+
+ a = ProxyConfig("")
+ test_dict = {"proxy" : {"auth" : "", "bla" : "f"}}
+
+ foo = test_dict['proxy']
+ replace(a, **test_dict['proxy']) # This fails
+ """
+ node = extract_node(code)
+ # Reproduces only with safe_infer()
+ assert helpers.safe_infer(node) is None
+
+
+def test_self_reference_infer_does_not_trigger_recursion_error():
+ # Prevents https://github.com/PyCQA/pylint/issues/1285
+ code = """
+ def func(elems):
+ return elems
+
+ class BaseModel(object):
+
+ def __init__(self, *args, **kwargs):
+ self._reference = func(*self._reference.split('.'))
+ BaseModel()._reference
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ assert inferred is util.Uninferable
+
+
+def test_inferring_properties_multiple_time_does_not_mutate_locals_multiple_times():
+ code = """
+ class A:
+ @property
+ def a(self):
+ return 42
+
+ A()
+ """
+ node = extract_node(code)
+ # Infer the class
+ cls = next(node.infer())
+ prop, = cls.getattr("a")
+
+ # Try to infer the property function *multiple* times. `A.locals` should be modified only once
+ for _ in range(3):
+ prop.inferred()
+ a_locals = cls.locals["a"]
+ # [FunctionDef, Property]
+ assert len(a_locals) == 2
+
+
+def test_getattr_fails_on_empty_values():
+ code = """
+ import collections
+ collections
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ with pytest.raises(exceptions.InferenceError):
+ next(inferred.igetattr(""))
+
+ with pytest.raises(exceptions.AttributeInferenceError):
+ inferred.getattr("")
+
+
+def test_infer_first_argument_of_static_method_in_metaclass():
+ code = """
+ class My(type):
+ @staticmethod
+ def test(args):
+ args #@
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ assert inferred is util.Uninferable
+
+
+def test_recursion_error_metaclass_monkeypatching():
+ module = resources.build_file(
+ "data/metaclass_recursion/monkeypatch.py", "data.metaclass_recursion"
+ )
+ cls = next(module.igetattr("MonkeyPatchClass"))
+ assert isinstance(cls, nodes.ClassDef)
+ assert cls.declared_metaclass() is None
+
+
+@pytest.mark.xfail(reason="Cannot fully infer all the base classes properly.")
+def test_recursion_error_self_reference_type_call():
+ # Fix for https://github.com/PyCQA/astroid/issues/199
+ code = """
+ class A(object):
+ pass
+ class SomeClass(object):
+ route_class = A
+ def __init__(self):
+ self.route_class = type('B', (self.route_class, ), {})
+ self.route_class() #@
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ assert isinstance(inferred, Instance)
+ assert inferred.name == "B"
+ # TODO: Cannot infer [B, A, object] but at least the recursion error is gone.
+ assert [cls.name for cls in inferred.mro()] == ["B", "A", "object"]
+
+
+def test_allow_retrieving_instance_attrs_and_special_attrs_for_functions():
+ code = """
+ class A:
+ def test(self):
+ "a"
+ # Add `__doc__` to `FunctionDef.instance_attrs` via an `AugAssign`
+ test.__doc__ += 'b'
+ test #@
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ attrs = inferred.getattr("__doc__")
+ # One from the `AugAssign`, one from the special attributes
+ assert len(attrs) == 2
+
+
+def test_implicit_parameters_bound_method():
+ code = """
+ class A(type):
+ @classmethod
+ def test(cls, first): return first
+ def __new__(cls, name, bases, dictionary):
+ return super().__new__(cls, name, bases, dictionary)
+
+ A.test #@
+ A.__new__ #@
+ """
+ test, dunder_new = extract_node(code)
+ test = next(test.infer())
+ assert isinstance(test, BoundMethod)
+ assert test.implicit_parameters() == 1
+
+ dunder_new = next(dunder_new.infer())
+ assert isinstance(dunder_new, BoundMethod)
+ assert dunder_new.implicit_parameters() == 0
+
+
+def test_super_inference_of_abstract_property():
+ code = """
+ from abc import abstractmethod
+
+ class A:
+ @property
+ def test(self):
+ return "super"
+
+ class C:
+ @property
+ @abstractmethod
+ def test(self):
+ "abstract method"
+
+ class B(A, C):
+
+ @property
+ def test(self):
+ super() #@
+
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ test = inferred.getattr("test")
+ assert len(test) == 2
+
+
+def test_infer_generated_setter():
+ code = """
+ class A:
+ @property
+ def test(self):
+ pass
+ A.test.setter
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ assert isinstance(inferred, nodes.FunctionDef)
+ assert isinstance(inferred.args, nodes.Arguments)
+ # This line used to crash because property generated functions
+ # did not have args properly set
+ assert list(inferred.nodes_of_class(nodes.Const)) == []
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/unittest_lookup.py b/tests/unittest_lookup.py
index 58effd53..84fd5433 100644
--- a/tests/unittest_lookup.py
+++ b/tests/unittest_lookup.py
@@ -1,8 +1,10 @@
# Copyright (c) 2007-2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
-# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_manager.py b/tests/unittest_manager.py
index b56a0580..d7878b59 100644
--- a/tests/unittest_manager.py
+++ b/tests/unittest_manager.py
@@ -1,12 +1,17 @@
+# -*- coding: utf-8 -*-
# Copyright (c) 2006, 2009-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2013 AndroWiiid <androwiiid@gmail.com>
-# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2017 Chris Philip <chrisp533@gmail.com>
# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
# Copyright (c) 2017 ioanatia <ioanatia@users.noreply.github.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
+# Copyright (c) 2020 Anubhav <35621759+anubh-v@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -67,6 +72,17 @@ class AstroidManagerTest(
exceptions.AstroidBuildingError, self.manager.ast_from_file, "unhandledName"
)
+ def test_ast_from_string(self):
+ filepath = unittest.__file__
+ dirname = os.path.dirname(filepath)
+ modname = os.path.basename(dirname)
+ with open(filepath, "r") as file:
+ data = file.read()
+ ast = self.manager.ast_from_string(data, modname, filepath)
+ self.assertEqual(ast.name, "unittest")
+ self.assertEqual(ast.file, filepath)
+ self.assertIn("unittest", self.manager.astroid_cache)
+
def test_do_not_expose_main(self):
obj = self.manager.ast_from_module_name("__main__")
self.assertEqual(obj.name, "__main__")
diff --git a/tests/unittest_modutils.py b/tests/unittest_modutils.py
index 1747dc22..b5c41bf0 100644
--- a/tests/unittest_modutils.py
+++ b/tests/unittest_modutils.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014-2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
@@ -7,6 +7,9 @@
# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2018 Mario Corchero <mcorcherojim@bloomberg.net>
# Copyright (c) 2018 Mario Corchero <mariocj89@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
+# Copyright (c) 2019 markmcclain <markmcclain@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -127,14 +130,6 @@ class ModPathFromFileTest(unittest.TestCase):
["xml", "etree", "ElementTree"],
)
- def test_knownValues_modpath_from_file_2(self):
- self.assertEqual(
- modutils.modpath_from_file(
- "unittest_modutils.py", {os.getcwd(): "arbitrary.pkg"}
- ),
- ["arbitrary", "pkg", "unittest_modutils"],
- )
-
def test_raise_modpath_from_file_Exception(self):
self.assertRaises(Exception, modutils.modpath_from_file, "/turlututu")
diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py
index 5fbef628..5b6a39e3 100644
--- a/tests/unittest_nodes.py
+++ b/tests/unittest_nodes.py
@@ -1,6 +1,6 @@
# Copyright (c) 2006-2007, 2009-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
-# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
@@ -8,9 +8,13 @@
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
# Copyright (c) 2017 rr- <rr-@sakuya.pl>
# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
+# Copyright (c) 2018 Serhiy Storchaka <storchaka@gmail.com>
# Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
+# Copyright (c) 2019-2020 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Alex Hall <alex.mojaki@gmail.com>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -22,6 +26,7 @@ import sys
import textwrap
import unittest
import copy
+import platform
import pytest
import six
@@ -147,21 +152,20 @@ def function(var):
ast = abuilder.string_build(code)
self.assertEqual(ast.as_string(), code)
- @test_utils.require_version("3.0")
- @unittest.expectedFailure
def test_3k_annotations_and_metaclass(self):
- code_annotations = textwrap.dedent(
- '''
- def function(var:int):
+ code = '''
+ def function(var: int):
nonlocal counter
class Language(metaclass=Natural):
"""natural language"""
'''
- )
+ code_annotations = textwrap.dedent(code)
+ # pylint: disable=line-too-long
+ expected = 'def function(var: int):\n nonlocal counter\n\n\nclass Language(metaclass=Natural):\n """natural language"""'
ast = abuilder.string_build(code_annotations)
- self.assertEqual(ast.as_string(), code_annotations)
+ self.assertEqual(ast.as_string().strip(), expected)
def test_ellipsis(self):
ast = abuilder.string_build("a[...]").body[0]
@@ -254,7 +258,12 @@ class D(metaclass=abc.ABCMeta):
ast = abuilder.string_build(code)
self.assertEqual(ast.as_string().strip(), code.strip())
- @pytest.mark.skipif(sys.version_info[:2] < (3, 6), reason="needs f-string support")
+ # This test is disabled on PyPy because we cannot get a proper release on TravisCI that has
+ # proper support for f-strings (we need 7.2 at least)
+ @pytest.mark.skipif(
+ sys.version_info[:2] < (3, 6) or platform.python_implementation() == "PyPy",
+ reason="Needs f-string support.",
+ )
def test_f_strings(self):
code = r'''
a = f"{'a'}"
@@ -1131,6 +1140,47 @@ def test_type_comments_arguments():
assert actual_arg.as_string() == expected_arg
+@pytest.mark.skipif(
+ not PY38, reason="needs to be able to parse positional only arguments"
+)
+def test_type_comments_posonly_arguments():
+ module = builder.parse(
+ """
+ def f_arg_comment(
+ a, # type: int
+ b, # type: int
+ /,
+ c, # type: Optional[int]
+ d, # type: Optional[int]
+ *,
+ e, # type: float
+ f, # type: float
+ ):
+ # type: (...) -> None
+ pass
+ """
+ )
+ expected_annotations = [
+ [["int", "int"], ["Optional[int]", "Optional[int]"], ["float", "float"]]
+ ]
+ for node, expected_types in zip(module.body, expected_annotations):
+ assert len(node.type_comment_args) == 1
+ if PY38:
+ assert isinstance(node.type_comment_args[0], astroid.Const)
+ assert node.type_comment_args[0].value == Ellipsis
+ else:
+ assert isinstance(node.type_comment_args[0], astroid.Ellipsis)
+ type_comments = [
+ node.args.type_comment_posonlyargs,
+ node.args.type_comment_args,
+ node.args.type_comment_kwonlyargs,
+ ]
+ for expected_args, actual_args in zip(expected_types, type_comments):
+ assert len(expected_args) == len(actual_args)
+ for expected_arg, actual_arg in zip(expected_args, actual_args):
+ assert actual_arg.as_string() == expected_arg
+
+
def test_is_generator_for_yield_assignments():
node = astroid.extract_node(
"""
@@ -1285,5 +1335,30 @@ def test_const_itered():
assert [elem.value for elem in itered] == list("string")
+def test_is_generator_for_yield_in_while():
+ code = """
+ def paused_iter(iterable):
+ while True:
+ # Continue to yield the same item until `next(i)` or `i.send(False)`
+ while (yield value):
+ pass
+ """
+ node = astroid.extract_node(code)
+ assert bool(node.is_generator())
+
+
+def test_is_generator_for_yield_in_if():
+ code = """
+ import asyncio
+
+ def paused_iter(iterable):
+ if (yield from asyncio.sleep(0.01)):
+ pass
+ return
+ """
+ node = astroid.extract_node(code)
+ assert bool(node.is_generator())
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/unittest_object_model.py b/tests/unittest_object_model.py
index ac42485a..5301a992 100644
--- a/tests/unittest_object_model.py
+++ b/tests/unittest_object_model.py
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2016-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2016-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -58,7 +59,7 @@ class InstanceModelTest(unittest.TestCase):
self.assertIsInstance(attr, astroid.Const)
self.assertEqual(attr.value, 42)
- @unittest.expectedFailure
+ @pytest.mark.xfail(reason="Instance lookup cannot override object model")
def test_instance_local_attributes_overrides_object_model(self):
# The instance lookup needs to be changed in order for this to work.
ast_node = builder.extract_node(
@@ -144,30 +145,6 @@ class ClassModelTest(unittest.TestCase):
self.assertIsInstance(inferred, astroid.Const)
self.assertEqual(inferred.value, "first")
- @test_utils.require_version(maxver="3.0")
- def test__mro__old_style(self):
- ast_node = builder.extract_node(
- """
- class A:
- pass
- A.__mro__
- """
- )
- with self.assertRaises(exceptions.InferenceError):
- next(ast_node.infer())
-
- @test_utils.require_version(maxver="3.0")
- def test__subclasses__old_style(self):
- ast_node = builder.extract_node(
- """
- class A:
- pass
- A.__subclasses__
- """
- )
- with self.assertRaises(exceptions.InferenceError):
- next(ast_node.infer())
-
def test_class_model_correct_mro_subclasses_proxied(self):
ast_nodes = builder.extract_node(
"""
@@ -354,7 +331,7 @@ class FunctionModelTest(unittest.TestCase):
inferred = next(node.infer())
assert inferred is util.Uninferable
- @unittest.expectedFailure
+ @pytest.mark.xfail(reason="Descriptors cannot infer what self is")
def test_descriptor_not_inferrring_self(self):
# We can't infer __get__(X, Y)() when the bounded function
# uses self, because of the tree's parent not being propagating good enough.
@@ -506,37 +483,6 @@ class FunctionModelTest(unittest.TestCase):
self.assertIsInstance(kwdefaults, astroid.Dict)
# self.assertEqual(kwdefaults.getitem('f').value, 'lala')
- @test_utils.require_version(maxver="3.0")
- def test_function_model_for_python2(self):
- ast_nodes = builder.extract_node(
- """
- def test(a=1):
- "a"
-
- test.func_name #@
- test.func_doc #@
- test.func_dict #@
- test.func_globals #@
- test.func_defaults #@
- test.func_code #@
- test.func_closure #@
- """
- )
- name = next(ast_nodes[0].infer())
- self.assertIsInstance(name, astroid.Const)
- self.assertEqual(name.value, "test")
- doc = next(ast_nodes[1].infer())
- self.assertIsInstance(doc, astroid.Const)
- self.assertEqual(doc.value, "a")
- pydict = next(ast_nodes[2].infer())
- self.assertIsInstance(pydict, astroid.Dict)
- pyglobals = next(ast_nodes[3].infer())
- self.assertIsInstance(pyglobals, astroid.Dict)
- defaults = next(ast_nodes[4].infer())
- self.assertIsInstance(defaults, astroid.Tuple)
- for node in ast_nodes[5:]:
- self.assertIs(next(node.infer()), astroid.Uninferable)
-
@test_utils.require_version(minver="3.8")
def test_annotation_positional_only(self):
ast_node = builder.extract_node(
@@ -640,6 +586,17 @@ class ExceptionModelTest(unittest.TestCase):
assert isinstance(inferred, astroid.Const)
assert inferred.value == value
+ def test_unicodedecodeerror(self):
+ code = """
+ try:
+ raise UnicodeDecodeError("utf-8", "blob", 0, 1, "reason")
+ except UnicodeDecodeError as error:
+ error.object[:1] #@
+ """
+ node = builder.extract_node(code)
+ inferred = next(node.infer())
+ assert isinstance(inferred, astroid.Const)
+
def test_import_error(self):
ast_nodes = builder.extract_node(
"""
diff --git a/tests/unittest_objects.py b/tests/unittest_objects.py
index 8c56c9fd..a9de6eb2 100644
--- a/tests/unittest_objects.py
+++ b/tests/unittest_objects.py
@@ -1,6 +1,7 @@
-# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -96,28 +97,6 @@ class SuperTests(unittest.TestCase):
self.assertIsInstance(second, bases.Instance)
self.assertEqual(second.qname(), "%s.super" % bases.BUILTINS)
- @test_utils.require_version(maxver="3.0")
- def test_super_on_old_style_class(self):
- # super doesn't work on old style class, but leave
- # that as an error for pylint. We'll infer Super objects,
- # but every call will result in a failure at some point.
- node = builder.extract_node(
- """
- class OldStyle:
- def __init__(self):
- super(OldStyle, self) #@
- """
- )
- old = next(node.infer())
- self.assertIsInstance(old, objects.Super)
- self.assertIsInstance(old.mro_pointer, nodes.ClassDef)
- self.assertEqual(old.mro_pointer.name, "OldStyle")
- with self.assertRaises(exceptions.SuperError) as cm:
- old.super_mro()
- self.assertEqual(
- str(cm.exception), "Unable to call super on old-style classes."
- )
-
@test_utils.require_version(minver="3.0")
def test_no_arguments_super(self):
ast_nodes = builder.extract_node(
diff --git a/tests/unittest_protocols.py b/tests/unittest_protocols.py
index 1b70a423..babff51e 100644
--- a/tests/unittest_protocols.py
+++ b/tests/unittest_protocols.py
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2015-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_python3.py b/tests/unittest_python3.py
index bc450227..b1759f03 100644
--- a/tests/unittest_python3.py
+++ b/tests/unittest_python3.py
@@ -5,8 +5,9 @@
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com>
+# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_raw_building.py b/tests/unittest_raw_building.py
index db32e7da..f782cdb2 100644
--- a/tests/unittest_raw_building.py
+++ b/tests/unittest_raw_building.py
@@ -1,9 +1,10 @@
# Copyright (c) 2013 AndroWiiid <androwiiid@gmail.com>
-# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_regrtest.py b/tests/unittest_regrtest.py
index 91fa8f16..79a86cb2 100644
--- a/tests/unittest_regrtest.py
+++ b/tests/unittest_regrtest.py
@@ -1,12 +1,13 @@
# Copyright (c) 2006-2008, 2010-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2007 Marien Zwart <marienz@gentoo.org>
# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -306,25 +307,41 @@ def test():
)
next(node.infer())
- @require_version(maxver="3.0")
- def test_reassignment_in_except_handler(self):
- node = extract_node(
- """
- import exceptions
- try:
- {}["a"]
- except KeyError, exceptions.IndexError:
- pass
-
- IndexError #@
+ def test_regression_inference_of_self_in_lambda(self):
+ code = """
+ class A:
+ @b(lambda self: __(self))
+ def d(self):
+ pass
"""
- )
- self.assertEqual(len(node.inferred()), 1)
+ node = extract_node(code)
+ inferred = next(node.infer())
+ assert isinstance(inferred, Instance)
+ assert inferred.qname() == ".A"
class Whatever:
a = property(lambda x: x, lambda x: x)
+def test_ancestor_looking_up_redefined_function():
+ code = """
+ class Foo:
+ def _format(self):
+ pass
+
+ def format(self):
+ self.format = self._format
+ self.format()
+ Foo
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ ancestor = next(inferred.ancestors())
+ _, found = ancestor.lookup("format")
+ assert len(found) == 1
+ assert isinstance(found[0], nodes.FunctionDef)
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/unittest_scoped_nodes.py b/tests/unittest_scoped_nodes.py
index d70b7351..c4597fa6 100644
--- a/tests/unittest_scoped_nodes.py
+++ b/tests/unittest_scoped_nodes.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2011, 2013-2015 Google, Inc.
-# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2013 Phil Schaf <flying-sheep@web.de>
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
@@ -9,11 +9,16 @@
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
# Copyright (c) 2015 Philip Lorenz <philip@bithub.de>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
+# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
+# Copyright (c) 2018-2019 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com>
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
+# Copyright (c) 2019 Peter de Blanc <peter@standard.ai>
+# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
@@ -1005,19 +1010,6 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
self.assertEqual(astroid["g2"].fromlineno, 9)
self.assertEqual(astroid["g2"].tolineno, 10)
- @test_utils.require_version(maxver="3.0")
- def test_simple_metaclass(self):
- astroid = builder.parse(
- """
- class Test(object):
- __metaclass__ = type
- """
- )
- klass = astroid["Test"]
- metaclass = klass.metaclass()
- self.assertIsInstance(metaclass, scoped_nodes.ClassDef)
- self.assertEqual(metaclass.name, "type")
-
def test_metaclass_error(self):
astroid = builder.parse(
"""
@@ -1028,21 +1020,6 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
klass = astroid["Test"]
self.assertFalse(klass.metaclass())
- @test_utils.require_version(maxver="3.0")
- def test_metaclass_imported(self):
- astroid = builder.parse(
- """
- from abc import ABCMeta
- class Test(object):
- __metaclass__ = ABCMeta
- """
- )
- klass = astroid["Test"]
-
- metaclass = klass.metaclass()
- self.assertIsInstance(metaclass, scoped_nodes.ClassDef)
- self.assertEqual(metaclass.name, "ABCMeta")
-
def test_metaclass_yes_leak(self):
astroid = builder.parse(
"""
@@ -1056,100 +1033,6 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
klass = astroid["Meta"]
self.assertIsNone(klass.metaclass())
- @test_utils.require_version(maxver="3.0")
- def test_newstyle_and_metaclass_good(self):
- astroid = builder.parse(
- """
- from abc import ABCMeta
- class Test:
- __metaclass__ = ABCMeta
- """
- )
- klass = astroid["Test"]
- self.assertTrue(klass.newstyle)
- self.assertEqual(klass.metaclass().name, "ABCMeta")
- astroid = builder.parse(
- """
- from abc import ABCMeta
- __metaclass__ = ABCMeta
- class Test:
- pass
- """
- )
- klass = astroid["Test"]
- self.assertTrue(klass.newstyle)
- self.assertEqual(klass.metaclass().name, "ABCMeta")
-
- @test_utils.require_version(maxver="3.0")
- def test_nested_metaclass(self):
- astroid = builder.parse(
- """
- from abc import ABCMeta
- class A(object):
- __metaclass__ = ABCMeta
- class B: pass
-
- __metaclass__ = ABCMeta
- class C:
- __metaclass__ = type
- class D: pass
- """
- )
- a = astroid["A"]
- b = a.locals["B"][0]
- c = astroid["C"]
- d = c.locals["D"][0]
- self.assertEqual(a.metaclass().name, "ABCMeta")
- self.assertFalse(b.newstyle)
- self.assertIsNone(b.metaclass())
- self.assertEqual(c.metaclass().name, "type")
- self.assertEqual(d.metaclass().name, "ABCMeta")
-
- @test_utils.require_version(maxver="3.0")
- def test_parent_metaclass(self):
- astroid = builder.parse(
- """
- from abc import ABCMeta
- class Test:
- __metaclass__ = ABCMeta
- class SubTest(Test): pass
- """
- )
- klass = astroid["SubTest"]
- self.assertTrue(klass.newstyle)
- metaclass = klass.metaclass()
- self.assertIsInstance(metaclass, scoped_nodes.ClassDef)
- self.assertEqual(metaclass.name, "ABCMeta")
-
- @test_utils.require_version(maxver="3.0")
- def test_metaclass_ancestors(self):
- astroid = builder.parse(
- """
- from abc import ABCMeta
-
- class FirstMeta(object):
- __metaclass__ = ABCMeta
-
- class SecondMeta(object):
- __metaclass__ = type
-
- class Simple(object):
- pass
-
- class FirstImpl(FirstMeta): pass
- class SecondImpl(FirstImpl): pass
- class ThirdImpl(Simple, SecondMeta):
- pass
- """
- )
- classes = {"ABCMeta": ("FirstImpl", "SecondImpl"), "type": ("ThirdImpl",)}
- for metaclass, names in classes.items():
- for name in names:
- impl = astroid[name]
- meta = impl.metaclass()
- self.assertIsInstance(meta, nodes.ClassDef)
- self.assertEqual(meta.name, metaclass)
-
def test_metaclass_type(self):
klass = builder.extract_node(
"""
@@ -1314,33 +1197,6 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
else:
self.assertEqual(list(expected_value), [node.value for node in slots])
- @test_utils.require_version(maxver="3.0")
- def test_slots_py2(self):
- module = builder.parse(
- """
- class UnicodeSlots(object):
- __slots__ = (u"a", u"b", "c")
- """
- )
- slots = module["UnicodeSlots"].slots()
- self.assertEqual(len(slots), 3)
- self.assertEqual(slots[0].value, "a")
- self.assertEqual(slots[1].value, "b")
- self.assertEqual(slots[2].value, "c")
-
- @test_utils.require_version(maxver="3.0")
- def test_slots_py2_not_implemented(self):
- module = builder.parse(
- """
- class OldStyle:
- __slots__ = ("a", "b")
- """
- )
- msg = "The concept of slots is undefined for old-style classes."
- with self.assertRaises(NotImplementedError) as cm:
- module["OldStyle"].slots()
- self.assertEqual(str(cm.exception), msg)
-
def test_slots_for_dict_keys(self):
module = builder.parse(
"""
@@ -1397,73 +1253,23 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
cls = module["B"]
self.assertIsNone(cls.slots())
- def assertEqualMro(self, klass, expected_mro):
- self.assertEqual([member.name for member in klass.mro()], expected_mro)
+ def test_slots_added_dynamically_still_inferred(self):
+ code = """
+ class NodeBase(object):
+ __slots__ = "a", "b"
- @test_utils.require_version(maxver="3.0")
- def test_no_mro_for_old_style(self):
- node = builder.extract_node(
- """
- class Old: pass"""
- )
- with self.assertRaises(NotImplementedError) as cm:
- node.mro()
- self.assertEqual(
- str(cm.exception), "Could not obtain mro for " "old-style classes."
- )
+ if Options.isFullCompat():
+ __slots__ += ("c",)
- @test_utils.require_version(maxver="3.0")
- def test_mro_for_classes_with_old_style_in_mro(self):
- node = builder.extract_node(
- """
- class Factory:
- pass
- class ClientFactory(Factory):
- pass
- class ReconnectingClientFactory(ClientFactory):
- pass
- class WebSocketAdapterFactory(object):
- pass
- class WebSocketClientFactory(WebSocketAdapterFactory, ClientFactory):
- pass
- class WampWebSocketClientFactory(WebSocketClientFactory):
- pass
- class RetryFactory(WampWebSocketClientFactory, ReconnectingClientFactory):
- pas
"""
- )
- self.assertEqualMro(
- node,
- [
- "RetryFactory",
- "WampWebSocketClientFactory",
- "WebSocketClientFactory",
- "WebSocketAdapterFactory",
- "object",
- "ReconnectingClientFactory",
- "ClientFactory",
- "Factory",
- ],
- )
+ node = builder.extract_node(code)
+ inferred = next(node.infer())
+ slots = inferred.slots()
+ assert len(slots) == 3, slots
+ assert [slot.value for slot in slots] == ["a", "b", "c"]
- @test_utils.require_version(maxver="3.0")
- def test_combined_newstyle_oldstyle_in_mro(self):
- node = builder.extract_node(
- """
- class Old:
- pass
- class New(object):
- pass
- class New1(object):
- pass
- class New2(New, New1):
- pass
- class NewOld(New2, Old): #@
- pass
- """
- )
- self.assertEqualMro(node, ["NewOld", "New2", "New", "New1", "object", "Old"])
- self.assertTrue(node.newstyle)
+ def assertEqualMro(self, klass, expected_mro):
+ self.assertEqual([member.name for member in klass.mro()], expected_mro)
def test_with_metaclass_mro(self):
astroid = builder.parse(
@@ -1741,15 +1547,6 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
static = next(acls.igetattr("static"))
self.assertIsInstance(static, scoped_nodes.FunctionDef)
- @test_utils.require_version(maxver="3.0")
- def test_implicit_metaclass_is_none(self):
- cls = builder.extract_node(
- """
- class A: pass
- """
- )
- self.assertIsNone(cls.implicit_metaclass())
-
def test_local_attr_invalid_mro(self):
cls = builder.extract_node(
"""
@@ -2021,6 +1818,12 @@ def test_metaclass_cannot_infer_call_yields_an_instance():
pass
"""
),
+ textwrap.dedent(
+ """
+ def __init__(self: int, other: float, /, **kw):
+ pass
+ """
+ ),
],
)
@test_utils.require_version("3.8")
diff --git a/tests/unittest_transforms.py b/tests/unittest_transforms.py
index 5df7ffad..922f10f9 100644
--- a/tests/unittest_transforms.py
+++ b/tests/unittest_transforms.py
@@ -1,7 +1,8 @@
-# Copyright (c) 2015-2017 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tests/unittest_utils.py b/tests/unittest_utils.py
index 3ae3d6b7..428026b1 100644
--- a/tests/unittest_utils.py
+++ b/tests/unittest_utils.py
@@ -1,8 +1,9 @@
# Copyright (c) 2008-2010, 2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
# Copyright (c) 2016 Dave Baum <dbaum@google.com>
+# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/tox.ini b/tox.ini
index 0b55bb6b..3ec33526 100644
--- a/tox.ini
+++ b/tox.ini
@@ -25,7 +25,7 @@ deps =
python-dateutil
pypy: singledispatch
six~=1.12
- wrapt==1.11.*
+ wrapt~=1.11
coverage<5
setenv =