Coder Social home page Coder Social logo

usort's Introduction

μsort

Safe, minimal import sorting for Python projects.

documentation version changelog license

μsort is a safe, minimal import sorter. Its primary goal is to make no "dangerous" changes to code. This is achieved by detecting distinct "blocks" of imports that are the most likely to be safely interchangeable, and only reordering imports within these blocks without altering formatting. Code style is left as an exercise for linters and formatters.

Within a block, µsort will follow common Python conventions for grouping imports based on source (standard library, third-party, first-party, or relative), and then sorting lexicographically within each group. This will commonly look like:

import re
from pathlib import Path
from typing import Iterable
from unittest.mock import call, Mock, patch

import aiohttp
from aiosqlite import connect

import foo
from bar import bar

from .main import main

Blocks are inferred from a number of real world conditions, including any intermediate statements between imports:

import warnings
warnings.filterwarnings(...)

import re
import sys

In this case, µsort detects two blocks–separated by the call to filterwarnings(), and will only sort imports inside of each block. Running µsort on this code will generate no changes, because each block is already sorted.

Imports can be excluded from blocks using the #usort:skip directive, or with #isort:skip for compatibility with existing codebases. µsort will leave these imports unchanged, and treat them as block separators.

See the User Guide for more details about how blocks are detected, and how sorting is performed.

Install

µsort requires Python 3.8 or newer to run. Install µsort with:

$ pip install usort

Usage

To format one or more files or directories in-place:

$ usort format <path> [<path> ...]

To generate a diff of changes without modifying files:

$ usort diff <path>

To just validate that files are formatted correctly, like during CI:

$ usort check <path>

pre-commit

µsort provides a pre-commit hook. To enforce sorted imports before every commit, add the following to your .pre-commit-config.yaml file:

- repo: https://github.com/facebook/usort
  rev: v1.0.7
  hooks:
    - id: usort

License

μsort is MIT licensed, as found in the LICENSE file.

usort's People

Contributors

amyreese avatar bobronium avatar brandonthebuilder avatar dependabot[bot] avatar facebook-github-bot avatar fried avatar pmeier avatar thatch avatar zpao avatar zsol avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

usort's Issues

Usort from STDIN

Hi! Thanks for making this tool!
I think it would be useful to allow formatting of imports from a stream coming from another app, eg: I want to integrate this in Emacs and I would have to select some text and pass it to usort.
I suppose there are other ways that this can be useful.
Is it planned sometime?
Thank you!

Unstable sorting with mixed category imports

When basic imports include modules from multiple categories, sorting can be unstable because the sort_key isn't updated when changing order of the import names:

Test case:

    def test_sorting_mixed_category_imports(self) -> None:
        self.assertUsortResult(
            """
                import os, attr
                import difflib
                import third_party
            """,
            """
                import difflib

                import attr, os
                import third_party
            """,
        )

Produces the following test failure:

(.venv) jreese@butterfree ~/workspace/usort mixed± » make format test lint
python -m ufmt format usort setup.py
python -m coverage run -m usort.tests
.......................................................................F.............
======================================================================
FAIL: test_sorting_mixed_category_imports (usort.tests.functional.UsortStringFunctionalTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/jreese/workspace/usort/usort/tests/functional.py", line 632, in test_sorting_mixed_category_imports
    self.assertUsortResult(
  File "/Users/jreese/workspace/usort/usort/tests/functional.py", line 58, in assertUsortResult
    self.fail(
AssertionError: µsort result was not stable on second pass:

Before:
-------

import os, attr
import difflib
import third_party

First Pass:
-----------

import attr, os
import difflib

import third_party

Second Pass:
------------

import difflib

import attr, os

import third_party


----------------------------------------------------------------------
Ran 85 tests in 4.119s

FAILED (failures=1)
make: *** [test] Error 1

KeyError while sorting duplicate imports

On this file:

from a import x
from b import y
from b import y
from c import x

usort fails with

Traceback (most recent call last):
  File "usort/cli.py", line 80, in list_imports
    blocks = sorter.sortable_blocks(mod.body)
  File "usort/sorting.py", line 192, in sortable_blocks
    current = self.split_inplace(current, overlap)
  File "usort/sorting.py", line 169, in split_inplace
    new.imported_names[key] = block.imported_names.pop(key)
KeyError: 'y'

Indent whitespace being added between imports

Reported in v0.6.4

Consider:

# fmt: off
if __name__ == "__main__":
    import sys
    from foo import bar

After formatting it, ident whitespace is being added between the imports:

$ hg diff -U 10 | cat -e
diff --git a/test.py b/test.py$
--- a/test.py$
+++ b/test.py$
@@ -1,6 +1,6 @@$
 # fmt: off$
 $
 if __name__ == "__main__":$
     import sys$
-$
+    $
     from foo import bar$

Sometimes requires two passes with duplicated names

A source file like

from a import A
from d import D
from e import E

from z import A
from b import B
from c import C

the first pass moves Z to the end, the second pass moves b and c to their correct location. This has everything to do with detecting a new block starting with z, but instead of freezing its location, allowing it to be moved (only down).

Consider: sorting collection literals

Not sure how feasible this would be, but it would be nice to have a # usort:keepsorted type of directive that would also sort collection literals, similar to IIRC what buildifier supports for bazel files. This would be really handy for keeping __all__ collections sorted in modules:

# usort: keepsorted
__all__ = [
  "do_something",
  "main",
  "SpecialType",
]

I think it would be good enough to only support dict/list/set literals, and dictionaries would/should be sorted by key. Use the same lexicographical sorting used for modules.

Not sure if we can "sort" CST nodes in LibCST, so I think it's reasonable if we only sort collections using primitives like numbers or strings that are easy to reason about from the CST. Either ignore collections with unsortable elements entirely, or maybe just keep the unsortables in their original order, but after any number/str elements that we can sort. Sort numbers before strings if they're mixed, or vice-versa, just something consistent.

Open to bikeshedding any other details you can think of.

Support for --stdout and -

isort uses --stdout argument to write sorted imports to the stdout and also uses - argument to read from stdin.

usort doesn't support this behavior because of which I am unable to auto format in Emacs buffers.

isort behavior

$ isort --stdout -
import sys
import os
sys.stdout.write(os.getcwd() + "\n")
import os
import sys

sys.stdout.write(os.getcwd() + "\n")

usort behavior

$ usort --stdout -
Usage: usort [OPTIONS] COMMAND [ARGS]...
Try 'usort --help' for help.

Error: no such option: --stdout

Consider using mypyc to speed things up like black does

Black is significantly faster than ufsort, especially when it produces no changes.
I believe such performance difference may be due to black using mypyc: https://github.com/ichard26/black-mypyc-wheels

black is 4.25 times faster on check:

curl -s https://raw.githubusercontent.com/facebookexperimental/usort/main/usort/api.py -o example.py

time black --check example.py

# All done! ✨ 🍰 ✨
# 1 file would be left unchanged.
# black --check example.py  0.08s user 0.02s system 75% cpu 0.137 total

time usort check example.py            
# usort check example.py  0.34s user 0.04s system 86% cpu 0.434 total

black is 2 times faster when making changes:

curl -s https://raw.githubusercontent.com/python/cpython/main/Lib/asyncio/futures.py -o example.py  
                                                                                                                          
time black example.py                                                                             
# reformatted example.py

# All done! ✨ 🍰 ✨
# 1 file reformatted.
# black example.py  0.15s user 0.02s system 75% cpu 0.229 total
                                                                                                                          
time usort format example.py                                                                      
# Sorted example.py
# usort format example.py  0.40s user 0.04s system 93% cpu 0.469 total

However looking at profiler run, I see that 3/5 of the time is taken by trailrunner (and nearly 90% of that time is spend in lock.acquire), despite usort was given only one file path:

pyinstrument --show-all -m usort format example.py
pyinstrument --show-all -m usort format example.py

_     ._   __/__   _ _  _  _ _/_   Recorded: 05:51:13  Samples:  167
/_//_/// /_\ / //_// / //_'/ //     Duration: 0.405     CPU time: 0.153
/   _/                      v4.1.1

Program: usort format example.py

0.404 <module>  <string>:1
└─ 0.404 run_module  runpy.py:199
 ├─ 0.266 _run_module_code  runpy.py:89
 │  └─ 0.266 _run_code  runpy.py:63
 │     └─ 0.266 <module>  usort/__main__.py:1
 │        ├─ 0.256 __call__  click/core.py:1128
 │        │  └─ 0.256 main  click/core.py:987
 │        │     └─ 0.256 invoke  click/core.py:1623
 │        │        └─ 0.256 invoke  click/core.py:1393
 │        │           └─ 0.256 invoke  click/core.py:709
 │        │              └─ 0.256 wrapper  usort/cli.py:37
 │        │                 └─ 0.256 format  usort/cli.py:177
 │        │                    └─ 0.255 usort_path  usort/api.py:120
 │        │                       ├─ 0.242 run  trailrunner/core.py:226
 │        │                       │  └─ 0.242 run  trailrunner/core.py:171
 │        │                       │     ├─ 0.216 _chain_from_iterable_of_lists  concurrent/futures/process.py:561
 │        │                       │     │  └─ 0.216 result_iterator  concurrent/futures/_base.py:601
 │        │                       │     │     └─ 0.216 result  concurrent/futures/_base.py:417
 │        │                       │     │        └─ 0.216 wait  threading.py:288
 │        │                       │     │           └─ 0.216 lock.acquire  <built-in>:0
 │        │                       │     ├─ 0.019 __exit__  concurrent/futures/_base.py:635
 │        │                       │     │  └─ 0.019 shutdown  concurrent/futures/process.py:739
 │        │                       │     │     └─ 0.019 join  threading.py:1057
 │        │                       │     │        └─ 0.019 _wait_for_tstate_lock  threading.py:1095
 │        │                       │     │           └─ 0.019 lock.acquire  <built-in>:0
 │        │                       │     └─ 0.004 _default_executor  trailrunner/core.py:128
 │        │                       │        └─ 0.004 __init__  concurrent/futures/process.py:581
 │        │                       └─ 0.012 walk  trailrunner/core.py:210
 │        │                          └─ 0.012 walk  trailrunner/core.py:136
 │        │                             └─ 0.012 gitignore  trailrunner/core.py:67
 │        │                                └─ 0.011 pathspec  trailrunner/core.py:54
 │        │                                   └─ 0.011 from_lines  pathspec/pathspec.py:100
 │        │                                      └─ 0.011 <listcomp>  pathspec/pathspec.py:126
 │        │                                         └─ 0.011 __init__  pathspec/pattern.py:71
 │        │                                            └─ 0.009 compile  re.py:249
 │        │                                               └─ 0.009 _compile  re.py:288
 │        │                                                  └─ 0.008 compile  sre_compile.py:759
 │        │                                                     └─ 0.006 parse  sre_parse.py:937
 │        │                                                        └─ 0.006 _parse_sub  sre_parse.py:435
 │        │                                                           └─ 0.006 _parse  sre_parse.py:493
 │        └─ 0.010 <module>  usort/cli.py:1
 │           └─ 0.007 <module>  click/__init__.py:1
 │              └─ 0.007 <module>  click/core.py:1
 └─ 0.138 _get_module_details  runpy.py:103
    └─ 0.138 _get_module_details  runpy.py:103
       └─ 0.138 <module>  usort/__init__.py:1
          └─ 0.138 <module>  usort/api.py:1
             ├─ 0.114 <module>  usort/sorting.py:1
             │  ├─ 0.101 <module>  libcst/__init__.py:1
             │  │  ├─ 0.038 <module>  libcst/_nodes/expression.py:1
             │  │  │  ├─ 0.017 wrap  dataclasses.py:1175
             │  │  │  │  └─ 0.017 _process_class  dataclasses.py:882
             │  │  │  │     ├─ 0.006 _frozen_get_del_attr  dataclasses.py:599
             │  │  │  │     │  └─ 0.006 _create_fn  dataclasses.py:412
             │  │  │  │     ├─ 0.004 _init_fn  dataclasses.py:529
             │  │  │  │     │  └─ 0.004 _create_fn  dataclasses.py:412
             │  │  │  │     └─ 0.004 _cmp_fn  dataclasses.py:624
             │  │  │  │        └─ 0.004 _create_fn  dataclasses.py:412
             │  │  │  └─ 0.016 <module>  libcst/_nodes/op.py:1
             │  │  │     └─ 0.011 wrap  dataclasses.py:1175
             │  │  │        └─ 0.010 _process_class  dataclasses.py:882
             │  │  ├─ 0.021 <module>  libcst/_nodes/module.py:1
             │  │  │  └─ 0.020 <module>  libcst/_nodes/statement.py:1
             │  │  │     └─ 0.016 wrap  dataclasses.py:1175
             │  │  │        └─ 0.016 _process_class  dataclasses.py:882
             │  │  │           ├─ 0.005 _init_fn  dataclasses.py:529
             │  │  │           │  └─ 0.004 _create_fn  dataclasses.py:412
             │  │  │           └─ 0.004 _cmp_fn  dataclasses.py:624
             │  │  │              └─ 0.004 _create_fn  dataclasses.py:412
             │  │  ├─ 0.014 <module>  libcst/_parser/entrypoints.py:1
             │  │  │  └─ 0.010 <module>  libcst/_parser/grammar.py:1
             │  │  │     └─ 0.008 <module>  libcst/_parser/conversions/expression.py:1
             │  │  │        └─ 0.006 <module>  libcst/_parser/types/partials.py:1
             │  │  │           └─ 0.006 wrap  dataclasses.py:1175
             │  │  │              └─ 0.006 _process_class  dataclasses.py:882
             │  │  ├─ 0.008 <module>  libcst/metadata/__init__.py:1
             │  │  ├─ 0.007 <module>  libcst/_exceptions.py:1
             │  │  │  └─ 0.004 <module>  libcst/_parser/parso/pgen2/generator.py:1
             │  │  │     └─ 0.004 <module>  libcst/_parser/parso/pgen2/grammar_parser.py:1
             │  │  └─ 0.005 <module>  libcst/_batched_visitor.py:1
             │  └─ 0.012 <module>  usort/translate.py:1
             │     └─ 0.012 <module>  usort/types.py:1
             │        └─ 0.009 <module>  attr/__init__.py:1
             │           └─ 0.005 <module>  attr/converters.py:1
             ├─ 0.016 <module>  trailrunner/__init__.py:1
             │  └─ 0.016 <module>  trailrunner/core.py:1
             │     ├─ 0.005 <module>  multiprocessing/__init__.py:1
             │     │  └─ 0.005 <module>  multiprocessing/context.py:1
             │     │     └─ 0.004 <module>  multiprocessing/reduction.py:1
             │     └─ 0.004 <module>  concurrent/futures/__init__.py:1
             └─ 0.005 <module>  usort/config.py:1

So clearly, there's a lot of room for optimizations even before compiling code with mypyc :)

cProfile isn't considered stdlib

I suspect this was broken with the case-related changes in #19 and reusing SortableImport.first_module for sorting as well as section detection.

This is properly sorted and formatted:

import cProfile
import os

but current (0.5.0a3) insists on putting cProfile last, outside the stdlib section.

$ usort list-imports --debug temp.py
temp.py 1 blocks:
  body[0:2]
    1 SortableImport(sort_key=SortKey(category_index=2, is_from_import=False, ndots=0), first_module='cprofile', first_dotted_import='cprofile', imported_names={'cProfile': 'cProfile'}) (third_party)
    0 SortableImport(sort_key=SortKey(category_index=1, is_from_import=False, ndots=0), first_module='os', first_dotted_import='os', imported_names={'os': 'os'}) (standard_library)

What to do when future/stdlib/first_party categories aren't used?

One thing that I've considered a couple times is the use of the hardcoded category values for stdlib and first party modules. Specifically for cases where the user potentially does the following in their project config:

[tool.usort]
categories = ["catchall", "numpy"]
default_category = "catchall"

The future, stdlib, and first-party detection, all hardcode categories that wouldn't be in the configured category list, and I'm not sure how that ends up working in the sorting system, or what the correct way to deal with this would be (other than more configuration options that I don't think we want). Maybe we need to do something like known[module] = CAT_FIRST_PARTY if CAT_FIRST_PARTY in config.categories else config.default_category. But maybe something that makes that easier rather than repeating that everywhere? 🤷

Support excluding files from a configuration file

As of now usort does not provide an option to exclude certain files from a configuration file. There is the # usort:skip (or # isort:skip) directive, but this gets tedious fast if one wants to skip a complete directory or files based on a pattern.

There are several options from other formatters / linters how to achieve this. Since usort should be used with a code formatter and black is an obvious choice, IMO it would be a good idea to adhere to their --exclude behavior, i.e. regular expression matching.

Respect names case when sorting (CONSTANT, Class, function/variable_name)

Problem

usort diff (no config) produces this diff:

-from .names import ABBR_TO_FULL_NAME, FULL_NAME_TO_ABBR, Path, deminify, minify
+from .names import ABBR_TO_FULL_NAME, deminify, FULL_NAME_TO_ABBR, minify, Path

However, I find the way it was ordered already is more consistent and "right".
isort respects that order and doesn't change it by default.

Proposal

Add option to preserve CONSTANT, Class, function/variable_name order when sorting.

compatibility: handle isort:skip on the _first_ line of a wrapped statement

Something like

from x import (  # isort:skip
  y,
  z,
)

Currently is considered sortable (because it looks for the directive at the end of the statement, which here is after the closing paren. The fix here is probably intertwined with wrapping in #16, and some other libcst or fixit efforts that make edits to change imports.

Misclassifies local imports as third party

Clone aql and sort it:

(.venv) jreese@legion ~/workspace/aql main  » python -m usort format --diff aql
--- a/aql/tests/engines/__init__.py
+++ b/aql/tests/engines/__init__.py
@@ -2,8 +2,8 @@
 # Licensed under the MIT license

 from .base import EngineTest
+
+from .integration import IntegrationTest  # isort:skip                                                                                                                               from .mysql import MysqlEngineTest
 from .sql import SqlEngineTest
 from .sqlite import SqliteEngineTest
-
-from .integration import IntegrationTest  # isort:skip
--- a/aql/tests/engines/integration.py
+++ b/aql/tests/engines/integration.py
@@ -3,9 +3,9 @@

 from sqlite3 import OperationalError

+import aql
+
 from aiounittest import AsyncTestCase
-
-import aql


 @aql.table

This results in the local import aql appearing above the third-party from aiounittest import ....

Add side_effect_modules to config

Some imports have known side-effects, and should be consdiered as block separators. Would be nice if these could be set in configuration. I don't think there are any in stdlib, but django.settings as an example may import your settings module that changes sys.path.

Should have more configurable sections

This will probably require some decisions about whether the special cases in sorting from isort should be preserved, such that this is still a no-op on already-isorted code so you can switch back and forth.

Merge duplicate imports

usort accepts the snippet below as sorted.

from foo import bar
from foo import baz
from foo import baz, bar

while it can be shrank to

from foo import baz, bar

Would it be possible to perform this automatically, so I don't have to pay attention to the imports if new ones are added?

AssertionError when formatting semicolon-delimited import lines

Originally noticed via omnilib/ufmt#115:

AssertionError is raised when formatting a file with multiple statements on a line that includes an import. The following example code will trigger this error in 1.0.4:

import sys; print(sys)
$ usort -V
usort, version 1.0.4

$ usort check foo.py
Error sorting foo.py:
Would sort foo.py

As seen from the API:

Python 3.9.9 (main, Nov 28 2021, 12:38:04)
Type 'copyright', 'credits' or 'license' for more information
IPython 8.5.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import usort

In [2]: from pathlib import Path

In [3]: foo = Path("foo.py")

In [4]: content = b"import sys; print(sys)\n\n"

In [5]: config = usort.Config()

In [6]: result = usort.usort(content, config, foo)

In [7]: result.error
Out[7]: AssertionError()

In [8]: print(result.trace)
Traceback (most recent call last):
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/usort/api.py", line 33, in usort
    new_mod = sorter.sort_module()
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/usort/sorting.py", line 305, in sort_module
    new_module = self.wrapper.visit(self.transformer)
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/libcst/metadata/wrapper.py", line 204, in visit
    return self.module.visit(visitor)
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/libcst/_nodes/module.py", line 90, in visit
    result = super(Module, self).visit(visitor)
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/libcst/_nodes/base.py", line 237, in visit
    leave_result = visitor.on_leave(self, with_updated_children)
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/libcst/_visitors.py", line 71, in on_leave
    updated_node = leave_func(original_node, updated_node)
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/usort/sorting.py", line 348, in leave_Module
    sorted_body = self.sorter.find_and_sort_blocks(
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/usort/sorting.py", line 278, in find_and_sort_blocks
    blocks = list(self.sortable_blocks(body))
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/usort/sorting.py", line 180, in sortable_blocks
    imp = import_from_node(stmt, self.config)
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/usort/translate.py", line 169, in import_from_node
    comments = import_comments_from_node(node)
  File "/Users/amethyst/workspace/ufmt/.venv/lib/python3.9/site-packages/usort/translate.py", line 39, in import_comments_from_node
    assert len(node.body) == 1
AssertionError

Black enforces a blank line before comments

If a and b are in the same category, we want to remove the blank line before the comment, but black wants to put it back.

import a

# comment
import b

We should just fixup_whitespace to include this so that check can be used independently.

Handle encoding correctly

Right now read_text uses the system encoding only. We should read bytes and allow libcst to decode/encode.

Feature: Add command line flag to specify location of usort config

By default, µsort will automatically look for the "nearest" pyproject.toml for each file being formatted, and load the config from that location before formatting the associated file. For some projects, it would be preferable to specify the location of a canonical pyproject.toml that will be used as configuration for all files being formatted, regardless of any other pyproject.toml found near these files. This would also enable loading configs from a generic toml file, but we should always expect to find config values in the PEP-518 compliant tool.usort table.

The flag should probably be something like:

$ usort --config path/to/config.toml format ...

Normally, usort.sorting.usort_path() and usort.sorting.usort_stdin() call Config.find(...) to locate pyproject.toml and receive an appropriate Config object. A solution for this should probably set a flag or call a classmethod on Config to override that behavior and always supply the preferred configuration.

Should combine imports (in reasonable cases)

For imports that don't have inline comments, we can combine and reorder their names. This is a common request, and although I don't want to do much in the way of formatting, we can support the common case pretty easily.

from os import signal
from os import path

becomes

from os import path, signal

Sorting names within a single statement is a simple case of this as well.

Feature: a new subcommand called "explain"

Right now list-imports --debug tells you a lot of information, but what most people are probably interested in is why blocks get split. This subcommand should tell you a human-readable version of why not is_sortable_import on the one that causes a new block to be created, in addition to a simplified version of the category for imports and maybe the sort_key.

Add pre-commit hook

Is there any interest in adding a .pre-commit-hooks.yaml file so that usort can be easily used with pre-commit? I'd be willing to put this together.

General directive customization

Some directives are standalone, e.g.

# type STANDALONE
# fmt: off

Others come before the line (or statement) they affect

# type LEADING
# pyre-ignore: ...
import x

While others must happen at end of line

# type TRAILING_FIRST_LINE
from x import (  # isort:skip
  y,
  # type TRAILING
  z,  # noqa: F123
)  # hypothetical TRAILING_LAST_LINE

These basic types can be detected by prefix match and handled when ordering. Right now leading comments (in a block) are special-cased in partition_leading_lines but ones that are of the LEADING type need to be excepted from that and kept with the statement.

These should be configurable, but with defaults for common tools.

Examples comparing to isort's heuristics in the documentation seem outdated

I've tried the examples of isort breaking code that are given here and it doesn't seem like any of those are broken by isort:
https://usort.readthedocs.io/en/latest/why.html#comparison-to-isort

Based on my testing, they haven't been broken since isort 5 (released July 2020) for sure so I'm a bit surprised to see those listed in the documentation that was added just 6 months ago. I can understand the benefit of usort being designed with safety as a priority but the way it's compared to isort is currently unfair.

isort doesn't really seem to advertise safety as a priority (though I'm pretty sure it's nonetheless considered important to it since isort 5) so that is still a good selling point (I find the mention of the strictly-parsed tree and how it causes bugs in isort to be particularly convincing), it would be good to not show code examples that isort doesn't actually break though :)

Normalize leading blank lines when moving imports

Running µsort on this sort of input does what I would consider to be the "wrong thing":

import re

import usort
import trailrunner

print("hello")

This gets sorted into:

import re

import trailrunner

import usort

print("hello")

Ie, the leading blank line above import usort is kept when moving usort below trailrunner, even though they are in the same category.

Support for Python 3.10's match operator

Currently the match operator is not supported because of the version of libcst that is in use. #180
0.4.1 only has support for Python up to 3.8, while 0.4.2 (and up) has support for every other Python version.
Is it likely to be fixed in the near future?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.