Skip to content

Module isort.sorting

None

None

View Source
import re

from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Optional

if TYPE_CHECKING:

    from .settings import Config

else:

    Config = Any

_import_line_intro_re = re.compile("^(?:from|import) ")

_import_line_midline_import_re = re.compile(" import ")

def module_key(

    module_name: str,

    config: Config,

    sub_imports: bool = False,

    ignore_case: bool = False,

    section_name: Optional[Any] = None,

    straight_import: Optional[bool] = False,

) -> str:

    match = re.match(r"^(\.+)\s*(.*)", module_name)

    if match:

        sep = " " if config.reverse_relative else "_"

        module_name = sep.join(match.groups())

    prefix = ""

    if ignore_case:

        module_name = str(module_name).lower()

    else:

        module_name = str(module_name)

    if sub_imports and config.order_by_type:

        if module_name in config.constants:

            prefix = "A"

        elif module_name in config.classes:

            prefix = "B"

        elif module_name in config.variables:

            prefix = "C"

        elif module_name.isupper() and len(module_name) > 1:  # see issue #376

            prefix = "A"

        elif module_name in config.classes or module_name[0:1].isupper():

            prefix = "B"

        else:

            prefix = "C"

    if not config.case_sensitive:

        module_name = module_name.lower()

    length_sort = (

        config.length_sort

        or (config.length_sort_straight and straight_import)

        or str(section_name).lower() in config.length_sort_sections

    )

    _length_sort_maybe = (str(len(module_name)) + ":" + module_name) if length_sort else module_name

    return f"{module_name in config.force_to_top and 'A' or 'B'}{prefix}{_length_sort_maybe}"

def section_key(line: str, config: Config) -> str:

    section = "B"

    if (

        not config.sort_relative_in_force_sorted_sections

        and config.reverse_relative

        and line.startswith("from .")

    ):

        match = re.match(r"^from (\.+)\s*(.*)", line)

        if match:  # pragma: no cover - regex always matches if line starts with "from ."

            line = f"from {' '.join(match.groups())}"

    if config.group_by_package and line.strip().startswith("from"):

        line = line.split(" import", 1)[0]

    if config.lexicographical:

        line = _import_line_intro_re.sub("", _import_line_midline_import_re.sub(".", line))

    else:

        line = re.sub("^from ", "", line)

        line = re.sub("^import ", "", line)

    if config.sort_relative_in_force_sorted_sections:

        sep = " " if config.reverse_relative else "_"

        line = re.sub(r"^(\.+)", rf"\1{sep}", line)

    if line.split(" ")[0] in config.force_to_top:

        section = "A"

    # * If honor_case_in_force_sorted_sections is true, and case_sensitive and

    #   order_by_type are different, only ignore case in part of the line.

    # * Otherwise, let order_by_type decide the sorting of the whole line. This

    #   is only "correct" if case_sensitive and order_by_type have the same value.

    if config.honor_case_in_force_sorted_sections and config.case_sensitive != config.order_by_type:

        split_module = line.split(" import ", 1)

        if len(split_module) > 1:

            module_name, names = split_module

            if not config.case_sensitive:

                module_name = module_name.lower()

            if not config.order_by_type:

                names = names.lower()

            line = " import ".join([module_name, names])

        elif not config.case_sensitive:

            line = line.lower()

    elif not config.order_by_type:

        line = line.lower()

    return f"{section}{len(line) if config.length_sort else ''}{line}"

def sort(

    config: Config,

    to_sort: Iterable[str],

    key: Optional[Callable[[str], Any]] = None,

    reverse: bool = False,

) -> List[str]:

    return config.sorting_function(to_sort, key=key, reverse=reverse)

def naturally(

    to_sort: Iterable[str], key: Optional[Callable[[str], Any]] = None, reverse: bool = False

) -> List[str]:

    """Returns a naturally sorted list"""

    if key is None:

        key_callback = _natural_keys

    else:

        def key_callback(text: str) -> List[Any]:

            return _natural_keys(key(text))  # type: ignore

    return sorted(to_sort, key=key_callback, reverse=reverse)

def _atoi(text: str) -> Any:

    return int(text) if text.isdigit() else text

def _natural_keys(text: str) -> List[Any]:

    return [_atoi(c) for c in re.split(r"(\d+)", text)]

Variables

TYPE_CHECKING

Functions

module_key

def module_key(
    module_name: str,
    config: Any,
    sub_imports: bool = False,
    ignore_case: bool = False,
    section_name: Optional[Any] = None,
    straight_import: Optional[bool] = False
) -> str
View Source
def module_key(

    module_name: str,

    config: Config,

    sub_imports: bool = False,

    ignore_case: bool = False,

    section_name: Optional[Any] = None,

    straight_import: Optional[bool] = False,

) -> str:

    match = re.match(r"^(\.+)\s*(.*)", module_name)

    if match:

        sep = " " if config.reverse_relative else "_"

        module_name = sep.join(match.groups())

    prefix = ""

    if ignore_case:

        module_name = str(module_name).lower()

    else:

        module_name = str(module_name)

    if sub_imports and config.order_by_type:

        if module_name in config.constants:

            prefix = "A"

        elif module_name in config.classes:

            prefix = "B"

        elif module_name in config.variables:

            prefix = "C"

        elif module_name.isupper() and len(module_name) > 1:  # see issue #376

            prefix = "A"

        elif module_name in config.classes or module_name[0:1].isupper():

            prefix = "B"

        else:

            prefix = "C"

    if not config.case_sensitive:

        module_name = module_name.lower()

    length_sort = (

        config.length_sort

        or (config.length_sort_straight and straight_import)

        or str(section_name).lower() in config.length_sort_sections

    )

    _length_sort_maybe = (str(len(module_name)) + ":" + module_name) if length_sort else module_name

    return f"{module_name in config.force_to_top and 'A' or 'B'}{prefix}{_length_sort_maybe}"

naturally

def naturally(
    to_sort: Iterable[str],
    key: Optional[Callable[[str], Any]] = None,
    reverse: bool = False
) -> List[str]

Returns a naturally sorted list

View Source
def naturally(

    to_sort: Iterable[str], key: Optional[Callable[[str], Any]] = None, reverse: bool = False

) -> List[str]:

    """Returns a naturally sorted list"""

    if key is None:

        key_callback = _natural_keys

    else:

        def key_callback(text: str) -> List[Any]:

            return _natural_keys(key(text))  # type: ignore

    return sorted(to_sort, key=key_callback, reverse=reverse)

section_key

def section_key(
    line: str,
    config: Any
) -> str
View Source
def section_key(line: str, config: Config) -> str:

    section = "B"

    if (

        not config.sort_relative_in_force_sorted_sections

        and config.reverse_relative

        and line.startswith("from .")

    ):

        match = re.match(r"^from (\.+)\s*(.*)", line)

        if match:  # pragma: no cover - regex always matches if line starts with "from ."

            line = f"from {' '.join(match.groups())}"

    if config.group_by_package and line.strip().startswith("from"):

        line = line.split(" import", 1)[0]

    if config.lexicographical:

        line = _import_line_intro_re.sub("", _import_line_midline_import_re.sub(".", line))

    else:

        line = re.sub("^from ", "", line)

        line = re.sub("^import ", "", line)

    if config.sort_relative_in_force_sorted_sections:

        sep = " " if config.reverse_relative else "_"

        line = re.sub(r"^(\.+)", rf"\1{sep}", line)

    if line.split(" ")[0] in config.force_to_top:

        section = "A"

    # * If honor_case_in_force_sorted_sections is true, and case_sensitive and

    #   order_by_type are different, only ignore case in part of the line.

    # * Otherwise, let order_by_type decide the sorting of the whole line. This

    #   is only "correct" if case_sensitive and order_by_type have the same value.

    if config.honor_case_in_force_sorted_sections and config.case_sensitive != config.order_by_type:

        split_module = line.split(" import ", 1)

        if len(split_module) > 1:

            module_name, names = split_module

            if not config.case_sensitive:

                module_name = module_name.lower()

            if not config.order_by_type:

                names = names.lower()

            line = " import ".join([module_name, names])

        elif not config.case_sensitive:

            line = line.lower()

    elif not config.order_by_type:

        line = line.lower()

    return f"{section}{len(line) if config.length_sort else ''}{line}"

sort

def sort(
    config: Any,
    to_sort: Iterable[str],
    key: Optional[Callable[[str], Any]] = None,
    reverse: bool = False
) -> List[str]
View Source
def sort(

    config: Config,

    to_sort: Iterable[str],

    key: Optional[Callable[[str], Any]] = None,

    reverse: bool = False,

) -> List[str]:

    return config.sorting_function(to_sort, key=key, reverse=reverse)