Skip to content

F822 Undefined name in __all__ does not work in __init__.py - again #19740

Description

@netrix

Summary

I am creating issue with the same name as for #10095 because the situation is the same but I cannot reopen the issue.

The problem is that this rule is not reporting a problem as it suppose to when there is no import which is defined in __all__ inside __init__.py or at least it's not documented whether it won't do that.

Unfortunately it's not possible to use https://play.ruff.rs/ to create a basic project structure that allows such case to be tested so please have an example here:

Given two files with following content:

  1. __init__.py:
"""Dasd"""
from .some_def import func_to_export


__all__ = ["func_to_export", "func_not_imported"]
  1. some_def.py:
def func_to_export() -> str:
    """Satisfy D103."""
    return "This function is exported from some_def.py"


def func_not_imported() -> str:
    """Satisfy D103."""
    return "This function is NOT imported from some_def.py"

both in directory: test_package

Now running ruff check --select ALL test_package gives following output:

warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
test_package/__init__.py:1:1: D400 First line should end with a period
  |
1 | """Dasd"""
  | ^^^^^^^^^^ D400
2 | from .some_def import func_to_export
  |
  = help: Add period

test_package/__init__.py:1:1: D415 First line should end with a period, question mark, or exclamation point
  |
1 | """Dasd"""
  | ^^^^^^^^^^ D415
2 | from .some_def import func_to_export
  |
  = help: Add closing punctuation

test_package/some_def.py:1:1: D100 Missing docstring in public module
Found 3 errors.
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).

The output of the same command with --show-settings:

Output with settings

 ruff check --select ALL test_package --show-settings
warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
Resolved settings for: "/home/<cut_out>/tmp/test_package/__init__.py"

# General Settings
cache_dir = "/home/<cut_out>/tmp/.ruff_cache"
fix = false
fix_only = false
output_format = full
show_fixes = false
unsafe_fixes = hint

# File Resolver Settings
file_resolver.exclude = [
        ".bzr",
        ".direnv",
        ".eggs",
        ".git",
        ".git-rewrite",
        ".hg",
        ".ipynb_checkpoints",
        ".mypy_cache",
        ".nox",
        ".pants.d",
        ".pyenv",
        ".pytest_cache",
        ".pytype",
        ".ruff_cache",
        ".svn",
        ".tox",
        ".venv",
        ".vscode",
        "__pypackages__",
        "_build",
        "buck-out",
        "dist",
        "node_modules",
        "site-packages",
        "venv",
]
file_resolver.extend_exclude = []
file_resolver.force_exclude = false
file_resolver.include = [
        "*.py",
        "*.pyi",
        "*.ipynb",
        "**/pyproject.toml",
]
file_resolver.extend_include = []
file_resolver.respect_gitignore = true
file_resolver.project_root = "/home/<cut_out>/tmp"

# Linter Settings
linter.exclude = []
linter.project_root = "/home/<cut_out>/tmp"
linter.rules.enabled = [
        ...
        forward-annotation-syntax-error (F722),
        redefined-while-unused (F811),
        undefined-name (F821),
        undefined-export (F822),
        undefined-local (F823),
        unused-variable (F841),
        unused-annotation (F842),
        ...
]
linter.rules.should_fix = [
        ...
        continue-outside-loop (F702),
        yield-outside-function (F704),
        return-outside-function (F706),
        default-except-not-last (F707),
        forward-annotation-syntax-error (F722),
        redefined-while-unused (F811),
        undefined-name (F821),
        undefined-export (F822),
        undefined-local (F823),
        unused-variable (F841),
        unused-annotation (F842),
        raise-not-implemented (F901),
        blanket-type-ignore (PGH003),
]
linter.per_file_ignores = {}
linter.safety_table.forced_safe = []
linter.safety_table.forced_unsafe = []
linter.unresolved_target_version = none
linter.per_file_target_version = {}
linter.preview = disabled
linter.explicit_preview_rules = false
linter.extension = ExtensionMapping({})
linter.allowed_confusables = []
linter.builtins = []
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
linter.external = []
linter.ignore_init_module_imports = true
linter.logger_objects = []
linter.namespace_packages = []
linter.src = [
        "/home/<cut_out>/tmp",
        "/home/<cut_out>/tmp/src",
]
linter.tab_size = 4
linter.line_length = 88
linter.task_tags = [
        TODO,
        FIXME,
        XXX,
]
linter.typing_modules = []
linter.typing_extensions = true

# Linter Plugins
linter.flake8_annotations.mypy_init_return = false
linter.flake8_annotations.suppress_dummy_args = false
linter.flake8_annotations.suppress_none_returning = false
linter.flake8_annotations.allow_star_arg_any = false
linter.flake8_annotations.ignore_fully_untyped = false
linter.flake8_bandit.hardcoded_tmp_directory = [
        /tmp,
        /var/tmp,
        /dev/shm,
]
linter.flake8_bandit.check_typed_exception = false
linter.flake8_bandit.extend_markup_names = []
linter.flake8_bandit.allowed_markup_calls = []
linter.flake8_bugbear.extend_immutable_calls = []
linter.flake8_builtins.allowed_modules = []
linter.flake8_builtins.ignorelist = []
linter.flake8_builtins.strict_checking = false
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|,\s)\d{4})*
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
        _,
        gettext,
        ngettext,
]
linter.flake8_implicit_str_concat.allow_multiline = true
linter.flake8_import_conventions.aliases = {
        altair = alt,
        holoviews = hv,
        matplotlib = mpl,
        matplotlib.pyplot = plt,
        networkx = nx,
        numpy = np,
        numpy.typing = npt,
        pandas = pd,
        panel = pn,
        plotly.express = px,
        polars = pl,
        pyarrow = pa,
        seaborn = sns,
        tensorflow = tf,
        tkinter = tk,
        xml.etree.ElementTree = ET,
}
linter.flake8_import_conventions.banned_aliases = {}
linter.flake8_import_conventions.banned_from = []
linter.flake8_pytest_style.fixture_parentheses = false
linter.flake8_pytest_style.parametrize_names_type = tuple
linter.flake8_pytest_style.parametrize_values_type = list
linter.flake8_pytest_style.parametrize_values_row_type = tuple
linter.flake8_pytest_style.raises_require_match_for = [
        BaseException,
        Exception,
        ValueError,
        OSError,
        IOError,
        EnvironmentError,
        socket.error,
]
linter.flake8_pytest_style.raises_extend_require_match_for = []
linter.flake8_pytest_style.mark_parentheses = false
linter.flake8_quotes.inline_quotes = double
linter.flake8_quotes.multiline_quotes = double
linter.flake8_quotes.docstring_quotes = double
linter.flake8_quotes.avoid_escape = true
linter.flake8_self.ignore_names = [
        _make,
        _asdict,
        _replace,
        _fields,
        _field_defaults,
        _name_,
        _value_,
]
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []
linter.flake8_type_checking.strict = false
linter.flake8_type_checking.exempt_modules = [
        typing,
        typing_extensions,
]
linter.flake8_type_checking.runtime_required_base_classes = []
linter.flake8_type_checking.runtime_required_decorators = []
linter.flake8_type_checking.quote_annotations = false
linter.flake8_unused_arguments.ignore_variadic_names = false
linter.isort.required_imports = []
linter.isort.combine_as_imports = false
linter.isort.force_single_line = false
linter.isort.force_sort_within_sections = false
linter.isort.detect_same_package = true
linter.isort.case_sensitive = false
linter.isort.force_wrap_aliases = false
linter.isort.force_to_top = []
linter.isort.known_modules = {}
linter.isort.order_by_type = true
linter.isort.relative_imports_order = furthest_to_closest
linter.isort.single_line_exclusions = []
linter.isort.split_on_trailing_comma = true
linter.isort.classes = []
linter.isort.constants = []
linter.isort.variables = []
linter.isort.no_lines_before = []
linter.isort.lines_after_imports = -1
linter.isort.lines_between_types = 0
linter.isort.forced_separate = []
linter.isort.section_order = [
        known { type = future },
        known { type = standard_library },
        known { type = third_party },
        known { type = first_party },
        known { type = local_folder },
]
linter.isort.default_section = known { type = third_party }
linter.isort.no_sections = false
linter.isort.from_first = false
linter.isort.length_sort = false
linter.isort.length_sort_straight = false
linter.mccabe.max_complexity = 10
linter.pep8_naming.ignore_names = [
        setUp,
        tearDown,
        setUpClass,
        tearDownClass,
        setUpModule,
        tearDownModule,
        asyncSetUp,
        asyncTearDown,
        setUpTestData,
        failureException,
        longMessage,
        maxDiff,
]
linter.pep8_naming.classmethod_decorators = []
linter.pep8_naming.staticmethod_decorators = []
linter.pycodestyle.max_line_length = 88
linter.pycodestyle.max_doc_length = none
linter.pycodestyle.ignore_overlong_task_comments = false
linter.pyflakes.extend_generics = []
linter.pyflakes.allowed_unused_imports = []
linter.pylint.allow_magic_value_types = [
        str,
        bytes,
]
linter.pylint.allow_dunder_method_names = []
linter.pylint.max_args = 5
linter.pylint.max_positional_args = 5
linter.pylint.max_returns = 6
linter.pylint.max_bool_expr = 5
linter.pylint.max_branches = 12
linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false

# Formatter Settings
formatter.exclude = []
formatter.unresolved_target_version = 3.9
formatter.per_file_target_version = {}
formatter.preview = disabled
formatter.line_width = 88
formatter.line_ending = auto
formatter.indent_style = space
formatter.indent_width = 4
formatter.quote_style = double
formatter.magic_trailing_comma = respect
formatter.docstring_code_format = disabled
formatter.docstring_code_line_width = dynamic

# Analyze Settings
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.9
analyze.detect_string_imports = false
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}

As you can see the F822 is enabled and rules D400 and D415 report problem so the project structure is valid.

The expected result would be that F822 reported missing import.

On the other hand, LSP correctly reports the problem:

Image

Version

0.12.7

Metadata

Metadata

Assignees

No one assigned

    Labels

    ruleImplementing or modifying a lint rule

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions