From fea571fc2e34d2f6293402cdd24a2077b668d9d9 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 7 Jun 2025 14:14:41 -0400 Subject: [PATCH 1/4] Remove release tests workflow --- .github/workflows/release-tests.yml | 38 ----------------------------- doc/release.md | 7 +++--- 2 files changed, 3 insertions(+), 42 deletions(-) delete mode 100644 .github/workflows/release-tests.yml diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml deleted file mode 100644 index 74b829f1b0..0000000000 --- a/.github/workflows/release-tests.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Release tests - -on: workflow_dispatch - -permissions: - contents: read - -jobs: - virtualenv-15-windows-test: - # Regression test added in https://github.com/pylint-dev/astroid/pull/1386 - name: Regression test for virtualenv==15.1.0 on Windows - runs-on: windows-latest - timeout-minutes: 5 - steps: - - name: Check out code from GitHub - uses: actions/checkout@v4.2.2 - - name: Set up Python 3.9 - id: python - uses: actions/setup-python@v5.6.0 - with: - # virtualenv 15.1.0 cannot be installed on Python 3.10+ - python-version: 3.9 - env: - PIP_TRUSTED_HOST: "pypi.python.org pypi.org files.pythonhosted.org" - - name: Create Python virtual environment with virtualenv==15.1.0 - env: - PIP_TRUSTED_HOST: "pypi.python.org pypi.org files.pythonhosted.org" - run: | - python -m pip install virtualenv==15.1.0 - python -m virtualenv venv2 - . venv2\scripts\activate - python -m pip install pylint - python -m pip install -e . - - name: Test no import-error from distutils.util - run: | - . venv2\scripts\activate - echo "import distutils.util # pylint: disable=unused-import" > test.py - pylint test.py diff --git a/doc/release.md b/doc/release.md index 7dc7453a49..8e4ca833d5 100644 --- a/doc/release.md +++ b/doc/release.md @@ -30,10 +30,9 @@ tbump 2.5.0-dev0 --no-tag --no-push git commit -am "Upgrade the version to 2.5.0-dev0 following 2.4.0 release" ``` -Check the commit and then push to a release branch +Check the commit and then push to a release branch: - Open a merge request with the two commits (no one can push directly on `main`) -- Trigger the "release tests" workflow in GitHub Actions. - After the merge, recover the merged commits on `main` and tag the first one (the version should be `X.Y.Z`) as `vX.Y.Z` (For example: `v2.4.0`) - Push the tag. @@ -92,8 +91,8 @@ is the version under development on `main`.) - Fix version conflicts properly, meaning preserve the version numbers of the form `X.Y.0-devZ` (For example: `2.4.0-dev6`). - Open a merge request against main. Ensure a merge commit is used, because pre-commit - need the patch release tag to be in the main branch history to consider the patch - release as the latest version and this won't be the case with rebase or squash. You + needs the patch release tag to be in the main branch history to consider the patch + release as the latest version, and this won't be the case with rebase or squash. You can defend against trigger-happy future selves by enabling auto-merge with the merge commit strategy. - Wait for approval. Again, use a merge commit. From 4c7a998388254fcbe7ad6f23bb50a98ffbed0313 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 13 Jul 2025 14:01:18 -0400 Subject: [PATCH 2/4] Drop support for Python 3.9 --- .github/workflows/ci.yaml | 6 +- .pre-commit-config.yaml | 2 +- ChangeLog | 2 + astroid/__init__.py | 14 +- astroid/_backport_stdlib_names.py | 352 ---------------------------- astroid/brain/brain_dataclasses.py | 18 +- astroid/brain/brain_ssl.py | 6 +- astroid/brain/brain_subprocess.py | 7 +- astroid/const.py | 1 - astroid/interpreter/_import/spec.py | 90 ++----- astroid/modutils.py | 8 +- astroid/nodes/_base_nodes.py | 12 +- pylintrc | 2 +- pyproject.toml | 9 +- tests/brain/test_brain.py | 101 -------- tests/brain/test_dataclasses.py | 65 ++--- tests/brain/test_pathlib.py | 9 +- tests/test_inference.py | 144 ++++++------ tests/test_modutils.py | 13 - tests/test_nodes.py | 2 - tests/test_nodes_lineno.py | 5 +- tests/test_protocols.py | 3 +- 22 files changed, 143 insertions(+), 728 deletions(-) delete mode 100644 astroid/_backport_stdlib_names.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6ed5faaf66..3df6fd31af 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -81,7 +81,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, "3.10", "3.11", "3.12", "3.13", "3.14-dev"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14-dev"] outputs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: @@ -139,7 +139,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, "3.10", "3.11", "3.12", "3.13", "3.14-dev"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14-dev"] steps: - name: Set temp directory run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV @@ -194,7 +194,7 @@ jobs: fail-fast: false matrix: # We only test on the lowest and highest supported PyPy versions - python-version: ["pypy3.9", "pypy3.10"] + python-version: ["pypy3.10"] steps: - name: Check out code from GitHub uses: actions/checkout@v4.2.2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 37565aa997..d35848ce3e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: hooks: - id: pyupgrade exclude: tests/testdata - args: [--py39-plus] + args: [--py310-plus] - repo: https://github.com/Pierre-Sassoulas/black-disable-checker/ rev: v1.1.3 hooks: diff --git a/ChangeLog b/ChangeLog index cce14ee90e..96f56eb7df 100644 --- a/ChangeLog +++ b/ChangeLog @@ -32,6 +32,8 @@ Release date: TBA Closes #2513 +* Remove support for Python 3.9 (and constant `PY310_PLUS`). + * Include subclasses of standard property classes as `property` decorators Closes #10377 diff --git a/astroid/__init__.py b/astroid/__init__.py index f04b4dfdc8..f3df1faf8d 100644 --- a/astroid/__init__.py +++ b/astroid/__init__.py @@ -30,9 +30,6 @@ * builder contains the class responsible to build astroid trees """ -import functools -import tokenize - # isort: off # We have an isort: off on 'astroid.nodes' because of a circular import. from astroid.nodes import node_classes, scoped_nodes @@ -44,7 +41,7 @@ from astroid.bases import BaseInstance, BoundMethod, Instance, UnboundMethod from astroid.brain.helpers import register_module_extender from astroid.builder import extract_node, parse -from astroid.const import PY310_PLUS, Context +from astroid.const import Context from astroid.exceptions import ( AstroidBuildingError, AstroidError, @@ -175,12 +172,3 @@ # isort: on from astroid.util import Uninferable - -# Performance hack for tokenize. See https://bugs.python.org/issue43014 -# Adapted from https://github.com/PyCQA/pycodestyle/pull/993 -if ( - not PY310_PLUS - and callable(getattr(tokenize, "_compile", None)) - and getattr(tokenize._compile, "__wrapped__", None) is None # type: ignore[attr-defined] -): - tokenize._compile = functools.lru_cache(tokenize._compile) # type: ignore[attr-defined] diff --git a/astroid/_backport_stdlib_names.py b/astroid/_backport_stdlib_names.py deleted file mode 100644 index 901f90b90d..0000000000 --- a/astroid/_backport_stdlib_names.py +++ /dev/null @@ -1,352 +0,0 @@ -# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html -# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE -# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt - -""" -Shim to support Python versions < 3.10 that don't have sys.stdlib_module_names - -These values were created by cherry-picking the commits from -https://bugs.python.org/issue42955 into each version, but may be updated -manually if changes are needed. -""" - -import sys - -# TODO: Remove this file when Python 3.9 is no longer supported - -PY_3_7 = frozenset( - { - "__future__", - "_abc", - "_ast", - "_asyncio", - "_bisect", - "_blake2", - "_bootlocale", - "_bz2", - "_codecs", - "_codecs_cn", - "_codecs_hk", - "_codecs_iso2022", - "_codecs_jp", - "_codecs_kr", - "_codecs_tw", - "_collections", - "_collections_abc", - "_compat_pickle", - "_compression", - "_contextvars", - "_crypt", - "_csv", - "_ctypes", - "_curses", - "_curses_panel", - "_datetime", - "_dbm", - "_decimal", - "_dummy_thread", - "_elementtree", - "_functools", - "_gdbm", - "_hashlib", - "_heapq", - "_imp", - "_io", - "_json", - "_locale", - "_lsprof", - "_lzma", - "_markupbase", - "_md5", - "_msi", - "_multibytecodec", - "_multiprocessing", - "_opcode", - "_operator", - "_osx_support", - "_pickle", - "_posixsubprocess", - "_py_abc", - "_pydecimal", - "_pyio", - "_queue", - "_random", - "_sha1", - "_sha256", - "_sha3", - "_sha512", - "_signal", - "_sitebuiltins", - "_socket", - "_sqlite3", - "_sre", - "_ssl", - "_stat", - "_string", - "_strptime", - "_struct", - "_symtable", - "_thread", - "_threading_local", - "_tkinter", - "_tracemalloc", - "_uuid", - "_warnings", - "_weakref", - "_weakrefset", - "_winapi", - "abc", - "aifc", - "antigravity", - "argparse", - "array", - "ast", - "asynchat", - "asyncio", - "asyncore", - "atexit", - "audioop", - "base64", - "bdb", - "binascii", - "binhex", - "bisect", - "builtins", - "bz2", - "cProfile", - "calendar", - "cgi", - "cgitb", - "chunk", - "cmath", - "cmd", - "code", - "codecs", - "codeop", - "collections", - "colorsys", - "compileall", - "concurrent", - "configparser", - "contextlib", - "contextvars", - "copy", - "copyreg", - "crypt", - "csv", - "ctypes", - "curses", - "dataclasses", - "datetime", - "dbm", - "decimal", - "difflib", - "dis", - "distutils", - "doctest", - "dummy_threading", - "email", - "encodings", - "ensurepip", - "enum", - "errno", - "faulthandler", - "fcntl", - "filecmp", - "fileinput", - "fnmatch", - "formatter", - "fractions", - "ftplib", - "functools", - "gc", - "genericpath", - "getopt", - "getpass", - "gettext", - "glob", - "grp", - "gzip", - "hashlib", - "heapq", - "hmac", - "html", - "http", - "idlelib", - "imaplib", - "imghdr", - "imp", - "importlib", - "inspect", - "io", - "ipaddress", - "itertools", - "json", - "keyword", - "lib2to3", - "linecache", - "locale", - "logging", - "lzma", - "macpath", - "mailbox", - "mailcap", - "marshal", - "math", - "mimetypes", - "mmap", - "modulefinder", - "msilib", - "msvcrt", - "multiprocessing", - "netrc", - "nis", - "nntplib", - "nt", - "ntpath", - "nturl2path", - "numbers", - "opcode", - "operator", - "optparse", - "os", - "ossaudiodev", - "parser", - "pathlib", - "pdb", - "pickle", - "pickletools", - "pipes", - "pkgutil", - "platform", - "plistlib", - "poplib", - "posix", - "posixpath", - "pprint", - "profile", - "pstats", - "pty", - "pwd", - "py_compile", - "pyclbr", - "pydoc", - "pydoc_data", - "pyexpat", - "queue", - "quopri", - "random", - "re", - "readline", - "reprlib", - "resource", - "rlcompleter", - "runpy", - "sched", - "secrets", - "select", - "selectors", - "shelve", - "shlex", - "shutil", - "signal", - "site", - "smtpd", - "smtplib", - "sndhdr", - "socket", - "socketserver", - "spwd", - "sqlite3", - "sre_compile", - "sre_constants", - "sre_parse", - "ssl", - "stat", - "statistics", - "string", - "stringprep", - "struct", - "subprocess", - "sunau", - "symbol", - "symtable", - "sys", - "sysconfig", - "syslog", - "tabnanny", - "tarfile", - "telnetlib", - "tempfile", - "termios", - "textwrap", - "this", - "threading", - "time", - "timeit", - "tkinter", - "token", - "tokenize", - "trace", - "traceback", - "tracemalloc", - "tty", - "turtle", - "turtledemo", - "types", - "typing", - "unicodedata", - "unittest", - "urllib", - "uu", - "uuid", - "venv", - "warnings", - "wave", - "weakref", - "webbrowser", - "winreg", - "winsound", - "wsgiref", - "xdrlib", - "xml", - "xmlrpc", - "zipapp", - "zipfile", - "zipimport", - "zlib", - } -) - -PY_3_8 = frozenset( - PY_3_7 - - { - "macpath", - } - | { - "_posixshmem", - "_statistics", - "_xxsubinterpreters", - } -) - -PY_3_9 = frozenset( - PY_3_8 - - { - "_dummy_thread", - "dummy_threading", - } - | { - "_aix_support", - "_bootsubprocess", - "_peg_parser", - "_zoneinfo", - "graphlib", - "zoneinfo", - } -) - -if sys.version_info[:2] == (3, 9): - stdlib_module_names = PY_3_9 -else: - raise AssertionError("This module is only intended as a backport for Python <= 3.9") diff --git a/astroid/brain/brain_dataclasses.py b/astroid/brain/brain_dataclasses.py index 92d983e2b0..16e992b93f 100644 --- a/astroid/brain/brain_dataclasses.py +++ b/astroid/brain/brain_dataclasses.py @@ -15,22 +15,22 @@ from __future__ import annotations from collections.abc import Iterator -from typing import Literal, Union +from typing import Literal from astroid import bases, context, nodes from astroid.builder import parse -from astroid.const import PY310_PLUS, PY313_PLUS +from astroid.const import PY313_PLUS from astroid.exceptions import AstroidSyntaxError, InferenceError, UseInferenceDefault from astroid.inference_tip import inference_tip from astroid.manager import AstroidManager from astroid.typing import InferenceResult from astroid.util import Uninferable, UninferableBase, safe_infer -_FieldDefaultReturn = Union[ - None, - tuple[Literal["default"], nodes.NodeNG], - tuple[Literal["default_factory"], nodes.Call], -] +_FieldDefaultReturn = ( + None + | tuple[Literal["default"], nodes.NodeNG] + | tuple[Literal["default_factory"], nodes.Call] +) DATACLASSES_DECORATORS = frozenset(("dataclass",)) FIELD_NAME = "field" @@ -72,7 +72,7 @@ def dataclass_transform(node: nodes.ClassDef) -> None: return kw_only_decorated = False - if PY310_PLUS and node.decorators.nodes: + if node.decorators.nodes: for decorator in node.decorators.nodes: if not isinstance(decorator, nodes.Call): kw_only_decorated = False @@ -562,8 +562,6 @@ def _is_class_var(node: nodes.NodeNG) -> bool: def _is_keyword_only_sentinel(node: nodes.NodeNG) -> bool: """Return True if node is the KW_ONLY sentinel.""" - if not PY310_PLUS: - return False inferred = safe_infer(node) return ( isinstance(inferred, bases.Instance) diff --git a/astroid/brain/brain_ssl.py b/astroid/brain/brain_ssl.py index de932dee5b..6b4fc5c212 100644 --- a/astroid/brain/brain_ssl.py +++ b/astroid/brain/brain_ssl.py @@ -7,7 +7,7 @@ from astroid import nodes from astroid.brain.helpers import register_module_extender from astroid.builder import parse -from astroid.const import PY310_PLUS, PY312_PLUS +from astroid.const import PY312_PLUS from astroid.manager import AstroidManager @@ -18,9 +18,7 @@ class VerifyFlags(_IntFlag): VERIFY_CRL_CHECK_LEAF = 1 VERIFY_CRL_CHECK_CHAIN = 2 VERIFY_X509_STRICT = 3 - VERIFY_X509_TRUSTED_FIRST = 4""" - if PY310_PLUS: - enum += """ + VERIFY_X509_TRUSTED_FIRST = 4 VERIFY_ALLOW_PROXY_CERTS = 5 VERIFY_X509_PARTIAL_CHAIN = 6 """ diff --git a/astroid/brain/brain_subprocess.py b/astroid/brain/brain_subprocess.py index 96855c6a15..3a99802c97 100644 --- a/astroid/brain/brain_subprocess.py +++ b/astroid/brain/brain_subprocess.py @@ -7,7 +7,7 @@ from astroid import nodes from astroid.brain.helpers import register_module_extender from astroid.builder import parse -from astroid.const import PY310_PLUS, PY311_PLUS +from astroid.const import PY311_PLUS from astroid.manager import AstroidManager @@ -19,10 +19,7 @@ def _subprocess_transform() -> nodes.Module: preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, encoding=None, errors=None, text=None, - user=None, group=None, extra_groups=None, umask=-1""" - - if PY310_PLUS: - args += ", pipesize=-1" + user=None, group=None, extra_groups=None, umask=-1, pipesize=-1""" if PY311_PLUS: args += ", process_group=None" diff --git a/astroid/const.py b/astroid/const.py index 0bc98c2e14..dcce0740c0 100644 --- a/astroid/const.py +++ b/astroid/const.py @@ -5,7 +5,6 @@ import enum import sys -PY310_PLUS = sys.version_info >= (3, 10) PY311_PLUS = sys.version_info >= (3, 11) PY312_PLUS = sys.version_info >= (3, 12) PY313 = sys.version_info[:2] == (3, 13) diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index e28f1d0c3e..af7c55bcfa 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -20,8 +20,6 @@ from pathlib import Path from typing import Literal, NamedTuple, Protocol -from astroid.const import PY310_PLUS - from . import util @@ -168,56 +166,30 @@ def find_module( if cached_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 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 and not processed) or ( - processed and processed[0] in sys.stdlib_module_names + # 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 and not processed) or ( + processed and processed[0] in sys.stdlib_module_names + ): + 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 + is importlib.machinery.FrozenImporter ): - 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 - is importlib.machinery.FrozenImporter - ): - return ModuleSpec( - name=modname, - location=getattr(spec.loader_state, "filename", None), - type=ModuleType.PY_FROZEN, - ) - 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 + return ModuleSpec( + name=modname, + location=getattr(spec.loader_state, "filename", None), + type=ModuleType.PY_FROZEN, + ) return None @@ -298,7 +270,7 @@ def __init__(self, path: Sequence[str]) -> None: for entry_path in path: if entry_path not in sys.path_importer_cache: try: - sys.path_importer_cache[entry_path] = zipimport.zipimporter( # type: ignore[assignment] + sys.path_importer_cache[entry_path] = zipimport.zipimporter( entry_path ) except zipimport.ZipImportError: @@ -396,19 +368,9 @@ def _search_zip( modpath: tuple[str, ...], ) -> tuple[Literal[ModuleType.PY_ZIPMODULE], str, str]: for filepath, importer in _get_zipimporters(): - if PY310_PLUS: - found = importer.find_spec(modpath[0]) - else: - found = importer.find_module(modpath[0]) + found = importer.find_spec(modpath[0]) if found: - if PY310_PLUS: - if not importer.find_spec(os.path.sep.join(modpath)): - raise ImportError( - "No module named {} in {}/{}".format( - ".".join(modpath[1:]), filepath, modpath - ) - ) - elif not importer.find_module(os.path.sep.join(modpath)): + if not importer.find_spec(os.path.sep.join(modpath)): raise ImportError( "No module named {} in {}/{}".format( ".".join(modpath[1:]), filepath, modpath diff --git a/astroid/modutils.py b/astroid/modutils.py index 918146c5a1..0868c60c0a 100644 --- a/astroid/modutils.py +++ b/astroid/modutils.py @@ -30,15 +30,11 @@ from collections.abc import Callable, Iterable, Sequence from contextlib import redirect_stderr, redirect_stdout from functools import lru_cache +from sys import stdlib_module_names -from astroid.const import IS_JYTHON, PY310_PLUS +from astroid.const import IS_JYTHON from astroid.interpreter._import import spec, util -if PY310_PLUS: - from sys import stdlib_module_names -else: - from astroid._backport_stdlib_names import stdlib_module_names - logger = logging.getLogger(__name__) diff --git a/astroid/nodes/_base_nodes.py b/astroid/nodes/_base_nodes.py index ca9fcd70a6..df452cb2b8 100644 --- a/astroid/nodes/_base_nodes.py +++ b/astroid/nodes/_base_nodes.py @@ -12,10 +12,9 @@ import itertools from collections.abc import Callable, Generator, Iterator from functools import cached_property, lru_cache, partial -from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union +from typing import TYPE_CHECKING, Any, ClassVar from astroid import bases, nodes, util -from astroid.const import PY310_PLUS from astroid.context import ( CallContext, InferenceContext, @@ -35,10 +34,10 @@ GetFlowFactory = Callable[ [ InferenceResult, - Optional[InferenceResult], - Union[nodes.AugAssign, nodes.BinOp], + InferenceResult | None, + nodes.AugAssign | nodes.BinOp, InferenceResult, - Optional[InferenceResult], + InferenceResult | None, InferenceContext, InferenceContext, ], @@ -605,8 +604,7 @@ def _get_binop_flow( # pylint: disable = too-many-boolean-expressions if ( - PY310_PLUS - and op == "|" + op == "|" and ( isinstance(left, (bases.UnionType, nodes.ClassDef)) or (isinstance(left, nodes.Const) and left.value is None) diff --git a/pylintrc b/pylintrc index 64218020ce..63db8dd52e 100644 --- a/pylintrc +++ b/pylintrc @@ -45,7 +45,7 @@ unsafe-load-any-extension=no extension-pkg-whitelist= # Minimum supported python version -py-version = 3.9.0 +py-version = 3.10.0 [REPORTS] diff --git a/pyproject.toml b/pyproject.toml index 69de5af227..b902c3061a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ keywords = [ "abstract syntax tree", "python", "static code analysis" ] license = "LGPL-2.1-or-later" license-files = [ "LICENSE", "CONTRIBUTORS.txt" ] -requires-python = ">=3.9.0" +requires-python = ">=3.10.0" classifiers = [ "Development Status :: 6 - Mature", "Environment :: Console", @@ -19,7 +19,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -50,7 +49,7 @@ include = [ "astroid*" ] version = { attr = "astroid.__pkginfo__.__version__" } [tool.ruff] -target-version = "py39" +target-version = "py310" # ruff is less lenient than pylint and does not make any exceptions # (for docstrings, strings and comments in particular). @@ -90,10 +89,9 @@ testpaths = [ "tests" ] filterwarnings = "error" [tool.mypy] -python_version = "3.9" +python_version = "3.10" files = [ "astroid/_ast.py", - "astroid/_backport_stdlib_names.py", "astroid/astroid_manager.py", "astroid/brain/brain_crypt.py", "astroid/brain/brain_ctypes.py", @@ -131,7 +129,6 @@ files = [ "astroid/nodes/utils.py", ] always_false = [ - "PY310_PLUS", "PY311_PLUS", "PY312_PLUS", "PY313_PLUS", diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py index c476451e60..75c7c94a5f 100644 --- a/tests/brain/test_brain.py +++ b/tests/brain/test_brain.py @@ -128,7 +128,6 @@ def test_sys_streams(self): self.assertEqual(raw.name, "FileIO") -@test_utils.require_version("3.9") class TypeBrain(unittest.TestCase): def test_type_subscript(self): """ @@ -265,43 +264,6 @@ def test_collections_object_subscriptable(self): inferred.getattr("__class_getitem__")[0], nodes.FunctionDef ) - @test_utils.require_version(maxver="3.9") - def test_collections_object_not_yet_subscriptable(self): - """ - Test that unsubscriptable types are detected as such. - Until python39 MutableSet of the collections module is not subscriptable. - """ - wrong_node = builder.extract_node( - """ - import collections.abc - collections.abc.MutableSet[int] - """ - ) - with self.assertRaises(InferenceError): - next(wrong_node.infer()) - right_node = builder.extract_node( - """ - import collections.abc - collections.abc.MutableSet - """ - ) - inferred = next(right_node.infer()) - check_metaclass_is_abc(inferred) - assertEqualMro( - inferred, - [ - "_collections_abc.MutableSet", - "_collections_abc.Set", - "_collections_abc.Collection", - "_collections_abc.Sized", - "_collections_abc.Iterable", - "_collections_abc.Container", - "builtins.object", - ], - ) - with self.assertRaises(AttributeInferenceError): - inferred.getattr("__class_getitem__") - def test_collections_object_subscriptable_2(self): """Starting with python39 Iterator in the collection.abc module is subscriptable""" node = builder.extract_node( @@ -323,18 +285,6 @@ class Derived(collections.abc.Iterator[int]): ], ) - @test_utils.require_version(maxver="3.9") - def test_collections_object_not_yet_subscriptable_2(self): - """Before python39 Iterator in the collection.abc module is not subscriptable""" - node = builder.extract_node( - """ - import collections.abc - collections.abc.Iterator[int] - """ - ) - with self.assertRaises(InferenceError): - next(node.infer()) - @pytest.mark.skipif( PY314_PLUS, reason="collections.abc.ByteString was removed in 3.14" ) @@ -705,7 +655,6 @@ def test_typing_no_duplicates_2(self): ) assert len(node.inferred()) == 1 - @test_utils.require_version(minver="3.10") def test_typing_param_spec(self): node = builder.extract_node( """ @@ -1025,55 +974,6 @@ def test_regex_flags(self) -> None: self.assertIn(name, re_ast) self.assertEqual(next(re_ast[name].infer()).value, getattr(re, name)) - @test_utils.require_version(maxver="3.9") - def test_re_pattern_unsubscriptable(self): - """ - re.Pattern and re.Match are unsubscriptable until PY39. - """ - right_node1 = builder.extract_node( - """ - import re - re.Pattern - """ - ) - inferred1 = next(right_node1.infer()) - assert isinstance(inferred1, nodes.ClassDef) - with self.assertRaises(AttributeInferenceError): - assert isinstance( - inferred1.getattr("__class_getitem__")[0], nodes.FunctionDef - ) - - right_node2 = builder.extract_node( - """ - import re - re.Pattern - """ - ) - inferred2 = next(right_node2.infer()) - assert isinstance(inferred2, nodes.ClassDef) - with self.assertRaises(AttributeInferenceError): - assert isinstance( - inferred2.getattr("__class_getitem__")[0], nodes.FunctionDef - ) - - wrong_node1 = builder.extract_node( - """ - import re - re.Pattern[int] - """ - ) - with self.assertRaises(InferenceError): - next(wrong_node1.infer()) - - wrong_node2 = builder.extract_node( - """ - import re - re.Match[int] - """ - ) - with self.assertRaises(InferenceError): - next(wrong_node2.infer()) - def test_re_pattern_subscriptable(self): """Test re.Pattern and re.Match are subscriptable in PY39+""" node1 = builder.extract_node( @@ -1203,7 +1103,6 @@ def test_subprcess_check_output(self) -> None: assert isinstance(inferred, astroid.Const) assert isinstance(inferred.value, (str, bytes)) - @test_utils.require_version("3.9") def test_popen_does_not_have_class_getitem(self): code = """import subprocess; subprocess.Popen""" node = astroid.extract_node(code) diff --git a/tests/brain/test_dataclasses.py b/tests/brain/test_dataclasses.py index cd3fcb4cfb..f34bcb4baf 100644 --- a/tests/brain/test_dataclasses.py +++ b/tests/brain/test_dataclasses.py @@ -6,7 +6,6 @@ import astroid from astroid import bases, nodes -from astroid.const import PY310_PLUS from astroid.exceptions import InferenceError from astroid.util import Uninferable @@ -422,19 +421,12 @@ class KeywordOnlyChild(KeywordOnlyParent): assert [a.name for a in child_init.args.args] == ["self", "a", "b", "c"] normal_init: bases.UnboundMethod = next(normal.infer()) - if PY310_PLUS: - assert [a.name for a in normal_init.args.args] == ["self", "a", "c"] - assert [a.name for a in normal_init.args.kwonlyargs] == ["b"] - else: - assert [a.name for a in normal_init.args.args] == ["self", "a", "b", "c"] - assert [a.name for a in normal_init.args.kwonlyargs] == [] + assert [a.name for a in normal_init.args.args] == ["self", "a", "c"] + assert [a.name for a in normal_init.args.kwonlyargs] == ["b"] keyword_only_init: bases.UnboundMethod = next(keyword_only.infer()) - if PY310_PLUS: - assert [a.name for a in keyword_only_init.args.args] == ["self"] - assert [a.name for a in keyword_only_init.args.kwonlyargs] == ["a", "b", "c"] - else: - assert [a.name for a in keyword_only_init.args.args] == ["self", "a", "b", "c"] + assert [a.name for a in keyword_only_init.args.args] == ["self"] + assert [a.name for a in keyword_only_init.args.kwonlyargs] == ["a", "b", "c"] def test_pydantic_field() -> None: @@ -806,10 +798,7 @@ class B: B.__init__ #@ """ ) - if PY310_PLUS: - expected = ["self", "y"] - else: - expected = ["self", "_", "y"] + expected = ["self", "y"] init = next(node_one.infer()) assert [a.name for a in init.args.args] == expected @@ -818,10 +807,7 @@ class B: def test_kw_only_decorator() -> None: - """Test that we update the signature correctly based on the keyword. - - kw_only was introduced in PY310. - """ + """Test that we update the signature correctly based on the keyword.""" foodef, bardef, cee, dee = astroid.extract_node( """ from dataclasses import dataclass @@ -855,43 +841,20 @@ class Dee(Cee): ) foo_init: bases.UnboundMethod = next(foodef.infer()) - if PY310_PLUS: - assert [a.name for a in foo_init.args.args] == ["self"] - assert [a.name for a in foo_init.args.kwonlyargs] == ["a", "e"] - else: - assert [a.name for a in foo_init.args.args] == ["self", "a", "e"] - assert [a.name for a in foo_init.args.kwonlyargs] == [] + assert [a.name for a in foo_init.args.args] == ["self"] + assert [a.name for a in foo_init.args.kwonlyargs] == ["a", "e"] bar_init: bases.UnboundMethod = next(bardef.infer()) - if PY310_PLUS: - assert [a.name for a in bar_init.args.args] == ["self", "c"] - assert [a.name for a in bar_init.args.kwonlyargs] == ["a", "e"] - else: - assert [a.name for a in bar_init.args.args] == ["self", "a", "e", "c"] - assert [a.name for a in bar_init.args.kwonlyargs] == [] + assert [a.name for a in bar_init.args.args] == ["self", "c"] + assert [a.name for a in bar_init.args.kwonlyargs] == ["a", "e"] cee_init: bases.UnboundMethod = next(cee.infer()) - if PY310_PLUS: - assert [a.name for a in cee_init.args.args] == ["self", "c", "d"] - assert [a.name for a in cee_init.args.kwonlyargs] == ["a", "e"] - else: - assert [a.name for a in cee_init.args.args] == ["self", "a", "e", "c", "d"] - assert [a.name for a in cee_init.args.kwonlyargs] == [] + assert [a.name for a in cee_init.args.args] == ["self", "c", "d"] + assert [a.name for a in cee_init.args.kwonlyargs] == ["a", "e"] dee_init: bases.UnboundMethod = next(dee.infer()) - if PY310_PLUS: - assert [a.name for a in dee_init.args.args] == ["self", "c", "d"] - assert [a.name for a in dee_init.args.kwonlyargs] == ["a", "e", "ee"] - else: - assert [a.name for a in dee_init.args.args] == [ - "self", - "a", - "e", - "c", - "d", - "ee", - ] - assert [a.name for a in dee_init.args.kwonlyargs] == [] + assert [a.name for a in dee_init.args.args] == ["self", "c", "d"] + assert [a.name for a in dee_init.args.kwonlyargs] == ["a", "e", "ee"] def test_kw_only_in_field_call() -> None: diff --git a/tests/brain/test_pathlib.py b/tests/brain/test_pathlib.py index b97b3e75fa..2335e28ad6 100644 --- a/tests/brain/test_pathlib.py +++ b/tests/brain/test_pathlib.py @@ -5,7 +5,7 @@ import astroid from astroid import bases -from astroid.const import PY310_PLUS, PY313 +from astroid.const import PY313 from astroid.util import Uninferable @@ -62,11 +62,8 @@ def test_inference_parents_subscript_slice() -> None: ) inferred = name_node.inferred() assert len(inferred) == 1 - if PY310_PLUS: - assert isinstance(inferred[0], bases.Instance) - assert inferred[0].qname() == "builtins.tuple" - else: - assert inferred[0] is Uninferable + assert isinstance(inferred[0], bases.Instance) + assert inferred[0].qname() == "builtins.tuple" def test_inference_parents_subscript_not_path() -> None: diff --git a/tests/test_inference.py b/tests/test_inference.py index a2a5094c33..b8714224eb 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -34,7 +34,7 @@ from astroid.arguments import CallSite from astroid.bases import BoundMethod, Generator, Instance, UnboundMethod, UnionType from astroid.builder import AstroidBuilder, _extract_single_node, extract_node, parse -from astroid.const import IS_PYPY, PY310_PLUS, PY312_PLUS, PY314_PLUS +from astroid.const import IS_PYPY, PY312_PLUS, PY314_PLUS from astroid.context import CallContext, InferenceContext from astroid.exceptions import ( AstroidTypeError, @@ -1292,61 +1292,57 @@ class B: ... tuple | int #@ """ ast_nodes = extract_node(code) - if not PY310_PLUS: - for n in ast_nodes: - assert n.inferred() == [util.Uninferable] + i0 = ast_nodes[0].inferred()[0] + assert isinstance(i0, UnionType) + assert isinstance(i0.left, nodes.ClassDef) + assert i0.left.name == "int" + assert isinstance(i0.right, nodes.Const) + assert i0.right.value is None + + # Assert basic UnionType properties and methods + assert i0.callable() is False + assert i0.bool_value() is True + assert i0.pytype() == "types.UnionType" + assert i0.display_type() == "UnionType" + if PY314_PLUS: + assert str(i0) == "UnionType(Union)" + assert repr(i0) == f"" else: - i0 = ast_nodes[0].inferred()[0] - assert isinstance(i0, UnionType) - assert isinstance(i0.left, nodes.ClassDef) - assert i0.left.name == "int" - assert isinstance(i0.right, nodes.Const) - assert i0.right.value is None - - # Assert basic UnionType properties and methods - assert i0.callable() is False - assert i0.bool_value() is True - assert i0.pytype() == "types.UnionType" - assert i0.display_type() == "UnionType" - if PY314_PLUS: - assert str(i0) == "UnionType(Union)" - assert repr(i0) == f"" - else: - assert str(i0) == "UnionType(UnionType)" - assert repr(i0) == f"" - - i1 = ast_nodes[1].inferred()[0] - assert isinstance(i1, UnionType) - - i2 = ast_nodes[2].inferred()[0] - assert isinstance(i2, UnionType) - assert isinstance(i2.left, UnionType) - assert isinstance(i2.left.left, nodes.ClassDef) - assert i2.left.left.name == "int" - assert isinstance(i2.left.right, nodes.ClassDef) - assert i2.left.right.name == "str" - assert isinstance(i2.right, nodes.Const) - assert i2.right.value is None - - i3 = ast_nodes[3].inferred()[0] - assert isinstance(i3, UnionType) - assert isinstance(i3.left, nodes.ClassDef) - assert i3.left.name == "A" - assert isinstance(i3.right, nodes.ClassDef) - assert i3.right.name == "B" - - i4 = ast_nodes[4].inferred()[0] - assert isinstance(i4, UnionType) - - i5 = ast_nodes[5].inferred()[0] - assert isinstance(i5, UnionType) - assert isinstance(i5.left, nodes.ClassDef) - assert i5.left.name == "List" - - i6 = ast_nodes[6].inferred()[0] - assert isinstance(i6, UnionType) - assert isinstance(i6.left, nodes.ClassDef) - assert i6.left.name == "tuple" + assert str(i0) == "UnionType(UnionType)" + assert repr(i0) == f"" + + i1 = ast_nodes[1].inferred()[0] + assert isinstance(i1, UnionType) + + i2 = ast_nodes[2].inferred()[0] + assert isinstance(i2, UnionType) + assert isinstance(i2.left, UnionType) + assert isinstance(i2.left.left, nodes.ClassDef) + assert i2.left.left.name == "int" + assert isinstance(i2.left.right, nodes.ClassDef) + assert i2.left.right.name == "str" + assert isinstance(i2.right, nodes.Const) + assert i2.right.value is None + + i3 = ast_nodes[3].inferred()[0] + assert isinstance(i3, UnionType) + assert isinstance(i3.left, nodes.ClassDef) + assert i3.left.name == "A" + assert isinstance(i3.right, nodes.ClassDef) + assert i3.right.name == "B" + + i4 = ast_nodes[4].inferred()[0] + assert isinstance(i4, UnionType) + + i5 = ast_nodes[5].inferred()[0] + assert isinstance(i5, UnionType) + assert isinstance(i5.left, nodes.ClassDef) + assert i5.left.name == "List" + + i6 = ast_nodes[6].inferred()[0] + assert isinstance(i6, UnionType) + assert isinstance(i6.left, nodes.ClassDef) + assert i6.left.name == "tuple" code = """ from typing import List @@ -1359,26 +1355,22 @@ class B: ... Alias1 | Alias2 #@ """ ast_nodes = extract_node(code) - if not PY310_PLUS: - for n in ast_nodes: - assert n.inferred() == [util.Uninferable] - else: - i0 = ast_nodes[0].inferred()[0] - assert isinstance(i0, UnionType) - assert isinstance(i0.left, nodes.ClassDef) - assert i0.left.name == "List" - - i1 = ast_nodes[1].inferred()[0] - assert isinstance(i1, UnionType) - assert isinstance(i1.left, UnionType) - assert isinstance(i1.left.left, nodes.ClassDef) - assert i1.left.left.name == "str" - - i2 = ast_nodes[2].inferred()[0] - assert isinstance(i2, UnionType) - assert isinstance(i2.left, nodes.ClassDef) - assert i2.left.name == "List" - assert isinstance(i2.right, UnionType) + i0 = ast_nodes[0].inferred()[0] + assert isinstance(i0, UnionType) + assert isinstance(i0.left, nodes.ClassDef) + assert i0.left.name == "List" + + i1 = ast_nodes[1].inferred()[0] + assert isinstance(i1, UnionType) + assert isinstance(i1.left, UnionType) + assert isinstance(i1.left.left, nodes.ClassDef) + assert i1.left.left.name == "str" + + i2 = ast_nodes[2].inferred()[0] + assert isinstance(i2, UnionType) + assert isinstance(i2.left, nodes.ClassDef) + assert i2.left.name == "List" + assert isinstance(i2.right, UnionType) def test_nonregr_lambda_arg(self) -> None: code = """ @@ -1888,7 +1880,7 @@ def do_a_thing(): self.assertEqual(node.type, "function") @pytest.mark.skipif( - IS_PYPY and PY310_PLUS, + IS_PYPY, reason="Persistent recursion error that we ignore and never fix", ) def test_no_infinite_ancestor_loop(self) -> None: diff --git a/tests/test_modutils.py b/tests/test_modutils.py index cdd677edd9..ef4eed2fc9 100644 --- a/tests/test_modutils.py +++ b/tests/test_modutils.py @@ -20,7 +20,6 @@ import astroid from astroid import modutils -from astroid.const import PY310_PLUS from astroid.interpreter._import import spec from . import resources @@ -502,18 +501,6 @@ def test_failure(self) -> None: assert not modutils.module_in_path("astroid", datadir) -class BackportStdlibNamesTest(resources.SysPathSetup, unittest.TestCase): - """ - Verify backport raises exception on newer versions - """ - - @pytest.mark.skipif(not PY310_PLUS, reason="Backport valid on <=3.9") - def test_import_error(self) -> None: - with pytest.raises(AssertionError): - # pylint: disable-next=import-outside-toplevel, unused-import - from astroid import _backport_stdlib_names # noqa - - class IsRelativeTest(unittest.TestCase): def test_known_values_is_relative_1(self) -> None: self.assertTrue(modutils.is_relative("utils", email.__path__[0])) diff --git a/tests/test_nodes.py b/tests/test_nodes.py index 7006bdb1b1..a1487b40c1 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -30,7 +30,6 @@ ) from astroid.const import ( IS_PYPY, - PY310_PLUS, PY311_PLUS, PY312_PLUS, PY314_PLUS, @@ -1918,7 +1917,6 @@ def test(): assert bool(node.is_generator()) -@pytest.mark.skipif(not PY310_PLUS, reason="pattern matching was added in PY310") class TestPatternMatching: @staticmethod def test_match_simple(): diff --git a/tests/test_nodes_lineno.py b/tests/test_nodes_lineno.py index 875d21d565..f8c6f91152 100644 --- a/tests/test_nodes_lineno.py +++ b/tests/test_nodes_lineno.py @@ -4,11 +4,9 @@ import textwrap -import pytest - import astroid from astroid import builder, nodes -from astroid.const import PY310_PLUS, PY312_PLUS +from astroid.const import PY312_PLUS class TestLinenoColOffset: @@ -987,7 +985,6 @@ def test_end_lineno_string() -> None: assert (s4.value.end_lineno, s4.value.end_col_offset) == (2, 14) @staticmethod - @pytest.mark.skipif(not PY310_PLUS, reason="pattern matching was added in PY310") def test_end_lineno_match() -> None: """Match, MatchValue, MatchSingleton, MatchSequence, MatchMapping, MatchClass, MatchStar, MatchOr, MatchAs. diff --git a/tests/test_protocols.py b/tests/test_protocols.py index 4a9f1f6022..94b0656ebc 100644 --- a/tests/test_protocols.py +++ b/tests/test_protocols.py @@ -13,7 +13,7 @@ import astroid from astroid import extract_node, nodes -from astroid.const import PY310_PLUS, PY312_PLUS +from astroid.const import PY312_PLUS from astroid.exceptions import InferenceError from astroid.manager import AstroidManager from astroid.util import Uninferable, UninferableBase @@ -364,7 +364,6 @@ def test(value=(p := 24)): return p assert node.value == 1 -@pytest.mark.skipif(not PY310_PLUS, reason="Match requires python 3.10") class TestPatternMatching: @staticmethod def test_assigned_stmts_match_mapping(): From 90b61ac2b9a7d6bd7495fce8331e8503480be61a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 18:03:52 +0000 Subject: [PATCH 3/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- astroid/decorators.py | 5 +- astroid/rebuilder.py | 362 ++++++++++++++++++++---------------------- 2 files changed, 175 insertions(+), 192 deletions(-) diff --git a/astroid/decorators.py b/astroid/decorators.py index 93c5fc99a4..a37d11d04c 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -18,10 +18,7 @@ from astroid.exceptions import InferenceError from astroid.typing import InferenceResult -if sys.version_info >= (3, 10): - from typing import ParamSpec -else: - from typing_extensions import ParamSpec +from typing import ParamSpec _R = TypeVar("_R") _P = ParamSpec("_P") diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index b208754c07..6fdbd09a79 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -413,60 +413,52 @@ def visit( self, node: ast.YieldFrom, parent: nodes.NodeNG ) -> nodes.YieldFrom: ... - if sys.version_info >= (3, 10): - - @overload - def visit(self, node: ast.Match, parent: nodes.NodeNG) -> nodes.Match: ... + @overload + def visit(self, node: ast.Match, parent: nodes.NodeNG) -> nodes.Match: ... - @overload - def visit( - self, node: ast.match_case, parent: nodes.NodeNG - ) -> nodes.MatchCase: ... + @overload + def visit( + self, node: ast.match_case, parent: nodes.NodeNG + ) -> nodes.MatchCase: ... - @overload - def visit( - self, node: ast.MatchValue, parent: nodes.NodeNG - ) -> nodes.MatchValue: ... + @overload + def visit( + self, node: ast.MatchValue, parent: nodes.NodeNG + ) -> nodes.MatchValue: ... - @overload - def visit( - self, node: ast.MatchSingleton, parent: nodes.NodeNG - ) -> nodes.MatchSingleton: ... + @overload + def visit( + self, node: ast.MatchSingleton, parent: nodes.NodeNG + ) -> nodes.MatchSingleton: ... - @overload - def visit( - self, node: ast.MatchSequence, parent: nodes.NodeNG - ) -> nodes.MatchSequence: ... + @overload + def visit( + self, node: ast.MatchSequence, parent: nodes.NodeNG + ) -> nodes.MatchSequence: ... - @overload - def visit( - self, node: ast.MatchMapping, parent: nodes.NodeNG - ) -> nodes.MatchMapping: ... + @overload + def visit( + self, node: ast.MatchMapping, parent: nodes.NodeNG + ) -> nodes.MatchMapping: ... - @overload - def visit( - self, node: ast.MatchClass, parent: nodes.NodeNG - ) -> nodes.MatchClass: ... + @overload + def visit( + self, node: ast.MatchClass, parent: nodes.NodeNG + ) -> nodes.MatchClass: ... - @overload - def visit( - self, node: ast.MatchStar, parent: nodes.NodeNG - ) -> nodes.MatchStar: ... + @overload + def visit( + self, node: ast.MatchStar, parent: nodes.NodeNG + ) -> nodes.MatchStar: ... - @overload - def visit( - self, node: ast.MatchAs, parent: nodes.NodeNG - ) -> nodes.MatchAs: ... + @overload + def visit(self, node: ast.MatchAs, parent: nodes.NodeNG) -> nodes.MatchAs: ... - @overload - def visit( - self, node: ast.MatchOr, parent: nodes.NodeNG - ) -> nodes.MatchOr: ... + @overload + def visit(self, node: ast.MatchOr, parent: nodes.NodeNG) -> nodes.MatchOr: ... - @overload - def visit( - self, node: ast.pattern, parent: nodes.NodeNG - ) -> nodes.Pattern: ... + @overload + def visit(self, node: ast.pattern, parent: nodes.NodeNG) -> nodes.Pattern: ... @overload def visit(self, node: ast.AST, parent: nodes.NodeNG) -> nodes.NodeNG: ... @@ -1790,156 +1782,150 @@ def visit_yieldfrom( newnode.postinit(self.visit(node.value, newnode)) return newnode - if sys.version_info >= (3, 10): + def visit_match(self, node: ast.Match, parent: nodes.NodeNG) -> nodes.Match: + newnode = nodes.Match( + lineno=node.lineno, + col_offset=node.col_offset, + end_lineno=node.end_lineno, + end_col_offset=node.end_col_offset, + parent=parent, + ) + newnode.postinit( + subject=self.visit(node.subject, newnode), + cases=[self.visit(case, newnode) for case in node.cases], + ) + return newnode - def visit_match(self, node: ast.Match, parent: nodes.NodeNG) -> nodes.Match: - newnode = nodes.Match( - lineno=node.lineno, - col_offset=node.col_offset, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset, - parent=parent, - ) - newnode.postinit( - subject=self.visit(node.subject, newnode), - cases=[self.visit(case, newnode) for case in node.cases], - ) - return newnode + def visit_matchcase( + self, node: ast.match_case, parent: nodes.NodeNG + ) -> nodes.MatchCase: + newnode = nodes.MatchCase(parent=parent) + newnode.postinit( + pattern=self.visit(node.pattern, newnode), + guard=self.visit(node.guard, newnode), + body=[self.visit(child, newnode) for child in node.body], + ) + return newnode - def visit_matchcase( - self, node: ast.match_case, parent: nodes.NodeNG - ) -> nodes.MatchCase: - newnode = nodes.MatchCase(parent=parent) - newnode.postinit( - pattern=self.visit(node.pattern, newnode), - guard=self.visit(node.guard, newnode), - body=[self.visit(child, newnode) for child in node.body], - ) - return newnode + def visit_matchvalue( + self, node: ast.MatchValue, parent: nodes.NodeNG + ) -> nodes.MatchValue: + newnode = nodes.MatchValue( + lineno=node.lineno, + col_offset=node.col_offset, + end_lineno=node.end_lineno, + end_col_offset=node.end_col_offset, + parent=parent, + ) + newnode.postinit(value=self.visit(node.value, newnode)) + return newnode - def visit_matchvalue( - self, node: ast.MatchValue, parent: nodes.NodeNG - ) -> nodes.MatchValue: - newnode = nodes.MatchValue( - lineno=node.lineno, - col_offset=node.col_offset, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset, - parent=parent, - ) - newnode.postinit(value=self.visit(node.value, newnode)) - return newnode + def visit_matchsingleton( + self, node: ast.MatchSingleton, parent: nodes.NodeNG + ) -> nodes.MatchSingleton: + return nodes.MatchSingleton( + value=node.value, + lineno=node.lineno, + col_offset=node.col_offset, + end_lineno=node.end_lineno, + end_col_offset=node.end_col_offset, + parent=parent, + ) - def visit_matchsingleton( - self, node: ast.MatchSingleton, parent: nodes.NodeNG - ) -> nodes.MatchSingleton: - return nodes.MatchSingleton( - value=node.value, - lineno=node.lineno, - col_offset=node.col_offset, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset, - parent=parent, - ) + def visit_matchsequence( + self, node: ast.MatchSequence, parent: nodes.NodeNG + ) -> nodes.MatchSequence: + newnode = nodes.MatchSequence( + lineno=node.lineno, + col_offset=node.col_offset, + end_lineno=node.end_lineno, + end_col_offset=node.end_col_offset, + parent=parent, + ) + newnode.postinit( + patterns=[self.visit(pattern, newnode) for pattern in node.patterns] + ) + return newnode - def visit_matchsequence( - self, node: ast.MatchSequence, parent: nodes.NodeNG - ) -> nodes.MatchSequence: - newnode = nodes.MatchSequence( - lineno=node.lineno, - col_offset=node.col_offset, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset, - parent=parent, - ) - newnode.postinit( - patterns=[self.visit(pattern, newnode) for pattern in node.patterns] - ) - return newnode + def visit_matchmapping( + self, node: ast.MatchMapping, parent: nodes.NodeNG + ) -> nodes.MatchMapping: + newnode = nodes.MatchMapping( + lineno=node.lineno, + col_offset=node.col_offset, + end_lineno=node.end_lineno, + end_col_offset=node.end_col_offset, + parent=parent, + ) + # Add AssignName node for 'node.name' + # https://bugs.python.org/issue43994 + newnode.postinit( + keys=[self.visit(child, newnode) for child in node.keys], + patterns=[self.visit(pattern, newnode) for pattern in node.patterns], + rest=self.visit_assignname(node, newnode, node.rest), + ) + return newnode - def visit_matchmapping( - self, node: ast.MatchMapping, parent: nodes.NodeNG - ) -> nodes.MatchMapping: - newnode = nodes.MatchMapping( - lineno=node.lineno, - col_offset=node.col_offset, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset, - parent=parent, - ) - # Add AssignName node for 'node.name' - # https://bugs.python.org/issue43994 - newnode.postinit( - keys=[self.visit(child, newnode) for child in node.keys], - patterns=[self.visit(pattern, newnode) for pattern in node.patterns], - rest=self.visit_assignname(node, newnode, node.rest), - ) - return newnode + def visit_matchclass( + self, node: ast.MatchClass, parent: nodes.NodeNG + ) -> nodes.MatchClass: + newnode = nodes.MatchClass( + lineno=node.lineno, + col_offset=node.col_offset, + end_lineno=node.end_lineno, + end_col_offset=node.end_col_offset, + parent=parent, + ) + newnode.postinit( + cls=self.visit(node.cls, newnode), + patterns=[self.visit(pattern, newnode) for pattern in node.patterns], + kwd_attrs=node.kwd_attrs, + kwd_patterns=[ + self.visit(pattern, newnode) for pattern in node.kwd_patterns + ], + ) + return newnode - def visit_matchclass( - self, node: ast.MatchClass, parent: nodes.NodeNG - ) -> nodes.MatchClass: - newnode = nodes.MatchClass( - lineno=node.lineno, - col_offset=node.col_offset, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset, - parent=parent, - ) - newnode.postinit( - cls=self.visit(node.cls, newnode), - patterns=[self.visit(pattern, newnode) for pattern in node.patterns], - kwd_attrs=node.kwd_attrs, - kwd_patterns=[ - self.visit(pattern, newnode) for pattern in node.kwd_patterns - ], - ) - return newnode + def visit_matchstar( + self, node: ast.MatchStar, parent: nodes.NodeNG + ) -> nodes.MatchStar: + newnode = nodes.MatchStar( + lineno=node.lineno, + col_offset=node.col_offset, + end_lineno=node.end_lineno, + end_col_offset=node.end_col_offset, + parent=parent, + ) + # Add AssignName node for 'node.name' + # https://bugs.python.org/issue43994 + newnode.postinit(name=self.visit_assignname(node, newnode, node.name)) + return newnode - def visit_matchstar( - self, node: ast.MatchStar, parent: nodes.NodeNG - ) -> nodes.MatchStar: - newnode = nodes.MatchStar( - lineno=node.lineno, - col_offset=node.col_offset, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset, - parent=parent, - ) - # Add AssignName node for 'node.name' - # https://bugs.python.org/issue43994 - newnode.postinit(name=self.visit_assignname(node, newnode, node.name)) - return newnode - - def visit_matchas( - self, node: ast.MatchAs, parent: nodes.NodeNG - ) -> nodes.MatchAs: - newnode = nodes.MatchAs( - lineno=node.lineno, - col_offset=node.col_offset, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset, - parent=parent, - ) - # Add AssignName node for 'node.name' - # https://bugs.python.org/issue43994 - newnode.postinit( - pattern=self.visit(node.pattern, newnode), - name=self.visit_assignname(node, newnode, node.name), - ) - return newnode + def visit_matchas(self, node: ast.MatchAs, parent: nodes.NodeNG) -> nodes.MatchAs: + newnode = nodes.MatchAs( + lineno=node.lineno, + col_offset=node.col_offset, + end_lineno=node.end_lineno, + end_col_offset=node.end_col_offset, + parent=parent, + ) + # Add AssignName node for 'node.name' + # https://bugs.python.org/issue43994 + newnode.postinit( + pattern=self.visit(node.pattern, newnode), + name=self.visit_assignname(node, newnode, node.name), + ) + return newnode - def visit_matchor( - self, node: ast.MatchOr, parent: nodes.NodeNG - ) -> nodes.MatchOr: - newnode = nodes.MatchOr( - lineno=node.lineno, - col_offset=node.col_offset, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset, - parent=parent, - ) - newnode.postinit( - patterns=[self.visit(pattern, newnode) for pattern in node.patterns] - ) - return newnode + def visit_matchor(self, node: ast.MatchOr, parent: nodes.NodeNG) -> nodes.MatchOr: + newnode = nodes.MatchOr( + lineno=node.lineno, + col_offset=node.col_offset, + end_lineno=node.end_lineno, + end_col_offset=node.end_col_offset, + parent=parent, + ) + newnode.postinit( + patterns=[self.visit(pattern, newnode) for pattern in node.patterns] + ) + return newnode From 75ad3a3719c1253bfdd5ec6adb2ab41dca4922ea Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 13 Jul 2025 15:01:37 -0400 Subject: [PATCH 4/4] Lint fixes post pre-commit fixes --- astroid/brain/brain_builtin_inference.py | 31 +++++++----------------- astroid/constraint.py | 4 +-- astroid/context.py | 4 +-- astroid/decorators.py | 4 +-- astroid/nodes/node_classes.py | 22 ++++++----------- astroid/nodes/node_ng.py | 3 +-- astroid/raw_building.py | 18 +++++++------- astroid/rebuilder.py | 8 +++--- astroid/transforms.py | 19 ++++++++------- astroid/typing.py | 1 + 10 files changed, 47 insertions(+), 67 deletions(-) diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py index 9ab7b8b238..02d4875814 100644 --- a/astroid/brain/brain_builtin_inference.py +++ b/astroid/brain/brain_builtin_inference.py @@ -9,7 +9,7 @@ import itertools from collections.abc import Callable, Iterable, Iterator from functools import partial -from typing import TYPE_CHECKING, Any, NoReturn, Union, cast +from typing import TYPE_CHECKING, Any, NoReturn, cast from astroid import arguments, helpers, nodes, objects, util from astroid.builder import AstroidBuilder @@ -33,26 +33,13 @@ if TYPE_CHECKING: from astroid.bases import Instance -ContainerObjects = Union[ - objects.FrozenSet, - objects.DictItems, - objects.DictKeys, - objects.DictValues, -] - -BuiltContainers = Union[ - type[tuple], - type[list], - type[set], - type[frozenset], -] - -CopyResult = Union[ - nodes.Dict, - nodes.List, - nodes.Set, - objects.FrozenSet, -] +ContainerObjects = ( + objects.FrozenSet | objects.DictItems | objects.DictKeys | objects.DictValues +) + +BuiltContainers = type[tuple] | type[list] | type[set] | type[frozenset] + +CopyResult = nodes.Dict | nodes.List | nodes.Set | objects.FrozenSet OBJECT_DUNDER_NEW = "object.__new__" @@ -282,7 +269,7 @@ def _container_generic_transform( if isinstance(arg, klass): return arg if isinstance(arg, iterables): - arg = cast(Union[nodes.BaseContainer, ContainerObjects], arg) + arg = cast((nodes.BaseContainer | ContainerObjects), arg) if all(isinstance(elt, nodes.Const) for elt in arg.elts): elts = [cast(nodes.Const, elt).value for elt in arg.elts] else: diff --git a/astroid/constraint.py b/astroid/constraint.py index 08bb80e3c9..458b55bac5 100644 --- a/astroid/constraint.py +++ b/astroid/constraint.py @@ -8,7 +8,7 @@ import sys from abc import ABC, abstractmethod from collections.abc import Iterator -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING from astroid import nodes, util from astroid.typing import InferenceResult @@ -21,7 +21,7 @@ if TYPE_CHECKING: from astroid import bases -_NameNodes = Union[nodes.AssignAttr, nodes.Attribute, nodes.AssignName, nodes.Name] +_NameNodes = nodes.AssignAttr | nodes.Attribute | nodes.AssignName | nodes.Name class Constraint(ABC): diff --git a/astroid/context.py b/astroid/context.py index 777565aeaa..3002b532c7 100644 --- a/astroid/context.py +++ b/astroid/context.py @@ -9,7 +9,7 @@ import contextlib import pprint from collections.abc import Iterator, Sequence -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from astroid.typing import InferenceResult, SuccessfulInferenceResult @@ -19,7 +19,7 @@ from astroid.nodes.node_ng import NodeNG _InferenceCache = dict[ - tuple["NodeNG", Optional[str], Optional[str], Optional[str]], Sequence["NodeNG"] + tuple["NodeNG", str | None, str | None, str | None], Sequence["NodeNG"] ] _INFERENCE_CACHE: _InferenceCache = {} diff --git a/astroid/decorators.py b/astroid/decorators.py index a37d11d04c..05d2dd391e 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -11,15 +11,13 @@ import sys import warnings from collections.abc import Callable, Generator -from typing import TypeVar +from typing import ParamSpec, TypeVar from astroid import util from astroid.context import InferenceContext from astroid.exceptions import InferenceError from astroid.typing import InferenceResult -from typing import ParamSpec - _R = TypeVar("_R") _P = ParamSpec("_P") diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index d3aef1718c..94a7189af9 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -15,14 +15,7 @@ import warnings from collections.abc import Callable, Generator, Iterable, Iterator, Mapping from functools import cached_property -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Literal, - Optional, - Union, -) +from typing import TYPE_CHECKING, Any, ClassVar, Literal, Union from astroid import decorators, protocols, util from astroid.bases import Instance, _infer_stmts @@ -71,23 +64,24 @@ def _is_const(value) -> bool: _NodesT = typing.TypeVar("_NodesT", bound=NodeNG) _BadOpMessageT = typing.TypeVar("_BadOpMessageT", bound=util.BadOperationMessage) +# pylint: disable-next=consider-alternative-union-syntax AssignedStmtsPossibleNode = Union["List", "Tuple", "AssignName", "AssignAttr", None] AssignedStmtsCall = Callable[ [ _NodesT, AssignedStmtsPossibleNode, - Optional[InferenceContext], - Optional[list[int]], + InferenceContext | None, + list[int] | None, ], Any, ] InferBinaryOperation = Callable[ - [_NodesT, Optional[InferenceContext]], - Generator[Union[InferenceResult, _BadOpMessageT]], + [_NodesT, InferenceContext | None], + Generator[InferenceResult | _BadOpMessageT], ] InferLHS = Callable[ - [_NodesT, Optional[InferenceContext]], - Generator[InferenceResult, None, Optional[InferenceErrorInfo]], + [_NodesT, InferenceContext | None], + Generator[InferenceResult, None, InferenceErrorInfo | None], ] InferUnaryOp = Callable[[_NodesT, str], ConstFactoryResult] diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py index dc8942b705..1af39c244b 100644 --- a/astroid/nodes/node_ng.py +++ b/astroid/nodes/node_ng.py @@ -14,7 +14,6 @@ Any, ClassVar, TypeVar, - Union, cast, overload, ) @@ -48,7 +47,7 @@ _NodesT = TypeVar("_NodesT", bound="NodeNG") _NodesT2 = TypeVar("_NodesT2", bound="NodeNG") _NodesT3 = TypeVar("_NodesT3", bound="NodeNG") -SkipKlassT = Union[None, type["NodeNG"], tuple[type["NodeNG"], ...]] +SkipKlassT = None | type["NodeNG"] | tuple[type["NodeNG"], ...] class NodeNG: diff --git a/astroid/raw_building.py b/astroid/raw_building.py index 150a40b1e4..d1bbbd5569 100644 --- a/astroid/raw_building.py +++ b/astroid/raw_building.py @@ -18,7 +18,7 @@ import warnings from collections.abc import Iterable from contextlib import redirect_stderr, redirect_stdout -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any from astroid import bases, nodes from astroid.const import _EMPTY_OBJECT_MARKER, IS_PYPY @@ -30,14 +30,14 @@ logger = logging.getLogger(__name__) -_FunctionTypes = Union[ - types.FunctionType, - types.MethodType, - types.BuiltinFunctionType, - types.WrapperDescriptorType, - types.MethodDescriptorType, - types.ClassMethodDescriptorType, -] +_FunctionTypes = ( + types.FunctionType + | types.MethodType + | types.BuiltinFunctionType + | types.WrapperDescriptorType + | types.MethodDescriptorType + | types.ClassMethodDescriptorType +) TYPE_NONE = type(None) TYPE_NOTIMPLEMENTED = type(NotImplemented) diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 6fdbd09a79..7aadf24fa4 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -14,7 +14,7 @@ from collections.abc import Callable, Generator from io import StringIO from tokenize import TokenInfo, generate_tokens -from typing import TYPE_CHECKING, Final, TypeVar, Union, cast, overload +from typing import TYPE_CHECKING, Final, TypeVar, cast, overload from astroid import nodes from astroid._ast import ParserModule, get_parser_module, parse_function_type_comment @@ -29,12 +29,12 @@ "T_Doc", "ast.Module", "ast.ClassDef", - Union["ast.FunctionDef", "ast.AsyncFunctionDef"], + "ast.FunctionDef" | "ast.AsyncFunctionDef", ) _FunctionT = TypeVar("_FunctionT", nodes.FunctionDef, nodes.AsyncFunctionDef) _ForT = TypeVar("_ForT", nodes.For, nodes.AsyncFor) _WithT = TypeVar("_WithT", nodes.With, nodes.AsyncWith) - NodesWithDocsType = Union[nodes.Module, nodes.ClassDef, nodes.FunctionDef] + NodesWithDocsType = nodes.Module | nodes.ClassDef | nodes.FunctionDef REDIRECT: Final[dict[str, str]] = { @@ -1429,7 +1429,7 @@ def visit_name( ) # XXX REMOVE me : if context in (Context.Del, Context.Store): # 'Aug' ?? - newnode = cast(Union[nodes.AssignName, nodes.DelName], newnode) + newnode = cast((nodes.AssignName | nodes.DelName), newnode) self._save_assignment(newnode) return newnode diff --git a/astroid/transforms.py b/astroid/transforms.py index 1831e74b5c..d44ec3dc13 100644 --- a/astroid/transforms.py +++ b/astroid/transforms.py @@ -7,7 +7,7 @@ import warnings from collections import defaultdict from collections.abc import Callable -from typing import TYPE_CHECKING, Optional, TypeVar, Union, cast, overload +from typing import TYPE_CHECKING, TypeVar, Union, cast, overload from astroid.context import _invalidate_cache from astroid.typing import SuccessfulInferenceResult, TransformFn @@ -18,18 +18,19 @@ _SuccessfulInferenceResultT = TypeVar( "_SuccessfulInferenceResultT", bound=SuccessfulInferenceResult ) - _Predicate = Optional[Callable[[_SuccessfulInferenceResultT], bool]] + _Predicate = Callable[[_SuccessfulInferenceResultT], bool] | None +# pylint: disable-next=consider-alternative-union-syntax _Vistables = Union[ "nodes.NodeNG", list["nodes.NodeNG"], tuple["nodes.NodeNG", ...], str, None ] -_VisitReturns = Union[ - SuccessfulInferenceResult, - list[SuccessfulInferenceResult], - tuple[SuccessfulInferenceResult, ...], - str, - None, -] +_VisitReturns = ( + SuccessfulInferenceResult + | list[SuccessfulInferenceResult] + | tuple[SuccessfulInferenceResult, ...] + | str + | None +) class TransformVisitor: diff --git a/astroid/typing.py b/astroid/typing.py index 77cc120306..37ea43452b 100644 --- a/astroid/typing.py +++ b/astroid/typing.py @@ -47,6 +47,7 @@ class AstroidManagerBrain(TypedDict): _transform: transforms.TransformVisitor +# pylint: disable=consider-alternative-union-syntax InferenceResult = Union["nodes.NodeNG", "util.UninferableBase", "bases.Proxy"] SuccessfulInferenceResult = Union["nodes.NodeNG", "bases.Proxy"] _SuccessfulInferenceResultT = TypeVar(