diff --git a/CHANGELOG.md b/CHANGELOG.md index b6ed2d7d0..9c7ef562c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Find out more about isort's release policy [here](https://pycqa.github.io/isort/ - Implemented #1705: More intuitive handling of isort:skip_file comments on streams. - Implemented #1737: Support for using action comments to avoid adding imports to individual files. - Implemented #1750: Ability to customize output format lines. + - Implemented #1732: Support for custom sort functions. - Fixed (https://github.com/PyCQA/isort/pull/1695): added imports being added to doc string in some cases. - Fixed (https://github.com/PyCQA/isort/pull/1714): in rare cases line continuation combined with tabs can output invalid code. - Fixed (https://github.com/PyCQA/isort/pull/1726): isort ignores reverse_sort when force_sort_within_sections is true. diff --git a/example_isort_sorting_plugin/example_isort_sorting_plugin.py b/example_isort_sorting_plugin/example_isort_sorting_plugin.py new file mode 100644 index 000000000..e0c9eacc5 --- /dev/null +++ b/example_isort_sorting_plugin/example_isort_sorting_plugin.py @@ -0,0 +1,6 @@ +from natsort import natsorted + + +def natural_plus(*args, **kwargs) -> str: + """An even more natural sorting order for isort using natsort.""" + return natsorted(*args, **kwargs) diff --git a/example_isort_sorting_plugin/poetry.lock b/example_isort_sorting_plugin/poetry.lock new file mode 100644 index 000000000..22307cf8d --- /dev/null +++ b/example_isort_sorting_plugin/poetry.lock @@ -0,0 +1,22 @@ +[[package]] +name = "natsort" +version = "7.1.1" +description = "Simple yet flexible natural sorting in Python." +category = "main" +optional = false +python-versions = ">=3.4" + +[package.extras] +fast = ["fastnumbers (>=2.0.0)"] +icu = ["PyICU (>=1.0.0)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.6" +content-hash = "7825ed9542cf5a39140d69f2d73e505bfab06f72cf1ef105202bc221681efd1a" + +[metadata.files] +natsort = [ + {file = "natsort-7.1.1-py3-none-any.whl", hash = "sha256:d0f4fc06ca163fa4a5ef638d9bf111c67f65eedcc7920f98dec08e489045b67e"}, + {file = "natsort-7.1.1.tar.gz", hash = "sha256:00c603a42365830c4722a2eb7663a25919551217ec09a243d3399fa8dd4ac403"}, +] diff --git a/example_isort_sorting_plugin/pyproject.toml b/example_isort_sorting_plugin/pyproject.toml new file mode 100644 index 000000000..ee6e910a4 --- /dev/null +++ b/example_isort_sorting_plugin/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "example_isort_sorting_plugin" +version = "0.0.2" +description = "An example plugin that modifies isorts sorting order to provide an even more natural sort by utilizing natsort." +authors = ["Timothy Crosley "] +license = "MIT" + +[tool.poetry.plugins."isort.sort_function"] +natural_plus = "example_isort_sorting_plugin:natural_plus" + +[tool.poetry.dependencies] +python = "^3.6" +natsort = "^7.1.1" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/example_shared_isort_profile/poetry.lock b/example_shared_isort_profile/poetry.lock index 12fbad913..f6de59971 100644 --- a/example_shared_isort_profile/poetry.lock +++ b/example_shared_isort_profile/poetry.lock @@ -1,7 +1,8 @@ package = [] [metadata] -content-hash = "8165d934e932435bf4742b9198674202413b43524911713d5c7c55cb8d314618" -python-versions = "^3.5" +lock-version = "1.1" +python-versions = "^3.6" +content-hash = "ae1f216c71b9b712a5c479d19bf075b718c35d9248fd89cb1eb7624528ec5ad1" [metadata.files] diff --git a/isort/exceptions.py b/isort/exceptions.py index 3a54d6429..275dc424d 100644 --- a/isort/exceptions.py +++ b/isort/exceptions.py @@ -1,6 +1,6 @@ """All isort specific exception classes should be defined here""" from pathlib import Path -from typing import Any, Dict, Union +from typing import Any, Dict, List, Union from .profiles import profiles @@ -82,6 +82,18 @@ def __init__(self, profile: str): self.profile = profile +class SortingFunctionDoesNotExist(ISortError): + """Raised when the specified sorting function isn't available""" + + def __init__(self, sort_order: str, available_sort_orders: List[str]): + super().__init__( + f"Specified sort_order of {sort_order} does not exist. " + f"Available sort_orders: {','.join(available_sort_orders)}." + ) + self.sort_order = sort_order + self.available_sort_orders = available_sort_orders + + class FormattingPluginDoesNotExist(ISortError): """Raised when a formatting plugin is set by the user that doesn't exist""" diff --git a/isort/main.py b/isort/main.py index e8af743fe..a57b3fd28 100644 --- a/isort/main.py +++ b/isort/main.py @@ -585,8 +585,8 @@ def _build_arg_parser() -> argparse.ArgumentParser: output_group.add_argument( "--sort-order", dest="sort_order", - choices=["default", "pythonic"], - help="Use natural language or pythonic sorting.", + help="Specify sorting function. Can be built in (natural[default] = force numbers " + "to be sequential, native = Python's built-in sorted function) or an installable plugin.", ) inline_args_group.add_argument( "--sl", diff --git a/isort/settings.py b/isort/settings.py index e4c32cab3..9eafb8644 100644 --- a/isort/settings.py +++ b/isort/settings.py @@ -30,13 +30,14 @@ ) from warnings import warn -from . import stdlibs +from . import sorting, stdlibs from ._future import dataclass, field from ._vendored import toml # type: ignore from .exceptions import ( FormattingPluginDoesNotExist, InvalidSettingsPath, ProfileDoesNotExist, + SortingFunctionDoesNotExist, UnsupportedSettings, ) from .profiles import profiles @@ -232,7 +233,7 @@ class _Config: git_ignore: Dict[Path, Set[Path]] = field(default_factory=dict) format_error: str = "{error}: {message}" format_success: str = "{success}: {message}" - sort_order: str = "default" + sort_order: str = "natural" def __post_init__(self) -> None: py_version = self.py_version @@ -294,6 +295,7 @@ def __init__( self._section_comments: Optional[Tuple[str, ...]] = None self._skips: Optional[FrozenSet[str]] = None self._skip_globs: Optional[FrozenSet[str]] = None + self._sorting_function: Optional[Callable[..., List[str]]] = None if config: config_vars = vars(config).copy() @@ -303,6 +305,7 @@ def __init__( config_vars.pop("_section_comments") config_vars.pop("_skips") config_vars.pop("_skip_globs") + config_vars.pop("_sorting_function") super().__init__(**config_vars) # type: ignore return @@ -651,6 +654,29 @@ def skip_globs(self) -> FrozenSet[str]: self._skip_globs = self.skip_glob.union(self.extend_skip_glob) return self._skip_globs + @property + def sorting_function(self) -> Callable[..., List[str]]: + if self._sorting_function is not None: + return self._sorting_function + + if self.sort_order == "natural": + self._sorting_function = sorting.naturally + elif self.sort_order == "native": + self._sorting_function = sorted + else: + available_sort_orders = ["natural", "native"] + import pkg_resources + + for sort_plugin in pkg_resources.iter_entry_points("isort.sort_function"): + available_sort_orders.append(sort_plugin.name) + if sort_plugin.name == self.sort_order: + self._sorting_function = sort_plugin.load() + break + else: + raise SortingFunctionDoesNotExist(self.sort_order, available_sort_orders) + + return self._sorting_function + def _parse_known_pattern(self, pattern: str) -> List[str]: """Expand pattern if identified as a directory and return found sub packages""" if pattern.endswith(os.path.sep): diff --git a/isort/sorting.py b/isort/sorting.py index 60727662f..22fdc3804 100644 --- a/isort/sorting.py +++ b/isort/sorting.py @@ -1,7 +1,10 @@ import re -from typing import Any, Callable, Iterable, List, Optional +from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Optional -from .settings import Config +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 ") @@ -102,10 +105,7 @@ def sort( key: Optional[Callable[[str], Any]] = None, reverse: bool = False, ) -> List[str]: - sorting_func: Callable[..., List[str]] = naturally - if config.sort_order == "pythonic": - sorting_func = sorted - return sorting_func(to_sort, key=key, reverse=reverse) + return config.sorting_function(to_sort, key=key, reverse=reverse) def naturally( diff --git a/poetry.lock b/poetry.lock index 492deaf67..27358a06b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -287,6 +287,17 @@ python-versions = ">=3.6,<4.0" black = ">=20.08b1,<21.0" isort = ">=5.1.4,<6.0.0" +[[package]] +name = "example-isort-sorting-plugin" +version = "0.0.2" +description = "An example plugin that modifies isorts sorting order to provide an even more natural sort by utilizing natsort." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +natsort = ">=7.1.1,<8.0.0" + [[package]] name = "example-shared-isort-profile" version = "0.0.1" @@ -397,11 +408,11 @@ gitdb = ">=4.0.1" [[package]] name = "gitpython" -version = "3.1.17" +version = "3.1.18" description = "Python Git Library" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] gitdb = ">=4.0.1,<5" @@ -868,6 +879,18 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "natsort" +version = "7.1.1" +description = "Simple yet flexible natural sorting in Python." +category = "dev" +optional = false +python-versions = ">=3.4" + +[package.extras] +fast = ["fastnumbers (>=2.0.0)"] +icu = ["PyICU (>=1.0.0)"] + [[package]] name = "nodeenv" version = "1.6.0" @@ -1117,11 +1140,11 @@ virtualenv = ">=20.0.8" [[package]] name = "prompt-toolkit" -version = "3.0.3" +version = "3.0.19" description = "Library for building powerful interactive command lines in Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.1" [package.dependencies] wcwidth = "*" @@ -1160,19 +1183,19 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pydantic" -version = "1.7.4" +version = "1.8.2" description = "Data validation and settings management using python 3.6 type hinting" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.1" [package.dependencies] dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} +typing-extensions = ">=3.7.4.3" [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] -typing_extensions = ["typing-extensions (>=3.7.2)"] [[package]] name = "pydocstyle" @@ -1571,7 +1594,7 @@ doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown- [[package]] name = "types-pkg-resources" -version = "0.1.2" +version = "0.1.3" description = "Typing stubs for pkg_resources" category = "dev" optional = false @@ -1715,8 +1738,8 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"] [metadata] lock-version = "1.1" -python-versions = "^3.6.1" -content-hash = "069575195bc09e4d3ac30c5600a5ac8a991c64a4a586eaf8339f6d3137cd18e9" +python-versions = ">=3.6.1,<4.0" +content-hash = "f9e26ab53135b8de55863d9138e5ebd78764d8d5172199ad95c516b8383b4bee" [metadata.files] appdirs = [ @@ -1800,6 +1823,9 @@ coverage = [ {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, + {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, + {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, + {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, @@ -1869,6 +1895,10 @@ example-isort-formatting-plugin = [ {file = "example_isort_formatting_plugin-0.0.2-py3-none-any.whl", hash = "sha256:ce428ab5deb4719e4bec56eae63978ff2d9c20dc2c2aa7cc39ece61044153db7"}, {file = "example_isort_formatting_plugin-0.0.2.tar.gz", hash = "sha256:8cb6401c9efe2f97ba3e776439cb647ee964dc7880bd9790b0324be2c7a55907"}, ] +example-isort-sorting-plugin = [ + {file = "example_isort_sorting_plugin-0.0.2-py3-none-any.whl", hash = "sha256:108d3d66bb5fd8ef51fde9bbab3207d2b6176b5df803dd88e31f2f48b8975b59"}, + {file = "example_isort_sorting_plugin-0.0.2.tar.gz", hash = "sha256:ea5ac1f9233023d6d949940ea00d08e2f8ade6e7acdebafe1e82acdf1ef1d969"}, +] example-shared-isort-profile = [ {file = "example_shared_isort_profile-0.0.1-py3-none-any.whl", hash = "sha256:3fa3e2d093e68285fc7893704b727791ed3e0969d07bdd2733e366303d1a2582"}, {file = "example_shared_isort_profile-0.0.1.tar.gz", hash = "sha256:fc2a0fa892611d6c1c2060dc0ca8e511e3f65fad8e4e449e8a375ef49549e710"}, @@ -1921,8 +1951,8 @@ gitdb2 = [ {file = "gitdb2-4.0.2.tar.gz", hash = "sha256:0986cb4003de743f2b3aba4c828edd1ab58ce98e1c4a8acf72ef02760d4beb4e"}, ] gitpython = [ - {file = "GitPython-3.1.17-py3-none-any.whl", hash = "sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135"}, - {file = "GitPython-3.1.17.tar.gz", hash = "sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"}, + {file = "GitPython-3.1.18-py3-none-any.whl", hash = "sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8"}, + {file = "GitPython-3.1.18.tar.gz", hash = "sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b"}, ] h11 = [ {file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"}, @@ -2036,6 +2066,7 @@ livereload = [ {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] mako = [ + {file = "Mako-1.1.4-py2.py3-none-any.whl", hash = "sha256:aea166356da44b9b830c8023cd9b557fa856bd8b4035d6de771ca027dfc5cc6e"}, {file = "Mako-1.1.4.tar.gz", hash = "sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab"}, ] markdown = [ @@ -2127,6 +2158,10 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +natsort = [ + {file = "natsort-7.1.1-py3-none-any.whl", hash = "sha256:d0f4fc06ca163fa4a5ef638d9bf111c67f65eedcc7920f98dec08e489045b67e"}, + {file = "natsort-7.1.1.tar.gz", hash = "sha256:00c603a42365830c4722a2eb7663a25919551217ec09a243d3399fa8dd4ac403"}, +] nodeenv = [ {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, @@ -2243,8 +2278,8 @@ pre-commit = [ {file = "pre_commit-2.13.0.tar.gz", hash = "sha256:764972c60693dc668ba8e86eb29654ec3144501310f7198742a767bec385a378"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"}, - {file = "prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e"}, + {file = "prompt_toolkit-3.0.19-py3-none-any.whl", hash = "sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"}, + {file = "prompt_toolkit-3.0.19.tar.gz", hash = "sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f"}, ] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, @@ -2262,28 +2297,28 @@ pycodestyle = [ {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pydantic = [ - {file = "pydantic-1.7.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3c60039e84552442defbcb5d56711ef0e057028ca7bfc559374917408a88d84e"}, - {file = "pydantic-1.7.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6e7e314acb170e143c6f3912f93f2ec80a96aa2009ee681356b7ce20d57e5c62"}, - {file = "pydantic-1.7.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:8ef77cd17b73b5ba46788d040c0e820e49a2d80cfcd66fda3ba8be31094fd146"}, - {file = "pydantic-1.7.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:115d8aa6f257a1d469c66b6bfc7aaf04cd87c25095f24542065c68ebcb42fe63"}, - {file = "pydantic-1.7.4-cp36-cp36m-win_amd64.whl", hash = "sha256:66757d4e1eab69a3cfd3114480cc1d72b6dd847c4d30e676ae838c6740fdd146"}, - {file = "pydantic-1.7.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c92863263e4bd89e4f9cf1ab70d918170c51bd96305fe7b00853d80660acb26"}, - {file = "pydantic-1.7.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3b8154babf30a5e0fa3aa91f188356763749d9b30f7f211fafb247d4256d7877"}, - {file = "pydantic-1.7.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:80cc46378505f7ff202879dcffe4bfbf776c15675028f6e08d1d10bdfbb168ac"}, - {file = "pydantic-1.7.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:dda60d7878a5af2d8560c55c7c47a8908344aa78d32ec1c02d742ede09c534df"}, - {file = "pydantic-1.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:4c1979d5cc3e14b35f0825caddea5a243dd6085e2a7539c006bc46997ef7a61a"}, - {file = "pydantic-1.7.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8857576600c32aa488f18d30833aa833b54a48e3bab3adb6de97e463af71f8f8"}, - {file = "pydantic-1.7.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1f86d4da363badb39426a0ff494bf1d8510cd2f7274f460eee37bdbf2fd495ec"}, - {file = "pydantic-1.7.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:3ea1256a9e782149381e8200119f3e2edea7cd6b123f1c79ab4bbefe4d9ba2c9"}, - {file = "pydantic-1.7.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:e28455b42a0465a7bf2cde5eab530389226ce7dc779de28d17b8377245982b1e"}, - {file = "pydantic-1.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:47c5b1d44934375a3311891cabd450c150a31cf5c22e84aa172967bf186718be"}, - {file = "pydantic-1.7.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:00250e5123dd0b123ff72be0e1b69140e0b0b9e404d15be3846b77c6f1b1e387"}, - {file = "pydantic-1.7.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d24aa3f7f791a023888976b600f2f389d3713e4f23b7a4c88217d3fce61cdffc"}, - {file = "pydantic-1.7.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2c44a9afd4c4c850885436a4209376857989aaf0853c7b118bb2e628d4b78c4e"}, - {file = "pydantic-1.7.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:e87edd753da0ca1d44e308a1b1034859ffeab1f4a4492276bff9e1c3230db4fe"}, - {file = "pydantic-1.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:a3026ee105b5360855e500b4abf1a1d0b034d88e75a2d0d66a4c35e60858e15b"}, - {file = "pydantic-1.7.4-py3-none-any.whl", hash = "sha256:a82385c6d5a77e3387e94612e3e34b77e13c39ff1295c26e3ba664e7b98073e2"}, - {file = "pydantic-1.7.4.tar.gz", hash = "sha256:0a1abcbd525fbb52da58c813d54c2ec706c31a91afdb75411a73dd1dec036595"}, + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, ] pydocstyle = [ {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, @@ -2340,18 +2375,26 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, @@ -2543,7 +2586,8 @@ typer = [ {file = "typer-0.3.2.tar.gz", hash = "sha256:5455d750122cff96745b0dec87368f56d023725a7ebc9d2e54dd23dc86816303"}, ] types-pkg-resources = [ - {file = "types_pkg_resources-0.1.2-py2.py3-none-any.whl", hash = "sha256:42d640500de564f1ccc21f918117afadf78039e4fa7f513c647ccf742d609aeb"}, + {file = "types-pkg_resources-0.1.3.tar.gz", hash = "sha256:834a9b8d3dbea343562fd99d5d3359a726f6bf9d3733bccd2b4f3096fbab9dae"}, + {file = "types_pkg_resources-0.1.3-py2.py3-none-any.whl", hash = "sha256:0cb9972cee992249f93fff1a491bf2dc3ce674e5a1926e27d4f0866f7d9b6d9c"}, ] typing-extensions = [ {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, diff --git a/pyproject.toml b/pyproject.toml index f339ef46a..301a54a78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,7 @@ gitdb2 = "^4.0.2" httpx = "^0.13.3" example_shared_isort_profile = "^0.0.1" example_isort_formatting_plugin = "^0.0.2" +example_isort_sorting_plugin = "^0.0.2" flake8 = "^3.8.4" hypothesis = "^6.10.1" libcst = "^0.3.18" diff --git a/scripts/clean.sh b/scripts/clean.sh index 7d446611e..632a023c2 100755 --- a/scripts/clean.sh +++ b/scripts/clean.sh @@ -2,6 +2,6 @@ set -euxo pipefail poetry run isort --profile hug isort/ tests/ scripts/ -poetry run isort --profile hug example_isort_formatting_plugin/ +poetry run isort --profile hug example_*/ poetry run black isort/ tests/ scripts/ -poetry run black example_isort_formatting_plugin/ +poetry run black example_*/ diff --git a/scripts/lint.sh b/scripts/lint.sh index 6783976fc..9af835d0a 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -5,7 +5,7 @@ poetry run cruft check poetry run mypy -p isort poetry run black --target-version py36 --check . poetry run isort --profile hug --check --diff isort/ tests/ -poetry run isort --profile hug --check --diff example_isort_formatting_plugin/ +poetry run isort --profile hug --check --diff example_*/ poetry run flake8 isort/ tests/ poetry run safety check -i 39462 -i 40291 poetry run bandit -r isort/ -x isort/_vendored diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py index d9eae8bf8..2cd17aa11 100644 --- a/tests/unit/test_exceptions.py +++ b/tests/unit/test_exceptions.py @@ -58,6 +58,15 @@ def test_variables(self): assert self.instance.profile == "profile" +class TestSortingFunctionDoesNotExist(TestISortError): + def setup_class(self): + self.instance = exceptions.SortingFunctionDoesNotExist("round", ["square", "peg"]) + + def test_variables(self): + assert self.instance.sort_order == "round" + assert self.instance.available_sort_orders == ["square", "peg"] + + class TestLiteralParsingFailure(TestISortError): def setup_class(self): self.instance = exceptions.LiteralParsingFailure("x = [", SyntaxError) diff --git a/tests/unit/test_isort.py b/tests/unit/test_isort.py index 9593c42c8..0599925e7 100644 --- a/tests/unit/test_isort.py +++ b/tests/unit/test_isort.py @@ -5236,22 +5236,3 @@ def seekable(self): test_input = NonSeekableTestStream("import m2\n" "import m1\n" "not_import = 7") identified_imports = list(map(str, api.find_imports_in_stream(test_input))) assert identified_imports == [":1 import m2", ":2 import m1"] - - -def test_sort_pythonic() -> None: - """ Test a plugged-in default python sort with packages/modules containing numbers. """ - test_input = ( - "from bob2.apples2 import aardvark as aardvark2\n" - "from bob.apples import aardvark \n" - "import module9\n" - "import module10\n" - "import module200\n" - ) - test_output = isort.code(test_input, sort_order="pythonic") - assert test_output == ( - "import module10\n" - "import module200\n" - "import module9\n" - "from bob.apples import aardvark\n" - "from bob2.apples2 import aardvark as aardvark2\n" - ) diff --git a/tests/unit/test_ticketed_features.py b/tests/unit/test_ticketed_features.py index a29700539..dde08be6c 100644 --- a/tests/unit/test_ticketed_features.py +++ b/tests/unit/test_ticketed_features.py @@ -1179,3 +1179,45 @@ def test_isort_can_turn_off_import_adds_with_action_comment_issue_1737(): import os """ ) + + +def test_sort_configurable_sort_issue_1732() -> None: + """Test support for pluggable isort sort functions.""" + test_input = ( + "from bob2.apples2 import aardvark as aardvark2\n" + "from bob.apples import aardvark \n" + "import module9\n" + "import module10\n" + "import module200\n" + ) + assert isort.code(test_input, sort_order="native") == ( + "import module10\n" + "import module200\n" + "import module9\n" + "from bob.apples import aardvark\n" + "from bob2.apples2 import aardvark as aardvark2\n" + ) + assert ( + isort.code(test_input, sort_order="natural") + == isort.code(test_input) + == ( + "import module9\n" + "import module10\n" + "import module200\n" + "from bob2.apples2 import aardvark as aardvark2\n" + "from bob.apples import aardvark\n" + ) + ) + assert ( + isort.code(test_input, sort_order="natural_plus") + == isort.code(test_input) + == ( + "import module9\n" + "import module10\n" + "import module200\n" + "from bob2.apples2 import aardvark as aardvark2\n" + "from bob.apples import aardvark\n" + ) + ) + with pytest.raises(exceptions.SortingFunctionDoesNotExist): + isort.code(test_input, sort_order="round")