diff --git a/README.rst b/README.rst index 6f1e4a7..74be7a0 100644 --- a/README.rst +++ b/README.rst @@ -73,7 +73,8 @@ Limitations ----------- Currently these checks are limited to module scope imports only. -Conditional imports in module scope will also be ignored. +Conditional imports in module scope will be ignored except imports +under ```if TYPE_CHECKING:``` block. Classification of an imported module is achieved by checking the module against a stdlib list and then if there is no match against the diff --git a/flake8_import_order/__init__.py b/flake8_import_order/__init__.py index fcf8fdb..24ff72e 100644 --- a/flake8_import_order/__init__.py +++ b/flake8_import_order/__init__.py @@ -17,7 +17,8 @@ ClassifiedImport = namedtuple( 'ClassifiedImport', - ['type', 'is_from', 'modules', 'names', 'lineno', 'level', 'package'], + ['type', 'is_from', 'modules', 'names', 'lineno', 'level', 'package', + 'type_checking'], ) NewLine = namedtuple('NewLine', ['lineno']) @@ -70,8 +71,13 @@ def __init__(self, application_import_names, application_package_names): self.application_import_names = frozenset(application_import_names) self.application_package_names = frozenset(application_package_names) + def generic_visit(self, node): + for child in ast.iter_child_nodes(node): + child.parent = node + return super().generic_visit(node) + def visit_Import(self, node): # noqa: N802 - if node.col_offset == 0: + if node.col_offset == 0 or self._type_checking_import(node): modules = [alias.name for alias in node.names] types_ = {self._classify_type(module) for module in modules} if len(types_) == 1: @@ -81,11 +87,12 @@ def visit_Import(self, node): # noqa: N802 classified_import = ClassifiedImport( type_, False, modules, [], node.lineno, 0, root_package_name(modules[0]), + self._type_checking_import(node), ) self.imports.append(classified_import) def visit_ImportFrom(self, node): # noqa: N802 - if node.col_offset == 0: + if node.col_offset == 0 or self._type_checking_import(node): module = node.module or '' if node.level > 0: type_ = ImportType.APPLICATION_RELATIVE @@ -96,9 +103,16 @@ def visit_ImportFrom(self, node): # noqa: N802 type_, True, [module], names, node.lineno, node.level, root_package_name(module), + self._type_checking_import(node), ) self.imports.append(classified_import) + def _type_checking_import(self, node): + return ( + isinstance(node.parent, ast.If) + and node.parent.test.id == "TYPE_CHECKING" + ) + def _classify_type(self, module): package_names = get_package_names(module) diff --git a/flake8_import_order/styles.py b/flake8_import_order/styles.py index ff13cee..cd8333d 100644 --- a/flake8_import_order/styles.py +++ b/flake8_import_order/styles.py @@ -37,6 +37,13 @@ def check(self): def _check(self, previous_import, previous, current_import): yield from self._check_I666(current_import) yield from self._check_I101(current_import) + if ( + previous_import is not None + and not previous_import.type_checking + and current_import.type_checking + ): + yield from self._check_I300(previous_import, current_import) + previous_import = None if previous_import is not None: yield from self._check_I100(previous_import, current_import) yield from self._check_I201(previous_import, previous, current_import) @@ -61,6 +68,14 @@ def _check_I101(self, current_import): # noqa: N802 "Should be {}".format(corrected), ) + def _check_I300(self, previous_import, current_import): # noqa: N802 + if current_import.lineno - previous_import.lineno != 3: + yield Error( + current_import.lineno, + 'I300', + "TYPE_CHECKING block should have one newline above.", + ) + def _check_I100(self, previous_import, current_import): # noqa: N802 previous_key = self.import_key(previous_import) current_key = self.import_key(current_import) diff --git a/tests/test_cases/additional_newline.py b/tests/test_cases/additional_newline.py index 95c4711..a98d4b9 100644 --- a/tests/test_cases/additional_newline.py +++ b/tests/test_cases/additional_newline.py @@ -22,3 +22,28 @@ from . import A from . import B # I202 + +if TYPE_CHECKING: + import ast + # This comment should not trigger a I202 (not a newline) + import os + + import signal # I202 + + import X + from X import B, b, \ + C, d + + from Y import A # I202 + from Y import ( + B, b, + C, d, + ) + from Z import A + + import flake8_import_order + + import tests # I202 + from . import A + + from . import B # I202 diff --git a/tests/test_cases/additional_newline_cryptography.py b/tests/test_cases/additional_newline_cryptography.py index 9e0a7bd..5b27e2f 100644 --- a/tests/test_cases/additional_newline_cryptography.py +++ b/tests/test_cases/additional_newline_cryptography.py @@ -14,3 +14,20 @@ from . import A from . import B # I202 + +if TYPE_CHECKING: + import ast + + import signal # I202 + + import X + + import Y + + import flake8_import_order + + import tests + + from . import A + + from . import B # I202 diff --git a/tests/test_cases/additional_newline_edited.py b/tests/test_cases/additional_newline_edited.py index 605324b..f1e651f 100644 --- a/tests/test_cases/additional_newline_edited.py +++ b/tests/test_cases/additional_newline_edited.py @@ -23,3 +23,29 @@ from . import A from . import B # I202 + +if TYPE_CHECKING: + import ast + # This comment should not trigger a I202 (not a newline) + import os + + import signal # I202 + + import X + from X import B, b, \ + C, d + + from Y import A # I202 + from Y import ( + B, b, + C, d, + ) + from Z import A + + import flake8_import_order + + import tests # I202 + + from . import A + + from . import B # I202 diff --git a/tests/test_cases/complete_appnexus.py b/tests/test_cases/complete_appnexus.py index 318852f..bca0ca6 100644 --- a/tests/test_cases/complete_appnexus.py +++ b/tests/test_cases/complete_appnexus.py @@ -33,3 +33,38 @@ from .. import B from ..A import A from ..B import B + +if TYPE_CHECKING: + import ast + from functools import * + import os + from os import path + import StringIO + import sys + + import X + from X import * + from X import A + from X import B, b, C, d + import Y + from Y import * + from Y import A + from Y import B, C, D + from Y import e + import Z + from Z import A + from Z.A import A + from Z.A.B import A + + from localpackage import A, b + + import flake8_import_order + from flake8_import_order import * + from . import A + from . import B + from .A import A + from .B import B + from .. import A + from .. import B + from ..A import A + from ..B import B diff --git a/tests/test_cases/complete_cryptography.py b/tests/test_cases/complete_cryptography.py index 70c64cf..8c46bbc 100644 --- a/tests/test_cases/complete_cryptography.py +++ b/tests/test_cases/complete_cryptography.py @@ -40,3 +40,45 @@ from .. import B from ..A import A from ..B import B + +if TYPE_CHECKING: + import ast + import os + import sys + from functools import * + from os import path + + import X + from X import * + from X import A + from X import B, C, D + + import Y + from Y import * + from Y import A + from Y import B, C, D + + import Z + from Z import A + from Z.A import A + from Z.A.B import A + + import localpackage + + import flake8_import_order + from flake8_import_order import * + from flake8_import_order import A + from flake8_import_order import B + + import tests + from tests import A + from tests import B + + from . import A + from . import B + from .A import A + from .B import B + from .. import A + from .. import B + from ..A import A + from ..B import B diff --git a/tests/test_cases/complete_edited.py b/tests/test_cases/complete_edited.py index 229b45e..968898c 100644 --- a/tests/test_cases/complete_edited.py +++ b/tests/test_cases/complete_edited.py @@ -36,3 +36,41 @@ from .. import B from ..A import A from ..B import B + +if TYPE_CHECKING: + import ast + import os + import StringIO + import sys + from functools import * + from os import path + + import X + import Y + import Z + from X import * + from X import A + from X import B, b, C, d + from Y import * + from Y import A + from Y import B, C, D + from Y import e + from Z import A + from Z.A import A + from Z.A.B import A + + import localpackage + + from localpackage import A, b + + import flake8_import_order + from flake8_import_order import * + + from . import A + from . import B + from .A import A + from .B import B + from .. import A + from .. import B + from ..A import A + from ..B import B \ No newline at end of file diff --git a/tests/test_cases/complete_google.py b/tests/test_cases/complete_google.py index 994032b..86c58bb 100644 --- a/tests/test_cases/complete_google.py +++ b/tests/test_cases/complete_google.py @@ -32,3 +32,37 @@ from .. import B from ..A import A from ..B import B + +if TYPE_CHECKING: + import ast + from functools import * + import os + from os import path + import StringIO + import sys + + import localpackage + import X + from X import * + from X import A + from X import B, b, C, d + import Y + from Y import * + from Y import A + from Y import B, C, D + from Y import e + import Z + from Z import A + from Z.A import A + from Z.A.B import A + + import flake8_import_order + from flake8_import_order import * + from . import A + from . import B + from .A import A + from .B import B + from .. import A + from .. import B + from ..A import A + from ..B import B diff --git a/tests/test_cases/complete_pep8.py b/tests/test_cases/complete_pep8.py index 1df6b1f..88e3f75 100644 --- a/tests/test_cases/complete_pep8.py +++ b/tests/test_cases/complete_pep8.py @@ -29,3 +29,34 @@ from .. import A from ..B import B from ..A import A + +if TYPE_CHECKING: + import sys + from os import path + import os + from functools import * + import ast + + import localpackage + import X + from X import A, d + from X import * + import Z + from Z import A + from Z.A.B import A + from Z.A import A + import Y + from Y import * + from Y import B + from Y import A, C, D + + import flake8_import_order + from flake8_import_order import * + from . import B + from . import A + from .B import B + from .A import A + from .. import B + from .. import A + from ..B import B + from ..A import A diff --git a/tests/test_cases/complete_pycharm.py b/tests/test_cases/complete_pycharm.py index 79f9c9f..7ece3fe 100644 --- a/tests/test_cases/complete_pycharm.py +++ b/tests/test_cases/complete_pycharm.py @@ -32,3 +32,37 @@ from .. import B from ..A import A from ..B import B + +if TYPE_CHECKING: + import StringIO + import ast + import os + import sys + from functools import * + from os import path + + import X + import Y + import Z + import localpackage + from X import * + from X import A + from X import B, C, b, d + from Y import * + from Y import A + from Y import B, C, D + from Y import e + from Z import A + from Z.A import A + from Z.A.B import A + + import flake8_import_order + from flake8_import_order import * + from . import A + from . import B + from .A import A + from .B import B + from .. import A + from .. import B + from ..A import A + from ..B import B diff --git a/tests/test_cases/complete_smarkets.py b/tests/test_cases/complete_smarkets.py index d03d215..13fa7bc 100644 --- a/tests/test_cases/complete_smarkets.py +++ b/tests/test_cases/complete_smarkets.py @@ -32,3 +32,37 @@ from .. import B from ..A import A from ..B import B + +if TYPE_CHECKING: + import ast + import os + import StringIO + import sys + from functools import * + from os import path + + import localpackage + import X + import Y + import Z + from X import * + from X import A + from X import B, b, C, d + from Y import * + from Y import A + from Y import B, C, D + from Y import e + from Z import A + from Z.A import A + from Z.A.B import A + + import flake8_import_order + from flake8_import_order import * + from . import A + from . import B + from .A import A + from .B import B + from .. import A + from .. import B + from ..A import A + from ..B import B diff --git a/tests/test_cases/missing_newline.py b/tests/test_cases/missing_newline.py index 8eb796c..4889745 100644 --- a/tests/test_cases/missing_newline.py +++ b/tests/test_cases/missing_newline.py @@ -1,5 +1,10 @@ -# appnexus cryptography edited google pep8 smarkets +# smarkets import ast # This comment should not prevent the I201 below, it is not a newline. import X # I201 import flake8_import_order # I201 +if TYPE_CHECKING: + import ast # I300 + # This comment should not prevent the I201 below, it is not a newline. + import X # I201 + import flake8_import_order # I201 diff --git a/tests/test_cases/missing_newline_cryptography.py b/tests/test_cases/missing_newline_cryptography.py index 8ebd7bf..541ac92 100644 --- a/tests/test_cases/missing_newline_cryptography.py +++ b/tests/test_cases/missing_newline_cryptography.py @@ -1,3 +1,7 @@ # cryptography import flake8_import_order import tests # I201 + +if TYPE_CHECKING: + import flake8_import_order + import tests # I201 diff --git a/tests/test_cases/mixed_groups.py b/tests/test_cases/mixed_groups.py index cf22129..0c6bbca 100644 --- a/tests/test_cases/mixed_groups.py +++ b/tests/test_cases/mixed_groups.py @@ -1,2 +1,5 @@ # appnexus cryptography edited google pep8 smarkets import ast, X, flake_import_order # I666 + +if TYPE_CHECKING: + import ast, X, flake_import_order # I666 diff --git a/tests/test_cases/namespace.py b/tests/test_cases/namespace.py index 439740c..4e204a7 100644 --- a/tests/test_cases/namespace.py +++ b/tests/test_cases/namespace.py @@ -4,3 +4,10 @@ import flake8_import_order import namespace.package_b + +if TYPE_CHECKING: + import namespace + import namespace.package_a + + import flake8_import_order + import namespace.package_b diff --git a/tests/test_cases/noqa.py b/tests/test_cases/noqa.py index efb6945..a650bb0 100644 --- a/tests/test_cases/noqa.py +++ b/tests/test_cases/noqa.py @@ -7,3 +7,13 @@ import unittest import X # noqa from . import B, C, A # I201 # noqa: I101 + +if TYPE_CHECKING: + import ast + + import sys # noqa: I202 + + import os # noqa + import unittest + import X # noqa + from . import B, C, A # I201 # noqa: I101 diff --git a/tests/test_cases/stdlib_shadowing.py b/tests/test_cases/stdlib_shadowing.py index eb4343e..1b915bb 100644 --- a/tests/test_cases/stdlib_shadowing.py +++ b/tests/test_cases/stdlib_shadowing.py @@ -1,3 +1,7 @@ # appnexus cryptography google pep8 smarkets from .filesystem import FilesystemStorage from os import path # I100 I201 + +if TYPE_CHECKING: + from .filesystem import FilesystemStorage + from os import path # I100 I201 diff --git a/tests/test_cases/wrong_from_import_order.py b/tests/test_cases/wrong_from_import_order.py index 3b18815..806eeda 100644 --- a/tests/test_cases/wrong_from_import_order.py +++ b/tests/test_cases/wrong_from_import_order.py @@ -2,3 +2,8 @@ from A import a, A # I101 from B import b, A # I101 from C import b, a # I101 + +if TYPE_CHECKING: + from A import a, A # I101 + from B import b, A # I101 + from C import b, a # I101 diff --git a/tests/test_cases/wrong_from_import_order_cryptography.py b/tests/test_cases/wrong_from_import_order_cryptography.py index e471eb5..f5f29a9 100644 --- a/tests/test_cases/wrong_from_import_order_cryptography.py +++ b/tests/test_cases/wrong_from_import_order_cryptography.py @@ -4,3 +4,10 @@ from B import A, a, B # I101 from C import b, a # I101 + +if TYPE_CHECKING: + from A import a, A # I101 + + from B import A, a, B # I101 + + from C import b, a # I101 diff --git a/tests/test_cases/wrong_from_import_same.py b/tests/test_cases/wrong_from_import_same.py index 70f8dd2..8185f71 100644 --- a/tests/test_cases/wrong_from_import_same.py +++ b/tests/test_cases/wrong_from_import_same.py @@ -1,3 +1,7 @@ # cryptography from os import system from os import path # I100 + +if TYPE_CHECKING: + from os import system + from os import path # I100 diff --git a/tests/test_cases/wrong_import_order.py b/tests/test_cases/wrong_import_order.py index 0bcac9e..2add7e5 100644 --- a/tests/test_cases/wrong_import_order.py +++ b/tests/test_cases/wrong_import_order.py @@ -1,3 +1,7 @@ # appnexus edited google smarkets import a import A # I100 + +if TYPE_CHECKING: + import a + import A # I100 diff --git a/tests/test_cases/wrong_import_order_cryptography.py b/tests/test_cases/wrong_import_order_cryptography.py index 4ed0df0..9436816 100644 --- a/tests/test_cases/wrong_import_order_cryptography.py +++ b/tests/test_cases/wrong_import_order_cryptography.py @@ -4,3 +4,10 @@ import a import B # I100 + +if TYPE_CHECKING: + import A + + import a + + import B # I100 diff --git a/tests/test_cases/wrong_relative_order.py b/tests/test_cases/wrong_relative_order.py index 4ca180f..18174bb 100644 --- a/tests/test_cases/wrong_relative_order.py +++ b/tests/test_cases/wrong_relative_order.py @@ -1,3 +1,7 @@ # appnexus cryptography edited google smarkets from .. import A from . import B # I100 + +if TYPE_CHECKING: + from .. import A + from . import B # I100 diff --git a/tests/test_cases/wrong_section.py b/tests/test_cases/wrong_section.py index 2e98c25..9e35892 100644 --- a/tests/test_cases/wrong_section.py +++ b/tests/test_cases/wrong_section.py @@ -6,3 +6,12 @@ import A import os # I100 + +if TYPE_CHECKING: + import ast + + import flake8_import_order # I100 + + import A + + import os # I100 diff --git a/tests/test_cases/wrong_section_appnexus.py b/tests/test_cases/wrong_section_appnexus.py index f2957e4..7f7ab37 100644 --- a/tests/test_cases/wrong_section_appnexus.py +++ b/tests/test_cases/wrong_section_appnexus.py @@ -3,3 +3,9 @@ import flake8_import_order # I201 I100 import localpackage # I201 I100 import X # I201 + +if TYPE_CHECKING: + import ast + import flake8_import_order # I201 I100 + import localpackage # I201 I100 + import X # I201