From c8bd28a53b919f1c0be4f71f3c6de3d26073d520 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 20:59:30 +0000 Subject: [PATCH 1/7] [fix] Crash when parsing an empty arbitrary expression with ``extract_node`` (#2736) (#2737) Closes #2734 (cherry picked from commit 59f36e755d04765b2b623c7c51e84509c6b248f6) Co-authored-by: Pierre Sassoulas --- ChangeLog | 2 ++ astroid/builder.py | 1 + tests/test_regrtest.py | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/ChangeLog b/ChangeLog index 94d8e482b0..912c9fe203 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,7 +13,9 @@ What's New in astroid 3.3.11? ============================= Release date: TBA +* Fix a crash when parsing an empty arbitrary expression with ``extract_node`` (``extract_node("__()")``). + Closes #2734 What's New in astroid 3.3.10? ============================= diff --git a/astroid/builder.py b/astroid/builder.py index f4be6972b8..22724fa9af 100644 --- a/astroid/builder.py +++ b/astroid/builder.py @@ -317,6 +317,7 @@ def _extract_expressions(node: nodes.NodeNG) -> Iterator[nodes.NodeNG]: isinstance(node, nodes.Call) and isinstance(node.func, nodes.Name) and node.func.name == _TRANSIENT_FUNCTION + and node.args ): real_expr = node.args[0] assert node.parent diff --git a/tests/test_regrtest.py b/tests/test_regrtest.py index f7383d25fb..a7a091b93e 100644 --- a/tests/test_regrtest.py +++ b/tests/test_regrtest.py @@ -497,3 +497,9 @@ def _get_option(self, option): ) ) assert node.inferred()[0].value == "mystr" + + +def test_regression_no_crash_during_build() -> None: + node: nodes.Attribute = extract_node("__()") + assert node.args == [] + assert node.as_string() == "__()" From 0aaf2131e8694c0009781b4144685d1c84d1f36a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 07:25:23 +0000 Subject: [PATCH 2/7] [fix] Prevent crash on slice decorator for 'six' decorated function (#2738) (#2740) Closes #2721 (cherry picked from commit 555a128e7b5392404185e8b868d59eb4f217a07c) Co-authored-by: Pierre Sassoulas --- ChangeLog | 5 +++++ astroid/brain/brain_six.py | 6 +++++- tests/test_regrtest.py | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 912c9fe203..4956f82c64 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,11 @@ Release date: TBA Closes #2734 +* Fix a crash when parsing a slice called in a decorator on a function that is also decorated with + a known ``six`` decorator. + + Closes #2721 + What's New in astroid 3.3.10? ============================= Release date: 2025-05-10 diff --git a/astroid/brain/brain_six.py b/astroid/brain/brain_six.py index c222a42206..12180097e1 100644 --- a/astroid/brain/brain_six.py +++ b/astroid/brain/brain_six.py @@ -182,7 +182,11 @@ def transform_six_add_metaclass(node): # pylint: disable=inconsistent-return-st func = next(decorator.func.infer()) except (InferenceError, StopIteration): continue - if func.qname() == SIX_ADD_METACLASS and decorator.args: + if ( + isinstance(func, (nodes.FunctionDef, nodes.ClassDef)) + and func.qname() == SIX_ADD_METACLASS + and decorator.args + ): metaclass = decorator.args[0] node._metaclass = metaclass return node diff --git a/tests/test_regrtest.py b/tests/test_regrtest.py index a7a091b93e..86bc193803 100644 --- a/tests/test_regrtest.py +++ b/tests/test_regrtest.py @@ -503,3 +503,19 @@ def test_regression_no_crash_during_build() -> None: node: nodes.Attribute = extract_node("__()") assert node.args == [] assert node.as_string() == "__()" + + +def test_regression_no_crash_on_called_slice() -> None: + """Regression test for issue #2721.""" + node: nodes.Attribute = extract_node( + textwrap.dedent( + """ + s = slice(-2) + @s() + @six.add_metaclass() + class a: ... + """ + ) + ) + assert isinstance(node, nodes.ClassDef) + assert node.name == "a" From b1adb1c1b58e1f7a13751bc52c33d124364633be Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 18 May 2025 11:42:42 +0200 Subject: [PATCH 3/7] [Backport maintenance/3.3.x] Initial fixes for Python 3.14 (#2747) (#2748) (cherry picked from commit 43111bb4e2b8ae618168a449dcabcd40d19e5c3a) --- astroid/brain/brain_pathlib.py | 4 ++-- astroid/brain/brain_typing.py | 19 +++++++++++++------ astroid/const.py | 2 ++ tests/brain/test_brain.py | 6 +++++- tests/brain/test_pathlib.py | 6 +++--- tests/test_inference.py | 10 +++++++--- 6 files changed, 32 insertions(+), 15 deletions(-) diff --git a/astroid/brain/brain_pathlib.py b/astroid/brain/brain_pathlib.py index d0f531324b..186a0c4f38 100644 --- a/astroid/brain/brain_pathlib.py +++ b/astroid/brain/brain_pathlib.py @@ -8,7 +8,7 @@ from astroid import bases, context, inference_tip, nodes from astroid.builder import _extract_single_node -from astroid.const import PY313_PLUS +from astroid.const import PY313 from astroid.exceptions import InferenceError, UseInferenceDefault from astroid.manager import AstroidManager @@ -28,7 +28,7 @@ def _looks_like_parents_subscript(node: nodes.Subscript) -> bool: value = next(node.value.infer()) except (InferenceError, StopIteration): return False - parents = "builtins.tuple" if PY313_PLUS else "pathlib._PathParents" + parents = "builtins.tuple" if PY313 else "pathlib._PathParents" return ( isinstance(value, bases.Instance) and isinstance(value._proxied, nodes.ClassDef) diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py index 7ce2e7cfb4..ed6dc46874 100644 --- a/astroid/brain/brain_typing.py +++ b/astroid/brain/brain_typing.py @@ -15,7 +15,7 @@ from astroid import context, extract_node, inference_tip from astroid.brain.helpers import register_module_extender from astroid.builder import AstroidBuilder, _extract_single_node -from astroid.const import PY312_PLUS, PY313_PLUS +from astroid.const import PY312_PLUS, PY313_PLUS, PY314_PLUS from astroid.exceptions import ( AstroidSyntaxError, AttributeInferenceError, @@ -77,7 +77,7 @@ class {0}(metaclass=Meta): "typing.MutableMapping", "typing.Sequence", "typing.MutableSequence", - "typing.ByteString", + "typing.ByteString", # removed in 3.14 "typing.Tuple", "typing.List", "typing.Deque", @@ -431,9 +431,8 @@ def infer_typing_cast( def _typing_transform(): - return AstroidBuilder(AstroidManager()).string_build( - textwrap.dedent( - """ + code = textwrap.dedent( + """ class Generic: @classmethod def __class_getitem__(cls, item): return cls @@ -467,8 +466,16 @@ class Match: @classmethod def __class_getitem__(cls, item): return cls """ - ) ) + if PY314_PLUS: + code += textwrap.dedent( + """ + class Union: + @classmethod + def __class_getitem__(cls, item): return cls + """ + ) + return AstroidBuilder(AstroidManager()).string_build(code) def register(manager: AstroidManager) -> None: diff --git a/astroid/const.py b/astroid/const.py index c010818063..0bc98c2e14 100644 --- a/astroid/const.py +++ b/astroid/const.py @@ -8,7 +8,9 @@ PY310_PLUS = sys.version_info >= (3, 10) PY311_PLUS = sys.version_info >= (3, 11) PY312_PLUS = sys.version_info >= (3, 12) +PY313 = sys.version_info[:2] == (3, 13) PY313_PLUS = sys.version_info >= (3, 13) +PY314_PLUS = sys.version_info >= (3, 14) WIN32 = sys.platform == "win32" diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py index bec2cf2e5c..0b082ab535 100644 --- a/tests/brain/test_brain.py +++ b/tests/brain/test_brain.py @@ -15,7 +15,7 @@ from astroid import MANAGER, 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 PY312_PLUS, PY313_PLUS +from astroid.const import PY312_PLUS, PY313_PLUS, PY314_PLUS from astroid.exceptions import ( AttributeInferenceError, InferenceError, @@ -335,6 +335,9 @@ def test_collections_object_not_yet_subscriptable_2(self): with self.assertRaises(InferenceError): next(node.infer()) + @pytest.mark.skipif( + PY314_PLUS, reason="collections.abc.ByteString was removed in 3.14" + ) def test_collections_object_subscriptable_3(self): """With Python 3.9 the ByteString class of the collections module is subscriptable (but not the same class from typing module)""" @@ -918,6 +921,7 @@ class Derived(typing.Hashable, typing.Iterator[int]): ], ) + @pytest.mark.skipif(PY314_PLUS, reason="typing.ByteString was removed in 3.14") def test_typing_object_notsubscriptable_3(self): """Until python39 ByteString class of the typing module is not subscriptable (whereas it is in the collections' module)""" diff --git a/tests/brain/test_pathlib.py b/tests/brain/test_pathlib.py index 5aea8d3769..b97b3e75fa 100644 --- a/tests/brain/test_pathlib.py +++ b/tests/brain/test_pathlib.py @@ -5,7 +5,7 @@ import astroid from astroid import bases -from astroid.const import PY310_PLUS, PY313_PLUS +from astroid.const import PY310_PLUS, PY313 from astroid.util import Uninferable @@ -23,7 +23,7 @@ def test_inference_parents() -> None: inferred = name_node.inferred() assert len(inferred) == 1 assert isinstance(inferred[0], bases.Instance) - if PY313_PLUS: + if PY313: assert inferred[0].qname() == "builtins.tuple" else: assert inferred[0].qname() == "pathlib._PathParents" @@ -43,7 +43,7 @@ def test_inference_parents_subscript_index() -> None: inferred = path.inferred() assert len(inferred) == 1 assert isinstance(inferred[0], bases.Instance) - if PY313_PLUS: + if PY313: assert inferred[0].qname() == "pathlib._local.Path" else: assert inferred[0].qname() == "pathlib.Path" diff --git a/tests/test_inference.py b/tests/test_inference.py index acc8a2b1f8..f1e7192d5f 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -34,7 +34,7 @@ from astroid.arguments import CallSite from astroid.bases import BoundMethod, Generator, Instance, UnboundMethod, UnionType from astroid.builder import AstroidBuilder, _extract_single_node, extract_node, parse -from astroid.const import IS_PYPY, PY310_PLUS, PY312_PLUS +from astroid.const import IS_PYPY, PY310_PLUS, PY312_PLUS, PY314_PLUS from astroid.context import CallContext, InferenceContext from astroid.exceptions import ( AstroidTypeError, @@ -1306,8 +1306,12 @@ class B: ... assert i0.bool_value() is True assert i0.pytype() == "types.UnionType" assert i0.display_type() == "UnionType" - assert str(i0) == "UnionType(UnionType)" - assert repr(i0) == f"" + if PY314_PLUS: + assert str(i0) == "UnionType(Union)" + assert repr(i0) == f"" + else: + assert str(i0) == "UnionType(UnionType)" + assert repr(i0) == f"" i1 = ast_nodes[1].inferred()[0] assert isinstance(i1, UnionType) From c1d9c73fd724dd1dd7ec64195f93e86be32414c6 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 18 May 2025 14:00:00 +0200 Subject: [PATCH 4/7] Improve backport job permissions (#2750) (cherry picked from commit fe5bb6c3d6055e3ddb696da32768d6ba8bf3edec) --- .github/workflows/backport.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 9fbc070e5d..eaa4b221e4 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -6,14 +6,14 @@ on: - labeled permissions: - actions: write - contents: write - pull-requests: write + contents: read jobs: backport: name: Backport runs-on: ubuntu-latest + environment: + name: Backport # Only react to merged PRs for security reasons. # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. if: > @@ -27,4 +27,4 @@ jobs: steps: - uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # v2.0.4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.BACKPORT_TOKEN }} From 18f96261f4736a10fc7b6824c4d0fed922b05c4b Mon Sep 17 00:00:00 2001 From: "pylint-backport-bot[bot]" <212256041+pylint-backport-bot[bot]@users.noreply.github.com> Date: Sun, 18 May 2025 21:14:23 +0000 Subject: [PATCH 5/7] Use custom Github App to authenticate backport job (#2751) (#2752) (cherry picked from commit a6f8d6a2525ff23710fa18d9ce078d895d24e458) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .github/workflows/backport.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index eaa4b221e4..9e83961662 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -25,6 +25,14 @@ jobs: ) ) steps: + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.PRIVATE_KEY }} + permission-contents: write # push branch to Github + permission-pull-requests: write # create PR / add comment for manual backport + permission-workflows: write # modify files in .github/workflows - uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # v2.0.4 with: - github_token: ${{ secrets.BACKPORT_TOKEN }} + github_token: ${{ steps.app-token.outputs.token }} From bf3977c3793be0e2111220864e02f8040fcaeb7f Mon Sep 17 00:00:00 2001 From: Mitch Harding Date: Tue, 20 May 2025 15:21:51 -0400 Subject: [PATCH 6/7] Include subclasses of standard property classes as property decorators (#2735) * Include subclasses of standard property types as property decorators * Modify astroid.bases and tests.test_nodes to reflect that enum.property was added in Python 3.11, not 3.10 * Apply suggestions from code review Co-authored-by: Pierre Sassoulas --------- Co-authored-by: Pierre Sassoulas (cherry picked from commit 30128b7f87b2f57c8d502a8f6558eccf6d93f803) --- ChangeLog | 5 + astroid/bases.py | 31 ++++-- tests/test_nodes.py | 263 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 259 insertions(+), 40 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4956f82c64..e299351eb3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,11 @@ What's New in astroid 3.4.0? Release date: TBA +* Include subclasses of standard property classes as `property` decorators + + Closes #10377 + +* Modify ``astroid.bases`` and ``tests.test_nodes`` to reflect that `enum.property` was added in Python 3.11, not 3.10 What's New in astroid 3.3.11? ============================= diff --git a/astroid/bases.py b/astroid/bases.py index 4a0d152656..c7097ebde3 100644 --- a/astroid/bases.py +++ b/astroid/bases.py @@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any, Literal from astroid import decorators, nodes -from astroid.const import PY310_PLUS +from astroid.const import PY311_PLUS from astroid.context import ( CallContext, InferenceContext, @@ -38,8 +38,9 @@ from astroid.constraint import Constraint -PROPERTIES = {"builtins.property", "abc.abstractproperty"} -if PY310_PLUS: +PROPERTIES = {"builtins.property", "abc.abstractproperty", "functools.cached_property"} +# enum.property was added in Python 3.11 +if PY311_PLUS: PROPERTIES.add("enum.property") # List of possible property names. We use this list in order @@ -79,24 +80,30 @@ def _is_property( if any(name in stripped for name in POSSIBLE_PROPERTIES): return True - # Lookup for subclasses of *property* if not meth.decorators: return False + # Lookup for subclasses of *property* for decorator in meth.decorators.nodes or (): inferred = safe_infer(decorator, context=context) if inferred is None or isinstance(inferred, UninferableBase): continue if isinstance(inferred, nodes.ClassDef): + # Check for a class which inherits from a standard property type + if any(inferred.is_subtype_of(pclass) for pclass in PROPERTIES): + return True for base_class in inferred.bases: - if not isinstance(base_class, nodes.Name): + # Check for a class which inherits from functools.cached_property + # and includes a subscripted type annotation + if isinstance(base_class, nodes.Subscript): + value = safe_infer(base_class.value, context=context) + if not isinstance(value, nodes.ClassDef): + continue + if value.name != "cached_property": + continue + module, _ = value.lookup(value.name) + if isinstance(module, nodes.Module) and module.name == "functools": + return True continue - module, _ = base_class.lookup(base_class.name) - if ( - isinstance(module, nodes.Module) - and module.name == "builtins" - and base_class.name == "property" - ): - return True return False diff --git a/tests/test_nodes.py b/tests/test_nodes.py index dced983625..3c0e7c218f 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -28,7 +28,7 @@ transforms, util, ) -from astroid.const import IS_PYPY, PY310_PLUS, PY312_PLUS, Context +from astroid.const import IS_PYPY, PY310_PLUS, PY311_PLUS, PY312_PLUS, Context from astroid.context import InferenceContext from astroid.exceptions import ( AstroidBuildingError, @@ -929,67 +929,274 @@ def test(self): class BoundMethodNodeTest(unittest.TestCase): - def test_is_property(self) -> None: + def _is_property(self, ast: nodes.Module, prop: str) -> None: + inferred = next(ast[prop].infer()) + self.assertIsInstance(inferred, nodes.Const, prop) + self.assertEqual(inferred.value, 42, prop) + + def test_is_standard_property(self) -> None: + # Test to make sure the Python-provided property decorators + # are properly interpreted as properties ast = builder.parse( """ import abc + import functools - def cached_property(): - # Not a real decorator, but we don't care - pass - def reify(): - # Same as cached_property - pass - def lazy_property(): - pass - def lazyproperty(): - pass - def lazy(): pass class A(object): @property - def builtin_property(self): - return 42 + def builtin_property(self): return 42 + @abc.abstractproperty - def abc_property(self): - return 42 + def abc_property(self): return 42 + + @property + @abc.abstractmethod + def abstractmethod_property(self): return 42 + + @functools.cached_property + def functools_property(self): return 42 + + cls = A() + builtin_p = cls.builtin_property + abc_p = cls.abc_property + abstractmethod_p = cls.abstractmethod_property + functools_p = cls.functools_property + """ + ) + for prop in ( + "builtin_p", + "abc_p", + "abstractmethod_p", + "functools_p", + ): + self._is_property(ast, prop) + + @pytest.mark.skipif(not PY311_PLUS, reason="Uses enum.property introduced in 3.11") + def test_is_standard_property_py311(self) -> None: + # Test to make sure the Python-provided property decorators + # are properly interpreted as properties + ast = builder.parse( + """ + import enum + + class A(object): + @enum.property + def enum_property(self): return 42 + + cls = A() + enum_p = cls.enum_property + """ + ) + self._is_property(ast, "enum_p") + + def test_is_possible_property(self) -> None: + # Test to make sure that decorators with POSSIBLE_PROPERTIES names + # are properly interpreted as properties + ast = builder.parse( + """ + # Not real decorators, but we don't care + def cachedproperty(): pass + def cached_property(): pass + def reify(): pass + def lazy_property(): pass + def lazyproperty(): pass + def lazy(): pass + def lazyattribute(): pass + def lazy_attribute(): pass + def LazyProperty(): pass + def DynamicClassAttribute(): pass + + class A(object): + @cachedproperty + def cachedproperty(self): return 42 + @cached_property def cached_property(self): return 42 + @reify def reified(self): return 42 + @lazy_property def lazy_prop(self): return 42 + @lazyproperty def lazyprop(self): return 42 - def not_prop(self): pass + @lazy def decorated_with_lazy(self): return 42 + @lazyattribute + def lazyattribute(self): return 42 + + @lazy_attribute + def lazy_attribute(self): return 42 + + @LazyProperty + def LazyProperty(self): return 42 + + @DynamicClassAttribute + def DynamicClassAttribute(self): return 42 + cls = A() - builtin_property = cls.builtin_property - abc_property = cls.abc_property + cachedp = cls.cachedproperty cached_p = cls.cached_property reified = cls.reified - not_prop = cls.not_prop lazy_prop = cls.lazy_prop lazyprop = cls.lazyprop decorated_with_lazy = cls.decorated_with_lazy + lazya = cls.lazyattribute + lazy_a = cls.lazy_attribute + LazyP = cls.LazyProperty + DynamicClassA = cls.DynamicClassAttribute """ ) for prop in ( - "builtin_property", - "abc_property", + "cachedp", "cached_p", "reified", "lazy_prop", "lazyprop", "decorated_with_lazy", + "lazya", + "lazy_a", + "LazyP", + "DynamicClassA", ): - inferred = next(ast[prop].infer()) - self.assertIsInstance(inferred, nodes.Const, prop) - self.assertEqual(inferred.value, 42, prop) + self._is_property(ast, prop) + + def test_is_standard_property_subclass(self) -> None: + # Test to make sure that subclasses of the Python-provided property decorators + # are properly interpreted as properties + ast = builder.parse( + """ + import abc + import functools + from typing import Generic, TypeVar + + class user_property(property): pass + class user_abc_property(abc.abstractproperty): pass + class user_functools_property(functools.cached_property): pass + T = TypeVar('T') + class annotated_user_functools_property(functools.cached_property[T], Generic[T]): pass + + class A(object): + @user_property + def user_property(self): return 42 - inferred = next(ast["not_prop"].infer()) - self.assertIsInstance(inferred, bases.BoundMethod) + @user_abc_property + def user_abc_property(self): return 42 + + @user_functools_property + def user_functools_property(self): return 42 + + @annotated_user_functools_property + def annotated_user_functools_property(self): return 42 + + cls = A() + user_p = cls.user_property + user_abc_p = cls.user_abc_property + user_functools_p = cls.user_functools_property + annotated_user_functools_p = cls.annotated_user_functools_property + """ + ) + for prop in ( + "user_p", + "user_abc_p", + "user_functools_p", + "annotated_user_functools_p", + ): + self._is_property(ast, prop) + + @pytest.mark.skipif(not PY311_PLUS, reason="Uses enum.property introduced in 3.11") + def test_is_standard_property_subclass_py311(self) -> None: + # Test to make sure that subclasses of the Python-provided property decorators + # are properly interpreted as properties + ast = builder.parse( + """ + import enum + + class user_enum_property(enum.property): pass + + class A(object): + @user_enum_property + def user_enum_property(self): return 42 + + cls = A() + user_enum_p = cls.user_enum_property + """ + ) + self._is_property(ast, "user_enum_p") + + @pytest.mark.skipif(not PY312_PLUS, reason="Uses 3.12 generic typing syntax") + def test_is_standard_property_subclass_py312(self) -> None: + ast = builder.parse( + """ + from functools import cached_property + + class annotated_user_cached_property[T](cached_property[T]): + pass + + class A(object): + @annotated_user_cached_property + def annotated_user_cached_property(self): return 42 + + cls = A() + annotated_user_cached_p = cls.annotated_user_cached_property + """ + ) + self._is_property(ast, "annotated_user_cached_p") + + def test_is_not_property(self) -> None: + ast = builder.parse( + """ + from collections.abc import Iterator + + class cached_property: pass + # If a decorator is named cached_property, we will accept it as a property, + # even if it isn't functools.cached_property. + # However, do not extend the same leniency to superclasses of decorators. + class wrong_superclass_type1(cached_property): pass + class wrong_superclass_type2(cached_property[float]): pass + cachedproperty = { float: int } + class wrong_superclass_type3(cachedproperty[float]): pass + class wrong_superclass_type4(Iterator[float]): pass + + class A(object): + def no_decorator(self): return 42 + + def property(self): return 42 + + @wrong_superclass_type1 + def wrong_superclass_type1(self): return 42 + + @wrong_superclass_type2 + def wrong_superclass_type2(self): return 42 + + @wrong_superclass_type3 + def wrong_superclass_type3(self): return 42 + + @wrong_superclass_type4 + def wrong_superclass_type4(self): return 42 + + cls = A() + no_decorator = cls.no_decorator + not_prop = cls.property + bad_superclass1 = cls.wrong_superclass_type1 + bad_superclass2 = cls.wrong_superclass_type2 + bad_superclass3 = cls.wrong_superclass_type3 + bad_superclass4 = cls.wrong_superclass_type4 + """ + ) + for prop in ( + "no_decorator", + "not_prop", + "bad_superclass1", + "bad_superclass2", + "bad_superclass3", + "bad_superclass4", + ): + inferred = next(ast[prop].infer()) + self.assertIsInstance(inferred, bases.BoundMethod) class AliasesTest(unittest.TestCase): From fbea510b9e7604be0afea5b25ca6b51a73f09870 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 13 Jul 2025 20:01:29 +0200 Subject: [PATCH 7/7] Bump astroid to 3.3.11, update changelog (#2777) --- CONTRIBUTORS.txt | 1 + ChangeLog | 3 ++- astroid/__pkginfo__.py | 2 +- script/.contributors_aliases.json | 3 ++- tbump.toml | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 322d5d29d2..45db79de53 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -133,6 +133,7 @@ Contributors - Oleh Prypin - Nicolas Noirbent - Neil Girdhar +- Mitch Harding - Miro Hrončok - Michał Masłowski - Mateusz Bysiek diff --git a/ChangeLog b/ChangeLog index e299351eb3..0d7dfb5ef7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,9 +14,10 @@ Release date: TBA * Modify ``astroid.bases`` and ``tests.test_nodes`` to reflect that `enum.property` was added in Python 3.11, not 3.10 + What's New in astroid 3.3.11? ============================= -Release date: TBA +Release date: 2025-07-13 * Fix a crash when parsing an empty arbitrary expression with ``extract_node`` (``extract_node("__()")``). diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index baae080cdf..6a09f40590 100644 --- a/astroid/__pkginfo__.py +++ b/astroid/__pkginfo__.py @@ -2,5 +2,5 @@ # For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE # Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt -__version__ = "3.3.10" +__version__ = "3.3.11" version = __version__ diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json index 84e26a3931..8a2ad6f7d1 100644 --- a/script/.contributors_aliases.json +++ b/script/.contributors_aliases.json @@ -61,7 +61,8 @@ "mails": [ "66853113+pre-commit-ci[bot]@users.noreply.github.com", "49699333+dependabot[bot]@users.noreply.github.com", - "41898282+github-actions[bot]@users.noreply.github.com" + "41898282+github-actions[bot]@users.noreply.github.com", + "212256041+pylint-backport-bot[bot]@users.noreply.github.com" ], "name": "bot" }, diff --git a/tbump.toml b/tbump.toml index 19c4091082..b3dfc11e1c 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/astroid" [version] -current = "3.3.10" +current = "3.3.11" regex = ''' ^(?P0|[1-9]\d*) \.