Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

B024: ruff in pre-commit fails to recognise B024 with the presence of a class variable #14455

Closed
cmp0xff opened this issue Nov 19, 2024 · 5 comments · Fixed by #14502
Closed
Assignees
Labels
question Asking for support or clarification

Comments

@cmp0xff
Copy link
Contributor

cmp0xff commented Nov 19, 2024

List of keywords I searched for before creating this issue

  • B024

A minimal code snippet that reproduces the bug

from abc import ABC


class Foo(ABC):
    a: int
    def method(self) -> None:
        print("nothing")

The command you invoked

poetry run pre-commit run -a

The current Ruff settings

Relevant snippet of .pre-commit-config.yaml

  - repo: https://github.com/astral-sh/ruff/
    rev: 0.7.4
    hooks:
      # Run the linter.
      - id: ruff
        args:
          - --fix
          - --exit-non-zero-on-fix

Relevant snippet of pyproject.py

[tool.ruff]
line-length = 88
force-exclude = true # Recommended for pre-commit (https://github.com/charliermarsh/ruff#force-exclude)
# Exclude a variety of commonly ignored directories.
exclude = [
    ".bzr",
    ".direnv",
    ".eggs",
    ".git",
    ".git-rewrite",
    ".hg",
    ".mypy_cache",
    ".nox",
    ".pants.d",
    ".pytype",
    ".ruff_cache",
    ".svn",
    ".tox",
    ".venv",
    "__pypackages__",
    "_build",
    "buck-out",
    "build",
    "dist",
    "node_modules",
    "venv",
]
target-version = "py310"
# unsafe-fixes = true

[tool.ruff.lint.per-file-ignores]
"*ipynb*" = [
  "B018",  # https://docs.astral.sh/ruff/rules/useless-expression/
]

[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`)  codes by default.
select = ["ALL"]
ignore = [
  "ANN001",  # https://docs.astral.sh/ruff/rules/missing-type-function-argument/
  "ANN201",  # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/
  "ANN202",  # https://docs.astral.sh/ruff/rules/missing-return-type-private-function/
  "ANN401",  # https://docs.astral.sh/ruff/rules/any-type/
  "ARG",     # https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg
  "B007",    # https://docs.astral.sh/ruff/rules/unused-loop-control-variable/
  "B023",    # https://docs.astral.sh/ruff/rules/function-uses-loop-variable/
  "B905",    # https://docs.astral.sh/ruff/rules/zip-without-explicit-strict/
  "C408",    # https://docs.astral.sh/ruff/rules/unnecessary-collection-call/
  "C901",    # https://docs.astral.sh/ruff/rules/complex-structure/
  "D",       # https://docs.astral.sh/ruff/rules/#pydocstyle-d
  "DTZ001",  # https://docs.astral.sh/ruff/rules/call-datetime-without-tzinfo/
  "E501",    # https://docs.astral.sh/ruff/rules/line-too-long/ (handled by black)
  "E722",    # https://docs.astral.sh/ruff/rules/bare-except/
  "E731",    # https://docs.astral.sh/ruff/rules/lambda-assignment/
  "F841",    # https://docs.astral.sh/ruff/rules/unused-variable/
  "G",       # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
  "S101",    # https://docs.astral.sh/ruff/rules/assert/
  "S301",    # https://docs.astral.sh/ruff/rules/suspicious-pickle-usage/
  "S608",    # https://docs.astral.sh/ruff/rules/hardcoded-sql-expression/
  "SIM114",  # https://docs.astral.sh/ruff/rules/if-with-same-arms/
  "BLE", "COM", "EM", "ERA", "FIX", "FBT", "FLY", "INP",
  "N",
  "PD",
  "PERF", "PGH",
  "PLC", "PLR", "PLW",
  "PT",
  "PTH",
  "PYI", "RET",
  "SLF",
  "T201",
  "TCH", "TD", "TID", "TRY", "UP", "W"
]

The current Ruff version

0.7.4

@dylwil3
Copy link
Collaborator

dylwil3 commented Nov 20, 2024

Thank you for submitting this issue!

This behavior is intentional - it is assumed that an unassigned, annotated class variable is intended to be abstract.

But that has certainly been a point of discussion/refinement. See this flake8-bugbear issue and the issues linked therein. Let us know if you think it should be further refined!

@MichaReiser MichaReiser added the question Asking for support or clarification label Nov 20, 2024
@cmp0xff
Copy link
Contributor Author

cmp0xff commented Nov 20, 2024

Hi, thank you for the reply.

In such a case, the documentation needs to be updated. For example, in abstract-base-class-without-abstract-method, instead of

Checks for abstract classes without abstract methods.

we can write

Checks for abstract classes without abstract methods or class variables.

Please let me know if I can continue completing and implementing this idea.

@dylwil3
Copy link
Collaborator

dylwil3 commented Nov 20, 2024

That'd be great! I think the check only skips if the class variable is annotated and not assigned, though. So you may want to adjust the wording a bit, maybe:

Checks for abstract classes without abstract methods. Class variables which are annotated but not assigned are regarded as abstract.

@cmp0xff
Copy link
Contributor Author

cmp0xff commented Nov 20, 2024

I am working on the documentation. Meanwhile, upon checking the Python documentation, it turns out that only when subscribed by typing.ClassVar is an annotate variable considered a class variable. Copied from the above-mentioned documentation:

class Starship:
    stats: ClassVar[dict[str, int]] = {} # class variable
    damage: int = 10                     # instance variable

What I had as an example in the original post is therefore an instance variable, not a class variable. This contradicts the discussion in PyCQA/flake8-bugbear#293, which only has to do with typing.ClassVar.

@dylwil3
Copy link
Collaborator

dylwil3 commented Nov 20, 2024

Hey @cmp0xff - you'll want to open a PR for ruff not docs. The relevant code is here:

/// ## What it does
/// Checks for abstract classes without abstract methods.
///
/// ## Why is this bad?
/// Abstract base classes are used to define interfaces. If an abstract base
/// class has no abstract methods, you may have forgotten to add an abstract
/// method to the class or omitted an `@abstractmethod` decorator.

Meanwhile, upon checking the Python documentation, it turns out that only when subscribed by typing.ClassVar is an annotate variable considered a class variable.

That's an interesting point... can you open a separate issue for this? For the present issue let's just change the documentation.

Thank you!

dylwil3 pushed a commit that referenced this issue Nov 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Asking for support or clarification
Projects
None yet
3 participants