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 2026-05-12 19:26:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-astroid (Old) and /work/SRC/openSUSE:Factory/.python-astroid.new.1966 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-astroid" Tue May 12 19:26:07 2026 rev:67 rq:1352301 version:4.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-astroid/python-astroid.changes 2026-03-24 18:48:17.441037478 +0100 +++ /work/SRC/openSUSE:Factory/.python-astroid.new.1966/python-astroid.changes 2026-05-12 19:26:14.282017235 +0200 @@ -1,0 +2,9 @@ +Sun May 3 17:24:47 UTC 2026 - Dirk Müller <[email protected]> + +- update to 4.2.0: + * Fix ``RecursionError`` in ``_compute_mro()`` when circular + class hierarchies are created through runtime name rebinding. + * Changed `block_range` to consider `else` its own block, + allowing `pylint` to apply disables to just the block. + +------------------------------------------------------------------- Old: ---- astroid-4.1.2-gh.tar.gz New: ---- astroid-4.2.0-gh.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-astroid.spec ++++++ --- /var/tmp/diff_new_pack.zUzFZ5/_old 2026-05-12 19:26:14.802038787 +0200 +++ /var/tmp/diff_new_pack.zUzFZ5/_new 2026-05-12 19:26:14.806038953 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-astroid -Version: 4.1.2 +Version: 4.2.0 Release: 0 Summary: Representation of Python source as an AST for pylint License: LGPL-2.1-or-later ++++++ astroid-4.1.2-gh.tar.gz -> astroid-4.2.0-gh.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/.github/workflows/backport.yml new/astroid-4.2.0/.github/workflows/backport.yml --- old/astroid-4.1.2/.github/workflows/backport.yml 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/.github/workflows/backport.yml 2026-04-28 11:27:11.000000000 +0200 @@ -14,6 +14,7 @@ runs-on: ubuntu-latest environment: name: Backport + deployment: false # 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: > @@ -25,15 +26,15 @@ ) ) steps: - - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 id: app-token with: - app-id: ${{ vars.BACKPORT_APP_ID }} + client-id: ${{ vars.BACKPORT_CLIENT_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: pylint-dev/backport@6accae9e09c5ad1bc3a0b56adf37c45357e7bcdc # v2.1.3 + - uses: pylint-dev/backport@3adad52e5a8ba2ea731c32037442d507eed68b4c # v2.1.8 with: github_token: ${{ steps.app-token.outputs.token }} user_name: ${{ vars.BACKPORT_USER_NAME }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/.github/workflows/ci.yaml new/astroid-4.2.0/.github/workflows/ci.yaml --- old/astroid-4.1.2/.github/workflows/ci.yaml 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/.github/workflows/ci.yaml 2026-04-28 11:27:11.000000000 +0200 @@ -43,7 +43,7 @@ - &cache-python name: Restore Python virtual environment id: cache-venv - uses: actions/[email protected] + uses: actions/[email protected] with: path: venv key: >- @@ -63,7 +63,7 @@ hashFiles('.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT - name: Restore pre-commit environment id: cache-precommit - uses: actions/[email protected] + uses: actions/[email protected] with: path: ${{ env.PRE_COMMIT_CACHE }} key: >- @@ -85,7 +85,8 @@ strategy: fail-fast: false matrix: - python-version: &matrix-python-version ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: + &matrix-python-version ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15-dev"] outputs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: @@ -122,7 +123,7 @@ . venv/bin/activate pytest --cov - name: Upload coverage artifact - uses: &actions-upload-artifact actions/[email protected] + uses: &actions-upload-artifact actions/[email protected] with: name: coverage-linux-${{ matrix.python-version }} path: .coverage @@ -136,7 +137,7 @@ strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15-dev"] steps: - name: Set temp directory run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV @@ -226,12 +227,12 @@ - name: Install dependencies run: pip install -U -r requirements_minimal.txt - name: Download all coverage artifacts - uses: actions/[email protected] + uses: actions/[email protected] - name: Combine Linux coverage results run: | coverage combine coverage-linux*/.coverage coverage xml -o coverage-linux.xml - - uses: &actions-codecov codecov/codecov-action@v5 + - uses: &actions-codecov codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/.github/workflows/release.yml new/astroid-4.2.0/.github/workflows/release.yml --- old/astroid-4.1.2/.github/workflows/release.yml 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/.github/workflows/release.yml 2026-04-28 11:27:11.000000000 +0200 @@ -34,7 +34,7 @@ run: | python -m build - name: Upload release assets - uses: actions/[email protected] + uses: actions/[email protected] with: name: release-assets path: dist/ @@ -50,7 +50,7 @@ id-token: write steps: - name: Download release assets - uses: actions/[email protected] + uses: actions/[email protected] with: name: release-assets path: dist/ @@ -67,13 +67,13 @@ id-token: write steps: - name: Download release assets - uses: actions/[email protected] + uses: actions/[email protected] with: name: release-assets path: dist/ - name: Sign the dists with Sigstore and upload assets to Github release if: github.event_name == 'release' - uses: sigstore/[email protected] + uses: sigstore/[email protected] with: inputs: | ./dist/*.tar.gz diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/.github/workflows/test-pylint.yml new/astroid-4.2.0/.github/workflows/test-pylint.yml --- old/astroid-4.1.2/.github/workflows/test-pylint.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/astroid-4.2.0/.github/workflows/test-pylint.yml 2026-04-28 11:27:11.000000000 +0200 @@ -0,0 +1,11 @@ +name: Test pylint + +on: + pull_request: + +jobs: + test-pylint-with-astroid-sha: + uses: pylint-dev/pylint/.github/workflows/tests.yaml@main + with: + repository: pylint-dev/pylint + astroid_sha: ${{ github.sha }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/.pre-commit-config.yaml new/astroid-4.2.0/.pre-commit-config.yaml --- old/astroid-4.1.2/.pre-commit-config.yaml 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/.pre-commit-config.yaml 2026-04-28 11:27:11.000000000 +0200 @@ -10,7 +10,7 @@ - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.15.0" + rev: "v0.15.11" hooks: - id: ruff-check args: ["--fix"] @@ -33,7 +33,7 @@ - id: black-disable-checker exclude: tests/test_nodes_lineno.py - repo: https://github.com/psf/black-pre-commit-mirror - rev: 26.1.0 + rev: 26.3.1 hooks: - id: black args: [--safe, --quiet] @@ -68,7 +68,7 @@ ] stages: [manual] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.19.1 + rev: v1.20.1 hooks: - id: mypy language: python @@ -76,11 +76,11 @@ require_serial: true additional_dependencies: ["types-typed-ast"] - repo: https://github.com/rbubley/mirrors-prettier - rev: v3.8.1 + rev: v3.8.3 hooks: - id: prettier args: [--prose-wrap=always, --print-width=88] - repo: https://github.com/tox-dev/pyproject-fmt - rev: "v2.15.0" + rev: "v2.21.1" hooks: - id: pyproject-fmt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/CONTRIBUTORS.txt new/astroid-4.2.0/CONTRIBUTORS.txt --- old/astroid-4.1.2/CONTRIBUTORS.txt 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/CONTRIBUTORS.txt 2026-04-28 11:27:11.000000000 +0200 @@ -31,13 +31,17 @@ Contributors ------------ - Emile Anclin <[email protected]> +- dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +- pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - Nick Drozd <[email protected]> +- github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> - correctmost <[email protected]> - Andrew Haigh <[email protected]> - Julien Cristau <[email protected]> - Artem Yurchenko <[email protected]> - David Liu <[email protected]> - Alexandre Fayolle <[email protected]> +- pylint-backport[bot] <212256041+pylint-backport[bot]@users.noreply.github.com> - Eevee (Alex Munroe) <[email protected]> - Emmanuel Ferdman <[email protected]> - David Gilman <[email protected]> @@ -221,6 +225,10 @@ - Alexander Presnyakov <[email protected]> - Ahmed Azzaoui <[email protected]> - Casey Jones <[email protected]> +- sergiochan <[email protected]> +- Joshix-1 <[email protected]> +- pylint-backport-bot[bot] <212256041+pylint-backport-bot[bot]@users.noreply.github.com> +- Copilot <[email protected]> Co-Author --------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/ChangeLog new/astroid-4.2.0/ChangeLog --- old/astroid-4.1.2/ChangeLog 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/ChangeLog 2026-04-28 11:27:11.000000000 +0200 @@ -3,17 +3,35 @@ =================== -What's New in astroid 4.2.0? +What's New in astroid 4.3.0? ============================ Release date: TBA -What's New in astroid 4.1.3? + +What's New in astroid 4.2.1? ============================ Release date: TBA +What's New in astroid 4.2.0? +============================ +Release date: 2026-04-28 + +* Fix ``RecursionError`` in ``_compute_mro()`` when circular class hierarchies + are created through runtime name rebinding. Circular bases are now resolved + to the original class instead of recursing. + + Closes #3023 + Closes pylint-dev/pylint#10821 + +* Changed `block_range` to consider `else` its own block, allowing `pylint` to apply + disables to just the block. + + References pylint-dev/pylint#872 + + What's New in astroid 4.1.2? ============================ Release date: 2026-03-22 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/__pkginfo__.py new/astroid-4.2.0/astroid/__pkginfo__.py --- old/astroid-4.1.2/astroid/__pkginfo__.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/__pkginfo__.py 2026-04-28 11:27:11.000000000 +0200 @@ -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__ = "4.1.2" +__version__ = "4.2.0" version = __version__ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/brain/brain_typing.py new/astroid-4.2.0/astroid/brain/brain_typing.py --- old/astroid-4.1.2/astroid/brain/brain_typing.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/brain/brain_typing.py 2026-04-28 11:27:11.000000000 +0200 @@ -15,7 +15,7 @@ from astroid import context, nodes from astroid.brain.helpers import register_module_extender from astroid.builder import AstroidBuilder, _extract_single_node, extract_node -from astroid.const import PY312_PLUS, PY313_PLUS, PY314_PLUS +from astroid.const import PY312_PLUS, PY313_PLUS, PY314_PLUS, PY315_PLUS from astroid.exceptions import ( AstroidSyntaxError, AttributeInferenceError, @@ -464,6 +464,13 @@ @classmethod def __class_getitem__(cls, item): return cls """) + if PY315_PLUS: + # typing.ByteString was removed from the typing module in Python 3.15 + # (it was deprecated since 3.12 and present at module level until 3.14). + # Inject a stub so code using `typing.ByteString` can still be inferred. + code += textwrap.dedent(""" + class ByteString: ... + """) return AstroidBuilder(AstroidManager()).string_build(code) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/const.py new/astroid-4.2.0/astroid/const.py --- old/astroid-4.1.2/astroid/const.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/const.py 2026-04-28 11:27:11.000000000 +0200 @@ -10,6 +10,7 @@ PY313 = sys.version_info[:2] == (3, 13) PY313_PLUS = sys.version_info >= (3, 13) PY314_PLUS = sys.version_info >= (3, 14) +PY315_PLUS = sys.version_info >= (3, 15) WIN32 = sys.platform == "win32" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/helpers.py new/astroid-4.2.0/astroid/helpers.py --- old/astroid-4.1.2/astroid/helpers.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/helpers.py 2026-04-28 11:27:11.000000000 +0200 @@ -6,7 +6,6 @@ from __future__ import annotations -import warnings from collections.abc import Generator from astroid import bases, manager, nodes, objects, raw_building, util @@ -20,20 +19,7 @@ ) from astroid.nodes import scoped_nodes from astroid.typing import InferenceResult -from astroid.util import safe_infer as real_safe_infer - - -def safe_infer( - node: nodes.NodeNG | bases.Proxy | util.UninferableBase, - context: InferenceContext | None = None, -) -> InferenceResult | None: - # When removing, also remove the real_safe_infer alias - warnings.warn( - "Import safe_infer from astroid.util; this shim in astroid.helpers will be removed.", - DeprecationWarning, - stacklevel=2, - ) - return real_safe_infer(node, context=context) +from astroid.util import safe_infer def _build_proxy_class(cls_name: str, builtins: nodes.Module) -> nodes.ClassDef: @@ -130,7 +116,7 @@ for klass in class_seq: if isinstance(klass, util.UninferableBase): raise AstroidTypeError( - f"arg 2 must be a type or tuple of types, not {type(klass)!r}" + "arg 2 must be a type or tuple of types, not <class 'astroid.util.UninferableBase'>" ) for obj_subclass in obj_type.mro(): @@ -201,7 +187,7 @@ except AttributeError: pass for base in klass.bases: - result = real_safe_infer(base, context=context) + result = safe_infer(base, context=context) # TODO: check for A->B->A->B pattern in class structure too? if ( not isinstance(result, scoped_nodes.ClassDef) @@ -274,7 +260,7 @@ # pylint: disable=import-outside-toplevel; circular import from astroid.objects import FrozenSet - inferred_node = real_safe_infer(node, context=context) + inferred_node = safe_infer(node, context=context) # prevent self referential length calls from causing a recursion error # see https://github.com/pylint-dev/astroid/issues/777 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/interpreter/_import/spec.py new/astroid-4.2.0/astroid/interpreter/_import/spec.py --- old/astroid-4.1.2/astroid/interpreter/_import/spec.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/interpreter/_import/spec.py 2026-04-28 11:27:11.000000000 +0200 @@ -185,7 +185,7 @@ and spec.loader # type: ignore[comparison-overlap] # noqa: E501 is importlib.machinery.FrozenImporter ): - return ModuleSpec( + return ModuleSpec( # type: ignore[unreachable] name=modname, location=getattr(spec.loader_state, "filename", None), type=ModuleType.PY_FROZEN, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/interpreter/_import/util.py new/astroid-4.2.0/astroid/interpreter/_import/util.py --- old/astroid-4.1.2/astroid/interpreter/_import/util.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/interpreter/_import/util.py 2026-04-28 11:27:11.000000000 +0200 @@ -72,10 +72,10 @@ # Workaround for "py" module # https://github.com/pytest-dev/apipkg/issues/13 return False - except KeyError: - # Intermediate steps might raise KeyErrors - # https://github.com/python/cpython/issues/93334 - # TODO: update if fixed in importlib + # PY314: When dropping support for 3.14, replace with just + # except ModuleNotFoundError: + except (KeyError, ModuleNotFoundError): + # Intermediate steps might raise ModuleNotFoundError # For tree a > b > c.py # >>> from importlib.machinery import PathFinder # >>> PathFinder.find_spec('a.b', ['a']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/modutils.py new/astroid-4.2.0/astroid/modutils.py --- old/astroid-4.1.2/astroid/modutils.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/modutils.py 2026-04-28 11:27:11.000000000 +0200 @@ -26,7 +26,6 @@ import sys import sysconfig import types -import warnings from collections.abc import Callable, Iterable, Sequence from contextlib import redirect_stderr, redirect_stdout from functools import lru_cache @@ -550,47 +549,6 @@ return any(filename.startswith(_cache_normalize_path(entry)) for entry in path) -def is_standard_module(modname: str, std_path: Iterable[str] | None = None) -> bool: - """Try to guess if a module is a standard python module (by default, - see `std_path` parameter's description). - - :param modname: name of the module we are interested in - - :param std_path: list of path considered has standard - - :return: - true if the module: - - is located on the path listed in one of the directory in `std_path` - - is a built-in module - """ - warnings.warn( - "is_standard_module() is deprecated. Use, is_stdlib_module() or module_in_path() instead", - DeprecationWarning, - stacklevel=2, - ) - - modname = modname.split(".")[0] - try: - filename = file_from_modpath([modname]) - except ImportError: - # import failed, i'm probably not so wrong by supposing it's - # not standard... - return False - # modules which are not living in a file are considered standard - # (sys and __builtin__ for instance) - if filename is None: - # we assume there are no namespaces in stdlib - return not util.is_namespace(modname) - filename = _normalize_path(filename) - for path in EXT_LIB_DIRS: - if filename.startswith(_cache_normalize_path(path)): - return False - if std_path is None: - std_path = STD_LIB_DIRS - - return any(filename.startswith(_cache_normalize_path(path)) for path in std_path) - - def is_relative(modname: str, from_file: str) -> bool: """Return true if the given module name is relative to the given file name. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/nodes/_base_nodes.py new/astroid-4.2.0/astroid/nodes/_base_nodes.py --- old/astroid-4.1.2/astroid/nodes/_base_nodes.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/nodes/_base_nodes.py 2026-04-28 11:27:11.000000000 +0200 @@ -237,22 +237,51 @@ class MultiLineWithElseBlockNode(MultiLineBlockNode): """Base node for multi-line blocks that can have else statements.""" + body: list[NodeNG] + """The contents of the block.""" + + orelse: list[NodeNG] + """The contents of the ``else`` block.""" + @cached_property def blockstart_tolineno(self): return self.lineno + def block_range(self, lineno: int) -> tuple[int, int]: + """Get a range from the given line number to where this node ends. + + :param lineno: The line number to start the range at. + + :returns: The range of line numbers that this node belongs to, + starting at the given line number. + """ + if lineno < self.fromlineno: + return lineno, self.tolineno + if lineno == self.body[0].fromlineno: + return lineno, lineno + if lineno <= self.body[-1].tolineno: + return lineno, self.body[-1].tolineno + return self._elsed_block_range(lineno, self.orelse, self.body[0].fromlineno - 1) + def _elsed_block_range( self, lineno: int, orelse: list[nodes.NodeNG], last: int | None = None ) -> tuple[int, int]: """Handle block line numbers range for try/finally, for, if and while statements. """ - if lineno == self.fromlineno: + # If at the end of the node, return same line + if lineno == self.tolineno: return lineno, lineno if orelse: - if lineno >= orelse[0].fromlineno: + # If the lineno is beyond the body of the node we check the orelse + if lineno >= self.body[-1].tolineno + 1: + # If the orelse has a scope of its own we determine the block range there + if isinstance(orelse[0], MultiLineWithElseBlockNode): + return orelse[0]._elsed_block_range(lineno, orelse[0].orelse) + # Return last line of orelse return lineno, orelse[-1].tolineno - return lineno, orelse[0].fromlineno - 1 + # If the lineno is within the body we take the last line of the body + return lineno, self.body[-1].tolineno return lineno, last or self.tolineno diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/nodes/node_classes.py new/astroid-4.2.0/astroid/nodes/node_classes.py --- old/astroid-4.1.2/astroid/nodes/node_classes.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/nodes/node_classes.py 2026-04-28 11:27:11.000000000 +0200 @@ -597,9 +597,6 @@ return _infer_stmts(stmts, context, frame) -DEPRECATED_ARGUMENT_DEFAULT = "DEPRECATED_ARGUMENT_DEFAULT" - - class Arguments( _base_nodes.AssignTypeNode ): # pylint: disable=too-many-instance-attributes @@ -969,7 +966,7 @@ return True return self.find_argname(name)[1] is not None - def find_argname(self, argname, rec=DEPRECATED_ARGUMENT_DEFAULT): + def find_argname(self, argname): """Get the index and :class:`AssignName` node for given name. :param argname: The name of the argument to search for. @@ -978,12 +975,6 @@ :returns: The index and node for the argument. :rtype: tuple(str or None, AssignName or None) """ - if rec != DEPRECATED_ARGUMENT_DEFAULT: # pragma: no cover - warnings.warn( - "The rec argument will be removed in astroid 3.1.", - DeprecationWarning, - stacklevel=2, - ) if self.arguments: index, argument = _find_arg(argname, self.arguments) if argument: @@ -3061,20 +3052,6 @@ """ return self.test.tolineno - def block_range(self, lineno: int) -> tuple[int, int]: - """Get a range from the given line number to where this node ends. - - :param lineno: The line number to start the range at. - - :returns: The range of line numbers that this node belongs to, - starting at the given line number. - """ - if lineno == self.body[0].fromlineno: - return lineno, lineno - if lineno <= self.body[-1].tolineno: - return lineno, self.body[-1].tolineno - return self._elsed_block_range(lineno, self.orelse, self.body[0].fromlineno - 1) - def get_children(self): yield self.test @@ -3915,27 +3892,20 @@ def block_range(self, lineno: int) -> tuple[int, int]: """Get a range from a given line number to where this node ends.""" - if lineno == self.fromlineno: - return lineno, lineno - if self.body and self.body[0].fromlineno <= lineno <= self.body[-1].tolineno: - # Inside try body - return from lineno till end of try body - return lineno, self.body[-1].tolineno for exhandler in self.handlers: if exhandler.type and lineno == exhandler.type.fromlineno: - return lineno, lineno + return lineno, exhandler.tolineno if exhandler.body[0].fromlineno <= lineno <= exhandler.body[-1].tolineno: return lineno, exhandler.body[-1].tolineno - if self.orelse: - if self.orelse[0].fromlineno - 1 == lineno: - return lineno, lineno - if self.orelse[0].fromlineno <= lineno <= self.orelse[-1].tolineno: - return lineno, self.orelse[-1].tolineno if self.finalbody: if self.finalbody[0].fromlineno - 1 == lineno: - return lineno, lineno + return lineno, self.finalbody[0].tolineno if self.finalbody[0].fromlineno <= lineno <= self.finalbody[-1].tolineno: return lineno, self.finalbody[-1].tolineno - return lineno, self.tolineno + + # If not within any of the ExceptHandlers or `finally` body, fall back to regular + # handling of block_range for nodes with a potential `else` statement. + return super().block_range(lineno) def get_children(self): yield from self.body @@ -4014,35 +3984,28 @@ def _infer_name(self, frame, name): return name + def get_children(self): + yield from self.body + yield from self.handlers + yield from self.orelse + yield from self.finalbody + def block_range(self, lineno: int) -> tuple[int, int]: """Get a range from a given line number to where this node ends.""" - if lineno == self.fromlineno: - return lineno, lineno - if self.body and self.body[0].fromlineno <= lineno <= self.body[-1].tolineno: - # Inside try body - return from lineno till end of try body - return lineno, self.body[-1].tolineno for exhandler in self.handlers: if exhandler.type and lineno == exhandler.type.fromlineno: - return lineno, lineno + return lineno, exhandler.tolineno if exhandler.body[0].fromlineno <= lineno <= exhandler.body[-1].tolineno: return lineno, exhandler.body[-1].tolineno - if self.orelse: - if self.orelse[0].fromlineno - 1 == lineno: - return lineno, lineno - if self.orelse[0].fromlineno <= lineno <= self.orelse[-1].tolineno: - return lineno, self.orelse[-1].tolineno if self.finalbody: if self.finalbody[0].fromlineno - 1 == lineno: - return lineno, lineno + return lineno, self.finalbody[0].tolineno if self.finalbody[0].fromlineno <= lineno <= self.finalbody[-1].tolineno: return lineno, self.finalbody[-1].tolineno - return lineno, self.tolineno - def get_children(self): - yield from self.body - yield from self.handlers - yield from self.orelse - yield from self.finalbody + # If not within any of the ExceptHandlers or `finally` body, fall back to regular + # handling of block_range for nodes with a potential `else` statement. + return super().block_range(lineno) class Tuple(BaseContainer): @@ -4472,16 +4435,6 @@ """ return self.test.tolineno - def block_range(self, lineno: int) -> tuple[int, int]: - """Get a range from the given line number to where this node ends. - - :param lineno: The line number to start the range at. - - :returns: The range of line numbers that this node belongs to, - starting at the given line number. - """ - return self._elsed_block_range(lineno, self.orelse) - def get_children(self): yield self.test diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/nodes/scoped_nodes/scoped_nodes.py new/astroid-4.2.0/astroid/nodes/scoped_nodes/scoped_nodes.py --- old/astroid-4.1.2/astroid/nodes/scoped_nodes/scoped_nodes.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/nodes/scoped_nodes/scoped_nodes.py 2026-04-28 11:27:11.000000000 +0200 @@ -2812,7 +2812,12 @@ def slots(self): return self._all_slots - def _inferred_bases(self, context: InferenceContext | None = None): + def _inferred_bases( + self, + context: InferenceContext | None = None, + *, + base_classes: frozenset[ClassDef] = frozenset(), + ): # Similar with .ancestors, but the difference is when one base is inferred, # only the first object is wanted. That's because # we aren't interested in superclasses, as in the following @@ -2841,7 +2846,7 @@ baseobj = baseobj._proxied if not isinstance(baseobj, ClassDef): continue - if baseobj is self: + if baseobj is self or baseobj in base_classes: # Circular base due to name rebinding (e.g. pdb.Pdb = CustomPdb # where CustomPdb inherits from pdb.Pdb). Fall back to the # first non-circular inferred value from the base expression. @@ -2876,17 +2881,25 @@ pass return None - def _compute_mro(self, context: InferenceContext | None = None): + def _compute_mro( + self, + context: InferenceContext, + *, + base_chain: frozenset[ClassDef] = frozenset(), + ): if self.qname() == "builtins.object": return [self] - inferred_bases = list(self._inferred_bases(context=context)) + inferred_bases = list( + self._inferred_bases(context=context, base_classes=base_chain) + ) bases_mro = [] + base_chain |= {self} for base in inferred_bases: - if base is self: + if base in base_chain: continue - mro = base._compute_mro(context=context) + mro = base._compute_mro(context=context, base_chain=base_chain) bases_mro.append(mro) unmerged_mro: list[list[ClassDef]] = [[self], *bases_mro, inferred_bases] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/protocols.py new/astroid-4.2.0/astroid/protocols.py --- old/astroid-4.1.2/astroid/protocols.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/protocols.py 2026-04-28 11:27:11.000000000 +0200 @@ -545,7 +545,7 @@ """))) assigned = objects.ExceptionInstance(eg) assigned.instance_attrs["exceptions"] = [ - nodes.List.from_elements(_generate_assigned()) + nodes.Tuple.from_elements(_generate_assigned()) ] yield assigned else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/rebuilder.py new/astroid-4.2.0/astroid/rebuilder.py --- old/astroid-4.1.2/astroid/rebuilder.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/rebuilder.py 2026-04-28 11:27:11.000000000 +0200 @@ -120,7 +120,6 @@ end_lineno = node.end_lineno if node.body: end_lineno = node.body[0].lineno - # pylint: disable-next=unsubscriptable-object data = "\n".join(self._data[node.lineno - 1 : end_lineno]) start_token: TokenInfo | None = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/astroid/util.py new/astroid-4.2.0/astroid/util.py --- old/astroid-4.1.2/astroid/util.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/astroid/util.py 2026-04-28 11:27:11.000000000 +0200 @@ -108,19 +108,6 @@ return msg.format(self.op, self.left_type.name, self.right_type.name) -def _instancecheck(cls, other) -> bool: - wrapped = cls.__wrapped__ - other_cls = other.__class__ - is_instance_of = wrapped is other_cls or issubclass(other_cls, wrapped) - warnings.warn( - "%r is deprecated and slated for removal in astroid " - "2.0, use %r instead" % (cls.__class__.__name__, wrapped.__name__), - PendingDeprecationWarning, - stacklevel=2, - ) - return is_instance_of - - def check_warnings_filter() -> bool: """Return True if any other than the default DeprecationWarning filter is enabled. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/pyproject.toml new/astroid-4.2.0/pyproject.toml --- old/astroid-4.1.2/pyproject.toml 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/pyproject.toml 2026-04-28 11:27:11.000000000 +0200 @@ -34,8 +34,8 @@ ] urls."Bug tracker" = "https://github.com/pylint-dev/astroid/issues" urls."Discord server" = "https://discord.gg/Egy6P8AMB5" -urls."Docs" = "https://pylint.readthedocs.io/projects/astroid/en/latest/" urls."Source Code" = "https://github.com/pylint-dev/astroid" +urls.Docs = "https://pylint.readthedocs.io/projects/astroid/en/latest/" [tool.setuptools] dynamic.version = { attr = "astroid.__pkginfo__.__version__" } @@ -76,12 +76,6 @@ [tool.pyproject-fmt] max_supported_python = "3.14" -[tool.pytest] -ini_options.addopts = '-m "not acceptance"' -ini_options.python_files = [ "*test_*.py" ] -ini_options.testpaths = [ "tests" ] -ini_options.filterwarnings = "error" - [tool.mypy] python_version = "3.10" files = [ @@ -135,6 +129,7 @@ strict = true warn_redundant_casts = true warn_unreachable = true +warn_unused_configs = false [[tool.mypy.overrides]] # Importlib typeshed stubs do not include the private functions we use @@ -148,5 +143,11 @@ ] ignore_missing_imports = true +[tool.pytest] +ini_options.addopts = '-m "not acceptance"' +ini_options.python_files = [ "*test_*.py" ] +ini_options.testpaths = [ "tests" ] +ini_options.filterwarnings = "error" + [tool.aliases] test = "pytest" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/requirements_minimal.txt new/astroid-4.2.0/requirements_minimal.txt --- old/astroid-4.1.2/requirements_minimal.txt 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/requirements_minimal.txt 2026-04-28 11:27:11.000000000 +0200 @@ -1,9 +1,9 @@ # Tools used when releasing -contributors-txt>=1.0.1 +contributors-txt>=1.0.2 tbump~=6.11 # Tools used to run tests coverage~=7.13 pytest -pytest-cov~=7.0 +pytest-cov~=7.1 mypy; platform_python_implementation!="PyPy" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/script/.contributors_aliases.json new/astroid-4.2.0/script/.contributors_aliases.json --- old/astroid-4.1.2/script/.contributors_aliases.json 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/script/.contributors_aliases.json 2026-04-28 11:27:11.000000000 +0200 @@ -24,8 +24,8 @@ }, "[email protected]": { "mails": [ - "[email protected]", - "[email protected]" + "[email protected]", + "[email protected]" ], "name": "Artem Yurchenko" }, @@ -68,26 +68,16 @@ }, "[email protected]": { "mails": [ + "[email protected]", "[email protected]", - "[email protected]", "[email protected]", - "[email protected]" + "[email protected]" ], "name": "Ashley Whetter", "team": "Maintainers" }, - "[email protected]": { - "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", - "212256041+pylint-backport[bot]@users.noreply.github.com", - "212256041+pylint-backport-bot[bot]@users.noreply.github.com" - ], - "name": "bot" - }, "[email protected]": { - "mails": ["[email protected]", "[email protected]"], + "mails": ["[email protected]", "[email protected]"], "name": "Bryce Guinta", "team": "Maintainers" }, @@ -124,8 +114,8 @@ }, "[email protected]": { "mails": [ - "[email protected]", - "[email protected]" + "[email protected]", + "[email protected]" ], "name": "Hugo van Kemenade" }, @@ -151,7 +141,7 @@ "name": "Keichi Takahashi" }, "[email protected]": { - "mails": ["[email protected]", "[email protected]"], + "mails": ["[email protected]", "[email protected]"], "name": "Mario Corchero" }, "[email protected]": { @@ -173,10 +163,10 @@ }, "[email protected]": { "mails": [ - "[email protected]", - "[email protected]", "[email protected]", - "[email protected]" + "[email protected]", + "[email protected]", + "[email protected]" ], "name": "Google, Inc." }, @@ -186,12 +176,12 @@ "team": "Ex-maintainers" }, "[email protected]": { - "mails": ["[email protected]", "[email protected]"], + "mails": ["[email protected]", "[email protected]"], "name": "Pierre Sassoulas", "team": "Maintainers" }, "[email protected]": { - "mails": ["[email protected]", "[email protected]"], + "mails": ["[email protected]", "[email protected]"], "name": "Raphael Gaschignard" }, "[email protected]": { @@ -209,14 +199,14 @@ "name": "Stefan Scherfke" }, "[email protected]": { - "mails": ["[email protected]", "[email protected]"], + "mails": ["[email protected]", "[email protected]"], "name": "Sylvain Thénault", "team": "Ex-maintainers" }, "[email protected]": { "mails": [ - "[email protected]", - "[email protected]" + "[email protected]", + "[email protected]" ], "name": "Tushar Sadhwani" }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tbump.toml new/astroid-4.2.0/tbump.toml --- old/astroid-4.1.2/tbump.toml 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tbump.toml 2026-04-28 11:27:11.000000000 +0200 @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/astroid" [version] -current = "4.1.2" +current = "4.2.0" regex = ''' ^(?P<major>0|[1-9]\d*) \. @@ -34,7 +34,7 @@ [[before_commit]] name = "Upgrade the contributors list" -cmd = "python3 script/create_contributor_list.py" +cmd = "python3 script/create_contributor_list.py --no-bots" [[before_commit]] name = "Apply pre-commit" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tests/brain/test_brain.py new/astroid-4.2.0/tests/brain/test_brain.py --- old/astroid-4.1.2/tests/brain/test_brain.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tests/brain/test_brain.py 2026-04-28 11:27:11.000000000 +0200 @@ -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, PY315_PLUS from astroid.exceptions import ( AttributeInferenceError, InferenceError, @@ -787,7 +787,10 @@ typing.ByteString """) inferred = next(right_node.infer()) - check_metaclass_is_abc(inferred) + # From Python 3.15 we add a stub definition of `ByteString`. It doesn't need all properties + # of the original implementation. + if not PY315_PLUS: + check_metaclass_is_abc(inferred) with self.assertRaises(AttributeInferenceError): self.assertIsInstance( inferred.getattr("__class_getitem__")[0], nodes.FunctionDef diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tests/test_group_exceptions.py new/astroid-4.2.0/tests/test_group_exceptions.py --- old/astroid-4.1.2/tests/test_group_exceptions.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tests/test_group_exceptions.py 2026-04-28 11:27:11.000000000 +0200 @@ -41,7 +41,7 @@ assert isinstance(node, nodes.Try) handler = node.handlers[0] assert node.block_range(lineno=1) == (1, 9) - assert node.block_range(lineno=2) == (2, 2) + assert node.block_range(lineno=2) == (2, 3) assert node.block_range(lineno=5) == (5, 9) assert isinstance(handler, nodes.ExceptHandler) assert handler.type.name == "ExceptionGroup" @@ -72,15 +72,15 @@ assert node.as_string() == code.replace('"', "'").strip() assert isinstance(node.body[0], nodes.Raise) assert node.block_range(1) == (1, 11) - assert node.block_range(2) == (2, 2) + assert node.block_range(2) == (2, 3) assert node.block_range(3) == (3, 3) - assert node.block_range(4) == (4, 4) + assert node.block_range(4) == (4, 5) assert node.block_range(5) == (5, 5) - assert node.block_range(6) == (6, 6) + assert node.block_range(6) == (6, 7) assert node.block_range(7) == (7, 7) - assert node.block_range(8) == (8, 8) + assert node.block_range(8) == (8, 9) assert node.block_range(9) == (9, 9) - assert node.block_range(10) == (10, 10) + assert node.block_range(10) == (10, 11) assert node.block_range(11) == (11, 11) assert node.handlers handler = node.handlers[0] @@ -126,12 +126,12 @@ assert isinstance(node, nodes.TryStar) inferred_ve = next(node.handlers[0].statement().name.infer()) assert inferred_ve.name == "ExceptionGroup" - assert isinstance(inferred_ve.getattr("exceptions")[0], nodes.List) + assert isinstance(inferred_ve.getattr("exceptions")[0], nodes.Tuple) assert ( inferred_ve.getattr("exceptions")[0].elts[0].pytype() == "builtins.ValueError" ) inferred_te = next(node.handlers[1].statement().name.infer()) assert inferred_te.name == "ExceptionGroup" - assert isinstance(inferred_te.getattr("exceptions")[0], nodes.List) + assert isinstance(inferred_te.getattr("exceptions")[0], nodes.Tuple) assert inferred_te.getattr("exceptions")[0].elts[0].pytype() == "builtins.TypeError" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tests/test_helpers.py new/astroid-4.2.0/tests/test_helpers.py --- old/astroid-4.1.2/tests/test_helpers.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tests/test_helpers.py 2026-04-28 11:27:11.000000000 +0200 @@ -11,7 +11,6 @@ from astroid.builder import AstroidBuilder from astroid.const import IS_PYPY from astroid.exceptions import InferenceError, _NonDeducibleTypeHierarchy -from astroid.nodes.node_classes import UNATTACHED_UNKNOWN class TestHelpers(unittest.TestCase): @@ -245,16 +244,6 @@ assert util.safe_infer(util.Uninferable) == uninfer -def test_safe_infer_shim() -> None: - with pytest.warns(DeprecationWarning) as records: - helpers.safe_infer(UNATTACHED_UNKNOWN) - - assert ( - "Import safe_infer from astroid.util; this shim in astroid.helpers will be removed." - in records[0].message.args[0] - ) - - def test_class_to_container() -> None: node = builder.extract_node("""isinstance(3, int)""") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tests/test_modutils.py new/astroid-4.2.0/tests/test_modutils.py --- old/astroid-4.1.2/tests/test_modutils.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tests/test_modutils.py 2026-04-28 11:27:11.000000000 +0200 @@ -13,7 +13,6 @@ import unittest import xml from pathlib import Path -from xml import etree from xml.etree import ElementTree import pytest @@ -377,81 +376,6 @@ ) -class IsStandardModuleTest(resources.SysPathSetup, unittest.TestCase): - """ - Return true if the module may be considered as a module from the standard - library. - """ - - def test_datetime(self) -> None: - # This is an interesting example, since datetime, on pypy, - # is under lib_pypy, rather than the usual Lib directory. - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("datetime") - - def test_builtins(self) -> None: - with pytest.warns(DeprecationWarning): - assert not modutils.is_standard_module("__builtin__") - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("builtins") - - def test_builtin(self) -> None: - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("sys") - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("marshal") - - def test_nonstandard(self) -> None: - with pytest.warns(DeprecationWarning): - assert not modutils.is_standard_module("astroid") - - def test_unknown(self) -> None: - with pytest.warns(DeprecationWarning): - assert not modutils.is_standard_module("unknown") - - def test_4(self) -> None: - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("hashlib") - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("pickle") - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("email") - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("io") - with pytest.warns(DeprecationWarning): - assert not modutils.is_standard_module("StringIO") - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("unicodedata") - - def test_custom_path(self) -> None: - datadir = resources.find("") - if any(datadir.startswith(p) for p in modutils.EXT_LIB_DIRS): - self.skipTest("known breakage of is_standard_module on installed package") - - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("data.module", (datadir,)) - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module( - "data.module", (os.path.abspath(datadir),) - ) - # "" will evaluate to cwd - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("data.module", ("",)) - - def test_failing_edge_cases(self) -> None: - # using a subpackage/submodule path as std_path argument - with pytest.warns(DeprecationWarning): - assert not modutils.is_standard_module("xml.etree", etree.__path__) - # using a module + object name as modname argument - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("sys.path") - # this is because only the first package/module is considered - with pytest.warns(DeprecationWarning): - assert modutils.is_standard_module("sys.whatever") - with pytest.warns(DeprecationWarning): - assert not modutils.is_standard_module("xml.whatever", etree.__path__) - - class IsStdLibModuleTest(resources.SysPathSetup, unittest.TestCase): """ Return true if the module is path of the standard library diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tests/test_nodes.py new/astroid-4.2.0/tests/test_nodes.py --- old/astroid-4.1.2/tests/test_nodes.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tests/test_nodes.py 2026-04-28 11:27:11.000000000 +0200 @@ -427,11 +427,22 @@ pass else: raise + + if 1: + print() + elif ( + 2 + and 3 + ): + print() + else: + # This is using else in a comment + raise """ def test_if_elif_else_node(self) -> None: """Test transformation for If node.""" - self.assertEqual(len(self.astroid.body), 4) + self.assertEqual(len(self.astroid.body), 5) for stmt in self.astroid.body: self.assertIsInstance(stmt, nodes.If) self.assertFalse(self.astroid.body[0].orelse) # simple If @@ -440,13 +451,50 @@ self.assertIsInstance(self.astroid.body[3].orelse[0].orelse[0], nodes.If) def test_block_range(self) -> None: - # XXX ensure expected values - self.assertEqual(self.astroid.block_range(1), (0, 22)) - self.assertEqual(self.astroid.block_range(10), (0, 22)) # XXX (10, 22) ? + """Test block_range of various scope constructs""" + # Module + self.assertEqual(self.astroid.block_range(1), (0, 33)) + # NOTE: Module does not consider the lineno argument. It would be more consistent to make + # this return (10, 33) but without a use case it seems better to not change behaviour. + self.assertEqual(self.astroid.block_range(10), (0, 33)) + + # if + self.assertEqual(self.astroid.body[0].block_range(2), (2, 3)) + self.assertEqual(self.astroid.body[0].block_range(3), (3, 3)) + + # if ... else self.assertEqual(self.astroid.body[1].block_range(5), (5, 6)) self.assertEqual(self.astroid.body[1].block_range(6), (6, 6)) - self.assertEqual(self.astroid.body[1].orelse[0].block_range(7), (7, 8)) - self.assertEqual(self.astroid.body[1].orelse[0].block_range(8), (8, 8)) + self.assertEqual(self.astroid.body[1].block_range(7), (7, 8)) + self.assertEqual(self.astroid.body[1].block_range(8), (8, 8)) + + # if ... elif + self.assertEqual(self.astroid.body[2].block_range(10), (10, 11)) + self.assertEqual(self.astroid.body[2].block_range(11), (11, 11)) + self.assertEqual(self.astroid.body[2].block_range(12), (12, 13)) + self.assertEqual(self.astroid.body[2].block_range(13), (13, 13)) + + # if ... elif ... elif ... else + self.assertEqual(self.astroid.body[3].block_range(15), (15, 16)) + self.assertEqual(self.astroid.body[3].block_range(16), (16, 16)) + self.assertEqual(self.astroid.body[3].block_range(17), (17, 18)) + self.assertEqual(self.astroid.body[3].block_range(18), (18, 18)) + self.assertEqual(self.astroid.body[3].block_range(19), (19, 20)) + self.assertEqual(self.astroid.body[3].block_range(20), (20, 20)) + self.assertEqual(self.astroid.body[3].block_range(21), (21, 22)) + self.assertEqual(self.astroid.body[3].block_range(22), (22, 22)) + + # if ... elif ... else + self.assertEqual(self.astroid.body[4].block_range(24), (24, 25)) + self.assertEqual(self.astroid.body[4].block_range(25), (25, 25)) + self.assertEqual(self.astroid.body[4].block_range(26), (26, 30)) + self.assertEqual(self.astroid.body[4].block_range(27), (27, 30)) + self.assertEqual(self.astroid.body[4].block_range(28), (28, 30)) + self.assertEqual(self.astroid.body[4].block_range(29), (29, 30)) + self.assertEqual(self.astroid.body[4].block_range(30), (30, 30)) + self.assertEqual(self.astroid.body[4].block_range(31), (31, 33)) + self.assertEqual(self.astroid.body[4].block_range(32), (32, 33)) + self.assertEqual(self.astroid.body[4].block_range(33), (33, 33)) class TryNodeTest(_NodeTest): @@ -466,81 +514,18 @@ def test_block_range(self) -> None: try_node = self.astroid.body[0] assert try_node.block_range(1) == (1, 11) - assert try_node.block_range(2) == (2, 2) + assert try_node.block_range(2) == (2, 3) assert try_node.block_range(3) == (3, 3) - assert try_node.block_range(4) == (4, 4) + assert try_node.block_range(4) == (4, 5) assert try_node.block_range(5) == (5, 5) - assert try_node.block_range(6) == (6, 6) + assert try_node.block_range(6) == (6, 7) assert try_node.block_range(7) == (7, 7) - assert try_node.block_range(8) == (8, 8) + assert try_node.block_range(8) == (8, 9) assert try_node.block_range(9) == (9, 9) - assert try_node.block_range(10) == (10, 10) + assert try_node.block_range(10) == (10, 11) assert try_node.block_range(11) == (11, 11) -class TryExceptNodeTest(_NodeTest): - CODE = """ - try: - print ('pouet') - except IOError: - pass - except UnicodeError: - print() - else: - print() - """ - - def test_block_range(self) -> None: - # XXX ensure expected values - self.assertEqual(self.astroid.body[0].block_range(1), (1, 9)) - self.assertEqual(self.astroid.body[0].block_range(2), (2, 2)) - self.assertEqual(self.astroid.body[0].block_range(3), (3, 3)) - self.assertEqual(self.astroid.body[0].block_range(4), (4, 4)) - self.assertEqual(self.astroid.body[0].block_range(5), (5, 5)) - self.assertEqual(self.astroid.body[0].block_range(6), (6, 6)) - self.assertEqual(self.astroid.body[0].block_range(7), (7, 7)) - self.assertEqual(self.astroid.body[0].block_range(8), (8, 8)) - self.assertEqual(self.astroid.body[0].block_range(9), (9, 9)) - - -class TryFinallyNodeTest(_NodeTest): - CODE = """ - try: - print ('pouet') - finally: - print ('pouet') - """ - - def test_block_range(self) -> None: - # XXX ensure expected values - self.assertEqual(self.astroid.body[0].block_range(1), (1, 5)) - self.assertEqual(self.astroid.body[0].block_range(2), (2, 2)) - self.assertEqual(self.astroid.body[0].block_range(3), (3, 3)) - self.assertEqual(self.astroid.body[0].block_range(4), (4, 4)) - self.assertEqual(self.astroid.body[0].block_range(5), (5, 5)) - - -class TryExceptFinallyNodeTest(_NodeTest): - CODE = """ - try: - print('pouet') - except Exception: - print ('oops') - finally: - print ('pouet') - """ - - def test_block_range(self) -> None: - # XXX ensure expected values - self.assertEqual(self.astroid.body[0].block_range(1), (1, 7)) - self.assertEqual(self.astroid.body[0].block_range(2), (2, 2)) - self.assertEqual(self.astroid.body[0].block_range(3), (3, 3)) - self.assertEqual(self.astroid.body[0].block_range(4), (4, 4)) - self.assertEqual(self.astroid.body[0].block_range(5), (5, 5)) - self.assertEqual(self.astroid.body[0].block_range(6), (6, 6)) - self.assertEqual(self.astroid.body[0].block_range(7), (7, 7)) - - class ImportNodeTest(resources.SysPathSetup, unittest.TestCase): def setUp(self) -> None: super().setUp() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tests/test_nodes_lineno.py new/astroid-4.2.0/tests/test_nodes_lineno.py --- old/astroid-4.1.2/tests/test_nodes_lineno.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tests/test_nodes_lineno.py 2026-04-28 11:27:11.000000000 +0200 @@ -878,6 +878,10 @@ assert (w1.body[0].end_lineno, w1.body[0].end_col_offset) == (2, 8) assert (w1.orelse[0].lineno, w1.orelse[0].col_offset) == (4, 4) assert (w1.orelse[0].end_lineno, w1.orelse[0].end_col_offset) == (4, 8) + assert w1.block_range(1) == (1, 2) + assert w1.block_range(2) == (2, 2) + assert w1.block_range(3) == (3, 4) + assert w1.block_range(4) == (4, 4) @staticmethod def test_end_lineno_string() -> None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tests/test_raw_building.py new/astroid-4.2.0/tests/test_raw_building.py --- old/astroid-4.1.2/tests/test_raw_building.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tests/test_raw_building.py 2026-04-28 11:27:11.000000000 +0200 @@ -10,7 +10,7 @@ from __future__ import annotations -import _io # pylint: disable=wrong-import-order +import _io import logging import os import sys diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tests/test_regrtest.py new/astroid-4.2.0/tests/test_regrtest.py --- old/astroid-4.1.2/tests/test_regrtest.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tests/test_regrtest.py 2026-04-28 11:27:11.000000000 +0200 @@ -12,7 +12,7 @@ from astroid import MANAGER, Instance, bases, manager, nodes, parse, test_utils from astroid.builder import AstroidBuilder, _extract_single_node, extract_node -from astroid.const import PY312_PLUS +from astroid.const import PY312_PLUS, PY315_PLUS from astroid.context import InferenceContext from astroid.exceptions import AstroidSyntaxError, InferenceError from astroid.manager import AstroidManager @@ -529,6 +529,9 @@ assert inferred.value == Uninferable +# On Python 3.15+ the parser emits a regular SyntaxError instead of a MemoryError for deeply nested +# parentheses, so the special-case test here is no longer needed. [email protected](PY315_PLUS, reason="No longer a MemoryError on Python 3.15+") def test_regression_parse_deeply_nested_parentheses() -> None: """Regression test for issue #2643.""" with pytest.raises(AstroidSyntaxError, match="Parsing Python code failed:") as ctx: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tests/test_scoped_nodes.py new/astroid-4.2.0/tests/test_scoped_nodes.py --- old/astroid-4.1.2/tests/test_scoped_nodes.py 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tests/test_scoped_nodes.py 2026-04-28 11:27:11.000000000 +0200 @@ -1719,6 +1719,30 @@ ["CustomPdb", "Pdb", "Bdb", "Cmd", "object"], ) + def test_mro_circular_name_rebinding_with_gap(self) -> None: + """MRO computation should handle circular name rebinding. + + The MRO computation should resolve the cycle by falling back + to the original class. + + Regression test for https://github.com/pylint-dev/pylint/issues/10821 + """ + astroid = builder.parse(""" + import pdb + + class PatchedPdb(pdb.Pdb): + pass + + class CustomPdb(PatchedPdb): + pass + + pdb.Pdb = CustomPdb + """) + self.assertEqualMro( + astroid["CustomPdb"], + ["CustomPdb", "PatchedPdb", "Pdb", "Bdb", "Cmd", "object"], + ) + def test_mro_with_factories(self) -> None: cls = builder.extract_node(""" def MixinFactory(cls): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astroid-4.1.2/tox.ini new/astroid-4.2.0/tox.ini --- old/astroid-4.1.2/tox.ini 2026-03-22 16:06:01.000000000 +0100 +++ new/astroid-4.2.0/tox.ini 2026-04-28 11:27:11.000000000 +0200 @@ -1,5 +1,5 @@ [tox] -envlist = py{39,310,311,312,313,314} +envlist = py{39,310,311,312,313,314,315} skip_missing_interpreters = true isolated_build = true
