ruff icon indicating copy to clipboard operation
ruff copied to clipboard

`PLR6301` doesn't consider base class methods

Open minusf opened this issue 1 year ago • 3 comments

Consider the following simple example (and common python pattern in general) in django: to build a management command, one simply inherits from BaseCommand and overrides some methods:

from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
    def add_arguments(self, parser):
        pass
    def handle(self, *args, **options):
        pass

Currently both of these are marked by ruff 0.5.0 as PLR6301 (https://docs.astral.sh/ruff/rules/no-self-use/).

I tell a lie: I copy pasted this "minimal" example into a new file and there is no warning 😁 However the moment I add actual code to the methods, the warnings show up:

from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
    def add_arguments(self, parser): 
        parser.add_argument("--id", required=True, type=int, help="Page id")

    def handle(self, *args, **options):
        pass

Out of curiosity I installed pylint and ran it on the file and it does not trigger this message...

[tool.ruff]
line-length = 100

[tool.ruff.lint]
preview = true
select = [
    "A",      # https://pypi.org/project/flake8-builtins/
    "B",      # https://pypi.org/project/flake8-bugbear/
    "C4",     # https://pypi.org/project/flake8-comprehensions/
    "C90",    # https://pypi.org/project/mccabe/
    "DJ",     # https://pypi.org/project/flake8-django/
    "DTZ",    # https://pypi.org/project/flake8-datetimez/
    "E", "W", # https://pypi.org/project/pycodestyle/
    "F",      # https://pypi.org/project/pyflakes/
    "FURB",   # https://pypi.org/project/refurb/
    "G",      # https://pypi.org/project/flake8-logging-format/
    "I",      # https://pypi.org/project/isort/
    "N",      # https://pypi.org/project/pep8-naming/
    "PERF",   # https://pypi.org/project/perflint/
    "PIE",    # https://pypi.org/project/flake8-pie/
    "PL",     # https://pypi.org/project/pylint/
    "PT",     # https://pypi.org/project/flake8-pytest-style/
    "PTH",    # https://pypi.org/project/flake8-use-pathlib/
    "RET",    # https://pypi.org/project/flake8-return/
    "RUF",    # Ruff-specific rules
    "S",      # https://pypi.org/project/flake8-bandit/
    "SIM",    # https://pypi.org/project/flake8-simplify/
    "UP",     # https://pypi.org/project/pyupgrade/
]
ignore = ["RUF012"]

[tool.ruff.lint.isort]
# Use a single line between direct and from import.
lines-between-types = 1
section-order = ["future", "standard-library", "third-party", "django", "wagtail", "first-party", "local-folder"]

[tool.ruff.lint.isort.sections]
# Group all Django imports into a separate section.
"django" = ["django"]
"wagtail" = ["wagtail"]

minusf avatar Jul 03 '24 15:07 minusf

I think this is because we're missing a check for method overrides from the base class which is done by Pylint (https://github.com/pylint-dev/pylint/blob/c8351395008e586170b0dda680594ba29b8fe0ef/pylint/extensions/no_self_use.py#L90).

We could possibly add this check with the current semantic model but it will only work if the base class is in the same file. In your case, it's being imported from another module which will require multifile analysis.

dhruvmanila avatar Jul 04 '24 04:07 dhruvmanila

Thank you for the quick analysis. This rule is clearly in preview mode, but in my understanding until multifile analysis is being done, even simple cases like this will be false positives. What is ruff's rule policy for these cases?

minusf avatar Jul 04 '24 08:07 minusf

The general suggestion for now is to mark such methods as overrides: https://play.ruff.rs/d0b3d8f4-1706-4a22-b19f-ea28c7acaacc

charliermarsh avatar Jul 04 '24 12:07 charliermarsh

I ran into something similar with a class that immediately raises a NotImplementedError. I've previously raised a similar issue (#12427) and there it was fixed using some generic definition of a "base class stub". I wonder whether that same definition could be used to determine when to flag PLR6301 and when not to.

DaniBodor avatar Jan 15 '25 12:01 DaniBodor

I have the same issue with a different library. I would like to just add a # noqa: PLR6301 to the whole class:

class Command(BaseCommand):  # noqa: PLR6301
    def add_arguments(self, parser): 
        print(1)

    def handle(self, *args, **options):
        print(2)

spaceone avatar Aug 11 '25 22:08 spaceone