From 26c42f36ed85bdf4f880c6b5dddb26acf47874d5 Mon Sep 17 00:00:00 2001 From: Alessandro Pietro Bardelli Date: Tue, 18 Nov 2025 18:40:06 +0100 Subject: [PATCH] FEAT: honour [tool.usort] line length option. --- docs/guide.rst | 20 ++++++++++++++++---- docs/why.rst | 3 ++- usort/config.py | 4 ++++ usort/tests/config.py | 25 ++++++++++++++++++++++++- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/docs/guide.rst b/docs/guide.rst index c2bcd4f..b7c750b 100644 --- a/docs/guide.rst +++ b/docs/guide.rst @@ -537,6 +537,17 @@ The following options are valid for the main ``tool.usort`` table: Whether to merge sequential imports from the same base module. See `Merging`_ for details on how this works. +.. attribute:: line_length + :type: int + :value: 88 + + The maximum line length µsort should target when rendering imports. Imports + that fit within this limit, including indentation and inline comments, will + collapse to a single line. Longer imports render as multi-line imports with + one name per line. When both ``[tool.usort]`` ``line_length`` and + ``[tool.black]`` ``line-length`` are provided, the value from Black takes + precedence. + .. attribute:: excludes :type: List[str] @@ -597,10 +608,11 @@ as adding the :mod:`example` module to the "first_party" category: :type: int :value: 88 - The target line length configured for Black will also be used by µsort when - rendering imports after merging and sorting. Imports that fit within this length, - including indentation and comments, will be rendered on a single line. Otherwise, - imports will be rendered as multi-line imports, with a single name per line. + The target line length configured for Black takes precedence over + ``[tool.usort]`` ``line_length``. µsort uses this value when rendering imports + after merging and sorting. Imports that fit within this length, including + indentation and comments, will be rendered on a single line. Otherwise, imports + will be rendered as multi-line imports, with a single name per line. .. _Black: https://black.readthedocs.io diff --git a/docs/why.rst b/docs/why.rst index f817cf5..11ce8db 100644 --- a/docs/why.rst +++ b/docs/why.rst @@ -44,7 +44,8 @@ developers writing code: - No support for configuring output style. µsort works best when run before a dedicated code formatter like `Black`_ for enforcing style choices. µsort does use - the configured line length for Black when rendering imports on one or more lines. + its configured line length when rendering imports on one or more lines, honoring + ``[tool.usort]`` ``line_length`` unless ``[tool.black]`` ``line-length`` is present. We use `µfmt`_ to combine µsort and Black into a single, atomic formatting step. - No vendored code. All dependencies are satisfied from PyPI, with unbound version diff --git a/usort/config.py b/usort/config.py index b42546a..221907f 100644 --- a/usort/config.py +++ b/usort/config.py @@ -169,6 +169,10 @@ def update_from_config(self, toml_path: Path) -> None: self.merge_imports = bool(tbl["merge_imports"]) if "excludes" in tbl: self.excludes = tbl["excludes"] + if "line_length" in tbl: + self.line_length = int(tbl["line_length"]) + elif "line-length" in tbl: + self.line_length = int(tbl["line-length"]) for cat, names in tbl.get("known", {}).items(): typed_cat = Category(cat) diff --git a/usort/tests/config.py b/usort/tests/config.py index 9bbf241..74e73cf 100644 --- a/usort/tests/config.py +++ b/usort/tests/config.py @@ -238,7 +238,7 @@ def test_config_excludes(self) -> None: expected = ["fixtures/", "*generated.py"] self.assertEqual(expected, conf.excludes) - def test_black_line_length(self) -> None: + def test_line_length_config_precedence(self) -> None: with tempfile.TemporaryDirectory() as d: d_path = Path(d) (d_path / "foo").mkdir(parents=True) @@ -249,6 +249,16 @@ def test_black_line_length(self) -> None: conf = Config.find(d_path / "foo" / "bar.py") self.assertEqual(Config.line_length, conf.line_length) + with self.subTest("with usort config"): + (d_path / "foo" / "pyproject.toml").write_text( + """\ +[tool.usort] +line_length = 120 +""" + ) + conf = Config.find(d_path / "foo" / "bar.py") + self.assertEqual(120, conf.line_length) + with self.subTest("with black config"): (d_path / "foo" / "pyproject.toml").write_text( """\ @@ -259,6 +269,19 @@ def test_black_line_length(self) -> None: conf = Config.find(d_path / "foo" / "bar.py") self.assertEqual(120, conf.line_length) + with self.subTest("black overrides usort"): + (d_path / "foo" / "pyproject.toml").write_text( + """\ +[tool.usort] +line_length = 120 + +[tool.black] +line-length = 90 +""" + ) + conf = Config.find(d_path / "foo" / "bar.py") + self.assertEqual(90, conf.line_length) + def test_config_parent_walk(self) -> None: with tempfile.TemporaryDirectory() as d: d_path = Path(d)