Skip to content

F401 creates infinite loop in preview mode when autofixing an unused first-party submodule import in an __init__.py file that has an __all__ definition #12513

@rshanker779

Description

@rshanker779

When having an __init__ file with an __all__ and an import that does not appear in __all__, an inifinte loop occurs as ruff tries to add the import to __all__. Full recreation details are below

List of keywords you searched for before creating this issue. Write them down here so that others can find this issue more easily and help provide feedback.

  • __all__ init bug
    A minimal code snippet that reproduces the bug.
import custom_module.a
from custom_module import b

__all__ = ['b']

This is with module structure

src/
   - custom_module
     - __init__.py # with the contents above
       - a
         - __init__.py #empty
       - b 
         -  __init__.py #empty

Running

ruff check --select=I,F401 --fix src/custom_module

Causes ruff to repeatedly add custom_module.a to the __all__ until it errors

The final output is

import custom_module.a
from custom_module import b

__all__ = ['b', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a', 'custom_module.a']

Ruff settings

[tool.ruff]
target-version = "py310"

line-length = 120
format.preview = true
format.docstring-code-format = true
lint.select = [
  "A",      # flake8-builtins
  "AIR",
  "ASYNC",
  "ASYNC1",
  "B",      # flake8-bugbear
  "BLE",    # flake8-blind-except
  "C4",
  "D",
  "DJ",
  "DTZ",
  "E",      # pycodestyle
  "ERA",    # eradicate
  "EXE",
  "F",      # pyflakes
  "FA",
  "FLY",    # flynt
  "FURB",
  "G",      # flake8-logging-format
  "I",      # isort
  "ICN",
  "INP",    # flake8-no-pep420
  "INT",
  "LOG",
  "N",      #pep8-naming
  "NPY",    # numpy
  "PD",     #pandas
  "PERF",
  "PGH",    # pygrep-hooks
  "PIE",
  "PLE",
  "PLW",
  "PT",     # flake8-pytest-style
  "PTH",    # flake8-use-pathlib
  "PYI",
  "Q",
  "RET",
  "RSE",
  "RUF",
  "S",      # flake8-bandit
  "SIM",
  "SLOT",
  "T",
  "TID",
  "TRY",    # tryceratops
  "UP",
  "W",      # pycodestyle
  "YTT",
]
lint.ignore = [
  "D100",  
  "D103",
  "D104",
  "D105",
  "D106",
  "D401",   
  "E501",
  "PIE790",
  "PLW1514",
  "PT001",
  "RUF002",
  "S101",
  "SIM300",
  "TRY003",
  "TRY300",
  "TRY301",
  # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
  "W191", # Enforced by the formatter
]
lint.per-file-ignores."**/{tests,scripts}/*" = [
  "ARG",
  "D",
  "INP",
  "PLR",
  "S101",
  "T20",
]
lint.fixable = [
  "F401",
  "I",
]
lint.pep8-naming.classmethod-decorators = [
  "pydantic.validator",
]
lint.pydocstyle.convention = "numpy"
lint.preview = true

Ruff version

ruff 0.5.2

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingfixesRelated to suggested fixes for violationspreviewRelated to preview mode features

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions