From c47a798b00ec5d53543ff0521d59bb43c684937d Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 20 Dec 2024 08:06:11 +0100 Subject: [PATCH 01/12] Fix `collections.abc` imports on Python 3.13.0 and 3.13.1 (#2657) (#2658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix `collections.abc` imports on Python 3.13.0 and 3.13.1 (#2657) * Add test for importing `collections.abc` * Fix issue with importing of frozen submodules * Fix `search_paths` * Do not reassign submodule_path parameters in method bodies This makes it easier to use less generic annotations with mypy. --------- Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: correctmost <134317971+correctmost@users.noreply.github.com> --- ChangeLog | 5 +++ astroid/brain/brain_collections.py | 11 +++-- astroid/const.py | 1 - astroid/interpreter/_import/spec.py | 69 +++++++++++++++++++++-------- tests/brain/test_brain.py | 16 +++++++ 5 files changed, 76 insertions(+), 26 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5de60c6fbd..a8536be7e1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,10 @@ What's New in astroid 3.3.7? ============================ Release date: TBA +* Fix inability to import `collections.abc` in python 3.13.1. The reported fix in astroid 3.3.6 + did not actually fix this issue. + + Closes pylint-dev/pylint#10112 What's New in astroid 3.3.6? @@ -20,6 +24,7 @@ What's New in astroid 3.3.6? Release date: 2024-12-08 * Fix inability to import `collections.abc` in python 3.13.1. + _It was later found that this did not resolve the linked issue. It was fixed in astroid 3.3.7_ Closes pylint-dev/pylint#10112 diff --git a/astroid/brain/brain_collections.py b/astroid/brain/brain_collections.py index 462c85add2..94944e67ad 100644 --- a/astroid/brain/brain_collections.py +++ b/astroid/brain/brain_collections.py @@ -8,7 +8,7 @@ from astroid.brain.helpers import register_module_extender from astroid.builder import AstroidBuilder, extract_node, parse -from astroid.const import PY313_0, PY313_PLUS +from astroid.const import PY313_PLUS from astroid.context import InferenceContext from astroid.exceptions import AttributeInferenceError from astroid.manager import AstroidManager @@ -20,8 +20,7 @@ def _collections_transform(): return parse( - (" import _collections_abc as abc" if PY313_PLUS and not PY313_0 else "") - + """ + """ class defaultdict(dict): default_factory = None def __missing__(self, key): pass @@ -33,7 +32,7 @@ def __getitem__(self, key): return default_factory ) -def _collections_abc_313_0_transform() -> nodes.Module: +def _collections_abc_313_transform() -> nodes.Module: """See https://github.com/python/cpython/pull/124735""" return AstroidBuilder(AstroidManager()).string_build( "from _collections_abc import *" @@ -133,7 +132,7 @@ def register(manager: AstroidManager) -> None: ClassDef, easy_class_getitem_inference, _looks_like_subscriptable ) - if PY313_0: + if PY313_PLUS: register_module_extender( - manager, "collections.abc", _collections_abc_313_0_transform + manager, "collections.abc", _collections_abc_313_transform ) diff --git a/astroid/const.py b/astroid/const.py index a10c0f4a2b..c010818063 100644 --- a/astroid/const.py +++ b/astroid/const.py @@ -9,7 +9,6 @@ PY311_PLUS = sys.version_info >= (3, 11) PY312_PLUS = sys.version_info >= (3, 12) PY313_PLUS = sys.version_info >= (3, 13) -PY313_0 = sys.version_info[:3] == (3, 13, 0) WIN32 = sys.platform == "win32" diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index 09e98c888b..832432d946 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -133,36 +133,66 @@ def find_module( processed: list[str], submodule_path: Sequence[str] | None, ) -> ModuleSpec | None: - if submodule_path is not None: - submodule_path = list(submodule_path) - elif modname in sys.builtin_module_names: + # Although we should be able to use `find_spec` this doesn't work on PyPy for builtins. + # Therefore, we use the `builtin_module_nams` heuristic for these. + if submodule_path is None and modname in sys.builtin_module_names: return ModuleSpec( name=modname, location=None, type=ModuleType.C_BUILTIN, ) - else: - try: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=UserWarning) - spec = importlib.util.find_spec(modname) + + # sys.stdlib_module_names was added in Python 3.10 + if PY310_PLUS: + # If the module is a stdlib module, check whether this is a frozen module. Note that + # `find_spec` actually imports the module, so we want to make sure we only run this code + # for stuff that can be expected to be frozen. For now this is only stdlib. + if modname in sys.stdlib_module_names or ( + processed and processed[0] in sys.stdlib_module_names + ): + spec = importlib.util.find_spec(".".join((*processed, modname))) if ( spec and spec.loader # type: ignore[comparison-overlap] # noqa: E501 is importlib.machinery.FrozenImporter ): - # No need for BuiltinImporter; builtins handled above return ModuleSpec( name=modname, location=getattr(spec.loader_state, "filename", None), type=ModuleType.PY_FROZEN, ) - except ValueError: - pass - submodule_path = sys.path + else: + # NOTE: This is broken code. It doesn't work on Python 3.13+ where submodules can also + # be frozen. However, we don't want to worry about this and we don't want to break + # support for older versions of Python. This is just copy-pasted from the old non + # working version to at least have no functional behaviour change on <=3.10. + # It can be removed after 3.10 is no longer supported in favour of the logic above. + if submodule_path is None: # pylint: disable=else-if-used + try: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=UserWarning) + spec = importlib.util.find_spec(modname) + if ( + spec + and spec.loader # type: ignore[comparison-overlap] # noqa: E501 + is importlib.machinery.FrozenImporter + ): + # No need for BuiltinImporter; builtins handled above + return ModuleSpec( + name=modname, + location=getattr(spec.loader_state, "filename", None), + type=ModuleType.PY_FROZEN, + ) + except ValueError: + pass + + if submodule_path is not None: + search_paths = list(submodule_path) + else: + search_paths = sys.path suffixes = (".py", ".pyi", importlib.machinery.BYTECODE_SUFFIXES[0]) - for entry in submodule_path: + for entry in search_paths: package_directory = os.path.join(entry, modname) for suffix in suffixes: package_file_name = "__init__" + suffix @@ -231,13 +261,12 @@ def find_module( if processed: modname = ".".join([*processed, modname]) if util.is_namespace(modname) and modname in sys.modules: - submodule_path = sys.modules[modname].__path__ return ModuleSpec( name=modname, location="", origin="namespace", type=ModuleType.PY_NAMESPACE, - submodule_search_locations=submodule_path, + submodule_search_locations=sys.modules[modname].__path__, ) return None @@ -353,13 +382,15 @@ def _search_zip( if PY310_PLUS: if not importer.find_spec(os.path.sep.join(modpath)): raise ImportError( - "No module named %s in %s/%s" - % (".".join(modpath[1:]), filepath, modpath) + "No module named {} in {}/{}".format( + ".".join(modpath[1:]), filepath, modpath + ) ) elif not importer.find_module(os.path.sep.join(modpath)): raise ImportError( - "No module named %s in %s/%s" - % (".".join(modpath[1:]), filepath, modpath) + "No module named {} in {}/{}".format( + ".".join(modpath[1:]), filepath, modpath + ) ) return ( ModuleType.PY_ZIPMODULE, diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py index 447c4cde26..bec2cf2e5c 100644 --- a/tests/brain/test_brain.py +++ b/tests/brain/test_brain.py @@ -192,6 +192,22 @@ def check_metaclass_is_abc(node: nodes.ClassDef): class CollectionsBrain(unittest.TestCase): + def test_collections_abc_is_importable(self) -> None: + """ + Test that we can import `collections.abc`. + + The collections.abc has gone through various formats of being frozen. Therefore, we ensure + that we can still import it (correctly). + """ + import_node = builder.extract_node("import collections.abc") + assert isinstance(import_node, nodes.Import) + imported_module = import_node.do_import_module(import_node.names[0][0]) + # Make sure that the file we have imported is actually the submodule of collections and + # not the `abc` module. (Which would happen if you call `importlib.util.find_spec("abc")` + # instead of `importlib.util.find_spec("collections.abc")`) + assert isinstance(imported_module.file, str) + assert "collections" in imported_module.file + def test_collections_object_not_subscriptable(self) -> None: """ Test that unsubscriptable types are detected From 50916d64150da365d4234b48afec73008f2a81f1 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 21 Dec 2024 11:59:36 +0100 Subject: [PATCH 02/12] Bump astroid to 3.3.7, update changelog (#2659) --- CONTRIBUTORS.txt | 16 +++++++++++----- ChangeLog | 8 +++++++- astroid/__pkginfo__.py | 2 +- tbump.toml | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 57270668c0..2de71f2df0 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -15,9 +15,9 @@ Maintainers ----------- - Pierre Sassoulas - Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -- Hippo91 -- Marc Mueller <30130371+cdce8p@users.noreply.github.com> - Jacob Walls +- Marc Mueller <30130371+cdce8p@users.noreply.github.com> +- Hippo91 - Bryce Guinta - Ceridwen - Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> @@ -41,6 +41,7 @@ Contributors - Tushar Sadhwani - Julien Jehannet - Calen Pennington +- Antonio - Hugo van Kemenade - Tim Martin - Phil Schaf @@ -53,6 +54,7 @@ Contributors - David Shea - Daniel Harding - Christian Clauss +- correctmost <134317971+correctmost@users.noreply.github.com> - Ville Skyttä - Rene Zhang - Philip Lorenz @@ -65,7 +67,6 @@ Contributors - FELD Boris - Enji Cooper - Dani Alcala <112832187+clavedeluna@users.noreply.github.com> -- Antonio - Adrien Di Mascio - tristanlatr <19967168+tristanlatr@users.noreply.github.com> - emile@crater.logilab.fr @@ -129,6 +130,7 @@ Contributors - Peter de Blanc - Peter Talley - Ovidiu Sabou +- Oleh Prypin - Nicolas Noirbent - Neil Girdhar - Miro Hrončok @@ -140,6 +142,8 @@ Contributors - Kian Meng, Ang - Kai Mueller <15907922+kasium@users.noreply.github.com> - Jörg Thalheim +- Jérome Perrin +- JulianJvn <128477611+JulianJvn@users.noreply.github.com> - Josef Kemetmüller - Jonathan Striebel - John Belmonte @@ -147,11 +151,14 @@ Contributors - Jeff Quast - Jarrad Hope - Jared Garst +- Jamie Scott - Jakub Wilk - Iva Miholic - Ionel Maries Cristian - HoverHell +- Hashem Nasarat - HQupgradeHQ <18361586+HQupgradeHQ@users.noreply.github.com> +- Gwyn Ciesla - Grygorii Iermolenko - Gregory P. Smith - Giuseppe Scrivano @@ -159,6 +166,7 @@ Contributors - Francis Charette Migneault - Felix Mölder - Federico Bond +- Eric Vergnaud - DudeNr33 <3929834+DudeNr33@users.noreply.github.com> - Dmitry Shachnev - Denis Laxalde @@ -191,8 +199,6 @@ Contributors - Alexander Scheel - Alexander Presnyakov - Ahmed Azzaoui -- JulianJvn <128477611+JulianJvn@users.noreply.github.com> -- Gwyn Ciesla Co-Author --------- diff --git a/ChangeLog b/ChangeLog index a8536be7e1..5ec03e2fa0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,10 +9,16 @@ Release date: TBA -What's New in astroid 3.3.7? +What's New in astroid 3.3.8? ============================ Release date: TBA + + +What's New in astroid 3.3.7? +============================ +Release date: 2024-12-20 + * Fix inability to import `collections.abc` in python 3.13.1. The reported fix in astroid 3.3.6 did not actually fix this issue. diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index 60f4185308..d905e66a1c 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.6" +__version__ = "3.3.7" version = __version__ diff --git a/tbump.toml b/tbump.toml index 96381c8fee..b0b01ed003 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/astroid" [version] -current = "3.3.6" +current = "3.3.7" regex = ''' ^(?P0|[1-9]\d*) \. From 7cfbad135cbfea51cf8298cd506c0ee03bd42d8a Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 23 Dec 2024 00:15:17 +0100 Subject: [PATCH 03/12] Skip flaky recursion test on PyPy (#2661) (#2663) Co-authored-by: Jacob Walls --- tests/test_transforms.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 460868e02b..f4875ca5f2 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -265,6 +265,9 @@ def transform_class(cls): """ ) + @pytest.mark.skipif( + IS_PYPY, reason="Could not find a useful recursion limit on all versions" + ) def test_transform_aborted_if_recursion_limited(self): def transform_call(node: Call) -> Const: return node @@ -274,7 +277,7 @@ def transform_call(node: Call) -> Const: ) original_limit = sys.getrecursionlimit() - sys.setrecursionlimit(500 if IS_PYPY else 1000) + sys.setrecursionlimit(1000) try: with pytest.warns(UserWarning) as records: From 68714dfe0bce926f5783212d9a2a5cdaedbc8264 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 23 Dec 2024 17:02:00 -0800 Subject: [PATCH 04/12] [Backport maintenance/3.3.x] Another attempt at fixing the `collections.abc` issue for Python 3.13 (#2665) (#2666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix issue with importing of frozen submodules (cherry picked from commit f94e855268895bf995fe8eb258ea7ff7d7ccd9cf) * Test on Python 3.13 final * Bump CI jobs to python 3.13 --------- Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- .github/workflows/ci.yaml | 10 ++--- .github/workflows/release.yml | 2 +- ChangeLog | 6 +++ astroid/interpreter/_import/spec.py | 60 ++++++++++++++++------------- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9ae694e6ba..5b689dbd5b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,7 +10,7 @@ on: env: CACHE_VERSION: 3 KEY_PREFIX: venv - DEFAULT_PYTHON: "3.12" + DEFAULT_PYTHON: "3.13" PRE_COMMIT_CACHE: ~/.cache/pre-commit concurrency: @@ -81,7 +81,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, "3.10", "3.11", "3.12", "3.13-dev"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] outputs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: @@ -138,7 +138,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, "3.10", "3.11", "3.12", "3.13-dev"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] steps: - name: Set temp directory run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV @@ -242,11 +242,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v4.1.7 - - name: Set up Python 3.12 + - name: Set up Python 3.13 id: python uses: actions/setup-python@v5.1.1 with: - python-version: "3.12" + python-version: "3.13" check-latest: true - name: Install dependencies run: pip install -U -r requirements_minimal.txt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d527a2f221..ff91c7fd57 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: - published env: - DEFAULT_PYTHON: "3.12" + DEFAULT_PYTHON: "3.13" permissions: contents: read diff --git a/ChangeLog b/ChangeLog index 5ec03e2fa0..d20554ec03 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,12 +13,18 @@ What's New in astroid 3.3.8? ============================ Release date: TBA +* Fix inability to import `collections.abc` in python 3.13.1. The reported fixes in astroid 3.3.6 + and 3.3.7 did not actually fix this issue. + + Closes pylint-dev/pylint#10112 What's New in astroid 3.3.7? ============================ Release date: 2024-12-20 +This release was yanked. + * Fix inability to import `collections.abc` in python 3.13.1. The reported fix in astroid 3.3.6 did not actually fix this issue. diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index 832432d946..fd53da56ea 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -142,15 +142,45 @@ def find_module( type=ModuleType.C_BUILTIN, ) + if submodule_path is not None: + search_paths = list(submodule_path) + else: + search_paths = sys.path + + suffixes = (".py", ".pyi", importlib.machinery.BYTECODE_SUFFIXES[0]) + for entry in search_paths: + package_directory = os.path.join(entry, modname) + for suffix in suffixes: + package_file_name = "__init__" + suffix + file_path = os.path.join(package_directory, package_file_name) + if os.path.isfile(file_path): + return ModuleSpec( + name=modname, + location=package_directory, + type=ModuleType.PKG_DIRECTORY, + ) + for suffix, type_ in ImportlibFinder._SUFFIXES: + file_name = modname + suffix + file_path = os.path.join(entry, file_name) + if os.path.isfile(file_path): + return ModuleSpec(name=modname, location=file_path, type=type_) + # sys.stdlib_module_names was added in Python 3.10 if PY310_PLUS: - # If the module is a stdlib module, check whether this is a frozen module. Note that - # `find_spec` actually imports the module, so we want to make sure we only run this code - # for stuff that can be expected to be frozen. For now this is only stdlib. + # If the module name matches a stdlib module name, check whether this is a frozen + # module. Note that `find_spec` actually imports parent modules, so we want to make + # sure we only run this code for stuff that can be expected to be frozen. For now + # this is only stdlib. if modname in sys.stdlib_module_names or ( processed and processed[0] in sys.stdlib_module_names ): - spec = importlib.util.find_spec(".".join((*processed, modname))) + try: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=Warning) + spec = importlib.util.find_spec(".".join((*processed, modname))) + except ValueError: + spec = None + if ( spec and spec.loader # type: ignore[comparison-overlap] # noqa: E501 @@ -186,28 +216,6 @@ def find_module( except ValueError: pass - if submodule_path is not None: - search_paths = list(submodule_path) - else: - search_paths = sys.path - - suffixes = (".py", ".pyi", importlib.machinery.BYTECODE_SUFFIXES[0]) - for entry in search_paths: - package_directory = os.path.join(entry, modname) - for suffix in suffixes: - package_file_name = "__init__" + suffix - file_path = os.path.join(package_directory, package_file_name) - if os.path.isfile(file_path): - return ModuleSpec( - name=modname, - location=package_directory, - type=ModuleType.PKG_DIRECTORY, - ) - for suffix, type_ in ImportlibFinder._SUFFIXES: - file_name = modname + suffix - file_path = os.path.join(entry, file_name) - if os.path.isfile(file_path): - return ModuleSpec(name=modname, location=file_path, type=type_) return None def contribute_to_path( From d52799b6e8359d89f20f020d77e98a71d2cde20f Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 23 Dec 2024 17:08:15 -0800 Subject: [PATCH 05/12] Bump astroid to 3.3.8, update changelog --- ChangeLog | 8 +++++++- astroid/__pkginfo__.py | 2 +- tbump.toml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index d20554ec03..411cd55672 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,10 +9,16 @@ Release date: TBA -What's New in astroid 3.3.8? +What's New in astroid 3.3.9? ============================ Release date: TBA + + +What's New in astroid 3.3.8? +============================ +Release date: 2024-12-23 + * Fix inability to import `collections.abc` in python 3.13.1. The reported fixes in astroid 3.3.6 and 3.3.7 did not actually fix this issue. diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index d905e66a1c..bad119adf8 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.7" +__version__ = "3.3.8" version = __version__ diff --git a/tbump.toml b/tbump.toml index b0b01ed003..8786e7b93d 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/astroid" [version] -current = "3.3.7" +current = "3.3.8" regex = ''' ^(?P0|[1-9]\d*) \. From 6aeafd586b00390c724863d9ff8b79755cb9bdc8 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 2 Mar 2025 08:41:39 -0500 Subject: [PATCH 06/12] Bump pylint in pre-commit configuration to 3.2.7 --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 4949455f52..c693a035b0 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,6 +3,6 @@ # Tools used during development, prefer running these with pre-commit black pre-commit -pylint>=3.2.0 +pylint>=3.2.7 mypy ruff From 234be58ddd7df5a9dff9bbb3773ce908199ed826 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 2 Mar 2025 09:08:55 -0500 Subject: [PATCH 07/12] Fix RuntimeError caused by analyzing live objects with `__getattribute__` or descriptors (#2687) (#2688) Avoid some attribute accesses in const_factory (cherry picked from commit 71dc6b2412f95e0ad01cefdd6ee0d8e9ea551376) Co-authored-by: aatle <168398276+aatle@users.noreply.github.com> --- ChangeLog | 5 +++++ astroid/nodes/node_classes.py | 13 ++++++++++--- tests/test_raw_building.py | 9 +++++++++ .../fake_module_with_collection_getattribute.py | 11 +++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 tests/testdata/python3/data/fake_module_with_collection_getattribute.py diff --git a/ChangeLog b/ChangeLog index 411cd55672..a6238422db 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,6 +14,11 @@ What's New in astroid 3.3.9? Release date: TBA +* Fix crash when `sys.modules` contains lazy loader objects during checking. + + Closes #2686 + Closes pylint-dev/pylint#8589 + What's New in astroid 3.3.8? ============================ diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 6845ca99c1..db78f8ce00 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -5488,6 +5488,7 @@ def _create_basic_elements( """Create a list of nodes to function as the elements of a new node.""" elements: list[NodeNG] = [] for element in value: + # NOTE: avoid accessing any attributes of element in the loop. element_node = const_factory(element) element_node.parent = node elements.append(element_node) @@ -5500,6 +5501,7 @@ def _create_dict_items( """Create a list of node pairs to function as the items of a new dict node.""" elements: list[tuple[SuccessfulInferenceResult, SuccessfulInferenceResult]] = [] for key, value in values.items(): + # NOTE: avoid accessing any attributes of both key and value in the loop. key_node = const_factory(key) key_node.parent = node value_node = const_factory(value) @@ -5510,18 +5512,23 @@ def _create_dict_items( def const_factory(value: Any) -> ConstFactoryResult: """Return an astroid node for a python value.""" - assert not isinstance(value, NodeNG) + # NOTE: avoid accessing any attributes of value until it is known that value + # is of a const type, to avoid possibly triggering code for a live object. + # Accesses include value.__class__ and isinstance(value, ...), but not type(value). + # See: https://github.com/pylint-dev/astroid/issues/2686 + value_type = type(value) + assert not issubclass(value_type, NodeNG) # This only handles instances of the CONST types. Any # subclasses get inferred as EmptyNode. # TODO: See if we should revisit these with the normal builder. - if value.__class__ not in CONST_CLS: + if value_type not in CONST_CLS: node = EmptyNode() node.object = value return node instance: List | Set | Tuple | Dict - initializer_cls = CONST_CLS[value.__class__] + initializer_cls = CONST_CLS[value_type] if issubclass(initializer_cls, (List, Set, Tuple)): instance = initializer_cls( lineno=None, diff --git a/tests/test_raw_building.py b/tests/test_raw_building.py index 951bf09d90..e0000c1ebd 100644 --- a/tests/test_raw_building.py +++ b/tests/test_raw_building.py @@ -22,6 +22,7 @@ import pytest import tests.testdata.python3.data.fake_module_with_broken_getattr as fm_getattr +import tests.testdata.python3.data.fake_module_with_collection_getattribute as fm_collection import tests.testdata.python3.data.fake_module_with_warnings as fm from astroid.builder import AstroidBuilder from astroid.const import IS_PYPY, PY312_PLUS @@ -118,6 +119,14 @@ def test_module_object_with_broken_getattr(self) -> None: # This should not raise an exception AstroidBuilder().inspect_build(fm_getattr, "test") + def test_module_collection_with_object_getattribute(self) -> None: + # Tests https://github.com/pylint-dev/astroid/issues/2686 + # When astroid live inspection of module's collection raises + # error when element __getattribute__ causes collection to change size. + + # This should not raise an exception + AstroidBuilder().inspect_build(fm_collection, "test") + @pytest.mark.skipif( "posix" not in sys.builtin_module_names, reason="Platform doesn't support posix" diff --git a/tests/testdata/python3/data/fake_module_with_collection_getattribute.py b/tests/testdata/python3/data/fake_module_with_collection_getattribute.py new file mode 100644 index 0000000000..e6ab45089f --- /dev/null +++ b/tests/testdata/python3/data/fake_module_with_collection_getattribute.py @@ -0,0 +1,11 @@ +class Changer: + def __getattribute__(self, name): + list_collection.append(self) + set_collection.add(self) + dict_collection[self] = self + return object.__getattribute__(self, name) + + +list_collection = [Changer()] +set_collection = {Changer()} +dict_collection = {Changer(): Changer()} From aad8e689943e7ea106c6a88624812c9946ab3c06 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 2 Mar 2025 15:30:12 +0100 Subject: [PATCH 08/12] [Backport maintenance/3.3.x] Fix missing __dict__ (#2685) (#2690) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stéphane Brunner --- astroid/raw_building.py | 6 +++++- requirements_dev.txt | 1 - requirements_minimal.txt | 1 + tests/test_raw_building.py | 9 +++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/astroid/raw_building.py b/astroid/raw_building.py index a89a87b571..b7aafb00e5 100644 --- a/astroid/raw_building.py +++ b/astroid/raw_building.py @@ -370,7 +370,11 @@ def _base_class_object_build( # this at least resolves common case such as Exception.args, # OSError.errno if issubclass(member, Exception): - instdict = member().__dict__ + member_object = member() + if hasattr(member_object, "__dict__"): + instdict = member_object.__dict__ + else: + raise TypeError else: raise TypeError except TypeError: diff --git a/requirements_dev.txt b/requirements_dev.txt index c693a035b0..77a6fd9f64 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -4,5 +4,4 @@ black pre-commit pylint>=3.2.7 -mypy ruff diff --git a/requirements_minimal.txt b/requirements_minimal.txt index 71170ce8bd..9810281aec 100644 --- a/requirements_minimal.txt +++ b/requirements_minimal.txt @@ -6,3 +6,4 @@ tbump~=6.11 coverage~=7.6 pytest pytest-cov~=5.0 +mypy diff --git a/tests/test_raw_building.py b/tests/test_raw_building.py index e0000c1ebd..f9536ebd20 100644 --- a/tests/test_raw_building.py +++ b/tests/test_raw_building.py @@ -19,6 +19,7 @@ from typing import Any from unittest import mock +import mypy.build import pytest import tests.testdata.python3.data.fake_module_with_broken_getattr as fm_getattr @@ -32,8 +33,11 @@ build_from_import, build_function, build_module, + object_build_class, ) +DUMMY_MOD = build_module("DUMMY") + class RawBuildingTC(unittest.TestCase): def test_attach_dummy_node(self) -> None: @@ -166,3 +170,8 @@ def mocked_sys_modules_getitem(name: str) -> types.ModuleType | CustomGetattr: assert expected_err in caplog.text assert not out assert not err + + +def test_missing__dict__(): + # This shouldn't raise an exception. + object_build_class(DUMMY_MOD, mypy.build.ModuleNotFound, "arbitrary_name") From 5512bf2603e0959668bc816f992c656f6bd108df Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 9 Mar 2025 07:48:07 +0100 Subject: [PATCH 09/12] Update release workflow to use Trusted Publishing (#2696) --- .github/workflows/release.yml | 59 ++++++++++++++++++++++++++++------- ChangeLog | 4 +++ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff91c7fd57..9164b9b92a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,12 +12,10 @@ permissions: contents: read jobs: - release-pypi: - name: Upload release to PyPI + build: + name: Build release assets runs-on: ubuntu-latest - environment: - name: PyPI - url: https://pypi.org/project/astroid/ + if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') steps: - name: Check out code from Github uses: actions/checkout@v4.1.7 @@ -31,15 +29,52 @@ jobs: run: | # Remove dist, build, and astroid.egg-info # when building locally for testing! - python -m pip install twine build + python -m pip install build - name: Build distributions run: | python -m build + - name: Upload release assets + uses: actions/upload-artifact@v4.6.1 + with: + name: release-assets + path: dist/ + + release-pypi: + name: Upload release to PyPI + runs-on: ubuntu-latest + needs: ["build"] + environment: + name: PyPI + url: https://pypi.org/project/astroid/ + permissions: + id-token: write + steps: + - name: Download release assets + uses: actions/download-artifact@v4.1.9 + with: + name: release-assets + path: dist/ - name: Upload to PyPI if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') - env: - TWINE_REPOSITORY: pypi - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: | - twine upload --verbose dist/* + uses: pypa/gh-action-pypi-publish@release/v1 + + release-github: + name: Upload assets to Github release + runs-on: ubuntu-latest + needs: ["build"] + permissions: + contents: write + id-token: write + steps: + - name: Download release assets + uses: actions/download-artifact@v4.1.9 + with: + name: release-assets + path: dist/ + - name: Sign the dists with Sigstore and upload assets to Github release + if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') + uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: | + ./dist/*.tar.gz + ./dist/*.whl diff --git a/ChangeLog b/ChangeLog index a6238422db..a672a5b09f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,10 @@ Release date: TBA Closes #2686 Closes pylint-dev/pylint#8589 +* Upload release assets to PyPI via Trusted Publishing. + + Refs pylint-dev/pylint#10256 + What's New in astroid 3.3.8? ============================ From 74c34fb3b7f66a2f8246f125c144281177b6915f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 9 Mar 2025 08:16:08 +0100 Subject: [PATCH 10/12] Bump actions/cache from 4.2.0 to 4.2.2 (#2692) Bumps [actions/cache](https://github.com/actions/cache) from 4.2.0 to 4.2.2. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4.2.0...v4.2.2) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 49 +++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5b689dbd5b..7c4e1b57c8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,10 +24,10 @@ jobs: timeout-minutes: 20 steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4.2.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.1 + uses: actions/setup-python@v5.4.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true @@ -39,7 +39,7 @@ jobs: 'requirements_full.txt', 'requirements_minimal.txt') }}" >> $GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.2 with: path: venv key: >- @@ -59,7 +59,7 @@ jobs: hashFiles('.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT - name: Restore pre-commit environment id: cache-precommit - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.2 with: path: ${{ env.PRE_COMMIT_CACHE }} key: >- @@ -86,10 +86,10 @@ jobs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4.2.2 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.1 + uses: actions/setup-python@v5.4.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -106,7 +106,7 @@ jobs: 'requirements_full.txt', 'requirements_minimal.txt') }}" >> $GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.2 with: path: venv key: >- @@ -117,7 +117,7 @@ jobs: run: | python -m venv venv . venv/bin/activate - python -m pip install -U pip setuptools wheel + python -m pip install -U pip wheel pip install -U -r requirements_full.txt pip install -e . - name: Run pytest @@ -125,10 +125,11 @@ jobs: . venv/bin/activate pytest --cov - name: Upload coverage artifact - uses: actions/upload-artifact@v4.3.4 + uses: actions/upload-artifact@v4.6.1 with: name: coverage-linux-${{ matrix.python-version }} path: .coverage + include-hidden-files: true tests-windows: name: tests / run / ${{ matrix.python-version }} / Windows @@ -145,10 +146,10 @@ jobs: # Workaround to set correct temp directory on Windows # https://github.com/actions/virtual-environments/issues/712 - name: Check out code from GitHub - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4.2.2 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.1 + uses: actions/setup-python@v5.4.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -160,7 +161,7 @@ jobs: 'requirements_full.txt', 'requirements_minimal.txt') }}" >> $GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.2 with: path: venv key: >- @@ -179,10 +180,11 @@ jobs: . venv\\Scripts\\activate pytest --cov - name: Upload coverage artifact - uses: actions/upload-artifact@v4.3.4 + uses: actions/upload-artifact@v4.6.1 with: name: coverage-windows-${{ matrix.python-version }} path: .coverage + include-hidden-files: true tests-pypy: name: tests / run / ${{ matrix.python-version }} / Linux @@ -195,10 +197,10 @@ jobs: python-version: ["pypy3.9", "pypy3.10"] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4.2.2 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.1 + uses: actions/setup-python@v5.4.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -210,7 +212,7 @@ jobs: }}" >> $GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.2 with: path: venv key: >- @@ -229,10 +231,11 @@ jobs: . venv/bin/activate pytest --cov - name: Upload coverage artifact - uses: actions/upload-artifact@v4.3.4 + uses: actions/upload-artifact@v4.6.1 with: name: coverage-pypy-${{ matrix.python-version }} path: .coverage + include-hidden-files: true coverage: name: tests / process / coverage @@ -241,22 +244,22 @@ jobs: needs: ["tests-linux", "tests-windows", "tests-pypy"] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4.2.2 - name: Set up Python 3.13 id: python - uses: actions/setup-python@v5.1.1 + uses: actions/setup-python@v5.4.0 with: python-version: "3.13" check-latest: true - name: Install dependencies run: pip install -U -r requirements_minimal.txt - name: Download all coverage artifacts - uses: actions/download-artifact@v4.1.8 + uses: actions/download-artifact@v4.1.9 - name: Combine Linux coverage results run: | coverage combine coverage-linux*/.coverage coverage xml -o coverage-linux.xml - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true @@ -267,7 +270,7 @@ jobs: run: | coverage combine coverage-windows*/.coverage coverage xml -o coverage-windows.xml - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true @@ -278,7 +281,7 @@ jobs: run: | coverage combine coverage-pypy*/.coverage coverage xml -o coverage-pypy.xml - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true From ec2df9727cf972a8536de208368c492c09a8c8db Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 9 Mar 2025 08:46:34 +0100 Subject: [PATCH 11/12] Add setuptools in order to run 3.12/3.13 tests --- requirements_minimal.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements_minimal.txt b/requirements_minimal.txt index 9810281aec..04cf4991ac 100644 --- a/requirements_minimal.txt +++ b/requirements_minimal.txt @@ -7,3 +7,4 @@ coverage~=7.6 pytest pytest-cov~=5.0 mypy +setuptools From a6ccad5ccff353f6bc50eb9e818a462521bd1457 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 9 Mar 2025 07:57:42 +0100 Subject: [PATCH 12/12] Bump astroid to 3.3.9, update changelog --- CONTRIBUTORS.txt | 7 +------ ChangeLog | 8 +++++++- astroid/__pkginfo__.py | 2 +- script/.contributors_aliases.json | 6 +++++- tbump.toml | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 2de71f2df0..322d5d29d2 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -200,6 +200,7 @@ Contributors - Alexander Presnyakov - Ahmed Azzaoui + Co-Author --------- The following persons were credited manually but did not commit themselves @@ -210,9 +211,3 @@ under this name, or we did not manage to find their commits in the history. - carl - alain lefroy - Mark Gius -- Jérome Perrin -- Jamie Scott -- correctmost <134317971+correctmost@users.noreply.github.com> -- Oleh Prypin -- Eric Vergnaud -- Hashem Nasarat diff --git a/ChangeLog b/ChangeLog index a672a5b09f..1bb530414a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,9 +9,15 @@ Release date: TBA +What's New in astroid 3.3.10? +============================= +Release date: TBA + + + What's New in astroid 3.3.9? ============================ -Release date: TBA +Release date: 2025-03-09 * Fix crash when `sys.modules` contains lazy loader objects during checking. diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index bad119adf8..331a37ff2f 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.8" +__version__ = "3.3.9" version = __version__ diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json index 1dc38a4870..20f67fc00f 100644 --- a/script/.contributors_aliases.json +++ b/script/.contributors_aliases.json @@ -1,4 +1,8 @@ { + "134317971+correctmost@users.noreply.github.com": { + "mails": ["134317971+correctmost@users.noreply.github.com"], + "name": "correctmost" + }, "13665637+DanielNoord@users.noreply.github.com": { "mails": ["13665637+DanielNoord@users.noreply.github.com"], "name": "Daniël van Noord", @@ -98,7 +102,7 @@ "name": "Jacob Bogdanov" }, "jacobtylerwalls@gmail.com": { - "mails": ["jacobtylerwalls@gmail.com"], + "mails": ["jacobtylerwalls@gmail.com", "jwalls@fargeo.com"], "name": "Jacob Walls", "team": "Maintainers" }, diff --git a/tbump.toml b/tbump.toml index 8786e7b93d..a86677bdfb 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/astroid" [version] -current = "3.3.8" +current = "3.3.9" regex = ''' ^(?P0|[1-9]\d*) \.