Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-astroid for openSUSE:Factory checked in at 2022-09-25 15:34:29 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-astroid (Old) and /work/SRC/openSUSE:Factory/.python-astroid.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-astroid" Sun Sep 25 15:34:29 2022 rev:37 rq:1005710 version:2.12.10 Changes: -------- --- /work/SRC/openSUSE:Factory/python-astroid/python-astroid.changes 2022-08-30 14:48:35.583984634 +0200 +++ /work/SRC/openSUSE:Factory/.python-astroid.new.2275/python-astroid.changes 2022-09-25 15:34:33.931516349 +0200 @@ -1,0 +2,39 @@ +Fri Sep 23 20:40:39 UTC 2022 - Ben Greiner <c...@bnavigator.de> + +- Update to v2.12.10 + * Fixed a crash when introspecting modules compiled by cffi. + * decorators.cached now gets its cache cleared by calling + AstroidManager.clear_cache. +- Release v2.12.9 + * Fixed creation of the __init__ of dataclassess with multiple + inheritance. + * Fixed a crash on namedtuples that use typename to specify + their name. + +------------------------------------------------------------------- +Thu Sep 22 22:50:11 UTC 2022 - Yogalakshmi Arunachalam <yarunacha...@suse.com> + +- Update to Version 2.12.8 + * Fixed a crash in the ``dataclass`` brain for ``InitVars`` without subscript typing. + Closes PyCQA/pylint#7422 + * Fixed parsing of default values in ``dataclass`` attributes. + Closes PyCQA/pylint#7425 + +- Update to Version 2.12.7 + * Fixed a crash in the ``dataclass`` brain for uninferable bases. + Closes PyCQA/pylint#7418 + +- Update to Version 2.12.6 + * Fix a crash involving ``Uninferable`` arguments to ``namedtuple()``. + Closes PyCQA/pylint#7375 + * The ``dataclass`` brain now understands the ``kw_only`` keyword in dataclass decorators. + Closes PyCQA/pylint#7290 + +- Update to Version 2.12.5 + * Prevent first-party imports from being resolved to `site-packages`. + Refs PyCQA/pylint#7365 + * Fix ``astroid.interpreter._import.util.is_namespace()`` incorrectly + returning ``True`` for frozen stdlib modules on PyPy. + Closes #1755 + +------------------------------------------------------------------- Old: ---- astroid-2.12.4-gh.tar.gz New: ---- astroid-2.12.10-gh.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-astroid.spec ++++++ --- /var/tmp/diff_new_pack.1SlFRm/_old 2022-09-25 15:34:34.511517746 +0200 +++ /var/tmp/diff_new_pack.1SlFRm/_new 2022-09-25 15:34:34.519517765 +0200 @@ -17,7 +17,7 @@ Name: python-astroid -Version: 2.12.4 +Version: 2.12.10 Release: 0 Summary: Representation of Python source as an AST for pylint License: LGPL-2.1-or-later ++++++ astroid-2.12.4-gh.tar.gz -> astroid-2.12.10-gh.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/ChangeLog new/astroid-2.12.10/ChangeLog --- old/astroid-2.12.4/ChangeLog 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/ChangeLog 2022-09-17 18:37:39.000000000 +0200 @@ -8,10 +8,86 @@ +What's New in astroid 2.12.11? +============================== +Release date: TBA + + + +What's New in astroid 2.12.10? +============================== +Release date: 2022-09-17 + + +* Fixed a crash when introspecting modules compiled by `cffi`. + + Closes #1776 + Closes PyCQA/pylint#7399 + +* ``decorators.cached`` now gets its cache cleared by calling ``AstroidManager.clear_cache``. + + Refs #1780 + +What's New in astroid 2.12.9? +============================= +Release date: 2022-09-07 + +* Fixed creation of the ``__init__`` of ``dataclassess`` with multiple inheritance. + + Closes PyCQA/pylint#7427 + +* Fixed a crash on ``namedtuples`` that use ``typename`` to specify their name. + + Closes PyCQA/pylint#7429 + + + +What's New in astroid 2.12.8? +============================= +Release date: 2022-09-06 + +* Fixed a crash in the ``dataclass`` brain for ``InitVars`` without subscript typing. + + Closes PyCQA/pylint#7422 + +* Fixed parsing of default values in ``dataclass`` attributes. + + Closes PyCQA/pylint#7425 + +What's New in astroid 2.12.7? +============================= +Release date: 2022-09-06 + +* Fixed a crash in the ``dataclass`` brain for uninferable bases. + + Closes PyCQA/pylint#7418 + + +What's New in astroid 2.12.6? +============================= +Release date: 2022-09-05 + +* Fix a crash involving ``Uninferable`` arguments to ``namedtuple()``. + + Closes PyCQA/pylint#7375 + +* The ``dataclass`` brain now understands the ``kw_only`` keyword in dataclass decorators. + + Closes PyCQA/pylint#7290 + + What's New in astroid 2.12.5? ============================= -Release date: TBA +Release date: 2022-08-29 + +* Prevent first-party imports from being resolved to `site-packages`. + + Refs PyCQA/pylint#7365 + +* Fix ``astroid.interpreter._import.util.is_namespace()`` incorrectly + returning ``True`` for frozen stdlib modules on PyPy. + Closes #1755 What's New in astroid 2.12.4? diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/astroid/__pkginfo__.py new/astroid-2.12.10/astroid/__pkginfo__.py --- old/astroid-2.12.4/astroid/__pkginfo__.py 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/astroid/__pkginfo__.py 2022-09-17 18:37:39.000000000 +0200 @@ -2,5 +2,5 @@ # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE # Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt -__version__ = "2.12.4" +__version__ = "2.12.10" version = __version__ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/astroid/_cache.py new/astroid-2.12.10/astroid/_cache.py --- old/astroid-2.12.4/astroid/_cache.py 1970-01-01 01:00:00.000000000 +0100 +++ new/astroid-2.12.10/astroid/_cache.py 2022-09-17 18:37:39.000000000 +0200 @@ -0,0 +1,26 @@ +# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html +# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt + +from __future__ import annotations + +from typing import Any + + +class CacheManager: + """Manager of caches, to be used as a singleton.""" + + def __init__(self) -> None: + self.dict_caches: list[dict[Any, Any]] = [] + + def clear_all_caches(self) -> None: + """Clear all caches.""" + for dict_cache in self.dict_caches: + dict_cache.clear() + + def add_dict_cache(self, cache: dict[Any, Any]) -> None: + """Add a dictionary cache to the manager.""" + self.dict_caches.append(cache) + + +CACHE_MANAGER = CacheManager() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/astroid/brain/brain_dataclasses.py new/astroid-2.12.10/astroid/brain/brain_dataclasses.py --- old/astroid-2.12.4/astroid/brain/brain_dataclasses.py 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/astroid/brain/brain_dataclasses.py 2022-09-17 18:37:39.000000000 +0200 @@ -16,31 +16,16 @@ from __future__ import annotations import sys -from collections.abc import Generator +from collections.abc import Iterator from typing import Tuple, Union -from astroid import bases, context, helpers, inference_tip +from astroid import bases, context, helpers, nodes from astroid.builder import parse from astroid.const import PY39_PLUS, PY310_PLUS -from astroid.exceptions import ( - AstroidSyntaxError, - InferenceError, - MroError, - UseInferenceDefault, -) +from astroid.exceptions import AstroidSyntaxError, InferenceError, UseInferenceDefault +from astroid.inference_tip import inference_tip from astroid.manager import AstroidManager -from astroid.nodes.node_classes import ( - AnnAssign, - Assign, - AssignName, - Attribute, - Call, - Name, - NodeNG, - Subscript, - Unknown, -) -from astroid.nodes.scoped_nodes import ClassDef, FunctionDef +from astroid.typing import InferenceResult from astroid.util import Uninferable if sys.version_info >= (3, 8): @@ -49,7 +34,9 @@ from typing_extensions import Literal _FieldDefaultReturn = Union[ - None, Tuple[Literal["default"], NodeNG], Tuple[Literal["default_factory"], Call] + None, + Tuple[Literal["default"], nodes.NodeNG], + Tuple[Literal["default_factory"], nodes.Call], ] DATACLASSES_DECORATORS = frozenset(("dataclass",)) @@ -60,9 +47,11 @@ DEFAULT_FACTORY = "_HAS_DEFAULT_FACTORY" # based on typing.py -def is_decorated_with_dataclass(node, decorator_names=DATACLASSES_DECORATORS): +def is_decorated_with_dataclass( + node: nodes.ClassDef, decorator_names: frozenset[str] = DATACLASSES_DECORATORS +) -> bool: """Return True if a decorated node has a `dataclass` decorator applied.""" - if not isinstance(node, ClassDef) or not node.decorators: + if not isinstance(node, nodes.ClassDef) or not node.decorators: return False return any( @@ -71,14 +60,14 @@ ) -def dataclass_transform(node: ClassDef) -> None: +def dataclass_transform(node: nodes.ClassDef) -> None: """Rewrite a dataclass to be easily understood by pylint""" node.is_dataclass = True for assign_node in _get_dataclass_attributes(node): name = assign_node.target.name - rhs_node = Unknown( + rhs_node = nodes.Unknown( lineno=assign_node.lineno, col_offset=assign_node.col_offset, parent=assign_node, @@ -89,21 +78,22 @@ if not _check_generate_dataclass_init(node): return - try: - reversed_mro = list(reversed(node.mro())) - except MroError: - reversed_mro = [node] - - field_assigns = {} - field_order = [] - for klass in (k for k in reversed_mro if is_decorated_with_dataclass(k)): - for assign_node in _get_dataclass_attributes(klass, init=True): - name = assign_node.target.name - if name not in field_assigns: - field_order.append(name) - field_assigns[name] = assign_node + kw_only_decorated = False + if PY310_PLUS and node.decorators.nodes: + for decorator in node.decorators.nodes: + if not isinstance(decorator, nodes.Call): + kw_only_decorated = False + break + for keyword in decorator.keywords: + if keyword.arg == "kw_only": + kw_only_decorated = keyword.value.bool_value() + + init_str = _generate_dataclass_init( + node, + list(_get_dataclass_attributes(node, init=True)), + kw_only_decorated, + ) - init_str = _generate_dataclass_init([field_assigns[name] for name in field_order]) try: init_node = parse(init_str)["__init__"] except AstroidSyntaxError: @@ -120,15 +110,17 @@ root.locals[DEFAULT_FACTORY] = [new_assign.targets[0]] -def _get_dataclass_attributes(node: ClassDef, init: bool = False) -> Generator: +def _get_dataclass_attributes( + node: nodes.ClassDef, init: bool = False +) -> Iterator[nodes.AnnAssign]: """Yield the AnnAssign nodes of dataclass attributes for the node. If init is True, also include InitVars, but exclude attributes from calls to field where init=False. """ for assign_node in node.body: - if not isinstance(assign_node, AnnAssign) or not isinstance( - assign_node.target, AssignName + if not isinstance(assign_node, nodes.AnnAssign) or not isinstance( + assign_node.target, nodes.AssignName ): continue @@ -141,11 +133,10 @@ if init: value = assign_node.value if ( - isinstance(value, Call) + isinstance(value, nodes.Call) and _looks_like_dataclass_field_call(value, check_scope=False) and any( - keyword.arg == "init" - and not keyword.value.bool_value() # type: ignore[union-attr] # value is never None + keyword.arg == "init" and not keyword.value.bool_value() for keyword in value.keywords ) ): @@ -156,7 +147,7 @@ yield assign_node -def _check_generate_dataclass_init(node: ClassDef) -> bool: +def _check_generate_dataclass_init(node: nodes.ClassDef) -> bool: """Return True if we should generate an __init__ method for node. This is True when: @@ -169,7 +160,7 @@ found = None for decorator_attribute in node.decorators.nodes: - if not isinstance(decorator_attribute, Call): + if not isinstance(decorator_attribute, nodes.Call): continue if _looks_like_dataclass_decorator(decorator_attribute): @@ -179,26 +170,67 @@ return True # Check for keyword arguments of the form init=False - return all( - keyword.arg != "init" - and keyword.value.bool_value() # type: ignore[union-attr] # value is never None + return not any( + keyword.arg == "init" + and not keyword.value.bool_value() # type: ignore[union-attr] # value is never None for keyword in found.keywords ) -def _generate_dataclass_init(assigns: list[AnnAssign]) -> str: +def _find_arguments_from_base_classes( + node: nodes.ClassDef, skippable_names: set[str] +) -> tuple[str, str]: + """Iterate through all bases and add them to the list of arguments to add to the init.""" + prev_pos_only = "" + prev_kw_only = "" + for base in node.mro(): + if not base.is_dataclass: + continue + try: + base_init: nodes.FunctionDef = base.locals["__init__"][0] + except KeyError: + continue + + # Skip the self argument and check for duplicate arguments + arguments = base_init.args.format_args(skippable_names=skippable_names) + try: + new_prev_pos_only, new_prev_kw_only = arguments.split("*, ") + except ValueError: + new_prev_pos_only, new_prev_kw_only = arguments, "" + + if new_prev_pos_only: + # The split on '*, ' can crete a pos_only string that consists only of a comma + if new_prev_pos_only == ", ": + new_prev_pos_only = "" + elif not new_prev_pos_only.endswith(", "): + new_prev_pos_only += ", " + + # Dataclasses put last seen arguments at the front of the init + prev_pos_only = new_prev_pos_only + prev_pos_only + prev_kw_only = new_prev_kw_only + prev_kw_only + + # Add arguments to skippable arguments + skippable_names.update(arg.name for arg in base_init.args.args) + skippable_names.update(arg.name for arg in base_init.args.kwonlyargs) + + return prev_pos_only, prev_kw_only + + +def _generate_dataclass_init( + node: nodes.ClassDef, assigns: list[nodes.AnnAssign], kw_only_decorated: bool +) -> str: """Return an init method for a dataclass given the targets.""" - target_names = [] - params = [] - assignments = [] + params: list[str] = [] + assignments: list[str] = [] + assign_names: list[str] = [] for assign in assigns: name, annotation, value = assign.target.name, assign.annotation, assign.value - target_names.append(name) + assign_names.append(name) if _is_init_var(annotation): # type: ignore[arg-type] # annotation is never None init_var = True - if isinstance(annotation, Subscript): + if isinstance(annotation, nodes.Subscript): annotation = annotation.slice else: # Cannot determine type annotation for parameter from InitVar @@ -208,13 +240,13 @@ init_var = False assignment_str = f"self.{name} = {name}" - if annotation: + if annotation is not None: param_str = f"{name}: {annotation.as_string()}" else: param_str = name if value: - if isinstance(value, Call) and _looks_like_dataclass_field_call( + if isinstance(value, nodes.Call) and _looks_like_dataclass_field_call( value, check_scope=False ): result = _get_field_default(value) @@ -235,14 +267,34 @@ if not init_var: assignments.append(assignment_str) - params_string = ", ".join(["self"] + params) + prev_pos_only, prev_kw_only = _find_arguments_from_base_classes( + node, set(assign_names + ["self"]) + ) + + # Construct the new init method paramter string + params_string = "self, " + if prev_pos_only: + params_string += prev_pos_only + if not kw_only_decorated: + params_string += ", ".join(params) + + if not params_string.endswith(", "): + params_string += ", " + + if prev_kw_only: + params_string += "*, " + prev_kw_only + ", " + if kw_only_decorated: + params_string += ", ".join(params) + ", " + elif kw_only_decorated: + params_string += "*, " + ", ".join(params) + ", " + assignments_string = "\n ".join(assignments) if assignments else "pass" return f"def __init__({params_string}) -> None:\n {assignments_string}" def infer_dataclass_attribute( - node: Unknown, ctx: context.InferenceContext | None = None -) -> Generator: + node: nodes.Unknown, ctx: context.InferenceContext | None = None +) -> Iterator[InferenceResult]: """Inference tip for an Unknown node that was dynamically generated to represent a dataclass attribute. @@ -250,7 +302,7 @@ Then, an Instance of the annotated class is yielded. """ assign = node.parent - if not isinstance(assign, AnnAssign): + if not isinstance(assign, nodes.AnnAssign): yield Uninferable return @@ -264,10 +316,10 @@ def infer_dataclass_field_call( - node: Call, ctx: context.InferenceContext | None = None -) -> Generator: + node: nodes.Call, ctx: context.InferenceContext | None = None +) -> Iterator[InferenceResult]: """Inference tip for dataclass field calls.""" - if not isinstance(node.parent, (AnnAssign, Assign)): + if not isinstance(node.parent, (nodes.AnnAssign, nodes.Assign)): raise UseInferenceDefault result = _get_field_default(node) if not result: @@ -283,14 +335,14 @@ def _looks_like_dataclass_decorator( - node: NodeNG, decorator_names: frozenset[str] = DATACLASSES_DECORATORS + node: nodes.NodeNG, decorator_names: frozenset[str] = DATACLASSES_DECORATORS ) -> bool: """Return True if node looks like a dataclass decorator. Uses inference to lookup the value of the node, and if that fails, matches against specific names. """ - if isinstance(node, Call): # decorator with arguments + if isinstance(node, nodes.Call): # decorator with arguments node = node.func try: inferred = next(node.infer()) @@ -298,21 +350,21 @@ inferred = Uninferable if inferred is Uninferable: - if isinstance(node, Name): + if isinstance(node, nodes.Name): return node.name in decorator_names - if isinstance(node, Attribute): + if isinstance(node, nodes.Attribute): return node.attrname in decorator_names return False return ( - isinstance(inferred, FunctionDef) + isinstance(inferred, nodes.FunctionDef) and inferred.name in decorator_names and inferred.root().name in DATACLASS_MODULES ) -def _looks_like_dataclass_attribute(node: Unknown) -> bool: +def _looks_like_dataclass_attribute(node: nodes.Unknown) -> bool: """Return True if node was dynamically generated as the child of an AnnAssign statement. """ @@ -322,13 +374,15 @@ scope = parent.scope() return ( - isinstance(parent, AnnAssign) - and isinstance(scope, ClassDef) + isinstance(parent, nodes.AnnAssign) + and isinstance(scope, nodes.ClassDef) and is_decorated_with_dataclass(scope) ) -def _looks_like_dataclass_field_call(node: Call, check_scope: bool = True) -> bool: +def _looks_like_dataclass_field_call( + node: nodes.Call, check_scope: bool = True +) -> bool: """Return True if node is calling dataclasses field or Field from an AnnAssign statement directly in the body of a ClassDef. @@ -338,9 +392,9 @@ stmt = node.statement(future=True) scope = stmt.scope() if not ( - isinstance(stmt, AnnAssign) + isinstance(stmt, nodes.AnnAssign) and stmt.value is not None - and isinstance(scope, ClassDef) + and isinstance(scope, nodes.ClassDef) and is_decorated_with_dataclass(scope) ): return False @@ -350,13 +404,13 @@ except (InferenceError, StopIteration): return False - if not isinstance(inferred, FunctionDef): + if not isinstance(inferred, nodes.FunctionDef): return False return inferred.name == FIELD_NAME and inferred.root().name in DATACLASS_MODULES -def _get_field_default(field_call: Call) -> _FieldDefaultReturn: +def _get_field_default(field_call: nodes.Call) -> _FieldDefaultReturn: """Return a the default value of a field call, and the corresponding keyword argument name. field(default=...) results in the ... node @@ -376,7 +430,7 @@ return "default", default if default is None and default_factory is not None: - new_call = Call( + new_call = nodes.Call( lineno=field_call.lineno, col_offset=field_call.col_offset, parent=field_call.parent, @@ -387,7 +441,7 @@ return None -def _is_class_var(node: NodeNG) -> bool: +def _is_class_var(node: nodes.NodeNG) -> bool: """Return True if node is a ClassVar, with or without subscripting.""" if PY39_PLUS: try: @@ -399,15 +453,15 @@ # Before Python 3.9, inference returns typing._SpecialForm instead of ClassVar. # Our backup is to inspect the node's structure. - return isinstance(node, Subscript) and ( - isinstance(node.value, Name) + return isinstance(node, nodes.Subscript) and ( + isinstance(node.value, nodes.Name) and node.value.name == "ClassVar" - or isinstance(node.value, Attribute) + or isinstance(node.value, nodes.Attribute) and node.value.attrname == "ClassVar" ) -def _is_keyword_only_sentinel(node: NodeNG) -> bool: +def _is_keyword_only_sentinel(node: nodes.NodeNG) -> bool: """Return True if node is the KW_ONLY sentinel.""" if not PY310_PLUS: return False @@ -418,7 +472,7 @@ ) -def _is_init_var(node: NodeNG) -> bool: +def _is_init_var(node: nodes.NodeNG) -> bool: """Return True if node is an InitVar, with or without subscripting.""" try: inferred = next(node.infer()) @@ -441,8 +495,8 @@ def _infer_instance_from_annotation( - node: NodeNG, ctx: context.InferenceContext | None = None -) -> Generator: + node: nodes.NodeNG, ctx: context.InferenceContext | None = None +) -> Iterator[type[Uninferable] | bases.Instance]: """Infer an instance corresponding to the type annotation represented by node. Currently has limited support for the typing module. @@ -452,7 +506,7 @@ klass = next(node.infer(context=ctx)) except (InferenceError, StopIteration): yield Uninferable - if not isinstance(klass, ClassDef): + if not isinstance(klass, nodes.ClassDef): yield Uninferable elif klass.root().name in { "typing", @@ -468,17 +522,17 @@ AstroidManager().register_transform( - ClassDef, dataclass_transform, is_decorated_with_dataclass + nodes.ClassDef, dataclass_transform, is_decorated_with_dataclass ) AstroidManager().register_transform( - Call, + nodes.Call, inference_tip(infer_dataclass_field_call, raise_on_overwrite=True), _looks_like_dataclass_field_call, ) AstroidManager().register_transform( - Unknown, + nodes.Unknown, inference_tip(infer_dataclass_attribute, raise_on_overwrite=True), _looks_like_dataclass_attribute, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/astroid/brain/brain_namedtuple_enum.py new/astroid-2.12.10/astroid/brain/brain_namedtuple_enum.py --- old/astroid-2.12.4/astroid/brain/brain_namedtuple_enum.py 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/astroid/brain/brain_namedtuple_enum.py 2022-09-17 18:37:39.000000000 +0200 @@ -538,7 +538,25 @@ extract a node from them later on. """ names = [] - for elt in next(node.args[1].infer()).elts: + container = None + try: + container = next(node.args[1].infer()) + except (InferenceError, StopIteration) as exc: + raise UseInferenceDefault from exc + # We pass on IndexError as we'll try to infer 'field_names' from the keywords + except IndexError: + pass + if not container: + for keyword_node in node.keywords: + if keyword_node.arg == "field_names": + try: + container = next(keyword_node.value.infer()) + except (InferenceError, StopIteration) as exc: + raise UseInferenceDefault from exc + break + if not isinstance(container, nodes.BaseContainer): + raise UseInferenceDefault + for elt in container.elts: if isinstance(elt, nodes.Const): names.append(elt.as_string()) continue diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/astroid/decorators.py new/astroid-2.12.10/astroid/decorators.py --- old/astroid-2.12.4/astroid/decorators.py 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/astroid/decorators.py 2022-09-17 18:37:39.000000000 +0200 @@ -15,7 +15,7 @@ import wrapt -from astroid import util +from astroid import _cache, util from astroid.context import InferenceContext from astroid.exceptions import InferenceError @@ -34,6 +34,7 @@ cache = getattr(instance, "__cache", None) if cache is None: instance.__cache = cache = {} + _cache.CACHE_MANAGER.add_dict_cache(cache) try: return cache[func] except KeyError: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/astroid/interpreter/_import/util.py new/astroid-2.12.10/astroid/interpreter/_import/util.py --- old/astroid-2.12.4/astroid/interpreter/_import/util.py 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/astroid/interpreter/_import/util.py 2022-09-17 18:37:39.000000000 +0200 @@ -10,9 +10,18 @@ from importlib._bootstrap_external import _NamespacePath from importlib.util import _find_spec_from_path # type: ignore[attr-defined] +from astroid.const import IS_PYPY + @lru_cache(maxsize=4096) def is_namespace(modname: str) -> bool: + from astroid.modutils import ( # pylint: disable=import-outside-toplevel + EXT_LIB_DIRS, + STD_LIB_DIRS, + ) + + STD_AND_EXT_LIB_DIRS = STD_LIB_DIRS.union(EXT_LIB_DIRS) + if modname in sys.builtin_module_names: return False @@ -38,7 +47,15 @@ return False try: # .pth files will be on sys.modules - return sys.modules[modname].__spec__ is None + # __spec__ is set inconsistently on PyPy so we can't really on the heuristic here + # See: https://foss.heptapod.net/pypy/pypy/-/issues/3736 + # Check first fragment of modname, e.g. "astroid", not "astroid.interpreter" + # because of cffi's behavior + # See: https://github.com/PyCQA/astroid/issues/1776 + return ( + sys.modules[processed_components[0]].__spec__ is None + and not IS_PYPY + ) except KeyError: return False except AttributeError: @@ -68,8 +85,15 @@ last_submodule_search_locations.append(str(assumed_location)) continue - # Update last_submodule_search_locations + # Update last_submodule_search_locations for next iteration if found_spec and found_spec.submodule_search_locations: + # But immediately return False if we can detect we are in stdlib + # or external lib (e.g site-packages) + if any( + any(location.startswith(lib_dir) for lib_dir in STD_AND_EXT_LIB_DIRS) + for location in found_spec.submodule_search_locations + ): + return False last_submodule_search_locations = found_spec.submodule_search_locations return ( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/astroid/manager.py new/astroid-2.12.10/astroid/manager.py --- old/astroid-2.12.4/astroid/manager.py 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/astroid/manager.py 2022-09-17 18:37:39.000000000 +0200 @@ -16,6 +16,7 @@ from importlib.util import find_spec, module_from_spec from typing import TYPE_CHECKING, ClassVar +from astroid._cache import CACHE_MANAGER from astroid.const import BRAIN_MODULES_DIRECTORY from astroid.exceptions import AstroidBuildingError, AstroidImportError from astroid.interpreter._import import spec, util @@ -382,6 +383,8 @@ # NB: not a new TransformVisitor() AstroidManager.brain["_transform"].transforms = collections.defaultdict(list) + CACHE_MANAGER.clear_all_caches() + for lru_cache in ( LookupMixIn.lookup, _cache_normalize_path_, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/astroid/nodes/node_classes.py new/astroid-2.12.10/astroid/nodes/node_classes.py --- old/astroid-2.12.4/astroid/nodes/node_classes.py 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/astroid/nodes/node_classes.py 2022-09-17 18:37:39.000000000 +0200 @@ -784,7 +784,7 @@ """Get all the arguments for this node, including positional only and positional and keyword""" return list(itertools.chain((self.posonlyargs or ()), self.args or ())) - def format_args(self): + def format_args(self, *, skippable_names: set[str] | None = None) -> str: """Get the arguments formatted as string. :returns: The formatted arguments. @@ -804,6 +804,7 @@ self.posonlyargs, positional_only_defaults, self.posonlyargs_annotations, + skippable_names=skippable_names, ) ) result.append("/") @@ -813,6 +814,7 @@ self.args, positional_or_keyword_defaults, getattr(self, "annotations", None), + skippable_names=skippable_names, ) ) if self.vararg: @@ -822,7 +824,10 @@ result.append("*") result.append( _format_args( - self.kwonlyargs, self.kw_defaults, self.kwonlyargs_annotations + self.kwonlyargs, + self.kw_defaults, + self.kwonlyargs_annotations, + skippable_names=skippable_names, ) ) if self.kwarg: @@ -929,7 +934,11 @@ return None, None -def _format_args(args, defaults=None, annotations=None): +def _format_args( + args, defaults=None, annotations=None, skippable_names: set[str] | None = None +) -> str: + if skippable_names is None: + skippable_names = set() values = [] if args is None: return "" @@ -939,6 +948,8 @@ default_offset = len(args) - len(defaults) packed = itertools.zip_longest(args, annotations) for i, (arg, annotation) in enumerate(packed): + if arg.name in skippable_names: + continue if isinstance(arg, Tuple): values.append(f"({_format_args(arg.elts)})") else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/tbump.toml new/astroid-2.12.10/tbump.toml --- old/astroid-2.12.4/tbump.toml 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/tbump.toml 2022-09-17 18:37:39.000000000 +0200 @@ -1,7 +1,7 @@ github_url = "https://github.com/PyCQA/astroid" [version] -current = "2.12.4" +current = "2.12.10" regex = ''' ^(?P<major>0|[1-9]\d*) \. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/tests/unittest_brain.py new/astroid-2.12.10/tests/unittest_brain.py --- old/astroid-2.12.4/tests/unittest_brain.py 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/tests/unittest_brain.py 2022-09-17 18:37:39.000000000 +0200 @@ -18,6 +18,7 @@ import astroid from astroid import MANAGER, bases, builder, nodes, objects, test_utils, util from astroid.bases import Instance +from astroid.brain.brain_namedtuple_enum import _get_namedtuple_fields from astroid.const import PY39_PLUS from astroid.exceptions import ( AttributeInferenceError, @@ -433,6 +434,23 @@ inferred = next(node.infer()) self.assertIs(util.Uninferable, inferred) + def test_name_as_typename(self) -> None: + """Reported in https://github.com/PyCQA/pylint/issues/7429 as a crash.""" + good_node, good_node_two, bad_node = builder.extract_node( + """ + import collections + collections.namedtuple(typename="MyTuple", field_names=["birth_date", "city"]) #@ + collections.namedtuple("MyTuple", field_names=["birth_date", "city"]) #@ + collections.namedtuple(["birth_date", "city"], typename="MyTuple") #@ + """ + ) + good_inferred = next(good_node.infer()) + assert isinstance(good_inferred, nodes.ClassDef) + good_node_two_inferred = next(good_node_two.infer()) + assert isinstance(good_node_two_inferred, nodes.ClassDef) + bad_node_inferred = next(bad_node.infer()) + assert bad_node_inferred == util.Uninferable + class DefaultDictTest(unittest.TestCase): def test_1(self) -> None: @@ -1644,6 +1662,25 @@ ) next(node.infer()) + def test_namedtuple_uninferable_member(self) -> None: + call = builder.extract_node( + """ + from typing import namedtuple + namedtuple('uninf', {x: x for x in range(0)}) #@""" + ) + with pytest.raises(UseInferenceDefault): + _get_namedtuple_fields(call) + + call = builder.extract_node( + """ + from typing import namedtuple + uninferable = {x: x for x in range(0)} + namedtuple('uninferable', uninferable) #@ + """ + ) + with pytest.raises(UseInferenceDefault): + _get_namedtuple_fields(call) + def test_typing_types(self) -> None: ast_nodes = builder.extract_node( """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/tests/unittest_brain_dataclasses.py new/astroid-2.12.10/tests/unittest_brain_dataclasses.py --- old/astroid-2.12.4/tests/unittest_brain_dataclasses.py 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/tests/unittest_brain_dataclasses.py 2022-09-17 18:37:39.000000000 +0200 @@ -625,12 +625,12 @@ """ ) init = next(node.infer()) - assert [a.name for a in init.args.args] == ["self", "arg0", "arg2", "arg1"] + assert [a.name for a in init.args.args] == ["self", "arg0", "arg1", "arg2"] assert [a.as_string() if a else None for a in init.args.annotations] == [ None, "float", - "list", # not str "int", + "list", # not str ] @@ -747,3 +747,300 @@ init = next(node_two.infer()) assert [a.name for a in init.args.args] == expected + + +def test_kw_only_decorator() -> None: + """Test that we update the signature correctly based on the keyword. + + kw_only was introduced in PY310. + """ + foodef, bardef, cee, dee = astroid.extract_node( + """ + from dataclasses import dataclass + + @dataclass(kw_only=True) + class Foo: + a: int + e: str + + + @dataclass(kw_only=False) + class Bar(Foo): + c: int + + + @dataclass(kw_only=False) + class Cee(Bar): + d: int + + + @dataclass(kw_only=True) + class Dee(Cee): + ee: int + + + Foo.__init__ #@ + Bar.__init__ #@ + Cee.__init__ #@ + Dee.__init__ #@ + """ + ) + + foo_init: bases.UnboundMethod = next(foodef.infer()) + if PY310_PLUS: + assert [a.name for a in foo_init.args.args] == ["self"] + assert [a.name for a in foo_init.args.kwonlyargs] == ["a", "e"] + else: + assert [a.name for a in foo_init.args.args] == ["self", "a", "e"] + assert [a.name for a in foo_init.args.kwonlyargs] == [] + + bar_init: bases.UnboundMethod = next(bardef.infer()) + if PY310_PLUS: + assert [a.name for a in bar_init.args.args] == ["self", "c"] + assert [a.name for a in bar_init.args.kwonlyargs] == ["a", "e"] + else: + assert [a.name for a in bar_init.args.args] == ["self", "a", "e", "c"] + assert [a.name for a in bar_init.args.kwonlyargs] == [] + + cee_init: bases.UnboundMethod = next(cee.infer()) + if PY310_PLUS: + assert [a.name for a in cee_init.args.args] == ["self", "c", "d"] + assert [a.name for a in cee_init.args.kwonlyargs] == ["a", "e"] + else: + assert [a.name for a in cee_init.args.args] == ["self", "a", "e", "c", "d"] + assert [a.name for a in cee_init.args.kwonlyargs] == [] + + dee_init: bases.UnboundMethod = next(dee.infer()) + if PY310_PLUS: + assert [a.name for a in dee_init.args.args] == ["self", "c", "d"] + assert [a.name for a in dee_init.args.kwonlyargs] == ["a", "e", "ee"] + else: + assert [a.name for a in dee_init.args.args] == [ + "self", + "a", + "e", + "c", + "d", + "ee", + ] + assert [a.name for a in dee_init.args.kwonlyargs] == [] + + +def test_dataclass_with_unknown_base() -> None: + """Regression test for dataclasses with unknown base classes. + + Reported in https://github.com/PyCQA/pylint/issues/7418 + """ + node = astroid.extract_node( + """ + import dataclasses + + from unknown import Unknown + + + @dataclasses.dataclass + class MyDataclass(Unknown): + pass + + MyDataclass() + """ + ) + + assert next(node.infer()) + + +def test_dataclass_with_unknown_typing() -> None: + """Regression test for dataclasses with unknown base classes. + + Reported in https://github.com/PyCQA/pylint/issues/7422 + """ + node = astroid.extract_node( + """ + from dataclasses import dataclass, InitVar + + + @dataclass + class TestClass: + '''Test Class''' + + config: InitVar = None + + TestClass.__init__ #@ + """ + ) + + init_def: bases.UnboundMethod = next(node.infer()) + assert [a.name for a in init_def.args.args] == ["self", "config"] + + +def test_dataclass_with_default_factory() -> None: + """Regression test for dataclasses with default values. + + Reported in https://github.com/PyCQA/pylint/issues/7425 + """ + bad_node, good_node = astroid.extract_node( + """ + from dataclasses import dataclass + from typing import Union + + @dataclass + class BadExampleParentClass: + xyz: Union[str, int] + + @dataclass + class BadExampleClass(BadExampleParentClass): + xyz: str = "" + + BadExampleClass.__init__ #@ + + @dataclass + class GoodExampleParentClass: + xyz: str + + @dataclass + class GoodExampleClass(GoodExampleParentClass): + xyz: str = "" + + GoodExampleClass.__init__ #@ + """ + ) + + bad_init: bases.UnboundMethod = next(bad_node.infer()) + assert bad_init.args.defaults + assert [a.name for a in bad_init.args.args] == ["self", "xyz"] + + good_init: bases.UnboundMethod = next(good_node.infer()) + assert bad_init.args.defaults + assert [a.name for a in good_init.args.args] == ["self", "xyz"] + + +def test_dataclass_with_multiple_inheritance() -> None: + """Regression test for dataclasses with multiple inheritance. + + Reported in https://github.com/PyCQA/pylint/issues/7427 + """ + first, second, overwritten, overwriting, mixed = astroid.extract_node( + """ + from dataclasses import dataclass + + @dataclass + class BaseParent: + _abc: int = 1 + + @dataclass + class AnotherParent: + ef: int = 2 + + @dataclass + class FirstChild(BaseParent, AnotherParent): + ghi: int = 3 + + @dataclass + class ConvolutedParent(AnotherParent): + '''Convoluted Parent''' + + @dataclass + class SecondChild(BaseParent, ConvolutedParent): + jkl: int = 4 + + @dataclass + class OverwritingParent: + ef: str = "2" + + @dataclass + class OverwrittenChild(OverwritingParent, AnotherParent): + '''Overwritten Child''' + + @dataclass + class OverwritingChild(BaseParent, AnotherParent): + _abc: float = 1.0 + ef: float = 2.0 + + class NotADataclassParent: + ef: int = 2 + + @dataclass + class ChildWithMixedParents(BaseParent, NotADataclassParent): + ghi: int = 3 + + FirstChild.__init__ #@ + SecondChild.__init__ #@ + OverwrittenChild.__init__ #@ + OverwritingChild.__init__ #@ + ChildWithMixedParents.__init__ #@ + """ + ) + + first_init: bases.UnboundMethod = next(first.infer()) + assert [a.name for a in first_init.args.args] == ["self", "ef", "_abc", "ghi"] + assert [a.value for a in first_init.args.defaults] == [2, 1, 3] + + second_init: bases.UnboundMethod = next(second.infer()) + assert [a.name for a in second_init.args.args] == ["self", "ef", "_abc", "jkl"] + assert [a.value for a in second_init.args.defaults] == [2, 1, 4] + + overwritten_init: bases.UnboundMethod = next(overwritten.infer()) + assert [a.name for a in overwritten_init.args.args] == ["self", "ef"] + assert [a.value for a in overwritten_init.args.defaults] == ["2"] + + overwriting_init: bases.UnboundMethod = next(overwriting.infer()) + assert [a.name for a in overwriting_init.args.args] == ["self", "_abc", "ef"] + assert [a.value for a in overwriting_init.args.defaults] == [1.0, 2.0] + + mixed_init: bases.UnboundMethod = next(mixed.infer()) + assert [a.name for a in mixed_init.args.args] == ["self", "_abc", "ghi"] + assert [a.value for a in mixed_init.args.defaults] == [1, 3] + + +def test_dataclass_inits_of_non_dataclasses() -> None: + """Regression test for __init__ mangling for non dataclasses. + + Regression test against changes tested in test_dataclass_with_multiple_inheritance + """ + first, second, third = astroid.extract_node( + """ + from dataclasses import dataclass + + @dataclass + class DataclassParent: + _abc: int = 1 + + + class NotADataclassParent: + ef: int = 2 + + + class FirstChild(DataclassParent, NotADataclassParent): + ghi: int = 3 + + + class SecondChild(DataclassParent, NotADataclassParent): + ghi: int = 3 + + def __init__(self, ef: int = 3): + self.ef = ef + + + class ThirdChild(NotADataclassParent, DataclassParent): + ghi: int = 3 + + def __init__(self, ef: int = 3): + self.ef = ef + + FirstChild.__init__ #@ + SecondChild.__init__ #@ + ThirdChild.__init__ #@ + """ + ) + + first_init: bases.UnboundMethod = next(first.infer()) + assert [a.name for a in first_init.args.args] == ["self", "_abc"] + assert [a.value for a in first_init.args.defaults] == [1] + + second_init: bases.UnboundMethod = next(second.infer()) + assert [a.name for a in second_init.args.args] == ["self", "ef"] + assert [a.value for a in second_init.args.defaults] == [3] + + third_init: bases.UnboundMethod = next(third.infer()) + assert [a.name for a in third_init.args.args] == ["self", "ef"] + assert [a.value for a in third_init.args.defaults] == [3] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-2.12.4/tests/unittest_manager.py new/astroid-2.12.10/tests/unittest_manager.py --- old/astroid-2.12.4/tests/unittest_manager.py 2022-08-25 12:14:40.000000000 +0200 +++ new/astroid-2.12.10/tests/unittest_manager.py 2022-09-17 18:37:39.000000000 +0200 @@ -10,12 +10,14 @@ from collections.abc import Iterator from contextlib import contextmanager +import pytest + import astroid from astroid import manager, test_utils -from astroid.const import IS_JYTHON +from astroid.const import IS_JYTHON, IS_PYPY from astroid.exceptions import AstroidBuildingError, AstroidImportError from astroid.interpreter._import import util -from astroid.modutils import is_standard_module +from astroid.modutils import EXT_LIB_DIRS, is_standard_module from astroid.nodes import Const from astroid.nodes.scoped_nodes import ClassDef @@ -128,6 +130,10 @@ def test_module_is_not_namespace(self) -> None: self.assertFalse(util.is_namespace("tests.testdata.python3.data.all")) self.assertFalse(util.is_namespace("__main__")) + self.assertFalse( + util.is_namespace(list(EXT_LIB_DIRS)[0].rsplit("/", maxsplit=1)[-1]), + ) + self.assertFalse(util.is_namespace("importlib._bootstrap")) def test_module_unexpectedly_missing_spec(self) -> None: astroid_module = sys.modules["astroid"] @@ -155,6 +161,10 @@ for _ in range(2): sys.path.pop(0) + @pytest.mark.skipif( + IS_PYPY, + reason="PyPy provides no way to tell apart frozen stdlib from old-style namespace packages", + ) def test_namespace_package_pth_support(self) -> None: pth = "foogle_fax-0.12.5-py2.7-nspkg.pth" site.addpackage(resources.RESOURCE_PATH, pth, []) @@ -169,6 +179,10 @@ finally: sys.modules.pop("foogle") + @pytest.mark.skipif( + IS_PYPY, + reason="PyPy provides no way to tell apart frozen stdlib from old-style namespace packages", + ) def test_nested_namespace_import(self) -> None: pth = "foogle_fax-0.12.5-py2.7-nspkg.pth" site.addpackage(resources.RESOURCE_PATH, pth, [])