Source code for rude.rules.pycodestyle.imports

"""
Import rules: E401, E402.

E401: multiple imports on one line
E402: module level import not at top of file
"""

from __future__ import annotations

from collections.abc import Iterator
from typing import TYPE_CHECKING, ClassVar

from rude.core.node_types import NodeType
from rude.core.rule import Rule
from rude.core.types import Diagnostic

if TYPE_CHECKING:
    from rude.core.node import Node


[docs] class MultipleImportsOnOneLine(Rule): """ E401: Multiple imports on one line. Rationale: PEP 8 requires each import on its own line for clarity and cleaner diffs. Example:: import os, sys # E401 import os # OK import sys """ code: ClassVar[str] = "E401" message: ClassVar[str] = "multiple imports on one line" node_types = {NodeType.IMPORT_STATEMENT}
[docs] def check(self, node: Node) -> Iterator[Diagnostic]: # Quick-reject: can't have multiple imports with <=1 named child if node.named_child_count <= 1: return # Count dotted_name children (each is an import) import_count = 0 for child in node.named_children: if child.type == "dotted_name": import_count += 1 if import_count > 1: yield self.diagnostic(node)
[docs] class ModuleLevelImportNotAtTop(Rule): """ E402: Module level import not at top of file. Imports should be at the top of the file, after any module docstring and comments, but before any other code. Example:: x = 1 import os # E402 import os # OK x = 1 """ code: ClassVar[str] = "E402" message: ClassVar[str] = "module level import not at top of file" node_types = {NodeType.IMPORT_STATEMENT, NodeType.IMPORT_FROM_STATEMENT}
[docs] def check(self, node: Node) -> Iterator[Diagnostic]: # Only check module-level imports if node.parent_type != "module": return # Get all module-level statements module = node.parent if module is None: return saw_code = False for child in module.named_children: if child == node: if saw_code: yield self.diagnostic(node) return # Skip docstrings, comments, __future__ imports, and type checking blocks if self._is_allowed_before_imports(child): continue # Any other statement means we've seen code saw_code = True
def _is_allowed_before_imports(self, node: Node) -> bool: """Check if node is allowed before imports.""" # Comments if node.type == "comment": return True # Module docstring (expression_statement with string) if node.type == "expression_statement": children = node.named_children if children and children[0].type in ("string", "concatenated_string"): return True # Import statements (including __future__) if node.type in ("import_statement", "import_from_statement", "future_import_statement"): return True # __all__, __version__, etc. assignments if node.type == "assignment": left = node.child_by_field("left") if left and left.is_identifier: name = left.text if name.startswith("__") and name.endswith("__"): return True # TYPE_CHECKING block if node.type == "if_statement": condition = node.child_by_field("condition") if condition and condition.text in ("TYPE_CHECKING", "typing.TYPE_CHECKING"): return True # try/except blocks (commonly used for conditional imports) return node.type == "try_statement"
IMPORT_RULES = [ MultipleImportsOnOneLine, ModuleLevelImportNotAtTop, ] __all__ = [ "IMPORT_RULES", "ModuleLevelImportNotAtTop", "MultipleImportsOnOneLine", ]