From f41828d3c010a032066b7a196b116e010d5ea986 Mon Sep 17 00:00:00 2001 From: AmanSal1 Date: Mon, 17 Jul 2023 11:53:49 +0530 Subject: [PATCH 01/23] Added the message for deprecated attribute. --- pylint/checkers/deprecated.py | 42 ++++++++++++++++++++++++++- tests/checkers/unittest_deprecated.py | 26 +++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index 821a9836cf..f0d0fd626a 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -11,6 +11,8 @@ import astroid from astroid import nodes +from astroid import Attribute + from pylint.checkers import utils from pylint.checkers.base_checker import BaseChecker @@ -22,6 +24,7 @@ astroid.UnboundMethod, nodes.FunctionDef, nodes.ClassDef, + astroid.Attribute, ) @@ -31,6 +34,15 @@ class DeprecatedMixin(BaseChecker): A class implementing mixin must define "deprecated-method" Message. """ + DEPRECATED_ATTRIBUTE_MESSAGE: dict[str, MessageDefinitionTuple] = { + "W4906": ( + "Using deprecated attribute %r", + "deprecated-attribute", + "The attribute is marked as deprecated and will be removed in the future.", + {"old_names": [("WXXXX", "old-deprecated-attribute")], "shared": True}, + ), + } + DEPRECATED_MODULE_MESSAGE: dict[str, MessageDefinitionTuple] = { "W4901": ( "Deprecated module %r", @@ -76,6 +88,11 @@ class DeprecatedMixin(BaseChecker): ), } + @utils.only_required_for_messages("deprecated-attribute") + def visit_attribute(self, node: astroid.Attribute) -> None: + """Called when an `astroid.Attribute` node is visited.""" + self.check_deprecated_attribute(node) + @utils.only_required_for_messages( "deprecated-method", "deprecated-argument", @@ -189,13 +206,29 @@ def deprecated_classes(self, module: str) -> Iterable[str]: # pylint: disable=unused-argument return () + def deprecated_attribute(self) -> Container[str]: + """Callback returning the deprecated attributes. + + Returns: + collections.abc.Container of deprecated attribute names. + """ + return () + + def check_deprecated_attribute(self, node: astroid.Attribute) -> None: + """Checks if the attribute is deprecated.""" + qnames = {node.expr.name} + if any(name in self.deprecated_attribute() for name in qnames): + self.add_message("deprecated-attribute", node=node, args=(node.attrname,)) + def check_deprecated_module(self, node: nodes.Import, mod_path: str | None) -> None: """Checks if the module is deprecated.""" for mod_name in self.deprecated_modules(): if mod_path == mod_name or mod_path and mod_path.startswith(mod_name + "."): self.add_message("deprecated-module", node=node, args=mod_path) - def check_deprecated_method(self, node: nodes.Call, inferred: nodes.NodeNG) -> None: + def check_deprecated_method( + self, node: nodes.Call, inferred: nodes.NodeNG + ) -> None: """Executes the checker for the given node. This method should be called from the checker implementing this mixin. @@ -205,6 +238,13 @@ def check_deprecated_method(self, node: nodes.Call, inferred: nodes.NodeNG) -> N if not isinstance(inferred, ACCEPTABLE_NODES): return + if isinstance(node.func, nodes.Attribute) and isinstance( + node.func.expr, nodes.Name + ): + mod_name = node.func.expr.name + attr_name = node.func.attrname + self.check_deprecated_attribute(node, attr_name) + if isinstance(node.func, nodes.Attribute): func_name = node.func.attrname elif isinstance(node.func, nodes.Name): diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py index a9efaa505f..6982b34463 100644 --- a/tests/checkers/unittest_deprecated.py +++ b/tests/checkers/unittest_deprecated.py @@ -9,6 +9,7 @@ from pylint.checkers import BaseChecker, DeprecatedMixin from pylint.interfaces import UNDEFINED from pylint.testutils import CheckerTestCase, MessageTest +from pylint.testutils import CheckerTestCase, MessageTest class _DeprecatedChecker(DeprecatedMixin, BaseChecker): @@ -57,6 +58,31 @@ def deprecated_decorators(self) -> set[str]: class TestDeprecatedChecker(CheckerTestCase): CHECKER_CLASS = _DeprecatedChecker + def test_deprecated_attribute(self) -> None: + # Tests detecting deprecated attribute + node = astroid.extract_node( + """ + class DeprecatedClass: + deprecated_attribute = 42 + + obj = DeprecatedClass() + obj.deprecated_attribute + """ + ) + with self.assertAddsMessages( + MessageTest( + msg_id="deprecated-attribute", + args=("deprecated_attribute",), + node=node, + confidence=UNDEFINED, + line=7, + col_offset=0, + end_line=7, + end_col_offset=26, + ) + ): + self.checker.visit_attribute(node) + def test_deprecated_function(self) -> None: # Tests detecting deprecated function node = astroid.extract_node( From 7991753dd989c933ae02e97f2c129c54d81f3c29 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 06:36:09 +0000 Subject: [PATCH 02/23] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pylint/checkers/deprecated.py | 9 ++------- tests/checkers/unittest_deprecated.py | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index f0d0fd626a..1b5fee501c 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -11,8 +11,6 @@ import astroid from astroid import nodes -from astroid import Attribute - from pylint.checkers import utils from pylint.checkers.base_checker import BaseChecker @@ -226,9 +224,7 @@ def check_deprecated_module(self, node: nodes.Import, mod_path: str | None) -> N if mod_path == mod_name or mod_path and mod_path.startswith(mod_name + "."): self.add_message("deprecated-module", node=node, args=mod_path) - def check_deprecated_method( - self, node: nodes.Call, inferred: nodes.NodeNG - ) -> None: + def check_deprecated_method(self, node: nodes.Call, inferred: nodes.NodeNG) -> None: """Executes the checker for the given node. This method should be called from the checker implementing this mixin. @@ -239,9 +235,8 @@ def check_deprecated_method( return if isinstance(node.func, nodes.Attribute) and isinstance( - node.func.expr, nodes.Name + node.func.expr, nodes.Name ): - mod_name = node.func.expr.name attr_name = node.func.attrname self.check_deprecated_attribute(node, attr_name) diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py index 6982b34463..7faa321005 100644 --- a/tests/checkers/unittest_deprecated.py +++ b/tests/checkers/unittest_deprecated.py @@ -9,7 +9,6 @@ from pylint.checkers import BaseChecker, DeprecatedMixin from pylint.interfaces import UNDEFINED from pylint.testutils import CheckerTestCase, MessageTest -from pylint.testutils import CheckerTestCase, MessageTest class _DeprecatedChecker(DeprecatedMixin, BaseChecker): @@ -70,16 +69,16 @@ class DeprecatedClass: """ ) with self.assertAddsMessages( - MessageTest( - msg_id="deprecated-attribute", - args=("deprecated_attribute",), - node=node, - confidence=UNDEFINED, - line=7, - col_offset=0, - end_line=7, - end_col_offset=26, - ) + MessageTest( + msg_id="deprecated-attribute", + args=("deprecated_attribute",), + node=node, + confidence=UNDEFINED, + line=7, + col_offset=0, + end_line=7, + end_col_offset=26, + ) ): self.checker.visit_attribute(node) From 5063ba8e681318e6089c58601bc9760b28f6f6cd Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 21 Jul 2023 06:31:54 -0700 Subject: [PATCH 03/23] Register message, add one deprecated attribute, adjust logic --- pylint/checkers/deprecated.py | 18 ++++++++---------- pylint/checkers/stdlib.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index 1b5fee501c..4ec14211f1 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -204,7 +204,7 @@ def deprecated_classes(self, module: str) -> Iterable[str]: # pylint: disable=unused-argument return () - def deprecated_attribute(self) -> Container[str]: + def deprecated_attributes(self) -> Container[str]: """Callback returning the deprecated attributes. Returns: @@ -214,9 +214,13 @@ def deprecated_attribute(self) -> Container[str]: def check_deprecated_attribute(self, node: astroid.Attribute) -> None: """Checks if the attribute is deprecated.""" - qnames = {node.expr.name} - if any(name in self.deprecated_attribute() for name in qnames): - self.add_message("deprecated-attribute", node=node, args=(node.attrname,)) + inferred_expr = safe_infer(node.expr) + if not isinstance(inferred_expr, (nodes.ClassDef, nodes.Module)): + return + attribute_qname = ".".join((inferred_expr.qname(), node.attrname)) + for deprecated_name in self.deprecated_attributes(): + if attribute_qname == deprecated_name: + self.add_message("deprecated-attribute", node=node, args=(node.attrname,)) def check_deprecated_module(self, node: nodes.Import, mod_path: str | None) -> None: """Checks if the module is deprecated.""" @@ -234,12 +238,6 @@ def check_deprecated_method(self, node: nodes.Call, inferred: nodes.NodeNG) -> N if not isinstance(inferred, ACCEPTABLE_NODES): return - if isinstance(node.func, nodes.Attribute) and isinstance( - node.func.expr, nodes.Name - ): - attr_name = node.func.attrname - self.check_deprecated_attribute(node, attr_name) - if isinstance(node.func, nodes.Attribute): func_name = node.func.attrname elif isinstance(node.func, nodes.Name): diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index e1213d1bdd..ad947cdc45 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -332,6 +332,13 @@ } +DEPRECATED_ATTRIBUTES: DeprecationDict = { + (3, 12, 0): { + "calendar.January", + }, +} + + def _check_mode_str(mode: Any) -> bool: # check type if not isinstance(mode, str): @@ -370,6 +377,7 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): **DeprecatedMixin.DEPRECATED_ARGUMENT_MESSAGE, **DeprecatedMixin.DEPRECATED_CLASS_MESSAGE, **DeprecatedMixin.DEPRECATED_DECORATOR_MESSAGE, + **DeprecatedMixin.DEPRECATED_ATTRIBUTE_MESSAGE, "W1501": ( '"%s" is not a valid mode for open.', "bad-open-mode", @@ -489,6 +497,7 @@ def __init__(self, linter: PyLinter) -> None: self._deprecated_arguments: dict[str, tuple[tuple[int | None, str], ...]] = {} self._deprecated_classes: dict[str, set[str]] = {} self._deprecated_decorators: set[str] = set() + self._deprecated_attributes: set[str] = set() for since_vers, func_list in DEPRECATED_METHODS[sys.version_info[0]].items(): if since_vers <= sys.version_info: @@ -502,6 +511,9 @@ def __init__(self, linter: PyLinter) -> None: for since_vers, decorator_list in DEPRECATED_DECORATORS.items(): if since_vers <= sys.version_info: self._deprecated_decorators.update(decorator_list) + for since_vers, attribute_list in DEPRECATED_ATTRIBUTES.items(): + if since_vers <= sys.version_info: + self._deprecated_attributes.update(attribute_list) # Modules are checked by the ImportsChecker, because the list is # synced with the config argument deprecated-modules @@ -868,6 +880,8 @@ def deprecated_classes(self, module: str) -> Iterable[str]: def deprecated_decorators(self) -> Iterable[str]: return self._deprecated_decorators + def deprecated_attributes(self) -> Iterable[str]: + return self._deprecated_attributes def register(linter: PyLinter) -> None: linter.register_checker(StdlibChecker(linter)) From 81421f0a66d00d2697af77fa7f24dd16cf80ab78 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:33:45 +0000 Subject: [PATCH 04/23] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pylint/checkers/deprecated.py | 4 +++- pylint/checkers/stdlib.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index 4ec14211f1..da7ce9289a 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -220,7 +220,9 @@ def check_deprecated_attribute(self, node: astroid.Attribute) -> None: attribute_qname = ".".join((inferred_expr.qname(), node.attrname)) for deprecated_name in self.deprecated_attributes(): if attribute_qname == deprecated_name: - self.add_message("deprecated-attribute", node=node, args=(node.attrname,)) + self.add_message( + "deprecated-attribute", node=node, args=(node.attrname,) + ) def check_deprecated_module(self, node: nodes.Import, mod_path: str | None) -> None: """Checks if the module is deprecated.""" diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index ad947cdc45..cf284f13c2 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -883,5 +883,6 @@ def deprecated_decorators(self) -> Iterable[str]: def deprecated_attributes(self) -> Iterable[str]: return self._deprecated_attributes + def register(linter: PyLinter) -> None: linter.register_checker(StdlibChecker(linter)) From bad6c982dfe6419ecdf3ec21246ebf73a1417ae5 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 21 Jul 2023 23:32:07 -0700 Subject: [PATCH 05/23] Address failing tests --- pylint/checkers/deprecated.py | 9 +++++++-- tests/checkers/unittest_deprecated.py | 14 +++++++++----- .../class_diagrams/colorized_output/colorized.puml | 1 + .../colorized_output/custom_colors.dot | 2 +- .../colorized_output/custom_colors.puml | 1 + 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index da7ce9289a..0700a8c2c5 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -11,10 +11,12 @@ import astroid from astroid import nodes +from astroid.bases import Instance from pylint.checkers import utils from pylint.checkers.base_checker import BaseChecker from pylint.checkers.utils import get_import_name, infer_all, safe_infer +from pylint.interfaces import INFERENCE from pylint.typing import MessageDefinitionTuple ACCEPTABLE_NODES = ( @@ -215,13 +217,16 @@ def deprecated_attributes(self) -> Container[str]: def check_deprecated_attribute(self, node: astroid.Attribute) -> None: """Checks if the attribute is deprecated.""" inferred_expr = safe_infer(node.expr) - if not isinstance(inferred_expr, (nodes.ClassDef, nodes.Module)): + if not isinstance(inferred_expr, (nodes.ClassDef, Instance, nodes.Module)): return attribute_qname = ".".join((inferred_expr.qname(), node.attrname)) for deprecated_name in self.deprecated_attributes(): if attribute_qname == deprecated_name: self.add_message( - "deprecated-attribute", node=node, args=(node.attrname,) + "deprecated-attribute", + node=node, + args=(node.attrname,), + confidence=INFERENCE, ) def check_deprecated_module(self, node: nodes.Import, mod_path: str | None) -> None: diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py index 7faa321005..f591048555 100644 --- a/tests/checkers/unittest_deprecated.py +++ b/tests/checkers/unittest_deprecated.py @@ -3,11 +3,12 @@ # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt from __future__ import annotations +from collections.abc import Container import astroid from pylint.checkers import BaseChecker, DeprecatedMixin -from pylint.interfaces import UNDEFINED +from pylint.interfaces import UNDEFINED, INFERENCE from pylint.testutils import CheckerTestCase, MessageTest @@ -52,6 +53,9 @@ def deprecated_arguments( def deprecated_decorators(self) -> set[str]: return {".deprecated_decorator"} + def deprecated_attributes(self) -> Container[str]: + return {".DeprecatedClass.deprecated_attribute"} + # pylint: disable-next = too-many-public-methods class TestDeprecatedChecker(CheckerTestCase): @@ -73,11 +77,11 @@ class DeprecatedClass: msg_id="deprecated-attribute", args=("deprecated_attribute",), node=node, - confidence=UNDEFINED, - line=7, + confidence=INFERENCE, + line=6, col_offset=0, - end_line=7, - end_col_offset=26, + end_line=6, + end_col_offset=24, ) ): self.checker.visit_attribute(node) diff --git a/tests/pyreverse/functional/class_diagrams/colorized_output/colorized.puml b/tests/pyreverse/functional/class_diagrams/colorized_output/colorized.puml index dc9b7034ba..1c3d49d806 100644 --- a/tests/pyreverse/functional/class_diagrams/colorized_output/colorized.puml +++ b/tests/pyreverse/functional/class_diagrams/colorized_output/colorized.puml @@ -26,6 +26,7 @@ class "StdlibChecker" as pylint.checkers.stdlib.StdlibChecker #44BB99 { msgs : dict[str, MessageDefinitionTuple] name : str deprecated_arguments(method: str) -> tuple[tuple[int | None, str], ...] + deprecated_attributes() -> Iterable[str] deprecated_classes(module: str) -> Iterable[str] deprecated_decorators() -> Iterable[str] deprecated_methods() -> set[str] diff --git a/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.dot b/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.dot index 84c8142a35..4f1b5f8b1c 100644 --- a/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.dot +++ b/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.dot @@ -4,7 +4,7 @@ charset="utf-8" "custom_colors.CheckerCollector" [color="red", fontcolor="black", label=<{CheckerCollector|checker1
checker2
checker3
|}>, shape="record", style="filled"]; "pylint.extensions.check_elif.ElseifUsedChecker" [color="#44BB88", fontcolor="black", label=<{ElseifUsedChecker|msgs : dict
name : str
|leave_module(_: nodes.Module): None
process_tokens(tokens: list[TokenInfo]): None
visit_if(node: nodes.If): None
}>, shape="record", style="filled"]; "pylint.checkers.exceptions.ExceptionsChecker" [color="yellow", fontcolor="black", label=<{ExceptionsChecker|msgs : dict
name : str
options : tuple
|open(): None
visit_binop(node: nodes.BinOp): None
visit_compare(node: nodes.Compare): None
visit_raise(node: nodes.Raise): None
visit_try(node: nodes.Try): None
}>, shape="record", style="filled"]; -"pylint.checkers.stdlib.StdlibChecker" [color="yellow", fontcolor="black", label=<{StdlibChecker|msgs : dict[str, MessageDefinitionTuple]
name : str
|deprecated_arguments(method: str): tuple[tuple[int \| None, str], ...]
deprecated_classes(module: str): Iterable[str]
deprecated_decorators(): Iterable[str]
deprecated_methods(): set[str]
visit_boolop(node: nodes.BoolOp): None
visit_call(node: nodes.Call): None
visit_functiondef(node: nodes.FunctionDef): None
visit_if(node: nodes.If): None
visit_ifexp(node: nodes.IfExp): None
visit_unaryop(node: nodes.UnaryOp): None
}>, shape="record", style="filled"]; +"pylint.checkers.stdlib.StdlibChecker" [color="yellow", fontcolor="black", label=<{StdlibChecker|msgs : dict[str, MessageDefinitionTuple]
name : str
|deprecated_arguments(method: str): tuple[tuple[int \| None, str], ...]
deprecated_attributes(): Iterable[str]
deprecated_classes(module: str): Iterable[str]
deprecated_decorators(): Iterable[str]
deprecated_methods(): set[str]
visit_boolop(node: nodes.BoolOp): None
visit_call(node: nodes.Call): None
visit_functiondef(node: nodes.FunctionDef): None
visit_if(node: nodes.If): None
visit_ifexp(node: nodes.IfExp): None
visit_unaryop(node: nodes.UnaryOp): None
}>, shape="record", style="filled"]; "pylint.checkers.exceptions.ExceptionsChecker" -> "custom_colors.CheckerCollector" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="checker1", style="solid"]; "pylint.checkers.stdlib.StdlibChecker" -> "custom_colors.CheckerCollector" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="checker3", style="solid"]; "pylint.extensions.check_elif.ElseifUsedChecker" -> "custom_colors.CheckerCollector" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="checker2", style="solid"]; diff --git a/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.puml b/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.puml index 7119900639..a132fb0d3e 100644 --- a/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.puml +++ b/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.puml @@ -26,6 +26,7 @@ class "StdlibChecker" as pylint.checkers.stdlib.StdlibChecker #yellow { msgs : dict[str, MessageDefinitionTuple] name : str deprecated_arguments(method: str) -> tuple[tuple[int | None, str], ...] + deprecated_attributes() -> Iterable[str] deprecated_classes(module: str) -> Iterable[str] deprecated_decorators() -> Iterable[str] deprecated_methods() -> set[str] From 1b310420c9ec5b977c183ba463f6e62cbdd6220b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 22 Jul 2023 06:32:57 +0000 Subject: [PATCH 06/23] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/checkers/unittest_deprecated.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py index f591048555..c8068834bb 100644 --- a/tests/checkers/unittest_deprecated.py +++ b/tests/checkers/unittest_deprecated.py @@ -3,12 +3,13 @@ # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt from __future__ import annotations + from collections.abc import Container import astroid from pylint.checkers import BaseChecker, DeprecatedMixin -from pylint.interfaces import UNDEFINED, INFERENCE +from pylint.interfaces import INFERENCE, UNDEFINED from pylint.testutils import CheckerTestCase, MessageTest From 16ae5abdf075ed1fba876b885c89fb4d6d3d9d11 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 4 Sep 2023 19:08:43 -0400 Subject: [PATCH 07/23] Remove old_names Co-authored-by: Pierre Sassoulas --- pylint/checkers/deprecated.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index 0700a8c2c5..a349bae31a 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -39,7 +39,6 @@ class DeprecatedMixin(BaseChecker): "Using deprecated attribute %r", "deprecated-attribute", "The attribute is marked as deprecated and will be removed in the future.", - {"old_names": [("WXXXX", "old-deprecated-attribute")], "shared": True}, ), } From b9b11d84e10fac1f0be781624c83501c20e3b8d0 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 4 Sep 2023 19:11:28 -0400 Subject: [PATCH 08/23] Remove Container --- tests/checkers/unittest_deprecated.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py index c8068834bb..739c6e0a9c 100644 --- a/tests/checkers/unittest_deprecated.py +++ b/tests/checkers/unittest_deprecated.py @@ -4,8 +4,6 @@ from __future__ import annotations -from collections.abc import Container - import astroid from pylint.checkers import BaseChecker, DeprecatedMixin @@ -54,7 +52,7 @@ def deprecated_arguments( def deprecated_decorators(self) -> set[str]: return {".deprecated_decorator"} - def deprecated_attributes(self) -> Container[str]: + def deprecated_attributes(self) -> set[str]: return {".DeprecatedClass.deprecated_attribute"} From 3df564718747a29c3577689e3d232a50e721c5c7 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 16:16:59 -0400 Subject: [PATCH 09/23] Use qname --- pylint/checkers/deprecated.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index a349bae31a..f415531040 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -205,12 +205,8 @@ def deprecated_classes(self, module: str) -> Iterable[str]: # pylint: disable=unused-argument return () - def deprecated_attributes(self) -> Container[str]: - """Callback returning the deprecated attributes. - - Returns: - collections.abc.Container of deprecated attribute names. - """ + def deprecated_attributes(self) -> Iterable[str]: + """Callback returning the deprecated attributes.""" return () def check_deprecated_attribute(self, node: astroid.Attribute) -> None: @@ -224,7 +220,7 @@ def check_deprecated_attribute(self, node: astroid.Attribute) -> None: self.add_message( "deprecated-attribute", node=node, - args=(node.attrname,), + args=(attribute_qname,), confidence=INFERENCE, ) From beec69c3310bb3b360751947d497160548c84f4a Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 16:24:37 -0400 Subject: [PATCH 10/23] shared: True --- pylint/checkers/deprecated.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index f415531040..3428e736f8 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -39,6 +39,7 @@ class DeprecatedMixin(BaseChecker): "Using deprecated attribute %r", "deprecated-attribute", "The attribute is marked as deprecated and will be removed in the future.", + {"shared": True}, ), } From 2a25f5aabb2cbfe1188d0b646d298b3bec1025d9 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 16:44:29 -0400 Subject: [PATCH 11/23] Add further 3.12 deprecations --- pylint/checkers/stdlib.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index cf284f13c2..ef62a04ef5 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -84,7 +84,9 @@ }, (3, 9, 0): {"random.Random.shuffle": ((1, "random"),)}, (3, 12, 0): { + "argparse.BooleanOptionalAction": ((3, "type"), (4, "choices"), (7, "metavar")), "coroutine.throw": ((1, "value"), (2, "traceback")), + "email.utils.localtime": ((1, "isdst"),), "shutil.rmtree": ((2, "onerror"),), }, } @@ -261,6 +263,10 @@ "builtins.bool.__invert__", "datetime.datetime.utcfromtimestamp", "datetime.datetime.utcnow", + "pkgutil.find_loader", + "pkgutil.get_loader", + "pty.master_open", + "pty.slave_open", "xml.etree.ElementTree.Element.__bool__", }, }, @@ -324,7 +330,29 @@ }, }, (3, 12, 0): { + "ast": { + "Bytes", + "Ellipsis", + "NameConstant", + "Num", + "Str", + }, + "asyncio": { + "AbstractChildWatcher", + "MultiLoopChildWatcher", + "FastChildWatcher", + "SafeChildWatcher", + }, + "collections.abc": { + "ByteString", + }, + "importlib.abc": { + "ResourceReader", + "Traversable", + "TraversableResources", + }, "typing": { + "ByteString", "Hashable", "Sized", }, @@ -335,6 +363,10 @@ DEPRECATED_ATTRIBUTES: DeprecationDict = { (3, 12, 0): { "calendar.January", + "calendar.February", + "sys.last_traceback", + "sys.last_type", + "sys.last_value", }, } From e2bf5c01ebb4ae1201c27507a00f160b42c8e142 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 16:48:41 -0400 Subject: [PATCH 12/23] Add news --- doc/whatsnew/fragments/8855.new_check | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 doc/whatsnew/fragments/8855.new_check diff --git a/doc/whatsnew/fragments/8855.new_check b/doc/whatsnew/fragments/8855.new_check new file mode 100644 index 0000000000..43eca39240 --- /dev/null +++ b/doc/whatsnew/fragments/8855.new_check @@ -0,0 +1,3 @@ +Added a ``deprecated-attribute`` message to check deprecated attributes in the stdlib. + +Closes #8855 From 2a310c661902ff9e4d4c7c0e8f45e8cd52944da8 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 16:52:41 -0400 Subject: [PATCH 13/23] Add functional test --- tests/functional/d/deprecated/deprecated_attribute_py312.py | 4 ++++ tests/functional/d/deprecated/deprecated_attribute_py312.rc | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 tests/functional/d/deprecated/deprecated_attribute_py312.py create mode 100644 tests/functional/d/deprecated/deprecated_attribute_py312.rc diff --git a/tests/functional/d/deprecated/deprecated_attribute_py312.py b/tests/functional/d/deprecated/deprecated_attribute_py312.py new file mode 100644 index 0000000000..f8613573e6 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_attribute_py312.py @@ -0,0 +1,4 @@ +"""Test deprecated-attribute""" + +import calendar +print(calendar.January) # [deprecated-attribute] diff --git a/tests/functional/d/deprecated/deprecated_attribute_py312.rc b/tests/functional/d/deprecated/deprecated_attribute_py312.rc new file mode 100644 index 0000000000..9c966d4bda --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_attribute_py312.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.12 From ac275d7925f2cf721270f798205165bf33a6be0b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 16:54:57 -0400 Subject: [PATCH 14/23] Fix tests --- tests/checkers/unittest_deprecated.py | 2 +- tests/functional/d/deprecated/deprecated_attribute_py312.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/functional/d/deprecated/deprecated_attribute_py312.txt diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py index 739c6e0a9c..f0aef8a06d 100644 --- a/tests/checkers/unittest_deprecated.py +++ b/tests/checkers/unittest_deprecated.py @@ -74,7 +74,7 @@ class DeprecatedClass: with self.assertAddsMessages( MessageTest( msg_id="deprecated-attribute", - args=("deprecated_attribute",), + args=(".DeprecatedClass.deprecated_attribute",), node=node, confidence=INFERENCE, line=6, diff --git a/tests/functional/d/deprecated/deprecated_attribute_py312.txt b/tests/functional/d/deprecated/deprecated_attribute_py312.txt new file mode 100644 index 0000000000..f86fde7c7e --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_attribute_py312.txt @@ -0,0 +1 @@ +deprecated-attribute:4:6:4:22::Using deprecated attribute 'calendar.January':INFERENCE From 9b1e4c01f8efb7ea3d8f462f509e6fc22d8df333 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 16:59:23 -0400 Subject: [PATCH 15/23] Document message with examples --- doc/data/messages/d/deprecated-argument/details.rst | 2 +- doc/data/messages/d/deprecated-attribute/bad.py | 3 +++ doc/data/messages/d/deprecated-attribute/details.rst | 2 ++ doc/data/messages/d/deprecated-attribute/good.py | 3 +++ doc/data/messages/d/deprecated-class/details.rst | 2 +- doc/data/messages/d/deprecated-decorator/details.rst | 2 +- doc/data/messages/d/deprecated-method/details.rst | 2 +- doc/data/messages/d/deprecated-module/details.rst | 2 +- 8 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 doc/data/messages/d/deprecated-attribute/bad.py create mode 100644 doc/data/messages/d/deprecated-attribute/details.rst create mode 100644 doc/data/messages/d/deprecated-attribute/good.py diff --git a/doc/data/messages/d/deprecated-argument/details.rst b/doc/data/messages/d/deprecated-argument/details.rst index 1bdc8563df..46294b9d57 100644 --- a/doc/data/messages/d/deprecated-argument/details.rst +++ b/doc/data/messages/d/deprecated-argument/details.rst @@ -1,2 +1,2 @@ -The actual replacement need to be studied on a case by case basis +The actual replacement needs to be studied on a case by case basis by reading the deprecation warning or the release notes. diff --git a/doc/data/messages/d/deprecated-attribute/bad.py b/doc/data/messages/d/deprecated-attribute/bad.py new file mode 100644 index 0000000000..4236064e88 --- /dev/null +++ b/doc/data/messages/d/deprecated-attribute/bad.py @@ -0,0 +1,3 @@ +import calendar + +month = calendar.January diff --git a/doc/data/messages/d/deprecated-attribute/details.rst b/doc/data/messages/d/deprecated-attribute/details.rst new file mode 100644 index 0000000000..46294b9d57 --- /dev/null +++ b/doc/data/messages/d/deprecated-attribute/details.rst @@ -0,0 +1,2 @@ +The actual replacement needs to be studied on a case by case basis +by reading the deprecation warning or the release notes. diff --git a/doc/data/messages/d/deprecated-attribute/good.py b/doc/data/messages/d/deprecated-attribute/good.py new file mode 100644 index 0000000000..0cf40f5be8 --- /dev/null +++ b/doc/data/messages/d/deprecated-attribute/good.py @@ -0,0 +1,3 @@ +import calendar + +month = calendar.JANUARY diff --git a/doc/data/messages/d/deprecated-class/details.rst b/doc/data/messages/d/deprecated-class/details.rst index 1bdc8563df..46294b9d57 100644 --- a/doc/data/messages/d/deprecated-class/details.rst +++ b/doc/data/messages/d/deprecated-class/details.rst @@ -1,2 +1,2 @@ -The actual replacement need to be studied on a case by case basis +The actual replacement needs to be studied on a case by case basis by reading the deprecation warning or the release notes. diff --git a/doc/data/messages/d/deprecated-decorator/details.rst b/doc/data/messages/d/deprecated-decorator/details.rst index 1bdc8563df..46294b9d57 100644 --- a/doc/data/messages/d/deprecated-decorator/details.rst +++ b/doc/data/messages/d/deprecated-decorator/details.rst @@ -1,2 +1,2 @@ -The actual replacement need to be studied on a case by case basis +The actual replacement needs to be studied on a case by case basis by reading the deprecation warning or the release notes. diff --git a/doc/data/messages/d/deprecated-method/details.rst b/doc/data/messages/d/deprecated-method/details.rst index 1bdc8563df..46294b9d57 100644 --- a/doc/data/messages/d/deprecated-method/details.rst +++ b/doc/data/messages/d/deprecated-method/details.rst @@ -1,2 +1,2 @@ -The actual replacement need to be studied on a case by case basis +The actual replacement needs to be studied on a case by case basis by reading the deprecation warning or the release notes. diff --git a/doc/data/messages/d/deprecated-module/details.rst b/doc/data/messages/d/deprecated-module/details.rst index 1bdc8563df..46294b9d57 100644 --- a/doc/data/messages/d/deprecated-module/details.rst +++ b/doc/data/messages/d/deprecated-module/details.rst @@ -1,2 +1,2 @@ -The actual replacement need to be studied on a case by case basis +The actual replacement needs to be studied on a case by case basis by reading the deprecation warning or the release notes. From dfeda75375fbfb58d38dcad1b9753fef1b2eb9fb Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 18:31:03 -0400 Subject: [PATCH 16/23] Update bad.py Co-authored-by: Pierre Sassoulas --- doc/data/messages/d/deprecated-attribute/bad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/data/messages/d/deprecated-attribute/bad.py b/doc/data/messages/d/deprecated-attribute/bad.py index 4236064e88..7b57863824 100644 --- a/doc/data/messages/d/deprecated-attribute/bad.py +++ b/doc/data/messages/d/deprecated-attribute/bad.py @@ -1,3 +1,3 @@ import calendar -month = calendar.January +month = calendar.January # [deprecated-attribute] From 9ac41f2f45670d6b679f86d891e9967e3d5e19c4 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 18:40:42 -0400 Subject: [PATCH 17/23] Bump Python to 3.12 on remaining CI jobs --- .github/workflows/checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 61abbaf4eb..068f60482b 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -13,7 +13,7 @@ on: env: CACHE_VERSION: 1 KEY_PREFIX: base-venv - DEFAULT_PYTHON: "3.11" + DEFAULT_PYTHON: "3.12" PRE_COMMIT_CACHE: ~/.cache/pre-commit concurrency: From 81a2574745cd6e64b3b8688e6c90293147785369 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 29 Oct 2023 10:13:44 -0400 Subject: [PATCH 18/23] Revert "Bump Python to 3.12 on remaining CI jobs" This reverts commit 9ac41f2f45670d6b679f86d891e9967e3d5e19c4. --- .github/workflows/checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 068f60482b..61abbaf4eb 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -13,7 +13,7 @@ on: env: CACHE_VERSION: 1 KEY_PREFIX: base-venv - DEFAULT_PYTHON: "3.12" + DEFAULT_PYTHON: "3.11" PRE_COMMIT_CACHE: ~/.cache/pre-commit concurrency: From 065d0bde678e7adcd9202f9dd3d4b71f5fe7c744 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 29 Oct 2023 10:26:37 -0400 Subject: [PATCH 19/23] Add further deprecated items --- pylint/checkers/stdlib.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index f7ad1c5fd3..df8b271bf7 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -230,6 +230,13 @@ "binascii.a2b_hqx", "binascii.rlecode_hqx", "binascii.rledecode_hqx", + "importlib.resources.contents", + "importlib.resources.is_resource", + "importlib.resources.open_binary", + "importlib.resources.open_text", + "importlib.resources.path", + "importlib.resources.read_binary", + "importlib.resources.read_text", }, (3, 10, 0): { "_sqlite3.enable_shared_cache", @@ -258,6 +265,7 @@ "unittest.TestLoader.loadTestsFromModule", "unittest.TestLoader.loadTestsFromTestCase", "unittest.TestLoader.getTestCaseNames", + "unittest.TestProgram.usageExit", }, (3, 12, 0): { "builtins.bool.__invert__", @@ -361,6 +369,9 @@ DEPRECATED_ATTRIBUTES: DeprecationDict = { + (3, 2, 0): { + "configparser.ParsingError.filename", + }, (3, 12, 0): { "calendar.January", "calendar.February", From efe03a47a3eaf4f2c99ccedce790a315c88a4611 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 29 Oct 2023 10:29:44 -0400 Subject: [PATCH 20/23] Update example --- doc/data/messages/d/deprecated-attribute/bad.py | 5 +++-- doc/data/messages/d/deprecated-attribute/good.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/data/messages/d/deprecated-attribute/bad.py b/doc/data/messages/d/deprecated-attribute/bad.py index 7b57863824..ccd4090b56 100644 --- a/doc/data/messages/d/deprecated-attribute/bad.py +++ b/doc/data/messages/d/deprecated-attribute/bad.py @@ -1,3 +1,4 @@ -import calendar +from configparser import ParsingError -month = calendar.January # [deprecated-attribute] +err = ParsingError("filename") +source = err.filename # [deprecated-attribute] diff --git a/doc/data/messages/d/deprecated-attribute/good.py b/doc/data/messages/d/deprecated-attribute/good.py index 0cf40f5be8..492cd8087a 100644 --- a/doc/data/messages/d/deprecated-attribute/good.py +++ b/doc/data/messages/d/deprecated-attribute/good.py @@ -1,3 +1,4 @@ -import calendar +from configparser import ParsingError -month = calendar.JANUARY +err = ParsingError("filename") +source = err.source From 934540b6e8ee88bbfa7ead83c33f855606811952 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 29 Oct 2023 11:16:09 -0400 Subject: [PATCH 21/23] Add deprecated-attribute to overview table --- doc/user_guide/messages/messages_overview.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst index cd63d3f85c..25073d4caf 100644 --- a/doc/user_guide/messages/messages_overview.rst +++ b/doc/user_guide/messages/messages_overview.rst @@ -229,6 +229,7 @@ All messages in the warning category: warning/consider-ternary-expression warning/dangerous-default-value warning/deprecated-argument + warning/deprecated-attribute warning/deprecated-class warning/deprecated-decorator warning/deprecated-method From c69302cd4874bdb9fb46b96685b2d0fcac8e05c3 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 29 Oct 2023 19:47:20 +0100 Subject: [PATCH 22/23] Upgrade feature.rst with new doc --- doc/user_guide/checkers/features.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 73b9bcdc0b..ad84f42454 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -1044,6 +1044,8 @@ Stdlib checker Messages emitted when using Python >= 3.5. :deprecated-argument (W4903): *Using deprecated argument %s of method %s()* The argument is marked as deprecated and will be removed in the future. +:deprecated-attribute (W4906): *Using deprecated attribute %r* + The attribute is marked as deprecated and will be removed in the future. :deprecated-class (W4904): *Using deprecated class %s of module %s* The class is marked as deprecated and will be removed in the future. :deprecated-decorator (W4905): *Using deprecated decorator %s()* From b8eae93746a571e6c98bd43f6e56b5a1cfc505c1 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 29 Oct 2023 19:59:59 +0100 Subject: [PATCH 23/23] missing whitespace --- doc/user_guide/checkers/features.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index ad84f42454..cd900b9943 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -1045,7 +1045,7 @@ Stdlib checker Messages :deprecated-argument (W4903): *Using deprecated argument %s of method %s()* The argument is marked as deprecated and will be removed in the future. :deprecated-attribute (W4906): *Using deprecated attribute %r* - The attribute is marked as deprecated and will be removed in the future. + The attribute is marked as deprecated and will be removed in the future. :deprecated-class (W4904): *Using deprecated class %s of module %s* The class is marked as deprecated and will be removed in the future. :deprecated-decorator (W4905): *Using deprecated decorator %s()*