From 8a177ce4da47b0b8eeb13a83a76a3e91a0227302 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 7 May 2024 07:39:30 -0400 Subject: [PATCH 01/63] Bump astroid to 3.3.0-dev0, update changelog --- astroid/__pkginfo__.py | 2 +- tbump.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index fc290f324f..b89e0aae97 100644 --- a/astroid/__pkginfo__.py +++ b/astroid/__pkginfo__.py @@ -2,5 +2,5 @@ # For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE # Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt -__version__ = "3.2.0" +__version__ = "3.3.0-dev0" version = __version__ diff --git a/tbump.toml b/tbump.toml index 47f7d98a29..5a51a9b618 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/astroid" [version] -current = "3.2.0" +current = "3.3.0-dev0" regex = ''' ^(?P0|[1-9]\d*) \. From 2c38c0275b790265ab450b79e8dc602e651ca9d3 Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Fri, 10 May 2024 15:46:58 -0400 Subject: [PATCH 02/63] Improve performance of _get_zipimporters _get_zipimporters can call isinstance millions of times when running pylint's import-error checker on a codebase like yt-dlp. Checking for None first avoids the overhead of invoking isinstance. Closes pylint-dev/pylint#9607. --- astroid/interpreter/_import/spec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index 469508da83..59546a016a 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -341,7 +341,7 @@ def _is_setuptools_namespace(location: pathlib.Path) -> bool: def _get_zipimporters() -> Iterator[tuple[str, zipimport.zipimporter]]: for filepath, importer in sys.path_importer_cache.items(): - if isinstance(importer, zipimport.zipimporter): + if importer is not None and isinstance(importer, zipimport.zipimporter): yield filepath, importer From 514463bb36c121030df57896b5707102522cf9f6 Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Sun, 12 May 2024 06:23:58 +0000 Subject: [PATCH 03/63] Fix ruff deprecation warnings for top-level linter settings (#2430) --- pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c5d4c15fd8..7f1354d517 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,11 +83,13 @@ ignore_missing_imports = true [tool.ruff] +target-version = "py38" # ruff is less lenient than pylint and does not make any exceptions # (for docstrings, strings and comments in particular). line-length = 110 +[tool.ruff.lint] select = [ "E", # pycodestyle "F", # pyflakes @@ -112,8 +114,7 @@ fixable = [ "RUF", # ruff ] unfixable = ["RUF001"] -target-version = "py38" -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] # Ruff is autofixing a tests with a voluntarily sneaky unicode "tests/test_regrtest.py" = ["RUF001"] From a536585846de2f1721283f2985775e703ecb1001 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 19:29:52 +0200 Subject: [PATCH 04/63] Bump furo from 2024.4.27 to 2024.5.6 (#2434) Bumps [furo](https://github.com/pradyunsg/furo) from 2024.4.27 to 2024.5.6. - [Release notes](https://github.com/pradyunsg/furo/releases) - [Changelog](https://github.com/pradyunsg/furo/blob/main/docs/changelog.md) - [Commits](https://github.com/pradyunsg/furo/compare/2024.04.27...2024.05.06) --- updated-dependencies: - dependency-name: furo dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index e29754b80a..6a0aa61ebf 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,3 @@ -e . sphinx~=7.3 -furo==2024.4.27 +furo==2024.5.6 From 414a45bf819e401dd47f6d642fdd68d476b2f7f7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 00:31:23 +0200 Subject: [PATCH 05/63] [pre-commit.ci] pre-commit autoupdate (#2435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.3 → v0.4.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.3...v0.4.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 977fd91612..b8e47ebd75 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.3" + rev: "v0.4.4" hooks: - id: ruff exclude: tests/testdata From d1c37a90359e6a8e40b9772258d0049394f8294a Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 14 May 2024 09:39:17 -0400 Subject: [PATCH 06/63] Fix RecursionError in `infer_call_result()` (#2432) --- ChangeLog | 3 +++ astroid/bases.py | 5 +++++ tests/test_inference.py | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/ChangeLog b/ChangeLog index 6212a95e40..17e69ccb6f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,9 @@ What's New in astroid 3.2.1? ============================ Release date: TBA +* Fix ``RecursionError`` in ``infer_call_result()`` for certain ``__call__`` methods. + + Closes pylint-dev/pylint#9139 What's New in astroid 3.2.0? diff --git a/astroid/bases.py b/astroid/bases.py index 4b866a6aed..4a684cf1fe 100644 --- a/astroid/bases.py +++ b/astroid/bases.py @@ -326,6 +326,11 @@ def infer_call_result( for node in self._proxied.igetattr("__call__", context): if isinstance(node, UninferableBase) or not node.callable(): continue + if isinstance(node, BaseInstance) and node._proxied is self._proxied: + inferred = True + yield node + # Prevent recursion. + continue for res in node.infer_call_result(caller, context): inferred = True yield res diff --git a/tests/test_inference.py b/tests/test_inference.py index 10fceb7b56..ec8fc71b69 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -4090,6 +4090,18 @@ class C: inferred = next(node.infer()) self.assertRaises(InferenceError, next, inferred.infer_call_result(node)) + def test_infer_call_result_same_proxied_class(self) -> None: + node = extract_node( + """ + class A: + __call__ = A() + A() #@ + """ + ) + inferred = next(node.infer()) + fully_evaluated_inference_results = list(inferred.infer_call_result(node)) + assert fully_evaluated_inference_results[0].name == "A" + def test_infer_call_result_with_metaclass(self) -> None: node = extract_node("def with_metaclass(meta, *bases): return 42") inferred = next(node.infer_call_result(caller=node)) From ee06feb96f5c697a76e9591d6d03b293e81fe6ef Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Thu, 16 May 2024 07:04:46 -0400 Subject: [PATCH 07/63] Add prefer_stubs configuration (#2437) --- ChangeLog | 5 +++++ astroid/interpreter/_import/spec.py | 8 ++------ astroid/manager.py | 14 +++++++++++++- astroid/modutils.py | 15 +++++++++------ tests/test_modutils.py | 3 ++- 5 files changed, 31 insertions(+), 14 deletions(-) diff --git a/ChangeLog b/ChangeLog index 17e69ccb6f..d3b37e696b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,11 @@ Release date: TBA Closes pylint-dev/pylint#9139 +* Add ``AstroidManager.prefer_stubs`` attribute to control the astroid 3.2.0 feature that prefers stubs. + + Refs pylint-dev/#9626 + Refs pylint-dev/#9623 + What's New in astroid 3.2.0? ============================ diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index 59546a016a..09e98c888b 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -161,14 +161,10 @@ def find_module( pass submodule_path = sys.path - # We're looping on pyi first because if a pyi exists there's probably a reason - # (i.e. the code is hard or impossible to parse), so we take pyi into account - # But we're not quite ready to do this for numpy, see https://github.com/pylint-dev/astroid/pull/2375 - suffixes = (".pyi", ".py", importlib.machinery.BYTECODE_SUFFIXES[0]) - numpy_suffixes = (".py", ".pyi", importlib.machinery.BYTECODE_SUFFIXES[0]) + suffixes = (".py", ".pyi", importlib.machinery.BYTECODE_SUFFIXES[0]) for entry in submodule_path: package_directory = os.path.join(entry, modname) - for suffix in numpy_suffixes if "numpy" in entry else suffixes: + for suffix in suffixes: package_file_name = "__init__" + suffix file_path = os.path.join(package_directory, package_file_name) if os.path.isfile(file_path): diff --git a/astroid/manager.py b/astroid/manager.py index fc30bf9e2f..ade31c0e91 100644 --- a/astroid/manager.py +++ b/astroid/manager.py @@ -61,6 +61,7 @@ class AstroidManager: "extension_package_whitelist": set(), "module_denylist": set(), "_transform": TransformVisitor(), + "prefer_stubs": False, } def __init__(self) -> None: @@ -73,6 +74,7 @@ def __init__(self) -> None: ] self.module_denylist = AstroidManager.brain["module_denylist"] self._transform = AstroidManager.brain["_transform"] + self.prefer_stubs = AstroidManager.brain["prefer_stubs"] @property def always_load_extensions(self) -> bool: @@ -111,6 +113,14 @@ def unregister_transform(self): def builtins_module(self) -> nodes.Module: return self.astroid_cache["builtins"] + @property + def prefer_stubs(self) -> bool: + return AstroidManager.brain["prefer_stubs"] + + @prefer_stubs.setter + def prefer_stubs(self, value: bool) -> None: + AstroidManager.brain["prefer_stubs"] = value + def visit_transforms(self, node: nodes.NodeNG) -> InferenceResult: """Visit the transforms and apply them to the given *node*.""" return self._transform.visit(node) @@ -136,7 +146,9 @@ def ast_from_file( # Call get_source_file() only after a cache miss, # since it calls os.path.exists(). try: - filepath = get_source_file(filepath, include_no_ext=True) + filepath = get_source_file( + filepath, include_no_ext=True, prefer_stubs=self.prefer_stubs + ) source = True except NoSourceFile: pass diff --git a/astroid/modutils.py b/astroid/modutils.py index 6f67d1ab9a..bcb602c6b8 100644 --- a/astroid/modutils.py +++ b/astroid/modutils.py @@ -44,10 +44,12 @@ if sys.platform.startswith("win"): - PY_SOURCE_EXTS = ("pyi", "pyw", "py") + PY_SOURCE_EXTS = ("py", "pyw", "pyi") + PY_SOURCE_EXTS_STUBS_FIRST = ("pyi", "pyw", "py") PY_COMPILED_EXTS = ("dll", "pyd") else: - PY_SOURCE_EXTS = ("pyi", "py") + PY_SOURCE_EXTS = ("py", "pyi") + PY_SOURCE_EXTS_STUBS_FIRST = ("pyi", "py") PY_COMPILED_EXTS = ("so",) @@ -484,7 +486,9 @@ def get_module_files( return files -def get_source_file(filename: str, include_no_ext: bool = False) -> str: +def get_source_file( + filename: str, include_no_ext: bool = False, prefer_stubs: bool = False +) -> str: """Given a python module's file name return the matching source file name (the filename will be returned identically if it's already an absolute path to a python source file). @@ -499,7 +503,7 @@ def get_source_file(filename: str, include_no_ext: bool = False) -> str: base, orig_ext = os.path.splitext(filename) if orig_ext == ".pyi" and os.path.exists(f"{base}{orig_ext}"): return f"{base}{orig_ext}" - for ext in PY_SOURCE_EXTS if "numpy" not in filename else reversed(PY_SOURCE_EXTS): + for ext in PY_SOURCE_EXTS_STUBS_FIRST if prefer_stubs else PY_SOURCE_EXTS: source_path = f"{base}.{ext}" if os.path.exists(source_path): return source_path @@ -671,8 +675,7 @@ def _has_init(directory: str) -> str | None: else return None. """ mod_or_pack = os.path.join(directory, "__init__") - exts = reversed(PY_SOURCE_EXTS) if "numpy" in directory else PY_SOURCE_EXTS - for ext in (*exts, "pyc", "pyo"): + for ext in (*PY_SOURCE_EXTS, "pyc", "pyo"): if os.path.exists(mod_or_pack + "." + ext): return mod_or_pack + "." + ext return None diff --git a/tests/test_modutils.py b/tests/test_modutils.py index 3741428bcc..85452b0f77 100644 --- a/tests/test_modutils.py +++ b/tests/test_modutils.py @@ -300,7 +300,8 @@ def test_pyi_preferred(self) -> None: package = resources.find("pyi_data/find_test") module = os.path.join(package, "__init__.py") self.assertEqual( - modutils.get_source_file(module), os.path.normpath(module) + "i" + modutils.get_source_file(module, prefer_stubs=True), + os.path.normpath(module) + "i", ) From 16da308bb3af0a13a7db88765de354355497981a Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Thu, 16 May 2024 08:13:59 -0400 Subject: [PATCH 08/63] Upgrade pylint in pre-commit config (#2440) Co-authored-by: Pierre Sassoulas --- astroid/nodes/node_classes.py | 4 +++- astroid/nodes/scoped_nodes/scoped_nodes.py | 2 -- requirements_dev.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 708b51a009..22bb4da81b 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -1057,6 +1057,8 @@ def _format_args( annotations = [] if defaults is not None: default_offset = len(args) - len(defaults) + else: + default_offset = None packed = itertools.zip_longest(args, annotations) for i, (arg, annotation) in enumerate(packed): if arg.name in skippable_names: @@ -1071,7 +1073,7 @@ def _format_args( default_sep = " = " values.append(argname) - if defaults is not None and i >= default_offset: + if default_offset is not None and i >= default_offset: if defaults[i - default_offset] is not None: values[-1] += default_sep + defaults[i - default_offset].as_string() return ", ".join(values) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index 79b7643e55..af68d217ff 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -1557,8 +1557,6 @@ def infer_yield_result(self, context: InferenceContext | None = None): :returns: What the function yields :rtype: iterable(NodeNG or Uninferable) or None """ - # pylint: disable=not-an-iterable - # https://github.com/pylint-dev/astroid/issues/1015 for yield_ in self.nodes_of_class(node_classes.Yield): if yield_.value is None: const = node_classes.Const(None) diff --git a/requirements_dev.txt b/requirements_dev.txt index ee1c41bc96..4949455f52 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,6 +3,6 @@ # Tools used during development, prefer running these with pre-commit black pre-commit -pylint +pylint>=3.2.0 mypy ruff From e43e045179fe4df7ba2aed96ad5ef180232f39cc Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Fri, 17 May 2024 05:05:41 +0000 Subject: [PATCH 09/63] Cache _has_init calls to avoid repeated stats (#2429) _has_init can end up checking for the presence of the same files over and over. For example, when running pylint's import-error checks on a codebase like yt-dlp, ~43,000 redundant stats were performed prior to caching. Closes pylint-dev/pylint#9613. --- astroid/manager.py | 2 ++ astroid/modutils.py | 1 + 2 files changed, 3 insertions(+) diff --git a/astroid/manager.py b/astroid/manager.py index ade31c0e91..e7c2c806f7 100644 --- a/astroid/manager.py +++ b/astroid/manager.py @@ -23,6 +23,7 @@ from astroid.modutils import ( NoSourceFile, _cache_normalize_path_, + _has_init, file_info_from_modpath, get_source_file, is_module_name_part_of_extension_package_whitelist, @@ -469,6 +470,7 @@ def clear_cache(self) -> None: for lru_cache in ( LookupMixIn.lookup, _cache_normalize_path_, + _has_init, util.is_namespace, ObjectModel.attributes, ClassDef._metaclass_lookup_attribute, diff --git a/astroid/modutils.py b/astroid/modutils.py index bcb602c6b8..c058c7d232 100644 --- a/astroid/modutils.py +++ b/astroid/modutils.py @@ -670,6 +670,7 @@ def _is_python_file(filename: str) -> bool: return filename.endswith((".py", ".pyi", ".so", ".pyd", ".pyw")) +@lru_cache(maxsize=1024) def _has_init(directory: str) -> str | None: """If the given directory has a valid __init__ file, return its path, else return None. From fadac920e7f812101e54a77beb1639045106c5e2 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 19 May 2024 23:45:35 +0200 Subject: [PATCH 10/63] Improve inference for generic classes (PEP 695) (#2433) --- ChangeLog | 4 ++++ astroid/brain/brain_typing.py | 19 +++++++++++++++++++ astroid/nodes/scoped_nodes/scoped_nodes.py | 5 ++++- astroid/protocols.py | 7 +++---- tests/brain/test_brain.py | 18 ++++++++++++++++++ tests/test_protocols.py | 15 ++++++++++++--- 6 files changed, 60 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index 309ef577e4..db804248a2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -22,6 +22,10 @@ Release date: TBA Refs pylint-dev/#9626 Refs pylint-dev/#9623 +* Improve inference for generic classes using the PEP 695 syntax (Python 3.12). + + Closes pylint-dev/#9406 + What's New in astroid 3.2.1? ============================ diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py index 20276b6c12..9965abc25c 100644 --- a/astroid/brain/brain_typing.py +++ b/astroid/brain/brain_typing.py @@ -196,6 +196,20 @@ def infer_typing_attr( return node.infer(context=ctx) +def _looks_like_generic_class_pep695(node: ClassDef) -> bool: + """Check if class is using type parameter. Python 3.12+.""" + return len(node.type_params) > 0 + + +def infer_typing_generic_class_pep695( + node: ClassDef, ctx: context.InferenceContext | None = None +) -> Iterator[ClassDef]: + """Add __class_getitem__ for generic classes. Python 3.12+.""" + func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE) + node.locals["__class_getitem__"] = [func_to_add] + return iter([node]) + + def _looks_like_typedDict( # pylint: disable=invalid-name node: FunctionDef | ClassDef, ) -> bool: @@ -490,3 +504,8 @@ def register(manager: AstroidManager) -> None: if PY312_PLUS: register_module_extender(manager, "typing", _typing_transform) + manager.register_transform( + ClassDef, + inference_tip(infer_typing_generic_class_pep695), + _looks_like_generic_class_pep695, + ) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index af68d217ff..42de1af2d2 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -2192,7 +2192,10 @@ def scope_lookup( and name in AstroidManager().builtins_module ) if ( - any(node == base or base.parent_of(node) for base in self.bases) + any( + node == base or base.parent_of(node) and not self.type_params + for base in self.bases + ) or lookup_upper_frame ): # Handle the case where we have either a name diff --git a/astroid/protocols.py b/astroid/protocols.py index 02e2111101..8e90ddab58 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -924,8 +924,7 @@ def generic_type_assigned_stmts( context: InferenceContext | None = None, assign_path: None = None, ) -> Generator[nodes.NodeNG, None, None]: - """Return empty generator (return -> raises StopIteration) so inferred value - is Uninferable. + """Hack. Return any Node so inference doesn't fail + when evaluating __class_getitem__. Revert if it's causing issues. """ - return - yield + yield nodes.Const(None) diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py index 0353d16036..b8bc84e31f 100644 --- a/tests/brain/test_brain.py +++ b/tests/brain/test_brain.py @@ -627,6 +627,24 @@ def test_typing_generic_subscriptable(self): assert isinstance(inferred, nodes.ClassDef) assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef) + @test_utils.require_version(minver="3.12") + def test_typing_generic_subscriptable_pep695(self): + """Test class using type parameters is subscriptable with __class_getitem__ (added in PY312)""" + node = builder.extract_node( + """ + class Foo[T]: ... + class Bar[T](Foo[T]): ... + """ + ) + inferred = next(node.infer()) + assert isinstance(inferred, nodes.ClassDef) + assert inferred.name == "Bar" + assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef) + ancestors = list(inferred.ancestors()) + assert len(ancestors) == 2 + assert ancestors[0].name == "Foo" + assert ancestors[1].name == "object" + @test_utils.require_version(minver="3.9") def test_typing_annotated_subscriptable(self): """Test typing.Annotated is subscriptable with __class_getitem__""" diff --git a/tests/test_protocols.py b/tests/test_protocols.py index 3466609cf1..72b91a1156 100644 --- a/tests/test_protocols.py +++ b/tests/test_protocols.py @@ -425,7 +425,10 @@ def test_assigned_stmts_type_var(): assign_stmts = extract_node("type Point[T] = tuple[float, float]") type_var: nodes.TypeVar = assign_stmts.type_params[0] assigned = next(type_var.name.assigned_stmts()) - assert assigned is Uninferable + # Hack so inference doesn't fail when evaluating __class_getitem__ + # Revert if it's causing issues. + assert isinstance(assigned, nodes.Const) + assert assigned.value is None @staticmethod def test_assigned_stmts_type_var_tuple(): @@ -433,7 +436,10 @@ def test_assigned_stmts_type_var_tuple(): assign_stmts = extract_node("type Alias[*Ts] = tuple[*Ts]") type_var_tuple: nodes.TypeVarTuple = assign_stmts.type_params[0] assigned = next(type_var_tuple.name.assigned_stmts()) - assert assigned is Uninferable + # Hack so inference doesn't fail when evaluating __class_getitem__ + # Revert if it's causing issues. + assert isinstance(assigned, nodes.Const) + assert assigned.value is None @staticmethod def test_assigned_stmts_param_spec(): @@ -441,4 +447,7 @@ def test_assigned_stmts_param_spec(): assign_stmts = extract_node("type Alias[**P] = Callable[P, int]") param_spec: nodes.ParamSpec = assign_stmts.type_params[0] assigned = next(param_spec.name.assigned_stmts()) - assert assigned is Uninferable + # Hack so inference doesn't fail when evaluating __class_getitem__ + # Revert if it's causing issues. + assert isinstance(assigned, nodes.Const) + assert assigned.value is None From 7ff0f4f8389dd59ea0a0c2c001a3a853c9de77fa Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 20 May 2024 04:50:25 -0400 Subject: [PATCH 11/63] Drop support for Python 3.8 (#2443) Also remove constants `PY38`, `PY39_PLUS`, and `PYPY_7_3_11_PLUS`. --- .github/workflows/ci.yaml | 6 +- .pre-commit-config.yaml | 2 +- ChangeLog | 4 +- astroid/_backport_stdlib_names.py | 6 +- astroid/brain/brain_builtin_inference.py | 12 ++-- astroid/brain/brain_collections.py | 24 +++---- astroid/brain/brain_dataclasses.py | 28 +++----- astroid/brain/brain_hashlib.py | 8 +-- astroid/brain/brain_re.py | 7 +- astroid/brain/brain_regex.py | 6 +- astroid/brain/brain_subprocess.py | 18 ++--- astroid/brain/brain_type.py | 8 +-- astroid/brain/brain_typing.py | 53 +++----------- astroid/const.py | 5 -- astroid/context.py | 8 +-- astroid/modutils.py | 24 ++----- astroid/nodes/_base_nodes.py | 4 +- astroid/nodes/node_classes.py | 9 ++- astroid/nodes/node_ng.py | 4 +- astroid/nodes/scoped_nodes/scoped_nodes.py | 22 ------ astroid/raw_building.py | 7 +- astroid/rebuilder.py | 74 +------------------ astroid/transforms.py | 8 +-- astroid/typing.py | 3 +- pylintrc | 2 +- pyproject.toml | 5 +- tests/brain/test_brain.py | 19 ----- tests/brain/test_hashlib.py | 7 +- tests/brain/test_regex.py | 3 +- tests/brain/test_unittest.py | 2 - tests/test_builder.py | 14 +--- tests/test_inference.py | 39 +--------- tests/test_lookup.py | 3 +- tests/test_nodes.py | 3 - tests/test_nodes_lineno.py | 84 +++------------------- tests/test_object_model.py | 4 +- tests/test_python3.py | 2 - tests/test_scoped_nodes.py | 10 +-- 38 files changed, 114 insertions(+), 433 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bdc8ad36c3..930b3910f4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -81,7 +81,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12"] outputs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: @@ -138,7 +138,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12"] steps: - name: Set temp directory run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV @@ -192,7 +192,7 @@ jobs: fail-fast: false matrix: # We only test on the lowest and highest supported PyPy versions - python-version: ["pypy3.8", "pypy3.10"] + python-version: ["pypy3.9", "pypy3.10"] steps: - name: Check out code from GitHub uses: actions/checkout@v4.1.5 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b8e47ebd75..6f2e245ac0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: hooks: - id: pyupgrade exclude: tests/testdata - args: [--py38-plus] + args: [--py39-plus] - repo: https://github.com/Pierre-Sassoulas/black-disable-checker/ rev: v1.1.3 hooks: diff --git a/ChangeLog b/ChangeLog index 66dd914c73..77e6eab199 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,9 @@ What's New in astroid 3.3.0? ============================ Release date: TBA +* Remove support for Python 3.8 (and constants `PY38`, `PY39_PLUS`, and `PYPY_7_3_11_PLUS`). + + Refs #2443 What's New in astroid 3.2.3? @@ -14,7 +17,6 @@ What's New in astroid 3.2.3? Release date: TBA - What's New in astroid 3.2.2? ============================ Release date: 2024-05-20 diff --git a/astroid/_backport_stdlib_names.py b/astroid/_backport_stdlib_names.py index 39c5f65bac..901f90b90d 100644 --- a/astroid/_backport_stdlib_names.py +++ b/astroid/_backport_stdlib_names.py @@ -346,11 +346,7 @@ } ) -if sys.version_info[:2] == (3, 7): - stdlib_module_names = PY_3_7 -elif sys.version_info[:2] == (3, 8): - stdlib_module_names = PY_3_8 -elif sys.version_info[:2] == (3, 9): +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_builtin_inference.py b/astroid/brain/brain_builtin_inference.py index d53520dc46..e9d00e2e1a 100644 --- a/astroid/brain/brain_builtin_inference.py +++ b/astroid/brain/brain_builtin_inference.py @@ -7,9 +7,9 @@ from __future__ import annotations import itertools -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Iterator from functools import partial -from typing import TYPE_CHECKING, Any, Iterator, NoReturn, Type, Union, cast +from typing import TYPE_CHECKING, Any, NoReturn, Union, cast from astroid import arguments, helpers, inference_tip, nodes, objects, util from astroid.builder import AstroidBuilder @@ -40,10 +40,10 @@ ] BuiltContainers = Union[ - Type[tuple], - Type[list], - Type[set], - Type[frozenset], + type[tuple], + type[list], + type[set], + type[frozenset], ] CopyResult = Union[ diff --git a/astroid/brain/brain_collections.py b/astroid/brain/brain_collections.py index 8f1fd6c306..22017786ac 100644 --- a/astroid/brain/brain_collections.py +++ b/astroid/brain/brain_collections.py @@ -6,7 +6,6 @@ from astroid.brain.helpers import register_module_extender from astroid.builder import extract_node, parse -from astroid.const import PY39_PLUS from astroid.context import InferenceContext from astroid.exceptions import AttributeInferenceError from astroid.manager import AstroidManager @@ -61,9 +60,7 @@ def __add__(self, other): pass def __iadd__(self, other): pass def __mul__(self, other): pass def __imul__(self, other): pass - def __rmul__(self, other): pass""" - if PY39_PLUS: - base_deque_class += """ + def __rmul__(self, other): pass @classmethod def __class_getitem__(self, item): return cls""" return base_deque_class @@ -73,9 +70,7 @@ def _ordered_dict_mock(): base_ordered_dict_class = """ class OrderedDict(dict): def __reversed__(self): return self[::-1] - def move_to_end(self, key, last=False): pass""" - if PY39_PLUS: - base_ordered_dict_class += """ + def move_to_end(self, key, last=False): pass @classmethod def __class_getitem__(cls, item): return cls""" return base_ordered_dict_class @@ -116,11 +111,10 @@ def easy_class_getitem_inference(node, context: InferenceContext | None = None): def register(manager: AstroidManager) -> None: register_module_extender(manager, "collections", _collections_transform) - if PY39_PLUS: - # Starting with Python39 some objects of the collection module are subscriptable - # thanks to the __class_getitem__ method but the way it is implemented in - # _collection_abc makes it difficult to infer. (We would have to handle AssignName inference in the - # getitem method of the ClassDef class) Instead we put here a mock of the __class_getitem__ method - manager.register_transform( - ClassDef, easy_class_getitem_inference, _looks_like_subscriptable - ) + # Starting with Python39 some objects of the collection module are subscriptable + # thanks to the __class_getitem__ method but the way it is implemented in + # _collection_abc makes it difficult to infer. (We would have to handle AssignName inference in the + # getitem method of the ClassDef class) Instead we put here a mock of the __class_getitem__ method + manager.register_transform( + ClassDef, easy_class_getitem_inference, _looks_like_subscriptable + ) diff --git a/astroid/brain/brain_dataclasses.py b/astroid/brain/brain_dataclasses.py index 88a4385fda..def859dc64 100644 --- a/astroid/brain/brain_dataclasses.py +++ b/astroid/brain/brain_dataclasses.py @@ -15,11 +15,11 @@ from __future__ import annotations from collections.abc import Iterator -from typing import Literal, Tuple, Union +from typing import Literal, Union from astroid import bases, context, nodes from astroid.builder import parse -from astroid.const import PY39_PLUS, PY310_PLUS +from astroid.const import PY310_PLUS from astroid.exceptions import AstroidSyntaxError, InferenceError, UseInferenceDefault from astroid.inference_tip import inference_tip from astroid.manager import AstroidManager @@ -28,8 +28,8 @@ _FieldDefaultReturn = Union[ None, - Tuple[Literal["default"], nodes.NodeNG], - Tuple[Literal["default_factory"], nodes.Call], + tuple[Literal["default"], nodes.NodeNG], + tuple[Literal["default_factory"], nodes.Call], ] DATACLASSES_DECORATORS = frozenset(("dataclass",)) @@ -539,22 +539,12 @@ def _get_field_default(field_call: nodes.Call) -> _FieldDefaultReturn: def _is_class_var(node: nodes.NodeNG) -> bool: """Return True if node is a ClassVar, with or without subscripting.""" - if PY39_PLUS: - try: - inferred = next(node.infer()) - except (InferenceError, StopIteration): - return False - - return getattr(inferred, "name", "") == "ClassVar" + try: + inferred = next(node.infer()) + except (InferenceError, StopIteration): + return False - # Before Python 3.9, inference returns typing._SpecialForm instead of ClassVar. - # Our backup is to inspect the node's structure. - return isinstance(node, nodes.Subscript) and ( - isinstance(node.value, nodes.Name) - and node.value.name == "ClassVar" - or isinstance(node.value, nodes.Attribute) - and node.value.attrname == "ClassVar" - ) + return getattr(inferred, "name", "") == "ClassVar" def _is_keyword_only_sentinel(node: nodes.NodeNG) -> bool: diff --git a/astroid/brain/brain_hashlib.py b/astroid/brain/brain_hashlib.py index ae0632a901..91aa4c4277 100644 --- a/astroid/brain/brain_hashlib.py +++ b/astroid/brain/brain_hashlib.py @@ -4,13 +4,11 @@ from astroid.brain.helpers import register_module_extender from astroid.builder import parse -from astroid.const import PY39_PLUS from astroid.manager import AstroidManager def _hashlib_transform(): - maybe_usedforsecurity = ", usedforsecurity=True" if PY39_PLUS else "" - init_signature = f"value=''{maybe_usedforsecurity}" + init_signature = "value='', usedforsecurity=True" digest_signature = "self" shake_digest_signature = "self, length" @@ -54,13 +52,13 @@ def digest_size(self): blake2b_signature = ( "data=b'', *, digest_size=64, key=b'', salt=b'', " "person=b'', fanout=1, depth=1, leaf_size=0, node_offset=0, " - f"node_depth=0, inner_size=0, last_node=False{maybe_usedforsecurity}" + "node_depth=0, inner_size=0, last_node=False, usedforsecurity=True" ) blake2s_signature = ( "data=b'', *, digest_size=32, key=b'', salt=b'', " "person=b'', fanout=1, depth=1, leaf_size=0, node_offset=0, " - f"node_depth=0, inner_size=0, last_node=False{maybe_usedforsecurity}" + "node_depth=0, inner_size=0, last_node=False, usedforsecurity=True" ) shake_algorithms = dict.fromkeys( diff --git a/astroid/brain/brain_re.py b/astroid/brain/brain_re.py index e675f66112..19f2a5b39c 100644 --- a/astroid/brain/brain_re.py +++ b/astroid/brain/brain_re.py @@ -7,7 +7,7 @@ from astroid import context, inference_tip, nodes from astroid.brain.helpers import register_module_extender from astroid.builder import _extract_single_node, parse -from astroid.const import PY39_PLUS, PY311_PLUS +from astroid.const import PY311_PLUS from astroid.manager import AstroidManager @@ -84,9 +84,8 @@ def infer_pattern_match(node: nodes.Call, ctx: context.InferenceContext | None = end_lineno=node.end_lineno, end_col_offset=node.end_col_offset, ) - if PY39_PLUS: - func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE) - class_def.locals["__class_getitem__"] = [func_to_add] + func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE) + class_def.locals["__class_getitem__"] = [func_to_add] return iter([class_def]) diff --git a/astroid/brain/brain_regex.py b/astroid/brain/brain_regex.py index aff0610cb4..5a2d81e809 100644 --- a/astroid/brain/brain_regex.py +++ b/astroid/brain/brain_regex.py @@ -7,7 +7,6 @@ from astroid import context, inference_tip, nodes from astroid.brain.helpers import register_module_extender from astroid.builder import _extract_single_node, parse -from astroid.const import PY39_PLUS from astroid.manager import AstroidManager @@ -83,9 +82,8 @@ def infer_pattern_match(node: nodes.Call, ctx: context.InferenceContext | None = end_lineno=node.end_lineno, end_col_offset=node.end_col_offset, ) - if PY39_PLUS: - func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE) - class_def.locals["__class_getitem__"] = [func_to_add] + func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE) + class_def.locals["__class_getitem__"] = [func_to_add] return iter([class_def]) diff --git a/astroid/brain/brain_subprocess.py b/astroid/brain/brain_subprocess.py index e7e1034bb8..fbc088a680 100644 --- a/astroid/brain/brain_subprocess.py +++ b/astroid/brain/brain_subprocess.py @@ -6,7 +6,7 @@ from astroid.brain.helpers import register_module_extender from astroid.builder import parse -from astroid.const import PY39_PLUS, PY310_PLUS, PY311_PLUS +from astroid.const import PY310_PLUS, PY311_PLUS from astroid.manager import AstroidManager @@ -17,10 +17,9 @@ def _subprocess_transform(): self, args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, 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""" + start_new_session=False, pass_fds=(), *, encoding=None, errors=None, text=None, + user=None, group=None, extra_groups=None, umask=-1""" - if PY39_PLUS: - args += ", user=None, group=None, extra_groups=None, umask=-1" if PY310_PLUS: args += ", pipesize=-1" if PY311_PLUS: @@ -87,14 +86,11 @@ def terminate(self): def kill(self): pass {ctx_manager} - """ - ) - if PY39_PLUS: - code += """ - @classmethod - def __class_getitem__(cls, item): - pass + @classmethod + def __class_getitem__(cls, item): + pass """ + ) init_lines = textwrap.dedent(init).splitlines() indented_init = "\n".join(" " * 4 + line for line in init_lines) diff --git a/astroid/brain/brain_type.py b/astroid/brain/brain_type.py index 02322ef026..d3461e68d4 100644 --- a/astroid/brain/brain_type.py +++ b/astroid/brain/brain_type.py @@ -23,7 +23,6 @@ from __future__ import annotations from astroid import extract_node, inference_tip, nodes -from astroid.const import PY39_PLUS from astroid.context import InferenceContext from astroid.exceptions import UseInferenceDefault from astroid.manager import AstroidManager @@ -64,7 +63,6 @@ def __class_getitem__(cls, key): def register(manager: AstroidManager) -> None: - if PY39_PLUS: - manager.register_transform( - nodes.Name, inference_tip(infer_type_sub), _looks_like_type_subscript - ) + manager.register_transform( + nodes.Name, inference_tip(infer_type_sub), _looks_like_type_subscript + ) diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py index 9965abc25c..1b5408ae4c 100644 --- a/astroid/brain/brain_typing.py +++ b/astroid/brain/brain_typing.py @@ -15,7 +15,7 @@ from astroid import context, extract_node, inference_tip from astroid.brain.helpers import register_module_extender from astroid.builder import AstroidBuilder, _extract_single_node -from astroid.const import PY39_PLUS, PY312_PLUS +from astroid.const import PY312_PLUS from astroid.exceptions import ( AstroidSyntaxError, AttributeInferenceError, @@ -33,7 +33,6 @@ Name, NodeNG, Subscript, - Tuple, ) from astroid.nodes.scoped_nodes import ClassDef, FunctionDef @@ -217,14 +216,6 @@ def _looks_like_typedDict( # pylint: disable=invalid-name return node.qname() in TYPING_TYPEDDICT_QUALIFIED -def infer_old_typedDict( # pylint: disable=invalid-name - node: ClassDef, ctx: context.InferenceContext | None = None -) -> Iterator[ClassDef]: - func_to_add = _extract_single_node("dict") - node.locals["__call__"] = [func_to_add] - return iter([node]) - - def infer_typedDict( # pylint: disable=invalid-name node: FunctionDef, ctx: context.InferenceContext | None = None ) -> Iterator[ClassDef]: @@ -328,13 +319,7 @@ def infer_typing_alias( class_def.postinit(bases=[res], body=[], decorators=None) maybe_type_var = node.args[1] - if ( - not PY39_PLUS - and not (isinstance(maybe_type_var, Tuple) and not maybe_type_var.elts) - or PY39_PLUS - and isinstance(maybe_type_var, Const) - and maybe_type_var.value > 0 - ): + if isinstance(maybe_type_var, Const) and maybe_type_var.value > 0: # If typing alias is subscriptable, add `__class_getitem__` to ClassDef func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE) class_def.locals["__class_getitem__"] = [func_to_add] @@ -362,23 +347,12 @@ def _looks_like_special_alias(node: Call) -> bool: PY39: Callable = _CallableType(collections.abc.Callable, 2) """ return isinstance(node.func, Name) and ( - not PY39_PLUS - and node.func.name == "_VariadicGenericAlias" - and ( - isinstance(node.args[0], Name) - and node.args[0].name == "tuple" - or isinstance(node.args[0], Attribute) - and node.args[0].as_string() == "collections.abc.Callable" - ) - or PY39_PLUS - and ( - node.func.name == "_TupleType" - and isinstance(node.args[0], Name) - and node.args[0].name == "tuple" - or node.func.name == "_CallableType" - and isinstance(node.args[0], Attribute) - and node.args[0].as_string() == "collections.abc.Callable" - ) + node.func.name == "_TupleType" + and isinstance(node.args[0], Name) + and node.args[0].name == "tuple" + or node.func.name == "_CallableType" + and isinstance(node.args[0], Attribute) + and node.args[0].as_string() == "collections.abc.Callable" ) @@ -486,14 +460,9 @@ def register(manager: AstroidManager) -> None: Call, inference_tip(infer_typing_cast), _looks_like_typing_cast ) - if PY39_PLUS: - manager.register_transform( - FunctionDef, inference_tip(infer_typedDict), _looks_like_typedDict - ) - else: - manager.register_transform( - ClassDef, inference_tip(infer_old_typedDict), _looks_like_typedDict - ) + manager.register_transform( + FunctionDef, inference_tip(infer_typedDict), _looks_like_typedDict + ) manager.register_transform( Call, inference_tip(infer_typing_alias), _looks_like_typing_alias diff --git a/astroid/const.py b/astroid/const.py index b57959be7b..c010818063 100644 --- a/astroid/const.py +++ b/astroid/const.py @@ -5,8 +5,6 @@ import enum import sys -PY38 = sys.version_info[:2] == (3, 8) -PY39_PLUS = sys.version_info >= (3, 9) PY310_PLUS = sys.version_info >= (3, 10) PY311_PLUS = sys.version_info >= (3, 11) PY312_PLUS = sys.version_info >= (3, 12) @@ -17,9 +15,6 @@ IS_PYPY = sys.implementation.name == "pypy" IS_JYTHON = sys.implementation.name == "jython" -# pylint: disable-next=no-member -PYPY_7_3_11_PLUS = IS_PYPY and sys.pypy_version_info >= (7, 3, 11) # type: ignore[attr-defined] - class Context(enum.Enum): Load = 1 diff --git a/astroid/context.py b/astroid/context.py index cccc81c077..0b8c259fc6 100644 --- a/astroid/context.py +++ b/astroid/context.py @@ -8,8 +8,8 @@ import contextlib import pprint -from collections.abc import Iterator -from typing import TYPE_CHECKING, Dict, Optional, Sequence, Tuple +from collections.abc import Iterator, Sequence +from typing import TYPE_CHECKING, Optional from astroid.typing import InferenceResult, SuccessfulInferenceResult @@ -17,8 +17,8 @@ from astroid import constraint, nodes from astroid.nodes.node_classes import Keyword, NodeNG -_InferenceCache = Dict[ - Tuple["NodeNG", Optional[str], Optional[str], Optional[str]], Sequence["NodeNG"] +_InferenceCache = dict[ + tuple["NodeNG", Optional[str], Optional[str], Optional[str]], Sequence["NodeNG"] ] _INFERENCE_CACHE: _InferenceCache = {} diff --git a/astroid/modutils.py b/astroid/modutils.py index c058c7d232..8f7d0d3fe9 100644 --- a/astroid/modutils.py +++ b/astroid/modutils.py @@ -30,9 +30,8 @@ from collections.abc import Callable, Iterable, Sequence from contextlib import redirect_stderr, redirect_stdout from functools import lru_cache -from pathlib import Path -from astroid.const import IS_JYTHON, IS_PYPY, PY310_PLUS +from astroid.const import IS_JYTHON, PY310_PLUS from astroid.interpreter._import import spec, util if PY310_PLUS: @@ -74,20 +73,6 @@ except AttributeError: pass -if IS_PYPY and sys.version_info < (3, 8): - # PyPy stores the stdlib in two places: sys.prefix/lib_pypy and sys.prefix/lib-python/3 - # sysconfig.get_path on PyPy returns the first, but without an underscore so we patch this manually. - # Beginning with 3.8 the stdlib is only stored in: sys.prefix/pypy{py_version_short} - STD_LIB_DIRS.add(str(Path(sysconfig.get_path("stdlib")).parent / "lib_pypy")) - STD_LIB_DIRS.add(str(Path(sysconfig.get_path("stdlib")).parent / "lib-python/3")) - - # TODO: This is a fix for a workaround in virtualenv. At some point we should revisit - # whether this is still necessary. See https://github.com/pylint-dev/astroid/pull/1324. - STD_LIB_DIRS.add(str(Path(sysconfig.get_path("platstdlib")).parent / "lib_pypy")) - STD_LIB_DIRS.add( - str(Path(sysconfig.get_path("platstdlib")).parent / "lib-python/3") - ) - if os.name == "posix": # Need the real prefix if we're in a virtualenv, otherwise # the usual one will do. @@ -190,9 +175,10 @@ def load_module_from_name(dotted_name: str) -> types.ModuleType: # Capture and log anything emitted during import to avoid # contaminating JSON reports in pylint - with redirect_stderr(io.StringIO()) as stderr, redirect_stdout( - io.StringIO() - ) as stdout: + with ( + redirect_stderr(io.StringIO()) as stderr, + redirect_stdout(io.StringIO()) as stdout, + ): module = importlib.import_module(dotted_name) stderr_value = stderr.getvalue() diff --git a/astroid/nodes/_base_nodes.py b/astroid/nodes/_base_nodes.py index ddcac994c6..d2bf331299 100644 --- a/astroid/nodes/_base_nodes.py +++ b/astroid/nodes/_base_nodes.py @@ -10,9 +10,9 @@ from __future__ import annotations import itertools -from collections.abc import Generator, Iterator +from collections.abc import Callable, Generator, Iterator from functools import cached_property, lru_cache, partial -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, Union +from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union from astroid import bases, nodes, util from astroid.const import PY310_PLUS diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 22bb4da81b..40aaff3e4e 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -13,12 +13,11 @@ import sys import typing import warnings -from collections.abc import Generator, Iterable, Iterator, Mapping +from collections.abc import Callable, Generator, Iterable, Iterator, Mapping from functools import cached_property from typing import ( TYPE_CHECKING, Any, - Callable, ClassVar, Literal, Optional, @@ -77,17 +76,17 @@ def _is_const(value) -> bool: _NodesT, AssignedStmtsPossibleNode, Optional[InferenceContext], - Optional[typing.List[int]], + Optional[list[int]], ], Any, ] InferBinaryOperation = Callable[ [_NodesT, Optional[InferenceContext]], - typing.Generator[Union[InferenceResult, _BadOpMessageT], None, None], + Generator[Union[InferenceResult, _BadOpMessageT], None, None], ] InferLHS = Callable[ [_NodesT, Optional[InferenceContext]], - typing.Generator[InferenceResult, None, Optional[InferenceErrorInfo]], + Generator[InferenceResult, None, Optional[InferenceErrorInfo]], ] InferUnaryOp = Callable[[_NodesT, str], ConstFactoryResult] diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py index 1c4eb9a90b..ed46a7a115 100644 --- a/astroid/nodes/node_ng.py +++ b/astroid/nodes/node_ng.py @@ -15,8 +15,6 @@ Any, ClassVar, Literal, - Tuple, - Type, TypeVar, Union, cast, @@ -53,7 +51,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 = Union[None, type["NodeNG"], tuple[type["NodeNG"], ...]] class NodeNG: diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index 42de1af2d2..cad91dcf0f 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -19,7 +19,6 @@ from typing import TYPE_CHECKING, Any, ClassVar, Literal, NoReturn, TypeVar from astroid import bases, protocols, util -from astroid.const import IS_PYPY, PY38, PY39_PLUS, PYPY_7_3_11_PLUS from astroid.context import ( CallContext, InferenceContext, @@ -2001,26 +2000,6 @@ def _newstyle_impl(self, context: InferenceContext | None = None): doc=("Whether this is a new style class or not\n\n" ":type: bool or None"), ) - @cached_property - def fromlineno(self) -> int: - """The first line that this node appears on in the source code. - - Can also return 0 if the line can not be determined. - """ - if IS_PYPY and PY38 and not PYPY_7_3_11_PLUS: - # For Python < 3.8 the lineno is the line number of the first decorator. - # We want the class statement lineno. Similar to 'FunctionDef.fromlineno' - # PyPy (3.8): Fixed with version v7.3.11 - lineno = self.lineno or 0 - if self.decorators is not None: - lineno += sum( - node.tolineno - (node.lineno or 0) + 1 - for node in self.decorators.nodes - ) - - return lineno or 0 - return super().fromlineno - @cached_property def blockstart_tolineno(self): """The line on which the beginning of this block ends. @@ -2638,7 +2617,6 @@ def getitem(self, index, context: InferenceContext | None = None): if ( isinstance(method, node_classes.EmptyNode) and self.pytype() == "builtins.type" - and PY39_PLUS ): return self raise diff --git a/astroid/raw_building.py b/astroid/raw_building.py index ba7a60712a..a89a87b571 100644 --- a/astroid/raw_building.py +++ b/astroid/raw_building.py @@ -557,9 +557,10 @@ def imported_member(self, node, member, name: str) -> bool: # check if it sounds valid and then add an import node, else use a # dummy node try: - with redirect_stderr(io.StringIO()) as stderr, redirect_stdout( - io.StringIO() - ) as stdout: + with ( + redirect_stderr(io.StringIO()) as stderr, + redirect_stdout(io.StringIO()) as stdout, + ): getattr(sys.modules[modname], name) stderr_value = stderr.getvalue() if stderr_value: diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index c1d547dd90..70ea53718a 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -18,7 +18,7 @@ from astroid import nodes from astroid._ast import ParserModule, get_parser_module, parse_function_type_comment -from astroid.const import IS_PYPY, PY38, PY39_PLUS, PY312_PLUS, Context +from astroid.const import PY312_PLUS, Context from astroid.manager import AstroidManager from astroid.nodes import NodeNG from astroid.nodes.node_classes import AssignName @@ -80,10 +80,6 @@ def _get_doc(self, node: T_Doc) -> tuple[T_Doc, ast.Constant | ast.Str | None]: ): doc_ast_node = first_value node.body = node.body[1:] - # The ast parser of python < 3.8 sets col_offset of multi-line strings to -1 - # as it is unable to determine the value correctly. We reset this to None. - if doc_ast_node.col_offset == -1: - doc_ast_node.col_offset = None return node, doc_ast_node except IndexError: pass # ast built from scratch @@ -157,25 +153,6 @@ def _get_position_info( end_col_offset=t.end[1], ) - def _reset_end_lineno(self, newnode: nodes.NodeNG) -> None: - """Reset end_lineno and end_col_offset attributes for PyPy 3.8. - - For some nodes, these are either set to -1 or only partially assigned. - To keep consistency across astroid and pylint, reset all. - - This has been fixed in PyPy 3.9. - For reference, an (incomplete) list of nodes with issues: - - ClassDef - For - - FunctionDef - While - - Call - If - - Decorators - Try - - With - Assign - """ - newnode.end_lineno = None - newnode.end_col_offset = None - for child_node in newnode.get_children(): - self._reset_end_lineno(child_node) - def visit_module( self, node: ast.Module, modname: str, modpath: str, package: bool ) -> nodes.Module: @@ -194,8 +171,6 @@ def visit_module( [self.visit(child, newnode) for child in node.body], doc_node=self.visit(doc_ast_node, newnode), ) - if IS_PYPY and PY38: - self._reset_end_lineno(newnode) return newnode if TYPE_CHECKING: # noqa: C901 @@ -315,16 +290,6 @@ def visit( @overload def visit(self, node: ast.NamedExpr, parent: NodeNG) -> nodes.NamedExpr: ... - if sys.version_info < (3, 9): - # Not used in Python 3.9+ - @overload - def visit( - self, node: ast.ExtSlice, parent: nodes.Subscript - ) -> nodes.Tuple: ... - - @overload - def visit(self, node: ast.Index, parent: nodes.Subscript) -> NodeNG: ... - @overload def visit(self, node: ast.keyword, parent: NodeNG) -> nodes.Keyword: ... @@ -542,14 +507,6 @@ def visit_arguments(self, node: ast.arguments, parent: NodeNG) -> nodes.Argument kwarg = node.kwarg.arg kwargannotation = self.visit(node.kwarg.annotation, newnode) - if PY38: - # In Python 3.8 'end_lineno' and 'end_col_offset' - # for 'kwonlyargs' don't include the annotation. - for arg in node.kwonlyargs: - if arg.annotation is not None: - arg.end_lineno = arg.annotation.end_lineno - arg.end_col_offset = arg.annotation.end_col_offset - kwonlyargs = [self.visit(child, newnode) for child in node.kwonlyargs] kw_defaults = [self.visit(child, newnode) for child in node.kw_defaults] annotations = [self.visit(arg.annotation, newnode) for arg in node.args] @@ -1047,14 +1004,9 @@ def _visit_for( self, cls: type[_ForT], node: ast.For | ast.AsyncFor, parent: NodeNG ) -> _ForT: """Visit a For node by returning a fresh instance of it.""" - col_offset = node.col_offset - if IS_PYPY and not PY39_PLUS and isinstance(node, ast.AsyncFor) and self._data: - # pylint: disable-next=unsubscriptable-object - col_offset = self._data[node.lineno - 1].index("async") - newnode = cls( lineno=node.lineno, - col_offset=col_offset, + col_offset=node.col_offset, end_lineno=node.end_lineno, end_col_offset=node.end_col_offset, parent=parent, @@ -1333,21 +1285,6 @@ def visit_namedexpr(self, node: ast.NamedExpr, parent: NodeNG) -> nodes.NamedExp ) return newnode - if sys.version_info < (3, 9): - # Not used in Python 3.9+. - def visit_extslice( - self, node: ast.ExtSlice, parent: nodes.Subscript - ) -> nodes.Tuple: - """Visit an ExtSlice node by returning a fresh instance of Tuple.""" - # ExtSlice doesn't have lineno or col_offset information - newnode = nodes.Tuple(ctx=Context.Load, parent=parent) - newnode.postinit([self.visit(dim, newnode) for dim in node.dims]) - return newnode - - def visit_index(self, node: ast.Index, parent: nodes.Subscript) -> NodeNG: - """Visit a Index node by returning a fresh instance of NodeNG.""" - return self.visit(node.value, parent) - def visit_keyword(self, node: ast.keyword, parent: NodeNG) -> nodes.Keyword: """Visit a Keyword node by returning a fresh instance of it.""" newnode = nodes.Keyword( @@ -1732,14 +1669,9 @@ def _visit_with( node: ast.With | ast.AsyncWith, parent: NodeNG, ) -> _WithT: - col_offset = node.col_offset - if IS_PYPY and not PY39_PLUS and isinstance(node, ast.AsyncWith) and self._data: - # pylint: disable-next=unsubscriptable-object - col_offset = self._data[node.lineno - 1].index("async") - newnode = cls( lineno=node.lineno, - col_offset=col_offset, + col_offset=node.col_offset, end_lineno=node.end_lineno, end_col_offset=node.end_col_offset, parent=parent, diff --git a/astroid/transforms.py b/astroid/transforms.py index 0d9c22e966..5f0e533136 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, List, Optional, Tuple, TypeVar, Union, cast, overload +from typing import TYPE_CHECKING, Optional, TypeVar, Union, cast, overload from astroid.context import _invalidate_cache from astroid.typing import SuccessfulInferenceResult, TransformFn @@ -21,12 +21,12 @@ _Predicate = Optional[Callable[[_SuccessfulInferenceResultT], bool]] _Vistables = Union[ - "nodes.NodeNG", List["nodes.NodeNG"], Tuple["nodes.NodeNG", ...], str, None + "nodes.NodeNG", list["nodes.NodeNG"], tuple["nodes.NodeNG", ...], str, None ] _VisitReturns = Union[ SuccessfulInferenceResult, - List[SuccessfulInferenceResult], - Tuple[SuccessfulInferenceResult, ...], + list[SuccessfulInferenceResult], + tuple[SuccessfulInferenceResult, ...], str, None, ] diff --git a/astroid/typing.py b/astroid/typing.py index 7e3fbec184..27d95c21fb 100644 --- a/astroid/typing.py +++ b/astroid/typing.py @@ -4,11 +4,10 @@ from __future__ import annotations +from collections.abc import Callable, Generator from typing import ( TYPE_CHECKING, Any, - Callable, - Generator, Generic, Protocol, TypedDict, diff --git a/pylintrc b/pylintrc index ee22082248..74661eef5e 100644 --- a/pylintrc +++ b/pylintrc @@ -39,7 +39,7 @@ unsafe-load-any-extension=no extension-pkg-whitelist= # Minimum supported python version -py-version = 3.8.0 +py-version = 3.9.0 [REPORTS] diff --git a/pyproject.toml b/pyproject.toml index 7f1354d517..019664cdfc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -28,7 +27,7 @@ classifiers = [ "Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Testing", ] -requires-python = ">=3.8.0" +requires-python = ">=3.9.0" dependencies = [ "typing-extensions>=4.0.0;python_version<'3.11'", ] @@ -83,7 +82,7 @@ ignore_missing_imports = true [tool.ruff] -target-version = "py38" +target-version = "py39" # ruff is less lenient than pylint and does not make any exceptions # (for docstrings, strings and comments in particular). diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py index b8bc84e31f..12560f201c 100644 --- a/tests/brain/test_brain.py +++ b/tests/brain/test_brain.py @@ -51,13 +51,6 @@ def test_deque_py35methods(self) -> None: self.assertIn("insert", inferred.locals) self.assertIn("index", inferred.locals) - @test_utils.require_version(maxver="3.8") - def test_deque_not_py39methods(self): - inferred = self._inferred_queue_instance() - with self.assertRaises(AttributeInferenceError): - inferred.getattr("__class_getitem__") - - @test_utils.require_version(minver="3.9") def test_deque_py39methods(self): inferred = self._inferred_queue_instance() self.assertTrue(inferred.getattr("__class_getitem__")) @@ -172,7 +165,6 @@ def test_invalid_type_subscript(self): # noinspection PyStatementEffect val_inf.getattr("__class_getitem__")[0] - @test_utils.require_version(minver="3.9") def test_builtin_subscriptable(self): """Starting with python3.9 builtin types such as list are subscriptable. Any builtin class such as "enumerate" or "staticmethod" also works.""" @@ -231,7 +223,6 @@ def test_collections_object_not_subscriptable(self) -> None: with self.assertRaises(AttributeInferenceError): inferred.getattr("__class_getitem__") - @test_utils.require_version(minver="3.9") def test_collections_object_subscriptable(self): """Starting with python39 some object of collections module are subscriptable. Test one of them""" right_node = builder.extract_node( @@ -295,7 +286,6 @@ def test_collections_object_not_yet_subscriptable(self): with self.assertRaises(AttributeInferenceError): inferred.getattr("__class_getitem__") - @test_utils.require_version(minver="3.9") def test_collections_object_subscriptable_2(self): """Starting with python39 Iterator in the collection.abc module is subscriptable""" node = builder.extract_node( @@ -329,7 +319,6 @@ def test_collections_object_not_yet_subscriptable_2(self): with self.assertRaises(InferenceError): next(node.infer()) - @test_utils.require_version(minver="3.9") def test_collections_object_subscriptable_3(self): """With Python 3.9 the ByteString class of the collections module is subscriptable (but not the same class from typing module)""" @@ -345,7 +334,6 @@ def test_collections_object_subscriptable_3(self): inferred.getattr("__class_getitem__")[0], nodes.FunctionDef ) - @test_utils.require_version(minver="3.9") def test_collections_object_subscriptable_4(self): """Multiple inheritance with subscriptable collection class""" node = builder.extract_node( @@ -645,7 +633,6 @@ class Bar[T](Foo[T]): ... assert ancestors[0].name == "Foo" assert ancestors[1].name == "object" - @test_utils.require_version(minver="3.9") def test_typing_annotated_subscriptable(self): """Test typing.Annotated is subscriptable with __class_getitem__""" node = builder.extract_node( @@ -676,7 +663,6 @@ def __init__(self, value): assert isinstance(slots[0], nodes.Const) assert slots[0].value == "value" - @test_utils.require_version(minver="3.9") def test_typing_no_duplicates(self): node = builder.extract_node( """ @@ -686,7 +672,6 @@ def test_typing_no_duplicates(self): ) assert len(node.inferred()) == 1 - @test_utils.require_version(minver="3.9") def test_typing_no_duplicates_2(self): node = builder.extract_node( """ @@ -753,7 +738,6 @@ def test_typing_namedtuple_dont_crash_on_no_fields(self) -> None: inferred = next(node.infer()) self.assertIsInstance(inferred, astroid.Instance) - @test_utils.require_version("3.8") def test_typed_dict(self): code = builder.extract_node( """ @@ -929,7 +913,6 @@ def test_typing_object_notsubscriptable_3(self): inferred.getattr("__class_getitem__")[0], nodes.FunctionDef ) - @test_utils.require_version(minver="3.9") def test_typing_object_builtin_subscriptable(self): """ Test that builtins alias, such as typing.List, are subscriptable @@ -945,7 +928,6 @@ def test_typing_object_builtin_subscriptable(self): self.assertIsInstance(inferred.getattr("__iter__")[0], nodes.FunctionDef) @staticmethod - @test_utils.require_version(minver="3.9") def test_typing_type_subscriptable(): node = builder.extract_node( """ @@ -1067,7 +1049,6 @@ def test_re_pattern_unsubscriptable(self): with self.assertRaises(InferenceError): next(wrong_node2.infer()) - @test_utils.require_version(minver="3.9") def test_re_pattern_subscriptable(self): """Test re.Pattern and re.Match are subscriptable in PY39+""" node1 = builder.extract_node( diff --git a/tests/brain/test_hashlib.py b/tests/brain/test_hashlib.py index 01177862f4..390dcc8469 100644 --- a/tests/brain/test_hashlib.py +++ b/tests/brain/test_hashlib.py @@ -7,7 +7,6 @@ import unittest from astroid import MANAGER -from astroid.const import PY39_PLUS from astroid.nodes.scoped_nodes import ClassDef @@ -19,10 +18,8 @@ def _assert_hashlib_class(self, class_obj: ClassDef) -> None: self.assertIn("block_size", class_obj) self.assertIn("digest_size", class_obj) # usedforsecurity was added in Python 3.9, see 8e7174a9 - self.assertEqual(len(class_obj["__init__"].args.args), 3 if PY39_PLUS else 2) - self.assertEqual( - len(class_obj["__init__"].args.defaults), 2 if PY39_PLUS else 1 - ) + self.assertEqual(len(class_obj["__init__"].args.args), 3) + self.assertEqual(len(class_obj["__init__"].args.defaults), 2) self.assertEqual(len(class_obj["update"].args.args), 2) def test_hashlib(self) -> None: diff --git a/tests/brain/test_regex.py b/tests/brain/test_regex.py index c3e0bbe7ef..1313ea40f2 100644 --- a/tests/brain/test_regex.py +++ b/tests/brain/test_regex.py @@ -11,7 +11,7 @@ import pytest -from astroid import MANAGER, builder, nodes, test_utils +from astroid import MANAGER, builder, nodes @pytest.mark.skipif(not HAS_REGEX, reason="This test requires the regex library.") @@ -27,7 +27,6 @@ def test_regex_flags(self) -> None: @pytest.mark.xfail( reason="Started failing on main, but no one reproduced locally yet" ) - @test_utils.require_version(minver="3.9") def test_regex_pattern_and_match_subscriptable(self): """Test regex.Pattern and regex.Match are subscriptable in PY39+.""" node1 = builder.extract_node( diff --git a/tests/brain/test_unittest.py b/tests/brain/test_unittest.py index aed05f7645..53f7d9f771 100644 --- a/tests/brain/test_unittest.py +++ b/tests/brain/test_unittest.py @@ -5,13 +5,11 @@ import unittest from astroid import builder -from astroid.test_utils import require_version class UnittestTest(unittest.TestCase): """A class that tests the brain_unittest module.""" - @require_version(minver="3.8.0") def test_isolatedasynciotestcase(self): """ Tests that the IsolatedAsyncioTestCase class is statically imported diff --git a/tests/test_builder.py b/tests/test_builder.py index 84ac65e099..8692baabae 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -19,7 +19,7 @@ import pytest from astroid import Instance, builder, nodes, test_utils, util -from astroid.const import IS_PYPY, PY38, PY39_PLUS, PYPY_7_3_11_PLUS +from astroid.const import IS_PYPY from astroid.exceptions import ( AstroidBuildingError, AstroidSyntaxError, @@ -57,10 +57,7 @@ def test_callfunc_lineno(self) -> None: self.assertIsInstance(strarg, nodes.Const) if IS_PYPY: self.assertEqual(strarg.fromlineno, 4) - if not PY39_PLUS: - self.assertEqual(strarg.tolineno, 4) - else: - self.assertEqual(strarg.tolineno, 5) + self.assertEqual(strarg.tolineno, 5) else: self.assertEqual(strarg.fromlineno, 4) self.assertEqual(strarg.tolineno, 5) @@ -157,12 +154,7 @@ class C: # L13 c = ast_module.body[2] assert isinstance(c, nodes.ClassDef) - if IS_PYPY and PY38 and not PYPY_7_3_11_PLUS: - # Not perfect, but best we can do for PyPy 3.8 (< v7.3.11). - # Can't detect closing bracket on new line. - assert c.fromlineno == 12 - else: - assert c.fromlineno == 13 + assert c.fromlineno == 13 assert c.tolineno == 14 @staticmethod diff --git a/tests/test_inference.py b/tests/test_inference.py index ec8fc71b69..b733da56cc 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -32,7 +32,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, PY39_PLUS, PY310_PLUS, PY312_PLUS +from astroid.const import IS_PYPY, PY310_PLUS, PY312_PLUS from astroid.context import CallContext, InferenceContext from astroid.exceptions import ( AstroidTypeError, @@ -2732,11 +2732,6 @@ def __radd__(self, other): msg.format(op="+=", lhs="int", rhs="list"), ] - # PEP-584 supports | for dictionary union - if not PY39_PLUS: - ast_nodes.append(extract_node("{} | {} #@")) - expected.append(msg.format(op="|", lhs="dict", rhs="dict")) - for node, expected_value in zip(ast_nodes, expected): errors = node.type_errors() self.assertEqual(len(errors), 1) @@ -4489,7 +4484,6 @@ def func(object): inferred = next(node.infer()) assert inferred is util.Uninferable - @test_utils.require_version(minver="3.9") def test_infer_arg_called_type_when_used_as_index_is_uninferable(self): # https://github.com/pylint-dev/astroid/pull/958 node = extract_node( @@ -4504,7 +4498,6 @@ def func(type): assert not isinstance(inferred, nodes.ClassDef) # was inferred as builtins.type assert inferred is util.Uninferable - @test_utils.require_version(minver="3.9") def test_infer_arg_called_type_when_used_as_subscript_is_uninferable(self): # https://github.com/pylint-dev/astroid/pull/958 node = extract_node( @@ -4517,7 +4510,6 @@ def func(type): assert not isinstance(inferred, nodes.ClassDef) # was inferred as builtins.type assert inferred is util.Uninferable - @test_utils.require_version(minver="3.9") def test_infer_arg_called_type_defined_in_outer_scope_is_uninferable(self): # https://github.com/pylint-dev/astroid/pull/958 node = extract_node( @@ -6354,7 +6346,6 @@ def check_equal(a, b): assert inferred.value is None -@test_utils.require_version(minver="3.8") def test_posonlyargs_inference() -> None: code = """ class A: @@ -6742,34 +6733,6 @@ def test_custom_decorators_for_classmethod_and_staticmethods(code, obj, obj_type assert inferred.type == obj_type -@pytest.mark.skipif( - PY39_PLUS, - reason="Exact inference with dataclasses (replace function) in python3.9", -) -def test_dataclasses_subscript_inference_recursion_error(): - code = """ - from dataclasses import dataclass, replace - - @dataclass - class ProxyConfig: - auth: str = "/auth" - - - a = ProxyConfig("") - test_dict = {"proxy" : {"auth" : "", "bla" : "f"}} - - foo = test_dict['proxy'] - replace(a, **test_dict['proxy']) # This fails - """ - node = extract_node(code) - # Reproduces only with safe_infer() - assert util.safe_infer(node) is None - - -@pytest.mark.skipif( - not PY39_PLUS, - reason="Exact inference with dataclasses (replace function) in python3.9", -) def test_dataclasses_subscript_inference_recursion_error_39(): code = """ from dataclasses import dataclass, replace diff --git a/tests/test_lookup.py b/tests/test_lookup.py index a19f287637..b452d62894 100644 --- a/tests/test_lookup.py +++ b/tests/test_lookup.py @@ -6,7 +6,7 @@ import functools import unittest -from astroid import builder, nodes, test_utils +from astroid import builder, nodes from astroid.exceptions import ( AttributeInferenceError, InferenceError, @@ -789,7 +789,6 @@ def f2(*, x): self.assertEqual(len(stmts2), 1) self.assertEqual(stmts2[0].lineno, 7) - @test_utils.require_version(minver="3.8") def test_assign_after_posonly_param(self): """When an assignment statement overwrites a function positional-only parameter, only the assignment is returned, even when the variable and assignment do diff --git a/tests/test_nodes.py b/tests/test_nodes.py index c5605a9328..64cae2f676 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -25,7 +25,6 @@ extract_node, nodes, parse, - test_utils, transforms, util, ) @@ -892,7 +891,6 @@ def func(*, x = "default"): assert isinstance(args.kw_defaults[0], nodes.Const) assert args.kw_defaults[0].value == "default" - @test_utils.require_version(minver="3.8") def test_positional_only(self): ast = builder.parse( """ @@ -1630,7 +1628,6 @@ def func(): assert node.doc_node is None -@test_utils.require_version(minver="3.8") def test_parse_fstring_debug_mode() -> None: node = astroid.extract_node('f"{3=}"') assert isinstance(node, nodes.JoinedStr) diff --git a/tests/test_nodes_lineno.py b/tests/test_nodes_lineno.py index b0cdb9850b..c8a8839e21 100644 --- a/tests/test_nodes_lineno.py +++ b/tests/test_nodes_lineno.py @@ -8,44 +8,9 @@ import astroid from astroid import builder, nodes -from astroid.const import IS_PYPY, PY38, PY39_PLUS, PY310_PLUS, PY312_PLUS +from astroid.const import PY310_PLUS, PY312_PLUS -@pytest.mark.skipif( - not (PY38 and IS_PYPY), - reason="end_lineno and end_col_offset were added in PY38", -) -class TestEndLinenoNotSet: - """Test 'end_lineno' and 'end_col_offset' are initialized as 'None' for Python < - 3.8. - """ - - @staticmethod - def test_end_lineno_not_set() -> None: - code = textwrap.dedent( - """ - [1, 2, 3] #@ - var #@ - """ - ).strip() - ast_nodes = builder.extract_node(code) - assert isinstance(ast_nodes, list) and len(ast_nodes) == 2 - - n1 = ast_nodes[0] - assert isinstance(n1, nodes.List) - assert (n1.lineno, n1.col_offset) == (1, 0) - assert (n1.end_lineno, n1.end_col_offset) == (None, None) - - n2 = ast_nodes[1] - assert isinstance(n2, nodes.Name) - assert (n2.lineno, n2.col_offset) == (2, 0) - assert (n2.end_lineno, n2.end_col_offset) == (None, None) - - -@pytest.mark.skipif( - PY38 and IS_PYPY, - reason="end_lineno and end_col_offset were added in PY38", -) class TestLinenoColOffset: """Test 'lineno', 'col_offset', 'end_lineno', and 'end_col_offset' for all nodes. @@ -200,13 +165,8 @@ def test_end_lineno_call() -> None: assert (c1.args[0].end_lineno, c1.args[0].end_col_offset) == (1, 9) # fmt: off - if PY39_PLUS: - # 'lineno' and 'col_offset' information only added in Python 3.9 - assert (c1.keywords[0].lineno, c1.keywords[0].col_offset) == (1, 11) - assert (c1.keywords[0].end_lineno, c1.keywords[0].end_col_offset) == (1, 21) - else: - assert (c1.keywords[0].lineno, c1.keywords[0].col_offset) == (None, None) - assert (c1.keywords[0].end_lineno, c1.keywords[0].end_col_offset) == (None, None) + assert (c1.keywords[0].lineno, c1.keywords[0].col_offset) == (1, 11) + assert (c1.keywords[0].end_lineno, c1.keywords[0].end_col_offset) == (1, 21) assert (c1.keywords[0].value.lineno, c1.keywords[0].value.col_offset) == (1, 16) assert (c1.keywords[0].value.end_lineno, c1.keywords[0].value.end_col_offset) == (1, 21) # fmt: on @@ -842,13 +802,8 @@ def test_end_lineno_subscript() -> None: assert isinstance(s3.slice, nodes.Tuple) assert (s3.lineno, s3.col_offset) == (3, 0) assert (s3.end_lineno, s3.end_col_offset) == (3, 11) - if PY39_PLUS: - # 'lineno' and 'col_offset' information only added in Python 3.9 - assert (s3.slice.lineno, s3.slice.col_offset) == (3, 4) - assert (s3.slice.end_lineno, s3.slice.end_col_offset) == (3, 10) - else: - assert (s3.slice.lineno, s3.slice.col_offset) == (None, None) - assert (s3.slice.end_lineno, s3.slice.end_col_offset) == (None, None) + assert (s3.slice.lineno, s3.slice.col_offset) == (3, 4) + assert (s3.slice.end_lineno, s3.slice.end_col_offset) == (3, 10) @staticmethod def test_end_lineno_import() -> None: @@ -995,14 +950,8 @@ def test_end_lineno_string() -> None: assert (s2.end_lineno, s2.end_col_offset) == (1, 29) assert isinstance(s2.value, nodes.Const) # 42.1234 - if PY39_PLUS: - assert (s2.value.lineno, s2.value.col_offset) == (1, 16) - assert (s2.value.end_lineno, s2.value.end_col_offset) == (1, 23) - else: - # Bug in Python 3.8 - # https://bugs.python.org/issue44885 - assert (s2.value.lineno, s2.value.col_offset) == (1, 1) - assert (s2.value.end_lineno, s2.value.end_col_offset) == (1, 8) + assert (s2.value.lineno, s2.value.col_offset) == (1, 16) + assert (s2.value.end_lineno, s2.value.end_col_offset) == (1, 23) assert isinstance(s2.format_spec, nodes.JoinedStr) # ':02d' if PY312_PLUS: assert (s2.format_spec.lineno, s2.format_spec.col_offset) == (1, 23) @@ -1033,14 +982,8 @@ def test_end_lineno_string() -> None: assert (s4.end_lineno, s4.end_col_offset) == (2, 17) assert isinstance(s4.value, nodes.Name) # 'name' - if PY39_PLUS: - assert (s4.value.lineno, s4.value.col_offset) == (2, 10) - assert (s4.value.end_lineno, s4.value.end_col_offset) == (2, 14) - else: - # Bug in Python 3.8 - # https://bugs.python.org/issue44885 - assert (s4.value.lineno, s4.value.col_offset) == (2, 1) - assert (s4.value.end_lineno, s4.value.end_col_offset) == (2, 5) + assert (s4.value.lineno, s4.value.col_offset) == (2, 10) + 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") @@ -1246,13 +1189,8 @@ class X(Parent, var=42): assert (c1.decorators.end_lineno, c1.decorators.end_col_offset) == (2, 11) assert (c1.bases[0].lineno, c1.bases[0].col_offset) == (3, 8) assert (c1.bases[0].end_lineno, c1.bases[0].end_col_offset) == (3, 14) - if PY39_PLUS: - # 'lineno' and 'col_offset' information only added in Python 3.9 - assert (c1.keywords[0].lineno, c1.keywords[0].col_offset) == (3, 16) - assert (c1.keywords[0].end_lineno, c1.keywords[0].end_col_offset) == (3, 22) - else: - assert (c1.keywords[0].lineno, c1.keywords[0].col_offset) == (None, None) - assert (c1.keywords[0].end_lineno, c1.keywords[0].end_col_offset) == (None, None) + assert (c1.keywords[0].lineno, c1.keywords[0].col_offset) == (3, 16) + assert (c1.keywords[0].end_lineno, c1.keywords[0].end_col_offset) == (3, 22) assert (c1.body[0].lineno, c1.body[0].col_offset) == (4, 4) assert (c1.body[0].end_lineno, c1.body[0].end_col_offset) == (4, 8) # fmt: on diff --git a/tests/test_object_model.py b/tests/test_object_model.py index b4b648a150..62d234b1fd 100644 --- a/tests/test_object_model.py +++ b/tests/test_object_model.py @@ -8,7 +8,7 @@ import pytest import astroid -from astroid import bases, builder, nodes, objects, test_utils, util +from astroid import bases, builder, nodes, objects, util from astroid.const import PY311_PLUS from astroid.exceptions import InferenceError @@ -362,7 +362,6 @@ def test(self): return 42 self.assertEqual(len(args), 2) self.assertEqual([arg.name for arg in args], ["self", "type"]) - @test_utils.require_version(minver="3.8") def test__get__and_positional_only_args(self): node = builder.extract_node( """ @@ -573,7 +572,6 @@ def test(a: 1, *args: 2, f:4='lala', **kwarg:3)->2: pass self.assertIsInstance(kwdefaults, astroid.Dict) # self.assertEqual(kwdefaults.getitem('f').value, 'lala') - @test_utils.require_version(minver="3.8") def test_annotation_positional_only(self): ast_node = builder.extract_node( """ diff --git a/tests/test_python3.py b/tests/test_python3.py index 7593a2ada9..8c3bc16950 100644 --- a/tests/test_python3.py +++ b/tests/test_python3.py @@ -9,7 +9,6 @@ from astroid import exceptions, nodes from astroid.builder import AstroidBuilder, extract_node -from astroid.test_utils import require_version class Python3TC(unittest.TestCase): @@ -351,7 +350,6 @@ def test_async_comprehensions(self) -> None: for comp in non_async_comprehensions: self.assertFalse(comp.generators[0].is_async) - @require_version("3.7") def test_async_comprehensions_outside_coroutine(self): # When async and await will become keywords, async comprehensions # will be allowed outside of coroutines body diff --git a/tests/test_scoped_nodes.py b/tests/test_scoped_nodes.py index 995f0428d9..ffbca4dadb 100644 --- a/tests/test_scoped_nodes.py +++ b/tests/test_scoped_nodes.py @@ -26,11 +26,10 @@ nodes, objects, parse, - test_utils, util, ) from astroid.bases import BoundMethod, Generator, Instance, UnboundMethod -from astroid.const import IS_PYPY, PY38, WIN32 +from astroid.const import WIN32 from astroid.exceptions import ( AstroidBuildingError, AttributeInferenceError, @@ -1349,10 +1348,7 @@ def g2(): astroid = builder.parse(data) self.assertEqual(astroid["g1"].fromlineno, 4) self.assertEqual(astroid["g1"].tolineno, 5) - if PY38 and IS_PYPY: - self.assertEqual(astroid["g2"].fromlineno, 9) - else: - self.assertEqual(astroid["g2"].fromlineno, 10) + self.assertEqual(astroid["g2"].fromlineno, 10) self.assertEqual(astroid["g2"].tolineno, 11) def test_metaclass_error(self) -> None: @@ -2750,13 +2746,11 @@ def __init__(self: int, other: float, /, **kw): ), ], ) -@test_utils.require_version("3.8") def test_posonlyargs_python_38(func): ast_node = builder.extract_node(func) assert ast_node.as_string().strip() == func.strip() -@test_utils.require_version("3.8") def test_posonlyargs_default_value() -> None: ast_node = builder.extract_node( """ From a3f1a98cf1f2ad7125e7191c64f849b420cac29e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 19:23:53 +0200 Subject: [PATCH 12/63] Bump actions/checkout from 4.1.5 to 4.1.6 (#2447) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.5 to 4.1.6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.5...v4.1.6) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 10 +++++----- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/release-tests.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 930b3910f4..f621b19840 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: timeout-minutes: 20 steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v5.1.0 @@ -86,7 +86,7 @@ jobs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v5.1.0 @@ -145,7 +145,7 @@ jobs: # Workaround to set correct temp directory on Windows # https://github.com/actions/virtual-environments/issues/712 - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v5.1.0 @@ -195,7 +195,7 @@ jobs: python-version: ["pypy3.9", "pypy3.10"] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v5.1.0 @@ -241,7 +241,7 @@ jobs: needs: ["tests-linux", "tests-windows", "tests-pypy"] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - name: Set up Python 3.12 id: python uses: actions/setup-python@v5.1.0 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 2ef77a4529..e04f5af291 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml index cea29641c2..febada57d7 100644 --- a/.github/workflows/release-tests.yml +++ b/.github/workflows/release-tests.yml @@ -13,7 +13,7 @@ jobs: timeout-minutes: 5 steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - name: Set up Python 3.9 id: python uses: actions/setup-python@v5.1.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ebc589970..5bb967decc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: url: https://pypi.org/project/astroid/ steps: - name: Check out code from Github - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v5.1.0 From 859037b1aa05f08faa16866f2a287f28421b5b18 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 23:43:03 +0200 Subject: [PATCH 13/63] [pre-commit.ci] pre-commit autoupdate (#2449) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.4 → v0.4.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.4...v0.4.7) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix _io order with pylint conf --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- .pre-commit-config.yaml | 2 +- pylintrc | 3 +++ tests/test_raw_building.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f2e245ac0..c602b1a7a3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.4" + rev: "v0.4.7" hooks: - id: ruff exclude: tests/testdata diff --git a/pylintrc b/pylintrc index 74661eef5e..76aa73716c 100644 --- a/pylintrc +++ b/pylintrc @@ -348,6 +348,9 @@ ext-import-graph= # not be disabled) int-import-graph= +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library=_io [EXCEPTIONS] diff --git a/tests/test_raw_building.py b/tests/test_raw_building.py index d206022b8f..951bf09d90 100644 --- a/tests/test_raw_building.py +++ b/tests/test_raw_building.py @@ -10,6 +10,7 @@ from __future__ import annotations +import _io import logging import os import sys @@ -18,7 +19,6 @@ from typing import Any from unittest import mock -import _io import pytest import tests.testdata.python3.data.fake_module_with_broken_getattr as fm_getattr From 20300c378c8e8cd792ebfa4a03621c342294928e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 06:09:19 +0200 Subject: [PATCH 14/63] [pre-commit.ci] pre-commit autoupdate (#2450) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.7 → v0.4.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.7...v0.4.8) - [github.com/asottile/pyupgrade: v3.15.2 → v3.16.0](https://github.com/asottile/pyupgrade/compare/v3.15.2...v3.16.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c602b1a7a3..046595fd5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.7" + rev: "v0.4.8" hooks: - id: ruff exclude: tests/testdata @@ -23,7 +23,7 @@ repos: exclude: tests/testdata|setup.py types: [python] - repo: https://github.com/asottile/pyupgrade - rev: v3.15.2 + rev: v3.16.0 hooks: - id: pyupgrade exclude: tests/testdata From 6666ca702322f873c681d3fb55933453578c2b37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:43:49 +0200 Subject: [PATCH 15/63] Bump actions/checkout from 4.1.6 to 4.1.7 (#2451) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.6...v4.1.7) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 10 +++++----- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/release-tests.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f621b19840..ed572cc8ef 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: timeout-minutes: 20 steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v5.1.0 @@ -86,7 +86,7 @@ jobs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v5.1.0 @@ -145,7 +145,7 @@ jobs: # Workaround to set correct temp directory on Windows # https://github.com/actions/virtual-environments/issues/712 - name: Check out code from GitHub - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v5.1.0 @@ -195,7 +195,7 @@ jobs: python-version: ["pypy3.9", "pypy3.10"] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v5.1.0 @@ -241,7 +241,7 @@ jobs: needs: ["tests-linux", "tests-windows", "tests-pypy"] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Set up Python 3.12 id: python uses: actions/setup-python@v5.1.0 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e04f5af291..fc639bf325 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml index febada57d7..add2faf718 100644 --- a/.github/workflows/release-tests.yml +++ b/.github/workflows/release-tests.yml @@ -13,7 +13,7 @@ jobs: timeout-minutes: 5 steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Set up Python 3.9 id: python uses: actions/setup-python@v5.1.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5bb967decc..5ce4dd9546 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: url: https://pypi.org/project/astroid/ steps: - name: Check out code from Github - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v5.1.0 From 453d307962d3df927a49a93faff5fefbffaa53ce Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 22:55:32 +0000 Subject: [PATCH 16/63] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.8 → v0.4.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.8...v0.4.9) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 046595fd5c..9af7e20d59 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.8" + rev: "v0.4.9" hooks: - id: ruff exclude: tests/testdata From 7b9942f22ad3f2559364f1a7de8bb8ab5e07eaf5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 05:55:11 +0200 Subject: [PATCH 17/63] [pre-commit.ci] pre-commit autoupdate (#2454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.9 → v0.4.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.9...v0.4.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9af7e20d59..855c883e13 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.9" + rev: "v0.4.10" hooks: - id: ruff exclude: tests/testdata From d50f0f29cc2e0fa0991b0ef129c2ffdbf5dafe16 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Tue, 2 Jul 2024 09:53:00 +0200 Subject: [PATCH 18/63] Make the "test_helpers.py" test file runnable by itself (#2455) Currently, although running `pytest` passes, running `pytest tests/test_helpers.py` does not, it gives the following error: ... File "/tmp/astroid/tests/test_helpers.py", line 20, in setUp self.builtins = astroid_manager.astroid_cache[builtins_name] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^ KeyError: 'builtins' --- tests/test_helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 1e57ac0777..2dd94a6ae3 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -8,6 +8,7 @@ import pytest from astroid import builder, helpers, manager, nodes, raw_building, util +from astroid.builder import AstroidBuilder from astroid.const import IS_PYPY from astroid.exceptions import _NonDeducibleTypeHierarchy from astroid.nodes.scoped_nodes import ClassDef @@ -17,6 +18,7 @@ class TestHelpers(unittest.TestCase): def setUp(self) -> None: builtins_name = builtins.__name__ astroid_manager = manager.AstroidManager() + AstroidBuilder(astroid_manager) # Only to ensure boostrap self.builtins = astroid_manager.astroid_cache[builtins_name] self.manager = manager.AstroidManager() From c437f4aaf054cbf4a0ca5d376db770a40b7135be Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Jul 2024 15:56:26 -0400 Subject: [PATCH 19/63] Pin numpy below 2.0.0 Refs #2442 --- requirements_full.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_full.txt b/requirements_full.txt index e8196e629d..1780bc89d3 100644 --- a/requirements_full.txt +++ b/requirements_full.txt @@ -4,7 +4,7 @@ # Packages used to run additional tests attrs nose -numpy>=1.17.0; python_version<"3.12" +numpy>=1.17.0,<2; python_version<"3.12" python-dateutil PyQt6 regex From 314e08b79b7c09e458b2f094c1597f0139eff9a4 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 29 Jun 2024 09:27:31 -0400 Subject: [PATCH 20/63] Fix unreachable-code --- tests/test_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_builder.py b/tests/test_builder.py index 8692baabae..24f4d4207d 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -769,7 +769,7 @@ def test_module_base_props(self) -> None: with self.assertRaises(StatementMissing): with pytest.warns(DeprecationWarning) as records: self.assertEqual(module.statement(future=True), module) - assert len(records) == 1 + assert len(records) == 1 with self.assertRaises(StatementMissing): module.statement() From fb6f5bc535e92471158333feddfa93e3614683bb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 7 Jul 2024 12:23:04 -0400 Subject: [PATCH 21/63] [pre-commit.ci] pre-commit autoupdate (#2456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.10 → v0.5.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.10...v0.5.0) - [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.0...v1.10.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 855c883e13..163764ffaf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.10" + rev: "v0.5.0" hooks: - id: ruff exclude: tests/testdata @@ -54,7 +54,7 @@ repos: ] exclude: tests/testdata|conf.py - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.0 + rev: v1.10.1 hooks: - id: mypy name: mypy From 0f9dfa6ba8e10fe46494797989711e853323f222 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 8 Jul 2024 08:51:10 -0400 Subject: [PATCH 22/63] Fix AssertionError when inferring a property consisting of a partial function. (#2458) Closes pylint-dev/pylint#9214 Thanks Martin Belanger for the report and Bryce Guinta for the test case. --- ChangeLog | 4 ++++ astroid/nodes/scoped_nodes/scoped_nodes.py | 4 ++++ tests/test_regrtest.py | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/ChangeLog b/ChangeLog index 77e6eab199..725f9d217b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,10 @@ What's New in astroid 3.2.3? ============================ Release date: TBA +* Fix ``AssertionError`` when inferring a property consisting of a partial function. + +Closes pylint-dev/pylint#9214 + What's New in astroid 3.2.2? ============================ diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index cad91dcf0f..efd5439b51 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -2518,6 +2518,10 @@ def igetattr( elif isinstance(inferred, objects.Property): function = inferred.function if not class_context: + if not context.callcontext: + context.callcontext = CallContext( + args=function.args.arguments, callee=function + ) # Through an instance so we can solve the property yield from function.infer_call_result( caller=self, context=context diff --git a/tests/test_regrtest.py b/tests/test_regrtest.py index 45f241f8cf..101e1d4417 100644 --- a/tests/test_regrtest.py +++ b/tests/test_regrtest.py @@ -477,3 +477,22 @@ def test_recursion_during_inference(mocked) -> None: with pytest.raises(InferenceError) as error: next(node.infer()) assert error.value.message.startswith("RecursionError raised") + + +def test_regression_missing_callcontext() -> None: + node: nodes.Attribute = _extract_single_node( + textwrap.dedent( + """ + import functools + + class MockClass: + def _get_option(self, option): + return "mystr" + + enabled = property(functools.partial(_get_option, option='myopt')) + + MockClass().enabled + """ + ) + ) + assert node.inferred()[0].value == "mystr" From 585bb2426d7b85f002274f953d97c7c137577a6b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 29 Jun 2024 08:45:52 -0400 Subject: [PATCH 23/63] Add setuptools to dev requirements --- requirements_full.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements_full.txt b/requirements_full.txt index 1780bc89d3..ea1cc3a758 100644 --- a/requirements_full.txt +++ b/requirements_full.txt @@ -8,6 +8,7 @@ numpy>=1.17.0,<2; python_version<"3.12" python-dateutil PyQt6 regex +setuptools six urllib3>1,<2 typing_extensions>=4.4.0 From 3b86ab3665dc41b090db552647e293c4a7f10ae8 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 29 Jun 2024 09:24:02 -0400 Subject: [PATCH 24/63] Avoid using generator in test_getitem_of_class_raised_type_error --- tests/test_inference.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_inference.py b/tests/test_inference.py index b733da56cc..beda532177 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -4453,8 +4453,7 @@ def test_getitem_of_class_raised_type_error(self) -> None: # and reraise it as a TypeError in Class.getitem node = extract_node( """ - def test(): - yield + def test(): ... test() """ ) From 5337737a03b5e152fd414494ed93ae953e3eaaa3 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Jul 2024 13:56:15 -0400 Subject: [PATCH 25/63] [PY313] Fix typing.Annotated inference --- astroid/brain/brain_typing.py | 11 ++++++++++- tests/brain/test_brain.py | 11 ++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py index 1b5408ae4c..8eadb9d602 100644 --- a/astroid/brain/brain_typing.py +++ b/astroid/brain/brain_typing.py @@ -15,7 +15,7 @@ from astroid import context, extract_node, inference_tip from astroid.brain.helpers import register_module_extender from astroid.builder import AstroidBuilder, _extract_single_node -from astroid.const import PY312_PLUS +from astroid.const import PY312_PLUS, PY313_PLUS from astroid.exceptions import ( AstroidSyntaxError, AttributeInferenceError, @@ -167,6 +167,15 @@ def infer_typing_attr( # If typing subscript belongs to an alias handle it separately. raise UseInferenceDefault + if ( + PY313_PLUS + and isinstance(value, FunctionDef) + and value.qname() == "typing.Annotated" + ): + # typing.Annotated is a FunctionDef on 3.13+ + node._explicit_inference = lambda node, context: iter([value]) + return iter([value]) + if isinstance(value, ClassDef) and value.qname() in { "typing.Generic", "typing.Annotated", diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py index 12560f201c..be0e052cf5 100644 --- a/tests/brain/test_brain.py +++ b/tests/brain/test_brain.py @@ -634,7 +634,7 @@ class Bar[T](Foo[T]): ... assert ancestors[1].name == "object" def test_typing_annotated_subscriptable(self): - """Test typing.Annotated is subscriptable with __class_getitem__""" + """typing.Annotated is subscriptable with __class_getitem__ below 3.13.""" node = builder.extract_node( """ import typing @@ -642,8 +642,13 @@ def test_typing_annotated_subscriptable(self): """ ) inferred = next(node.infer()) - assert isinstance(inferred, nodes.ClassDef) - assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef) + if PY313_PLUS: + assert isinstance(inferred, nodes.FunctionDef) + else: + assert isinstance(inferred, nodes.ClassDef) + assert isinstance( + inferred.getattr("__class_getitem__")[0], nodes.FunctionDef + ) def test_typing_generic_slots(self): """Test slots for Generic subclass.""" From ec91d22962bf427c8bb84b792dda5a2be01c5784 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Jul 2024 15:03:34 -0400 Subject: [PATCH 26/63] [PY313] Preserve inference of dataclasses.replace() --- astroid/brain/brain_dataclasses.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/astroid/brain/brain_dataclasses.py b/astroid/brain/brain_dataclasses.py index def859dc64..ca07541cb0 100644 --- a/astroid/brain/brain_dataclasses.py +++ b/astroid/brain/brain_dataclasses.py @@ -19,7 +19,7 @@ from astroid import bases, context, nodes from astroid.builder import parse -from astroid.const import PY310_PLUS +from astroid.const import PY310_PLUS, PY313_PLUS from astroid.exceptions import AstroidSyntaxError, InferenceError, UseInferenceDefault from astroid.inference_tip import inference_tip from astroid.manager import AstroidManager @@ -503,6 +503,15 @@ def _looks_like_dataclass_field_call( return inferred.name == FIELD_NAME and inferred.root().name in DATACLASS_MODULES +def _looks_like_dataclasses(node: nodes.Module) -> bool: + return node.qname() == "dataclasses" + + +def _resolve_private_replace_to_public(node: nodes.Module) -> None: + if "_replace" in node.locals: + node.locals["replace"] = node.locals["_replace"] + + def _get_field_default(field_call: nodes.Call) -> _FieldDefaultReturn: """Return a the default value of a field call, and the corresponding keyword argument name. @@ -608,6 +617,13 @@ def _infer_instance_from_annotation( def register(manager: AstroidManager) -> None: + if PY313_PLUS: + manager.register_transform( + nodes.Module, + _resolve_private_replace_to_public, + _looks_like_dataclasses, + ) + manager.register_transform( nodes.ClassDef, dataclass_transform, is_decorated_with_dataclass ) From 851bba72006a422bc928e79ed3cf9f3bae278b63 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Jul 2024 15:34:32 -0400 Subject: [PATCH 27/63] [PY313] Account for pathlib._abc --- astroid/brain/brain_pathlib.py | 4 +++- tests/brain/test_pathlib.py | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/astroid/brain/brain_pathlib.py b/astroid/brain/brain_pathlib.py index 116cd2eef9..9e08e0b660 100644 --- a/astroid/brain/brain_pathlib.py +++ b/astroid/brain/brain_pathlib.py @@ -8,6 +8,7 @@ from astroid import bases, context, inference_tip, nodes from astroid.builder import _extract_single_node +from astroid.const import PY313_PLUS from astroid.exceptions import InferenceError, UseInferenceDefault from astroid.manager import AstroidManager @@ -27,10 +28,11 @@ def _looks_like_parents_subscript(node: nodes.Subscript) -> bool: value = next(node.value.infer()) except (InferenceError, StopIteration): return False + parents = "pathlib._abc._PathParents" if PY313_PLUS else "pathlib._PathParents" return ( isinstance(value, bases.Instance) and isinstance(value._proxied, nodes.ClassDef) - and value.qname() == "pathlib._PathParents" + and value.qname() == parents ) diff --git a/tests/brain/test_pathlib.py b/tests/brain/test_pathlib.py index d935d964ab..3c46bf6601 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 +from astroid.const import PY310_PLUS, PY313_PLUS from astroid.util import Uninferable @@ -23,7 +23,10 @@ def test_inference_parents() -> None: inferred = name_node.inferred() assert len(inferred) == 1 assert isinstance(inferred[0], bases.Instance) - assert inferred[0].qname() == "pathlib._PathParents" + if PY313_PLUS: + assert inferred[0].qname() == "pathlib._abc._PathParents" + else: + assert inferred[0].qname() == "pathlib._PathParents" def test_inference_parents_subscript_index() -> None: From b76d2e50870d101223324dcb6dade1d5198e415b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Jul 2024 15:49:13 -0400 Subject: [PATCH 28/63] [PY313] pathlib.Path.parents is now a tuple See python/cpython@37bd893. --- astroid/brain/brain_pathlib.py | 2 +- tests/brain/test_pathlib.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/astroid/brain/brain_pathlib.py b/astroid/brain/brain_pathlib.py index 9e08e0b660..d0f531324b 100644 --- a/astroid/brain/brain_pathlib.py +++ b/astroid/brain/brain_pathlib.py @@ -28,7 +28,7 @@ def _looks_like_parents_subscript(node: nodes.Subscript) -> bool: value = next(node.value.infer()) except (InferenceError, StopIteration): return False - parents = "pathlib._abc._PathParents" if PY313_PLUS else "pathlib._PathParents" + parents = "builtins.tuple" if PY313_PLUS else "pathlib._PathParents" return ( isinstance(value, bases.Instance) and isinstance(value._proxied, nodes.ClassDef) diff --git a/tests/brain/test_pathlib.py b/tests/brain/test_pathlib.py index 3c46bf6601..5aea8d3769 100644 --- a/tests/brain/test_pathlib.py +++ b/tests/brain/test_pathlib.py @@ -24,7 +24,7 @@ def test_inference_parents() -> None: assert len(inferred) == 1 assert isinstance(inferred[0], bases.Instance) if PY313_PLUS: - assert inferred[0].qname() == "pathlib._abc._PathParents" + assert inferred[0].qname() == "builtins.tuple" else: assert inferred[0].qname() == "pathlib._PathParents" @@ -43,7 +43,10 @@ def test_inference_parents_subscript_index() -> None: inferred = path.inferred() assert len(inferred) == 1 assert isinstance(inferred[0], bases.Instance) - assert inferred[0].qname() == "pathlib.Path" + if PY313_PLUS: + assert inferred[0].qname() == "pathlib._local.Path" + else: + assert inferred[0].qname() == "pathlib.Path" def test_inference_parents_subscript_slice() -> None: From 245fb28453ee7b2a0c04be15a7b8b5aa3de22014 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 6 May 2024 22:20:57 +0200 Subject: [PATCH 29/63] Add python 3.13 to the continuous integration --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ed572cc8ef..41e752d983 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"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13-dev"] outputs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: @@ -138,7 +138,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13-dev"] steps: - name: Set temp directory run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV From a1e3b36e07a63a83372f0fd72cf54d13e7889189 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Jul 2024 15:35:28 -0400 Subject: [PATCH 30/63] Add changelog --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index 725f9d217b..7236c34ad4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,8 @@ What's New in astroid 3.3.0? ============================ Release date: TBA +* Add support for Python 3.13. + * Remove support for Python 3.8 (and constants `PY38`, `PY39_PLUS`, and `PYPY_7_3_11_PLUS`). Refs #2443 From 62bbcae2bc076c7f0c27a4376c80214506a2f33f Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Jul 2024 16:30:34 -0400 Subject: [PATCH 31/63] Add 3.13 classifier --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 019664cdfc..b0078e813e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Python Modules", From 580a36550024f730896f39542a6e506d1dcbc3c3 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 8 Jul 2024 10:17:14 -0400 Subject: [PATCH 32/63] Add comment --- astroid/brain/brain_dataclasses.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/astroid/brain/brain_dataclasses.py b/astroid/brain/brain_dataclasses.py index ca07541cb0..845295bf9b 100644 --- a/astroid/brain/brain_dataclasses.py +++ b/astroid/brain/brain_dataclasses.py @@ -508,6 +508,8 @@ def _looks_like_dataclasses(node: nodes.Module) -> bool: def _resolve_private_replace_to_public(node: nodes.Module) -> None: + """In python/cpython@6f3c138, a _replace() method was extracted from + replace(), and this indirection made replace() uninferable.""" if "_replace" in node.locals: node.locals["replace"] = node.locals["_replace"] From b6ce2f708f82a4fb78954ee7abd9453eeb8d2d31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 21:50:18 +0200 Subject: [PATCH 33/63] Bump actions/upload-artifact from 4.3.3 to 4.3.4 (#2462) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.3 to 4.3.4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4.3.3...v4.3.4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 41e752d983..a1d065b411 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -125,7 +125,7 @@ jobs: . venv/bin/activate pytest --cov - name: Upload coverage artifact - uses: actions/upload-artifact@v4.3.3 + uses: actions/upload-artifact@v4.3.4 with: name: coverage-linux-${{ matrix.python-version }} path: .coverage @@ -179,7 +179,7 @@ jobs: . venv\\Scripts\\activate pytest --cov - name: Upload coverage artifact - uses: actions/upload-artifact@v4.3.3 + uses: actions/upload-artifact@v4.3.4 with: name: coverage-windows-${{ matrix.python-version }} path: .coverage @@ -229,7 +229,7 @@ jobs: . venv/bin/activate pytest --cov - name: Upload coverage artifact - uses: actions/upload-artifact@v4.3.3 + uses: actions/upload-artifact@v4.3.4 with: name: coverage-pypy-${{ matrix.python-version }} path: .coverage From 960db110dcb4585e97a4d79cfdf3ce6934e8ec18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 21:50:45 +0200 Subject: [PATCH 34/63] Bump actions/download-artifact from 4.1.7 to 4.1.8 (#2463) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.7 to 4.1.8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4.1.7...v4.1.8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a1d065b411..564edae993 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -251,7 +251,7 @@ jobs: - name: Install dependencies run: pip install -U -r requirements_minimal.txt - name: Download all coverage artifacts - uses: actions/download-artifact@v4.1.7 + uses: actions/download-artifact@v4.1.8 - name: Combine Linux coverage results run: | coverage combine coverage-linux*/.coverage From b28ea1fbff03873b54c7fcda0de0602ec8a54df1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 08:19:24 +0200 Subject: [PATCH 35/63] [pre-commit.ci] pre-commit autoupdate (#2464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.0...v0.5.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 163764ffaf..ee950c605f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.5.0" + rev: "v0.5.1" hooks: - id: ruff exclude: tests/testdata From 98e626cf1455b6a0c123060235dceb2dc05147f2 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 13 Jul 2024 11:54:42 -0400 Subject: [PATCH 36/63] Run pylint with github reporter (#2470) --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee950c605f..eb362eec53 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,6 +50,7 @@ repos: "-rn", "-sn", "--rcfile=pylintrc", + "--output-format=github", # "--load-plugins=pylint.extensions.docparams", We're not ready for that ] exclude: tests/testdata|conf.py From bf3199747c46758ffa4fa46783ff1237791fb741 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 15 Jul 2024 11:20:10 -0400 Subject: [PATCH 37/63] Avoid reporting unary/binary op type errors for ambiguous inference (#2468) --- ChangeLog | 4 +++ astroid/nodes/node_classes.py | 60 +++++++++++++++++++---------------- tests/test_inference.py | 27 ++++++++++++++++ 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/ChangeLog b/ChangeLog index fdbb5e96fb..e850acd4c6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,10 @@ What's New in astroid 3.2.4? ============================ Release date: TBA +* Avoid reporting unary/binary op type errors when inference is ambiguous. + + Closes #2467 + What's New in astroid 3.2.3? ============================ diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 40aaff3e4e..4c8b63411d 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -1380,24 +1380,26 @@ def postinit(self, target: Name | Attribute | Subscript, value: NodeNG) -> None: See astroid/protocols.py for actual implementation. """ - def type_errors(self, context: InferenceContext | None = None): + def type_errors( + self, context: InferenceContext | None = None + ) -> list[util.BadBinaryOperationMessage]: """Get a list of type errors which can occur during inference. Each TypeError is represented by a :class:`BadBinaryOperationMessage` , which holds the original exception. - :returns: The list of possible type errors. - :rtype: list(BadBinaryOperationMessage) + If any inferred result is uninferable, an empty list is returned. """ + bad = [] try: - results = self._infer_augassign(context=context) - return [ - result - for result in results - if isinstance(result, util.BadBinaryOperationMessage) - ] + for result in self._infer_augassign(context=context): + if result is util.Uninferable: + raise InferenceError + if isinstance(result, util.BadBinaryOperationMessage): + bad.append(result) except InferenceError: return [] + return bad def get_children(self): yield self.target @@ -1496,24 +1498,26 @@ def postinit(self, left: NodeNG, right: NodeNG) -> None: self.left = left self.right = right - def type_errors(self, context: InferenceContext | None = None): + def type_errors( + self, context: InferenceContext | None = None + ) -> list[util.BadBinaryOperationMessage]: """Get a list of type errors which can occur during inference. Each TypeError is represented by a :class:`BadBinaryOperationMessage`, which holds the original exception. - :returns: The list of possible type errors. - :rtype: list(BadBinaryOperationMessage) + If any inferred result is uninferable, an empty list is returned. """ + bad = [] try: - results = self._infer_binop(context=context) - return [ - result - for result in results - if isinstance(result, util.BadBinaryOperationMessage) - ] + for result in self._infer_binop(context=context): + if result is util.Uninferable: + raise InferenceError + if isinstance(result, util.BadBinaryOperationMessage): + bad.append(result) except InferenceError: return [] + return bad def get_children(self): yield self.left @@ -4261,24 +4265,26 @@ def __init__( def postinit(self, operand: NodeNG) -> None: self.operand = operand - def type_errors(self, context: InferenceContext | None = None): + def type_errors( + self, context: InferenceContext | None = None + ) -> list[util.BadUnaryOperationMessage]: """Get a list of type errors which can occur during inference. Each TypeError is represented by a :class:`BadUnaryOperationMessage`, which holds the original exception. - :returns: The list of possible type errors. - :rtype: list(BadUnaryOperationMessage) + If any inferred result is uninferable, an empty list is returned. """ + bad = [] try: - results = self._infer_unaryop(context=context) - return [ - result - for result in results - if isinstance(result, util.BadUnaryOperationMessage) - ] + for result in self._infer_unaryop(context=context): + if result is util.Uninferable: + raise InferenceError + if isinstance(result, util.BadUnaryOperationMessage): + bad.append(result) except InferenceError: return [] + return bad def get_children(self): yield self.operand diff --git a/tests/test_inference.py b/tests/test_inference.py index beda532177..ce2177332a 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -2738,6 +2738,15 @@ def __radd__(self, other): error = errors[0] self.assertEqual(str(error), expected_value) + def test_binary_type_errors_partially_uninferable(self) -> None: + def patched_infer_binop(context): + return iter([util.BadBinaryOperationMessage(None, None, None), Uninferable]) + + binary_op_node = extract_node("0 + 0") + binary_op_node._infer_binop = patched_infer_binop + errors = binary_op_node.type_errors() + self.assertEqual(errors, []) + def test_unary_type_errors(self) -> None: ast_nodes = extract_node( """ @@ -2805,6 +2814,15 @@ def test_unary_type_errors_for_non_instance_objects(self) -> None: self.assertEqual(len(errors), 1) self.assertEqual(str(errors[0]), "bad operand type for unary ~: slice") + def test_unary_type_errors_partially_uninferable(self) -> None: + def patched_infer_unary_op(context): + return iter([util.BadUnaryOperationMessage(None, None, "msg"), Uninferable]) + + unary_op_node = extract_node("~0") + unary_op_node._infer_unaryop = patched_infer_unary_op + errors = unary_op_node.type_errors() + self.assertEqual(errors, []) + def test_bool_value_recursive(self) -> None: pairs = [ ("{}", False), @@ -3523,6 +3541,15 @@ def __radd__(self, other): return NotImplemented self.assertIsInstance(inferred, Instance) self.assertEqual(inferred.name, "B") + def test_augop_type_errors_partially_uninferable(self) -> None: + def patched_infer_augassign(context) -> None: + return iter([util.BadBinaryOperationMessage(None, None, None), Uninferable]) + + aug_op_node = extract_node("__name__ += 'test'") + aug_op_node._infer_augassign = patched_infer_augassign + errors = aug_op_node.type_errors() + self.assertEqual(errors, []) + def test_string_interpolation(self): ast_nodes = extract_node( """ From 1c51b70d8cfd13eeeae339b11828e7674872fb4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:33:15 +0000 Subject: [PATCH 38/63] Bump actions/setup-python from 5.1.0 to 5.1.1 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.1.0 to 5.1.1. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5.1.0...v5.1.1) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 10 +++++----- .github/workflows/release-tests.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 564edae993..9ae694e6ba 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.1.1 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true @@ -89,7 +89,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.1.1 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -148,7 +148,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.1.1 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -198,7 +198,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.1.1 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -244,7 +244,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Set up Python 3.12 id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.1.1 with: python-version: "3.12" check-latest: true diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml index add2faf718..7f8d50c684 100644 --- a/.github/workflows/release-tests.yml +++ b/.github/workflows/release-tests.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Set up Python 3.9 id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.1.1 with: # virtualenv 15.1.0 cannot be installed on Python 3.10+ python-version: 3.9 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ce4dd9546..d527a2f221 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.1.1 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true From 4d0cff301aae598fe0b1889d38b8fe229698f3e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:09:44 +0000 Subject: [PATCH 39/63] Update sphinx requirement from ~=7.3 to ~=7.4 Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v7.3.0...v7.4.3) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 6a0aa61ebf..860294df99 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,3 @@ -e . -sphinx~=7.3 +sphinx~=7.4 furo==2024.5.6 From 79a0fa391acccc26c46aa0aac299cb0b61c1f2ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:09:40 +0000 Subject: [PATCH 40/63] Update coverage requirement from ~=7.5 to ~=7.6 Updates the requirements on [coverage](https://github.com/nedbat/coveragepy) to permit the latest version. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.5.0...7.6.0) --- updated-dependencies: - dependency-name: coverage dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements_minimal.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_minimal.txt b/requirements_minimal.txt index 3d0518caf7..71170ce8bd 100644 --- a/requirements_minimal.txt +++ b/requirements_minimal.txt @@ -3,6 +3,6 @@ contributors-txt>=0.7.4 tbump~=6.11 # Tools used to run tests -coverage~=7.5 +coverage~=7.6 pytest pytest-cov~=5.0 From c2e1ccac6d293ce3fc9deee83acc125ab6c3f64f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 22:48:02 +0000 Subject: [PATCH 41/63] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.1 → v0.5.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.1...v0.5.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eb362eec53..fdef4a627b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.5.1" + rev: "v0.5.2" hooks: - id: ruff exclude: tests/testdata From a5f6d72487624cab53a7fb477128383c7c1b3edc Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 20 Jul 2024 22:18:47 +0200 Subject: [PATCH 42/63] Update .pre-commit-config.yaml --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fdef4a627b..e73a986574 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.5.2" + rev: "v0.5.4" hooks: - id: ruff exclude: tests/testdata From a93018e12e4992fa3a6d8ebd053c5d3aae70659e Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 20 Jul 2024 22:23:41 +0200 Subject: [PATCH 43/63] fix ruff --- astroid/builder.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/astroid/builder.py b/astroid/builder.py index cff859124e..932b461fa5 100644 --- a/astroid/builder.py +++ b/astroid/builder.py @@ -246,10 +246,7 @@ def delayed_assattr(self, node: nodes.AssignAttr) -> None: try: # pylint: disable=unidiomatic-typecheck # We want a narrow check on the # parent type, not all of its subclasses - if ( - type(inferred) == bases.Instance - or type(inferred) == objects.ExceptionInstance - ): + if type(inferred) in {bases.Instance, objects.ExceptionInstance}: inferred = inferred._proxied iattrs = inferred.instance_attrs if not _can_assign_attr(inferred, node.attrname): From 5b665e7e760a7181625a24b3635e9fec7b174d87 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 23 Jul 2024 23:42:43 -0400 Subject: [PATCH 44/63] Skip test_numpy_distutils on 3.12+ --- tests/test_regrtest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_regrtest.py b/tests/test_regrtest.py index 101e1d4417..f7383d25fb 100644 --- a/tests/test_regrtest.py +++ b/tests/test_regrtest.py @@ -11,6 +11,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.context import InferenceContext from astroid.exceptions import InferenceError from astroid.raw_building import build_module @@ -100,7 +101,7 @@ def test_numpy_crash(self): inferred = callfunc.inferred() self.assertEqual(len(inferred), 1) - @unittest.skipUnless(HAS_NUMPY, "Needs numpy") + @unittest.skipUnless(HAS_NUMPY and not PY312_PLUS, "Needs numpy and < Python 3.12") def test_numpy_distutils(self): """Special handling of virtualenv's patching of distutils shouldn't interfere with numpy.distutils. From e68c016bc842a317e749b2f4c038b93c28dcac5f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 22:55:07 +0000 Subject: [PATCH 45/63] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.10.1 → v1.11.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.1...v1.11.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e73a986574..bea3f401c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,7 +55,7 @@ repos: ] exclude: tests/testdata|conf.py - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.1 + rev: v1.11.0 hooks: - id: mypy name: mypy From 6f1eb891d37d140f7b9c2c0bd7fb15194638ec73 Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Sun, 28 Jul 2024 15:34:23 +0100 Subject: [PATCH 46/63] Add the ``__annotations__`` attribute to the ``ClassDef`` object model. (#2431) * Add the ``__annotations__`` attribute to the ``ClassDef`` object model. Pylint now does not emit a ``no-member`` error when accessing ``__annotations`` in the following cases: ``` class Test: print(__annotations__) ``` ``` from typing import TypedDict OtherTypedDict = TypedDict('OtherTypedDict', {'a': int, 'b': str}) print(OtherTypedDict.__annotations__) ``` Closes pylint-dev/pylint#7126 --- astroid/interpreter/objectmodel.py | 4 ++ astroid/nodes/scoped_nodes/scoped_nodes.py | 5 ++- tests/test_builder.py | 3 +- tests/test_object_model.py | 44 ++++++++++++++++++++++ tests/test_scoped_nodes.py | 1 + 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py index 199e8285cc..0f553ab084 100644 --- a/astroid/interpreter/objectmodel.py +++ b/astroid/interpreter/objectmodel.py @@ -492,6 +492,10 @@ def __init__(self): super().__init__() + @property + def attr___annotations__(self) -> node_classes.Unkown: + return node_classes.Unknown() + @property def attr___module__(self): return node_classes.Const(self._instance.root().qname()) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index efd5439b51..a66f3fc530 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -1946,7 +1946,10 @@ def implicit_locals(self): """ locals_ = (("__module__", self.special_attributes.attr___module__),) # __qualname__ is defined in PEP3155 - locals_ += (("__qualname__", self.special_attributes.attr___qualname__),) + locals_ += ( + ("__qualname__", self.special_attributes.attr___qualname__), + ("__annotations__", self.special_attributes.attr___annotations__), + ) return locals_ # pylint: disable=redefined-outer-name diff --git a/tests/test_builder.py b/tests/test_builder.py index 24f4d4207d..f9dac6169e 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -841,12 +841,13 @@ def test_class_locals(self) -> None: klass1 = module["YO"] locals1 = klass1.locals keys = sorted(locals1.keys()) - assert_keys = ["__init__", "__module__", "__qualname__", "a"] + assert_keys = ["__annotations__", "__init__", "__module__", "__qualname__", "a"] self.assertEqual(keys, assert_keys) klass2 = module["YOUPI"] locals2 = klass2.locals keys = locals2.keys() assert_keys = [ + "__annotations__", "__init__", "__module__", "__qualname__", diff --git a/tests/test_object_model.py b/tests/test_object_model.py index 62d234b1fd..9ad4d39a90 100644 --- a/tests/test_object_model.py +++ b/tests/test_object_model.py @@ -855,3 +855,47 @@ def foo(): assert wrapped.name == "foo" cache_info = next(ast_nodes[2].infer()) assert isinstance(cache_info, astroid.Instance) + + +def test_class_annotations() -> None: + """Test that the `__annotations__` attribute is avaiable in the class scope""" + annotations, klass_attribute = builder.extract_node( + """ + class Test: + __annotations__ #@ + Test.__annotations__ #@ + """ + ) + # Test that `__annotations__` attribute is available in the class scope: + assert isinstance(annotations, nodes.Name) + # The `__annotations__` attribute is `Uninferable`: + assert next(annotations.infer()) is astroid.Uninferable + + # Test that we can access the class annotations: + assert isinstance(klass_attribute, nodes.Attribute) + + +def test_class_annotations_typed_dict() -> None: + """Test that we can access class annotations on various TypedDicts""" + apple, pear = builder.extract_node( + """ + from typing import TypedDict + + + class Apple(TypedDict): + a: int + b: str + + + Pear = TypedDict('OtherTypedDict', {'a': int, 'b': str}) + + + Apple.__annotations__ #@ + Pear.__annotations__ #@ + """ + ) + + assert isinstance(apple, nodes.Attribute) + assert next(apple.infer()) is astroid.Uninferable + assert isinstance(pear, nodes.Attribute) + assert next(pear.infer()) is astroid.Uninferable diff --git a/tests/test_scoped_nodes.py b/tests/test_scoped_nodes.py index ffbca4dadb..209710b86a 100644 --- a/tests/test_scoped_nodes.py +++ b/tests/test_scoped_nodes.py @@ -1209,6 +1209,7 @@ def registered(cls, application): astroid = builder.parse(data, __name__) cls = astroid["WebAppObject"] assert_keys = [ + "__annotations__", "__module__", "__qualname__", "appli", From d41c5834bd959e896a7504ce90254a68bd979440 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 28 Jul 2024 10:37:05 -0400 Subject: [PATCH 47/63] [skip ci] Add changelog for #2431 --- ChangeLog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 2492e58d1f..59e9b8d094 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,10 @@ Release date: TBA Refs #2443 +* Add the ``__annotations__`` attribute to the ``ClassDef`` object model. + + Closes pylint-dev/pylint#7126 + What's New in astroid 3.2.5? ============================ From 76d5429329159433591d029387b2b7b4475a384b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 28 Jul 2024 12:40:52 -0400 Subject: [PATCH 48/63] Remove f-strings brain (#2484) The line number issue for FormattedValue.value (Name node) was solved in CPython shortly after the brain was contributed. --- astroid/brain/brain_fstrings.py | 72 --------------------------------- astroid/brain/helpers.py | 2 - tests/brain/test_brain.py | 17 -------- 3 files changed, 91 deletions(-) delete mode 100644 astroid/brain/brain_fstrings.py diff --git a/astroid/brain/brain_fstrings.py b/astroid/brain/brain_fstrings.py deleted file mode 100644 index 262a27d259..0000000000 --- a/astroid/brain/brain_fstrings.py +++ /dev/null @@ -1,72 +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 - -from __future__ import annotations - -import collections.abc -from typing import TypeVar - -from astroid import nodes -from astroid.manager import AstroidManager - -_NodeT = TypeVar("_NodeT", bound=nodes.NodeNG) - - -def _clone_node_with_lineno( - node: _NodeT, parent: nodes.NodeNG, lineno: int | None -) -> _NodeT: - cls = node.__class__ - other_fields = node._other_fields - _astroid_fields = node._astroid_fields - init_params = { - "lineno": lineno, - "col_offset": node.col_offset, - "parent": parent, - "end_lineno": node.end_lineno, - "end_col_offset": node.end_col_offset, - } - postinit_params = {param: getattr(node, param) for param in _astroid_fields} - if other_fields: - init_params.update({param: getattr(node, param) for param in other_fields}) - new_node = cls(**init_params) - if hasattr(node, "postinit") and _astroid_fields: - for param, child in postinit_params.items(): - if child and not isinstance(child, collections.abc.Sequence): - cloned_child = _clone_node_with_lineno( - node=child, lineno=new_node.lineno, parent=new_node - ) - postinit_params[param] = cloned_child - new_node.postinit(**postinit_params) - return new_node - - -def _transform_formatted_value( # pylint: disable=inconsistent-return-statements - node: nodes.FormattedValue, -) -> nodes.FormattedValue | None: - if node.value and node.value.lineno == 1: - if node.lineno != node.value.lineno: - new_node = nodes.FormattedValue( - lineno=node.lineno, - col_offset=node.col_offset, - parent=node.parent, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset, - ) - new_value = _clone_node_with_lineno( - node=node.value, lineno=node.lineno, parent=new_node - ) - new_node.postinit( - value=new_value, - conversion=node.conversion, - format_spec=node.format_spec, - ) - return new_node - - -# TODO: this fix tries to *patch* http://bugs.python.org/issue29051 -# The problem is that FormattedValue.value, which is a Name node, -# has wrong line numbers, usually 1. This creates problems for pylint, -# which expects correct line numbers for things such as message control. -def register(manager: AstroidManager) -> None: - manager.register_transform(nodes.FormattedValue, _transform_formatted_value) diff --git a/astroid/brain/helpers.py b/astroid/brain/helpers.py index baf6c5c854..79d778b5a3 100644 --- a/astroid/brain/helpers.py +++ b/astroid/brain/helpers.py @@ -38,7 +38,6 @@ def register_all_brains(manager: AstroidManager) -> None: brain_dataclasses, brain_datetime, brain_dateutil, - brain_fstrings, brain_functools, brain_gi, brain_hashlib, @@ -91,7 +90,6 @@ def register_all_brains(manager: AstroidManager) -> None: brain_dataclasses.register(manager) brain_datetime.register(manager) brain_dateutil.register(manager) - brain_fstrings.register(manager) brain_functools.register(manager) brain_gi.register(manager) brain_hashlib.register(manager) diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py index be0e052cf5..447c4cde26 100644 --- a/tests/brain/test_brain.py +++ b/tests/brain/test_brain.py @@ -1077,23 +1077,6 @@ def test_re_pattern_subscriptable(self): assert isinstance(inferred2.getattr("__class_getitem__")[0], nodes.FunctionDef) -class BrainFStrings(unittest.TestCase): - def test_no_crash_on_const_reconstruction(self) -> None: - node = builder.extract_node( - """ - max_width = 10 - - test1 = f'{" ":{max_width+4}}' - print(f'"{test1}"') - - test2 = f'[{"7":>{max_width}}:0]' - test2 - """ - ) - inferred = next(node.infer()) - self.assertIs(inferred, util.Uninferable) - - class BrainNamedtupleAnnAssignTest(unittest.TestCase): def test_no_crash_on_ann_assign_in_namedtuple(self) -> None: node = builder.extract_node( From 590498dba052b10b81eb8c63c93ee165da51c023 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 28 Jul 2024 12:37:12 -0400 Subject: [PATCH 49/63] Qualify setuptools requirement to Python >=3.12 Partner to pylint-dev/pylint@dfff77d. --- requirements_full.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_full.txt b/requirements_full.txt index ea1cc3a758..99cd84a6ee 100644 --- a/requirements_full.txt +++ b/requirements_full.txt @@ -8,7 +8,7 @@ numpy>=1.17.0,<2; python_version<"3.12" python-dateutil PyQt6 regex -setuptools +setuptools; python_version<"3.12" six urllib3>1,<2 typing_extensions>=4.4.0 From fb2fb3679cf6fb69aa843860b4f05fb78a033488 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 28 Jul 2024 17:24:40 -0400 Subject: [PATCH 50/63] Improve typing of ClassDef private helpers --- astroid/nodes/scoped_nodes/scoped_nodes.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index a66f3fc530..da2f3a0999 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -1735,7 +1735,11 @@ async def func(things): """ -def _is_metaclass(klass, seen=None, context: InferenceContext | None = None) -> bool: +def _is_metaclass( + klass: ClassDef, + seen: set[str] | None = None, + context: InferenceContext | None = None, +) -> bool: """Return if the given class can be used as a metaclass. """ @@ -1767,7 +1771,11 @@ def _is_metaclass(klass, seen=None, context: InferenceContext | None = None) -> return False -def _class_type(klass, ancestors=None, context: InferenceContext | None = None): +def _class_type( + klass: ClassDef, + ancestors: set[str] | None = None, + context: InferenceContext | None = None, +): """return a ClassDef node type to differ metaclass and exception from 'regular' classes """ @@ -1859,7 +1867,7 @@ def my_meth(self, arg): :type: objectmodel.ClassModel """ - _type = None + _type: Literal["class", "exception", "metaclass"] | None = None _metaclass: NodeNG | None = None _metaclass_hack = False hide = False From 3585f7677d29771ea993e297d80f2199797ef6de Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 28 Jul 2024 17:24:54 -0400 Subject: [PATCH 51/63] Avoid rechecking _is_metaclass() --- astroid/nodes/scoped_nodes/scoped_nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index da2f3a0999..48a1ccfc3a 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -1775,7 +1775,7 @@ def _class_type( klass: ClassDef, ancestors: set[str] | None = None, context: InferenceContext | None = None, -): +) -> Literal["class", "exception", "metaclass"]: """return a ClassDef node type to differ metaclass and exception from 'regular' classes """ @@ -1798,7 +1798,7 @@ def _class_type( for base in klass.ancestors(recurs=False): name = _class_type(base, ancestors) if name != "class": - if name == "metaclass" and not _is_metaclass(klass): + if name == "metaclass" and klass._type != "metaclass": # don't propagate it if the current class # can't be a metaclass continue From 3bcf5ec78f3c5a064b5149c47ec5ca03ea9f3769 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 29 Jul 2024 08:28:26 -0400 Subject: [PATCH 52/63] Skip `test_no_user_warning()` without pip (#2487) --- tests/test_manager.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_manager.py b/tests/test_manager.py index c91ec0a4cf..34ddd06d4a 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -16,7 +16,7 @@ import astroid from astroid import manager, test_utils -from astroid.const import IS_JYTHON, IS_PYPY +from astroid.const import IS_JYTHON, IS_PYPY, PY312_PLUS from astroid.exceptions import ( AstroidBuildingError, AstroidImportError, @@ -391,13 +391,18 @@ def test_denied_modules_raise(self) -> None: class IsolatedAstroidManagerTest(unittest.TestCase): + @pytest.mark.skipif(PY312_PLUS, reason="distutils was removed in python 3.12") def test_no_user_warning(self): + """When Python 3.12 is minimum, this test will no longer provide value.""" mgr = manager.AstroidManager() self.addCleanup(mgr.clear_cache) with warnings.catch_warnings(): warnings.filterwarnings("error", category=UserWarning) mgr.ast_from_module_name("setuptools") - mgr.ast_from_module_name("pip") + try: + mgr.ast_from_module_name("pip") + except astroid.AstroidImportError: + pytest.skip("pip is not installed") class BorgAstroidManagerTC(unittest.TestCase): From 3dbf1393de191fd10b09741badfe1b075fdadbac Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Fri, 2 Aug 2024 15:21:54 +0200 Subject: [PATCH 53/63] Implement inference for `JoinedStr` and `FormattedValue` (#2459) --- ChangeLog | 3 ++ astroid/nodes/node_classes.py | 59 +++++++++++++++++++++++++++++++++++ tests/test_inference.py | 29 +++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/ChangeLog b/ChangeLog index 59e9b8d094..7bcf764e0b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,9 @@ Release date: TBA Closes pylint-dev/pylint#7126 +* Implement inference for JoinedStr and FormattedValue + + What's New in astroid 3.2.5? ============================ diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 4c8b63411d..a87514866d 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -4673,6 +4673,37 @@ def get_children(self): if self.format_spec is not None: yield self.format_spec + def _infer( + self, context: InferenceContext | None = None, **kwargs: Any + ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: + if self.format_spec is None: + yield from self.value.infer(context, **kwargs) + return + uninferable_already_generated = False + for format_spec in self.format_spec.infer(context, **kwargs): + if not isinstance(format_spec, Const): + if not uninferable_already_generated: + yield util.Uninferable + uninferable_already_generated = True + continue + for value in self.value.infer(context, **kwargs): + if not isinstance(value, Const): + if not uninferable_already_generated: + yield util.Uninferable + uninferable_already_generated = True + continue + formatted = format(value.value, format_spec.value) + yield Const( + formatted, + lineno=self.lineno, + col_offset=self.col_offset, + end_lineno=self.end_lineno, + end_col_offset=self.end_col_offset, + ) + + +MISSING_VALUE = "{MISSING_VALUE}" + class JoinedStr(NodeNG): """Represents a list of string expressions to be joined. @@ -4734,6 +4765,34 @@ def postinit(self, values: list[NodeNG] | None = None) -> None: def get_children(self): yield from self.values + def _infer( + self, context: InferenceContext | None = None, **kwargs: Any + ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: + yield from self._infer_from_values(self.values, context) + + @classmethod + def _infer_from_values( + cls, nodes: list[NodeNG], context: InferenceContext | None = None, **kwargs: Any + ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: + if len(nodes) == 1: + yield from nodes[0]._infer(context, **kwargs) + return + uninferable_already_generated = False + for prefix in nodes[0]._infer(context, **kwargs): + for suffix in cls._infer_from_values(nodes[1:], context, **kwargs): + result = "" + for node in (prefix, suffix): + if isinstance(node, Const): + result += str(node.value) + continue + result += MISSING_VALUE + if MISSING_VALUE in result: + if not uninferable_already_generated: + uninferable_already_generated = True + yield util.Uninferable + else: + yield Const(result) + class NamedExpr(_base_nodes.AssignTypeNode): """Represents the assignment from the assignment expression diff --git a/tests/test_inference.py b/tests/test_inference.py index ce2177332a..61378043c3 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -19,6 +19,7 @@ import pytest from astroid import ( + Const, Slice, Uninferable, arguments, @@ -652,6 +653,34 @@ def process_line(word_pos): ) ) + def test_fstring_inference(self) -> None: + code = """ + name = "John" + result = f"Hello {name}!" + """ + ast = parse(code, __name__) + node = ast["result"] + inferred = node.inferred() + self.assertEqual(len(inferred), 1) + value_node = inferred[0] + self.assertIsInstance(value_node, Const) + self.assertEqual(value_node.value, "Hello John!") + + def test_formatted_fstring_inference(self) -> None: + code = """ + width = 10 + precision = 4 + value = 12.34567 + result = f"result: {value:{width}.{precision}}!" + """ + ast = parse(code, __name__) + node = ast["result"] + inferred = node.inferred() + self.assertEqual(len(inferred), 1) + value_node = inferred[0] + self.assertIsInstance(value_node, Const) + self.assertEqual(value_node.value, "result: 12.35!") + def test_float_complex_ambiguity(self) -> None: code = ''' def no_conjugate_member(magic_flag): #@ From ed4276bd9228d62cfc1816947568f0b98f3f94fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Aug 2024 07:41:15 -0400 Subject: [PATCH 54/63] Bump furo from 2024.5.6 to 2024.7.18 (#2480) --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 860294df99..6f446f6323 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,3 @@ -e . sphinx~=7.4 -furo==2024.5.6 +furo==2024.7.18 From c68759564e1d94c302c86340086be3fcbe59fff0 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 3 Aug 2024 14:01:39 -0400 Subject: [PATCH 55/63] [PY312] Add support for ssl.OP_LEGACY_SERVER_CONNECT (#2489) --- ChangeLog | 3 +++ astroid/brain/brain_ssl.py | 11 ++++++++--- tests/brain/test_ssl.py | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7bcf764e0b..c9f5a26f81 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,9 @@ Release date: TBA * Implement inference for JoinedStr and FormattedValue +* Add support for ``ssl.OP_LEGACY_SERVER_CONNECT`` (new in Python 3.12). + + Closes pylint-dev/pylint#9849 What's New in astroid 3.2.5? diff --git a/astroid/brain/brain_ssl.py b/astroid/brain/brain_ssl.py index 42018b5bfa..23d7ee4f73 100644 --- a/astroid/brain/brain_ssl.py +++ b/astroid/brain/brain_ssl.py @@ -6,7 +6,7 @@ from astroid import parse from astroid.brain.helpers import register_module_extender -from astroid.const import PY310_PLUS +from astroid.const import PY310_PLUS, PY312_PLUS from astroid.manager import AstroidManager @@ -42,13 +42,16 @@ class Options(_IntFlag): OP_NO_COMPRESSION = 11 OP_NO_TICKET = 12 OP_NO_RENEGOTIATION = 13 - OP_ENABLE_MIDDLEBOX_COMPAT = 14""" + OP_ENABLE_MIDDLEBOX_COMPAT = 14 + """ + if PY312_PLUS: + enum += "OP_LEGACY_SERVER_CONNECT = 15" return enum def ssl_transform(): return parse( - """ + f""" # Import necessary for conversion of objects defined in C into enums from enum import IntEnum as _IntEnum, IntFlag as _IntFlag @@ -71,6 +74,8 @@ def ssl_transform(): OP_NO_TLSv1, OP_NO_TLSv1_1, OP_NO_TLSv1_2, OP_SINGLE_DH_USE, OP_SINGLE_ECDH_USE) + {"from _ssl import OP_LEGACY_SERVER_CONNECT" if PY312_PLUS else ""} + from _ssl import (ALERT_DESCRIPTION_ACCESS_DENIED, ALERT_DESCRIPTION_BAD_CERTIFICATE, ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE, ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE, diff --git a/tests/brain/test_ssl.py b/tests/brain/test_ssl.py index 798bebfb72..418c589953 100644 --- a/tests/brain/test_ssl.py +++ b/tests/brain/test_ssl.py @@ -4,7 +4,10 @@ """Tests for the ssl brain.""" +import pytest + from astroid import bases, nodes, parse +from astroid.const import PY312_PLUS def test_ssl_brain() -> None: @@ -41,3 +44,21 @@ def test_ssl_brain() -> None: inferred_cert_required = next(module.body[4].value.infer()) assert isinstance(inferred_cert_required, bases.Instance) assert inferred_cert_required._proxied.name == "CERT_REQUIRED" + + +@pytest.mark.skipif(not PY312_PLUS, reason="Uses new 3.12 constant") +def test_ssl_brain_py312() -> None: + """Test ssl brain transform.""" + module = parse( + """ + import ssl + ssl.OP_LEGACY_SERVER_CONNECT + ssl.Options.OP_LEGACY_SERVER_CONNECT + """ + ) + + inferred_constant = next(module.body[1].value.infer()) + assert isinstance(inferred_constant, nodes.Const) + + inferred_instance = next(module.body[2].value.infer()) + assert isinstance(inferred_instance, bases.Instance) From 4122248931871c5c9ac3f2e72b0e5ac489168b73 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 22:47:54 +0000 Subject: [PATCH 56/63] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.4 → v0.5.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.4...v0.5.5) - [github.com/asottile/pyupgrade: v3.16.0 → v3.17.0](https://github.com/asottile/pyupgrade/compare/v3.16.0...v3.17.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bea3f401c8..91a2ddb2d4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer exclude: tests/testdata - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.5.4" + rev: "v0.5.5" hooks: - id: ruff exclude: tests/testdata @@ -23,7 +23,7 @@ repos: exclude: tests/testdata|setup.py types: [python] - repo: https://github.com/asottile/pyupgrade - rev: v3.16.0 + rev: v3.17.0 hooks: - id: pyupgrade exclude: tests/testdata From 92baf7833d667f69df05a33b860a6e8693e3ff1c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 22:48:57 +0000 Subject: [PATCH 57/63] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- astroid/decorators.py | 16 +++++-------- astroid/helpers.py | 2 +- astroid/inference_tip.py | 2 +- astroid/nodes/_base_nodes.py | 18 +++++++-------- astroid/nodes/node_classes.py | 26 +++++++++++----------- astroid/nodes/node_ng.py | 2 +- astroid/nodes/scoped_nodes/scoped_nodes.py | 8 +++---- astroid/objects.py | 2 +- astroid/protocols.py | 16 ++++++------- astroid/rebuilder.py | 2 +- 10 files changed, 45 insertions(+), 49 deletions(-) diff --git a/astroid/decorators.py b/astroid/decorators.py index 8baca60b67..6c8b1bac32 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -60,11 +60,9 @@ def wrapped( def yes_if_nothing_inferred( - func: Callable[_P, Generator[InferenceResult, None, None]] -) -> Callable[_P, Generator[InferenceResult, None, None]]: - def inner( - *args: _P.args, **kwargs: _P.kwargs - ) -> Generator[InferenceResult, None, None]: + func: Callable[_P, Generator[InferenceResult]] +) -> Callable[_P, Generator[InferenceResult]]: + def inner(*args: _P.args, **kwargs: _P.kwargs) -> Generator[InferenceResult]: generator = func(*args, **kwargs) try: @@ -80,11 +78,9 @@ def inner( def raise_if_nothing_inferred( - func: Callable[_P, Generator[InferenceResult, None, None]], -) -> Callable[_P, Generator[InferenceResult, None, None]]: - def inner( - *args: _P.args, **kwargs: _P.kwargs - ) -> Generator[InferenceResult, None, None]: + func: Callable[_P, Generator[InferenceResult]], +) -> Callable[_P, Generator[InferenceResult]]: + def inner(*args: _P.args, **kwargs: _P.kwargs) -> Generator[InferenceResult]: generator = func(*args, **kwargs) try: yield next(generator) diff --git a/astroid/helpers.py b/astroid/helpers.py index 244612146f..fe57b16bbc 100644 --- a/astroid/helpers.py +++ b/astroid/helpers.py @@ -60,7 +60,7 @@ def _function_type( def _object_type( node: InferenceResult, context: InferenceContext | None = None -) -> Generator[InferenceResult | None, None, None]: +) -> Generator[InferenceResult | None]: astroid_manager = manager.AstroidManager() builtins = astroid_manager.builtins_module context = context or InferenceContext() diff --git a/astroid/inference_tip.py b/astroid/inference_tip.py index cb1fb37909..c3187c0670 100644 --- a/astroid/inference_tip.py +++ b/astroid/inference_tip.py @@ -40,7 +40,7 @@ def inner( node: _NodesT, context: InferenceContext | None = None, **kwargs: Any, - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: partial_cache_key = (func, node) if partial_cache_key in _CURRENTLY_INFERRING: # If through recursion we end up trying to infer the same diff --git a/astroid/nodes/_base_nodes.py b/astroid/nodes/_base_nodes.py index d2bf331299..96c3c1c06d 100644 --- a/astroid/nodes/_base_nodes.py +++ b/astroid/nodes/_base_nodes.py @@ -328,11 +328,11 @@ class OperatorNode(NodeNG): def _filter_operation_errors( infer_callable: Callable[ [InferenceContext | None], - Generator[InferenceResult | util.BadOperationMessage, None, None], + Generator[InferenceResult | util.BadOperationMessage], ], context: InferenceContext | None, error: type[util.BadOperationMessage], - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: for result in infer_callable(context): if isinstance(result, error): # For the sake of .infer(), we don't care about operation @@ -392,7 +392,7 @@ def _invoke_binop_inference( other: InferenceResult, context: InferenceContext, method_name: str, - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: """Invoke binary operation inference on the given instance.""" methods = dunder_lookup.lookup(instance, method_name) context = bind_context_to_node(context, instance) @@ -431,7 +431,7 @@ def _aug_op( other: InferenceResult, context: InferenceContext, reverse: bool = False, - ) -> partial[Generator[InferenceResult, None, None]]: + ) -> partial[Generator[InferenceResult]]: """Get an inference callable for an augmented binary operation.""" method_name = AUGMENTED_OP_METHOD[op] return partial( @@ -452,7 +452,7 @@ def _bin_op( other: InferenceResult, context: InferenceContext, reverse: bool = False, - ) -> partial[Generator[InferenceResult, None, None]]: + ) -> partial[Generator[InferenceResult]]: """Get an inference callable for a normal binary operation. If *reverse* is True, then the reflected method will be used instead. @@ -475,7 +475,7 @@ def _bin_op( def _bin_op_or_union_type( left: bases.UnionType | nodes.ClassDef | nodes.Const, right: bases.UnionType | nodes.ClassDef | nodes.Const, - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: """Create a new UnionType instance for binary or, e.g. int | str.""" yield bases.UnionType(left, right) @@ -509,7 +509,7 @@ def _get_aug_flow( right_type: InferenceResult | None, context: InferenceContext, reverse_context: InferenceContext, - ) -> list[partial[Generator[InferenceResult, None, None]]]: + ) -> list[partial[Generator[InferenceResult]]]: """Get the flow for augmented binary operations. The rules are a bit messy: @@ -566,7 +566,7 @@ def _get_binop_flow( right_type: InferenceResult | None, context: InferenceContext, reverse_context: InferenceContext, - ) -> list[partial[Generator[InferenceResult, None, None]]]: + ) -> list[partial[Generator[InferenceResult]]]: """Get the flow for binary operations. The rules are a bit messy: @@ -627,7 +627,7 @@ def _infer_binary_operation( binary_opnode: nodes.AugAssign | nodes.BinOp, context: InferenceContext, flow_factory: GetFlowFactory, - ) -> Generator[InferenceResult | util.BadBinaryOperationMessage, None, None]: + ) -> Generator[InferenceResult | util.BadBinaryOperationMessage]: """Infer a binary operation between a left operand and a right operand. This is used by both normal binary operations and augmented binary diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index a87514866d..c1c7af36da 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -1028,7 +1028,7 @@ def get_children(self): @decorators.raise_if_nothing_inferred def _infer( self: nodes.Arguments, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: # pylint: disable-next=import-outside-toplevel from astroid.protocols import _arguments_infer_argname @@ -1417,7 +1417,7 @@ def _get_yield_nodes_skip_lambdas(self): def _infer_augassign( self, context: InferenceContext | None = None - ) -> Generator[InferenceResult | util.BadBinaryOperationMessage, None, None]: + ) -> Generator[InferenceResult | util.BadBinaryOperationMessage]: """Inference logic for augmented binary operations.""" context = context or InferenceContext() @@ -1447,7 +1447,7 @@ def _infer_augassign( @decorators.path_wrapper def _infer( self: nodes.AugAssign, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: return self._filter_operation_errors( self._infer_augassign, context, util.BadBinaryOperationMessage ) @@ -1532,7 +1532,7 @@ def op_left_associative(self) -> bool: def _infer_binop( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: """Binary operation inference logic.""" left = self.left right = self.right @@ -1562,7 +1562,7 @@ def _infer_binop( @decorators.path_wrapper def _infer( self: nodes.BinOp, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: return self._filter_operation_errors( self._infer_binop, context, util.BadBinaryOperationMessage ) @@ -1911,7 +1911,7 @@ def _do_compare( def _infer( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[nodes.Const | util.UninferableBase, None, None]: + ) -> Generator[nodes.Const | util.UninferableBase]: """Chained comparison inference logic.""" retval: bool | util.UninferableBase = True @@ -2561,7 +2561,7 @@ def has_underlying_object(self) -> bool: @decorators.path_wrapper def _infer( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: if not self.has_underlying_object(): yield util.Uninferable else: @@ -2851,7 +2851,7 @@ def _infer( context: InferenceContext | None = None, asname: bool = True, **kwargs: Any, - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: """Infer a ImportFrom node: return the imported module/object.""" context = context or InferenceContext() name = context.lookupname @@ -2976,7 +2976,7 @@ def _infer_name(self, frame, name): @decorators.path_wrapper def _infer( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: if context is None or context.lookupname is None: raise InferenceError(node=self, context=context) try: @@ -3093,7 +3093,7 @@ def op_left_associative(self) -> Literal[False]: @decorators.raise_if_nothing_inferred def _infer( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: """Support IfExp inference. If we can't infer the truthiness of the condition, we default @@ -3182,7 +3182,7 @@ def _infer( context: InferenceContext | None = None, asname: bool = True, **kwargs: Any, - ) -> Generator[nodes.Module, None, None]: + ) -> Generator[nodes.Module]: """Infer an Import node: return the imported module/object.""" context = context or InferenceContext() name = context.lookupname @@ -4123,7 +4123,7 @@ def _infer( InferenceContext | None, None, ], - Generator[NodeNG, None, None], + Generator[NodeNG], ] ] = protocols.assign_assigned_stmts @@ -4984,7 +4984,7 @@ def __init__( def _infer( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[NodeNG | util.UninferableBase, None, None]: + ) -> Generator[NodeNG | util.UninferableBase]: yield self.value diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py index ed46a7a115..3a482f3cc9 100644 --- a/astroid/nodes/node_ng.py +++ b/astroid/nodes/node_ng.py @@ -124,7 +124,7 @@ def __init__( def infer( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, None]: + ) -> Generator[InferenceResult]: """Get a generator of the inferred values. This is the main entry point to the inference system. diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index 48a1ccfc3a..af3b9d39a8 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -602,7 +602,7 @@ def frame(self: _T, *, future: Literal[None, True] = None) -> _T: def _infer( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[Module, None, None]: + ) -> Generator[Module]: yield self @@ -1052,7 +1052,7 @@ def getattr( def _infer( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[Lambda, None, None]: + ) -> Generator[Lambda]: yield self def _get_yield_nodes_skip_functions(self): @@ -2222,7 +2222,7 @@ def basenames(self): def ancestors( self, recurs: bool = True, context: InferenceContext | None = None - ) -> Generator[ClassDef, None, None]: + ) -> Generator[ClassDef]: """Iterate over the base classes in prefixed depth first order. :param recurs: Whether to recurse or return direct ancestors only. @@ -2975,5 +2975,5 @@ def frame(self: _T, *, future: Literal[None, True] = None) -> _T: def _infer( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[ClassDef, None, None]: + ) -> Generator[ClassDef]: yield self diff --git a/astroid/objects.py b/astroid/objects.py index f94d1f16ff..2d12f59805 100644 --- a/astroid/objects.py +++ b/astroid/objects.py @@ -362,5 +362,5 @@ def infer_call_result( def _infer( self: _T, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[_T, None, None]: + ) -> Generator[_T]: yield self diff --git a/astroid/protocols.py b/astroid/protocols.py index 8e90ddab58..bacb786a99 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -106,7 +106,7 @@ def const_infer_binary_op( other: InferenceResult, context: InferenceContext, _: SuccessfulInferenceResult, -) -> Generator[ConstFactoryResult | util.UninferableBase, None, None]: +) -> Generator[ConstFactoryResult | util.UninferableBase]: not_implemented = nodes.Const(NotImplemented) if isinstance(other, nodes.Const): if ( @@ -176,7 +176,7 @@ def tl_infer_binary_op( other: InferenceResult, context: InferenceContext, method: SuccessfulInferenceResult, -) -> Generator[_TupleListNodeT | nodes.Const | util.UninferableBase, None, None]: +) -> Generator[_TupleListNodeT | nodes.Const | util.UninferableBase]: """Infer a binary operation on a tuple or list. The instance on which the binary operation is performed is a tuple @@ -224,7 +224,7 @@ def instance_class_infer_binary_op( other: InferenceResult, context: InferenceContext, method: SuccessfulInferenceResult, -) -> Generator[InferenceResult, None, None]: +) -> Generator[InferenceResult]: return method.infer_call_result(self, context) @@ -347,7 +347,7 @@ def assend_assigned_stmts( def _arguments_infer_argname( self, name: str | None, context: InferenceContext -) -> Generator[InferenceResult, None, None]: +) -> Generator[InferenceResult]: # arguments information may be missing, in which case we can't do anything # more from astroid import arguments # pylint: disable=import-outside-toplevel @@ -877,7 +877,7 @@ def match_mapping_assigned_stmts( node: nodes.AssignName, context: InferenceContext | None = None, assign_path: None = None, -) -> Generator[nodes.NodeNG, None, None]: +) -> Generator[nodes.NodeNG]: """Return empty generator (return -> raises StopIteration) so inferred value is Uninferable. """ @@ -891,7 +891,7 @@ def match_star_assigned_stmts( node: nodes.AssignName, context: InferenceContext | None = None, assign_path: None = None, -) -> Generator[nodes.NodeNG, None, None]: +) -> Generator[nodes.NodeNG]: """Return empty generator (return -> raises StopIteration) so inferred value is Uninferable. """ @@ -905,7 +905,7 @@ def match_as_assigned_stmts( node: nodes.AssignName, context: InferenceContext | None = None, assign_path: None = None, -) -> Generator[nodes.NodeNG, None, None]: +) -> Generator[nodes.NodeNG]: """Infer MatchAs as the Match subject if it's the only MatchCase pattern else raise StopIteration to yield Uninferable. """ @@ -923,7 +923,7 @@ def generic_type_assigned_stmts( node: nodes.AssignName, context: InferenceContext | None = None, assign_path: None = None, -) -> Generator[nodes.NodeNG, None, None]: +) -> Generator[nodes.NodeNG]: """Hack. Return any Node so inference doesn't fail when evaluating __class_getitem__. Revert if it's causing issues. """ diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 70ea53718a..b783885019 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -912,7 +912,7 @@ def visit_delete(self, node: ast.Delete, parent: NodeNG) -> nodes.Delete: def _visit_dict_items( self, node: ast.Dict, parent: NodeNG, newnode: nodes.Dict - ) -> Generator[tuple[NodeNG, NodeNG], None, None]: + ) -> Generator[tuple[NodeNG, NodeNG]]: for key, value in zip(node.keys, node.values): rebuilt_key: NodeNG rebuilt_value = self.visit(value, newnode) From f37549ebb455315e4df97e442defc4cc257fc2fb Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 4 Aug 2024 09:34:54 -0400 Subject: [PATCH 58/63] Fix release tests https://github.com/actions/setup-python/issues/866#issuecomment-2154968862 --- .github/workflows/release-tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml index 7f8d50c684..11a6e2b384 100644 --- a/.github/workflows/release-tests.yml +++ b/.github/workflows/release-tests.yml @@ -20,7 +20,11 @@ jobs: 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 From 47030b1b84760ca6f993c6b462797eb525fcdc47 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 4 Aug 2024 09:07:18 -0400 Subject: [PATCH 59/63] Bump astroid to 3.3.0, update changelog --- CONTRIBUTORS.txt | 3 +++ ChangeLog | 20 +++++++++++++------- astroid/__pkginfo__.py | 2 +- tbump.toml | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 8ec45caf50..04345d7cb5 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -206,3 +206,6 @@ under this name, or we did not manage to find their commits in the history. - Mark Gius - Jérome Perrin - Jamie Scott +- correctmost <134317971+correctmost@users.noreply.github.com> +- Oleh Prypin +- Eric Vergnaud diff --git a/ChangeLog b/ChangeLog index c9f5a26f81..4560e5d2b7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,10 +3,22 @@ astroid's ChangeLog =================== -What's New in astroid 3.3.0? +What's New in astroid 3.4.0? +============================ +Release date: TBA + + + +What's New in astroid 3.3.1? ============================ Release date: TBA + + +What's New in astroid 3.3.0? +============================ +Release date: 2024-08-04 + * Add support for Python 3.13. * Remove support for Python 3.8 (and constants `PY38`, `PY39_PLUS`, and `PYPY_7_3_11_PLUS`). @@ -24,12 +36,6 @@ Release date: TBA Closes pylint-dev/pylint#9849 -What's New in astroid 3.2.5? -============================ -Release date: TBA - - - What's New in astroid 3.2.4? ============================ Release date: 2024-07-20 diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index b89e0aae97..fa3f8a4f47 100644 --- a/astroid/__pkginfo__.py +++ b/astroid/__pkginfo__.py @@ -2,5 +2,5 @@ # For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE # Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt -__version__ = "3.3.0-dev0" +__version__ = "3.3.0" version = __version__ diff --git a/tbump.toml b/tbump.toml index 5a51a9b618..a25b9c889f 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/astroid" [version] -current = "3.3.0-dev0" +current = "3.3.0" regex = ''' ^(?P0|[1-9]\d*) \. From 8357bd31cb80bd28045d29bc723d49825f8a5812 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 08:07:39 -0400 Subject: [PATCH 60/63] Fix pylint regression with invalid format strings (#2496) (#2497) Catch exceptions when calling string.format (cherry picked from commit 04f4f3ff34e67bf00e8567752af92fac6f66e173) Co-authored-by: Eric Vergnaud --- ChangeLog | 3 ++ astroid/nodes/node_classes.py | 31 ++++++++++-------- tests/test_inference.py | 60 ++++++++++++++++++++++++++--------- 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4560e5d2b7..c08b1cbf2c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,9 @@ What's New in astroid 3.3.1? ============================ Release date: TBA +* Fix a crash introduced in 3.3.0 involving invalid format strings. + + Closes #2492 What's New in astroid 3.3.0? diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index c1c7af36da..1924c78eba 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -4687,19 +4687,24 @@ def _infer( uninferable_already_generated = True continue for value in self.value.infer(context, **kwargs): - if not isinstance(value, Const): - if not uninferable_already_generated: - yield util.Uninferable - uninferable_already_generated = True - continue - formatted = format(value.value, format_spec.value) - yield Const( - formatted, - lineno=self.lineno, - col_offset=self.col_offset, - end_lineno=self.end_lineno, - end_col_offset=self.end_col_offset, - ) + if isinstance(value, Const): + try: + formatted = format(value.value, format_spec.value) + yield Const( + formatted, + lineno=self.lineno, + col_offset=self.col_offset, + end_lineno=self.end_lineno, + end_col_offset=self.end_col_offset, + ) + continue + except (ValueError, TypeError): + # happens when format_spec.value is invalid + pass # fall through + if not uninferable_already_generated: + yield util.Uninferable + uninferable_already_generated = True + continue MISSING_VALUE = "{MISSING_VALUE}" diff --git a/tests/test_inference.py b/tests/test_inference.py index 61378043c3..a8b11b1614 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -666,21 +666,6 @@ def test_fstring_inference(self) -> None: self.assertIsInstance(value_node, Const) self.assertEqual(value_node.value, "Hello John!") - def test_formatted_fstring_inference(self) -> None: - code = """ - width = 10 - precision = 4 - value = 12.34567 - result = f"result: {value:{width}.{precision}}!" - """ - ast = parse(code, __name__) - node = ast["result"] - inferred = node.inferred() - self.assertEqual(len(inferred), 1) - value_node = inferred[0] - self.assertIsInstance(value_node, Const) - self.assertEqual(value_node.value, "result: 12.35!") - def test_float_complex_ambiguity(self) -> None: code = ''' def no_conjugate_member(magic_flag): #@ @@ -5517,6 +5502,51 @@ class instance(object): self.assertIsInstance(inferred, Instance) +@pytest.mark.parametrize( + "code, result", + [ + # regular f-string + ( + """width = 10 +precision = 4 +value = 12.34567 +result = f"result: {value:{width}.{precision}}!" +""", + "result: 12.35!", + ), + # unsupported format + ( + """width = None +precision = 4 +value = 12.34567 +result = f"result: {value:{width}.{precision}}!" +""", + None, + ), + # unsupported value + ( + """width = 10 +precision = 4 +value = None +result = f"result: {value:{width}.{precision}}!" +""", + None, + ), + ], +) +def test_formatted_fstring_inference(code, result) -> None: + ast = parse(code, __name__) + node = ast["result"] + inferred = node.inferred() + assert len(inferred) == 1 + value_node = inferred[0] + if result is None: + assert value_node is util.Uninferable + else: + assert isinstance(value_node, Const) + assert value_node.value == result + + def test_augassign_recursion() -> None: """Make sure inference doesn't throw a RecursionError. From de58003b577c6111df1505b794f836fb1d643ef0 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 6 Aug 2024 08:13:34 -0400 Subject: [PATCH 61/63] Bump astroid to 3.3.1, update changelog --- ChangeLog | 8 +++++++- astroid/__pkginfo__.py | 2 +- tbump.toml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index c08b1cbf2c..c67ec5dd18 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,10 +9,16 @@ Release date: TBA -What's New in astroid 3.3.1? +What's New in astroid 3.3.2? ============================ Release date: TBA + + +What's New in astroid 3.3.1? +============================ +Release date: 2024-08-06 + * Fix a crash introduced in 3.3.0 involving invalid format strings. Closes #2492 diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index fa3f8a4f47..f3dc052c63 100644 --- a/astroid/__pkginfo__.py +++ b/astroid/__pkginfo__.py @@ -2,5 +2,5 @@ # For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE # Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt -__version__ = "3.3.0" +__version__ = "3.3.1" version = __version__ diff --git a/tbump.toml b/tbump.toml index a25b9c889f..ce5d1f3ddd 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/astroid" [version] -current = "3.3.0" +current = "3.3.1" regex = ''' ^(?P0|[1-9]\d*) \. From b00b86c37e438b5d40b81956e86fb55fbd242d9b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 10 Aug 2024 17:06:09 -0400 Subject: [PATCH 62/63] [PY313] Add stubs for soft-deprecated typing members (#2503) (cherry picked from commit 86c7871563a52e47466837d4a8c7a7a91e43bf46) --- ChangeLog | 3 +++ astroid/brain/brain_typing.py | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/ChangeLog b/ChangeLog index c67ec5dd18..1196f75cee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,9 @@ What's New in astroid 3.3.2? ============================ Release date: TBA +* Restore support for soft-deprecated members of the ``typing`` module with python 3.13. + + Refs pylint-dev/pylint#9852 What's New in astroid 3.3.1? diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py index 8eadb9d602..38b01778b1 100644 --- a/astroid/brain/brain_typing.py +++ b/astroid/brain/brain_typing.py @@ -451,6 +451,18 @@ class TypeVar: @classmethod def __class_getitem__(cls, item): return cls class TypeVarTuple: ... + class ContextManager: + @classmethod + def __class_getitem__(cls, item): return cls + class AsyncContextManager: + @classmethod + def __class_getitem__(cls, item): return cls + class Pattern: + @classmethod + def __class_getitem__(cls, item): return cls + class Match: + @classmethod + def __class_getitem__(cls, item): return cls """ ) ) From 4ae46172a26bc9fd2a9668c69e4327c31cd48240 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 11 Aug 2024 07:47:19 -0400 Subject: [PATCH 63/63] Bump astroid to 3.3.2, update changelog --- ChangeLog | 8 +++++++- astroid/__pkginfo__.py | 2 +- tbump.toml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1196f75cee..d4b2ca2450 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,10 +9,16 @@ Release date: TBA -What's New in astroid 3.3.2? +What's New in astroid 3.3.3? ============================ Release date: TBA + + +What's New in astroid 3.3.2? +============================ +Release date: 2024-08-11 + * Restore support for soft-deprecated members of the ``typing`` module with python 3.13. Refs pylint-dev/pylint#9852 diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index f3dc052c63..99c1a03511 100644 --- a/astroid/__pkginfo__.py +++ b/astroid/__pkginfo__.py @@ -2,5 +2,5 @@ # For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE # Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt -__version__ = "3.3.1" +__version__ = "3.3.2" version = __version__ diff --git a/tbump.toml b/tbump.toml index ce5d1f3ddd..83d0b32940 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/astroid" [version] -current = "3.3.1" +current = "3.3.2" regex = ''' ^(?P0|[1-9]\d*) \.