Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f41828d
Added the message for deprecated attribute.
AmanSal1 Jul 17, 2023
7991753
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 17, 2023
5063ba8
Register message, add one deprecated attribute, adjust logic
jacobtylerwalls Jul 21, 2023
81421f0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 21, 2023
bad6c98
Address failing tests
jacobtylerwalls Jul 22, 2023
1b31042
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 22, 2023
16ae5ab
Remove old_names
jacobtylerwalls Sep 4, 2023
b9b11d8
Remove Container
jacobtylerwalls Sep 4, 2023
3df5647
Use qname
jacobtylerwalls Oct 28, 2023
beec69c
shared: True
jacobtylerwalls Oct 28, 2023
2a25f5a
Add further 3.12 deprecations
jacobtylerwalls Oct 28, 2023
e2bf5c0
Add news
jacobtylerwalls Oct 28, 2023
2a310c6
Add functional test
jacobtylerwalls Oct 28, 2023
ac275d7
Fix tests
jacobtylerwalls Oct 28, 2023
9b1e4c0
Document message with examples
jacobtylerwalls Oct 28, 2023
d6e06d9
Merge branch 'main' into Attribute_message
jacobtylerwalls Oct 28, 2023
dfeda75
Update bad.py
jacobtylerwalls Oct 28, 2023
9ac41f2
Bump Python to 3.12 on remaining CI jobs
jacobtylerwalls Oct 28, 2023
81a2574
Revert "Bump Python to 3.12 on remaining CI jobs"
jacobtylerwalls Oct 29, 2023
065d0bd
Add further deprecated items
jacobtylerwalls Oct 29, 2023
efe03a4
Update example
jacobtylerwalls Oct 29, 2023
934540b
Add deprecated-attribute to overview table
jacobtylerwalls Oct 29, 2023
c69302c
Upgrade feature.rst with new doc
Pierre-Sassoulas Oct 29, 2023
b8eae93
missing whitespace
Pierre-Sassoulas Oct 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions doc/data/messages/d/deprecated-argument/details.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,2 @@
This message can be raised on your own code using a `custom deprecation checker`_ (follow link for a full example).

Loading this custom checker using ``load-plugins`` would start raising ``deprecated-argument``.

The actual replacement then need to be studied on a case by case basis by reading the
deprecation warning or the release notes.

.. _`custom deprecation checker`: https://github.com/pylint-dev/pylint/blob/main/examples/deprecation_checker.py
The actual replacement needs to be studied on a case by case basis
by reading the deprecation warning or the release notes.
4 changes: 4 additions & 0 deletions doc/data/messages/d/deprecated-attribute/bad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from configparser import ParsingError

err = ParsingError("filename")
source = err.filename # [deprecated-attribute]
2 changes: 2 additions & 0 deletions doc/data/messages/d/deprecated-attribute/details.rst
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions doc/data/messages/d/deprecated-attribute/good.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from configparser import ParsingError

err = ParsingError("filename")
source = err.source
2 changes: 1 addition & 1 deletion doc/data/messages/d/deprecated-class/details.rst
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion doc/data/messages/d/deprecated-decorator/details.rst
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion doc/data/messages/d/deprecated-method/details.rst
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion doc/data/messages/d/deprecated-module/details.rst
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions doc/user_guide/checkers/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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()*
Expand Down
1 change: 1 addition & 0 deletions doc/user_guide/messages/messages_overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/8855.new_check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Added a ``deprecated-attribute`` message to check deprecated attributes in the stdlib.

Closes #8855
36 changes: 36 additions & 0 deletions pylint/checkers/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@

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 = (
astroid.BoundMethod,
astroid.UnboundMethod,
nodes.FunctionDef,
nodes.ClassDef,
astroid.Attribute,
)


Expand All @@ -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.",
{"shared": True},
),
}

DEPRECATED_MODULE_MESSAGE: dict[str, MessageDefinitionTuple] = {
"W4901": (
"Deprecated module %r",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -189,6 +206,25 @@ def deprecated_classes(self, module: str) -> Iterable[str]:
# pylint: disable=unused-argument
return ()

def deprecated_attributes(self) -> Iterable[str]:
"""Callback returning the deprecated attributes."""
return ()

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, 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=(attribute_qname,),
confidence=INFERENCE,
)

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():
Expand Down
58 changes: 58 additions & 0 deletions pylint/checkers/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),),
},
}
Expand Down Expand Up @@ -228,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",
Expand Down Expand Up @@ -256,11 +265,16 @@
"unittest.TestLoader.loadTestsFromModule",
"unittest.TestLoader.loadTestsFromTestCase",
"unittest.TestLoader.getTestCaseNames",
"unittest.TestProgram.usageExit",
},
(3, 12, 0): {
"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__",
},
},
Expand Down Expand Up @@ -324,14 +338,50 @@
},
},
(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",
},
},
}


DEPRECATED_ATTRIBUTES: DeprecationDict = {
(3, 2, 0): {
"configparser.ParsingError.filename",
},
(3, 12, 0): {
"calendar.January",
"calendar.February",
"sys.last_traceback",
"sys.last_type",
"sys.last_value",
},
}


def _check_mode_str(mode: Any) -> bool:
# check type
if not isinstance(mode, str):
Expand Down Expand Up @@ -370,6 +420,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",
Expand Down Expand Up @@ -489,6 +540,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:
Expand All @@ -502,6 +554,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

Expand Down Expand Up @@ -868,6 +923,9 @@ 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))
30 changes: 29 additions & 1 deletion tests/checkers/unittest_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import astroid

from pylint.checkers import BaseChecker, DeprecatedMixin
from pylint.interfaces import UNDEFINED
from pylint.interfaces import INFERENCE, UNDEFINED
from pylint.testutils import CheckerTestCase, MessageTest


Expand Down Expand Up @@ -52,11 +52,39 @@ def deprecated_arguments(
def deprecated_decorators(self) -> set[str]:
return {".deprecated_decorator"}

def deprecated_attributes(self) -> set[str]:
return {".DeprecatedClass.deprecated_attribute"}


# pylint: disable-next = too-many-public-methods
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=(".DeprecatedClass.deprecated_attribute",),
node=node,
confidence=INFERENCE,
line=6,
col_offset=0,
end_line=6,
end_col_offset=24,
)
):
self.checker.visit_attribute(node)

def test_deprecated_function(self) -> None:
# Tests detecting deprecated function
node = astroid.extract_node(
Expand Down
4 changes: 4 additions & 0 deletions tests/functional/d/deprecated/deprecated_attribute_py312.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""Test deprecated-attribute"""

import calendar
print(calendar.January) # [deprecated-attribute]
2 changes: 2 additions & 0 deletions tests/functional/d/deprecated/deprecated_attribute_py312.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[testoptions]
min_pyver=3.12
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
deprecated-attribute:4:6:4:22::Using deprecated attribute 'calendar.January':INFERENCE
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ charset="utf-8"
"custom_colors.CheckerCollector" [color="red", fontcolor="black", label=<{CheckerCollector|checker1<br ALIGN="LEFT"/>checker2<br ALIGN="LEFT"/>checker3<br ALIGN="LEFT"/>|}>, shape="record", style="filled"];
"pylint.extensions.check_elif.ElseifUsedChecker" [color="#44BB88", fontcolor="black", label=<{ElseifUsedChecker|msgs : dict<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>|leave_module(_: nodes.Module): None<br ALIGN="LEFT"/>process_tokens(tokens: list[TokenInfo]): None<br ALIGN="LEFT"/>visit_if(node: nodes.If): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"];
"pylint.checkers.exceptions.ExceptionsChecker" [color="yellow", fontcolor="black", label=<{ExceptionsChecker|msgs : dict<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>options : tuple<br ALIGN="LEFT"/>|open(): None<br ALIGN="LEFT"/>visit_binop(node: nodes.BinOp): None<br ALIGN="LEFT"/>visit_compare(node: nodes.Compare): None<br ALIGN="LEFT"/>visit_raise(node: nodes.Raise): None<br ALIGN="LEFT"/>visit_try(node: nodes.Try): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"];
"pylint.checkers.stdlib.StdlibChecker" [color="yellow", fontcolor="black", label=<{StdlibChecker|msgs : dict[str, MessageDefinitionTuple]<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>|deprecated_arguments(method: str): tuple[tuple[int \| None, str], ...]<br ALIGN="LEFT"/>deprecated_classes(module: str): Iterable[str]<br ALIGN="LEFT"/>deprecated_decorators(): Iterable[str]<br ALIGN="LEFT"/>deprecated_methods(): set[str]<br ALIGN="LEFT"/>visit_boolop(node: nodes.BoolOp): None<br ALIGN="LEFT"/>visit_call(node: nodes.Call): None<br ALIGN="LEFT"/>visit_functiondef(node: nodes.FunctionDef): None<br ALIGN="LEFT"/>visit_if(node: nodes.If): None<br ALIGN="LEFT"/>visit_ifexp(node: nodes.IfExp): None<br ALIGN="LEFT"/>visit_unaryop(node: nodes.UnaryOp): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"];
"pylint.checkers.stdlib.StdlibChecker" [color="yellow", fontcolor="black", label=<{StdlibChecker|msgs : dict[str, MessageDefinitionTuple]<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>|deprecated_arguments(method: str): tuple[tuple[int \| None, str], ...]<br ALIGN="LEFT"/>deprecated_attributes(): Iterable[str]<br ALIGN="LEFT"/>deprecated_classes(module: str): Iterable[str]<br ALIGN="LEFT"/>deprecated_decorators(): Iterable[str]<br ALIGN="LEFT"/>deprecated_methods(): set[str]<br ALIGN="LEFT"/>visit_boolop(node: nodes.BoolOp): None<br ALIGN="LEFT"/>visit_call(node: nodes.Call): None<br ALIGN="LEFT"/>visit_functiondef(node: nodes.FunctionDef): None<br ALIGN="LEFT"/>visit_if(node: nodes.If): None<br ALIGN="LEFT"/>visit_ifexp(node: nodes.IfExp): None<br ALIGN="LEFT"/>visit_unaryop(node: nodes.UnaryOp): None<br ALIGN="LEFT"/>}>, 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"];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down