Repository: facebookexperimental/usort
Pull request #197 (2022-09-01 23:58:52)

Bump flake8 from 4.0.1 to 5.0.4
Bumps [flake8](https://github.com/pycqa/flake8) from 4.0.1 to 5.0.4.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/PyCQA/flake8/commit/6027577d325b0dd8bf1e465ebd29b71b5f0d005b"><code>6027577</code></a> Release 5.0.4</li>
<li><a href="https://github.com/PyCQA/flake8/commit/213e006ce61b3c155445235b3f0d9453391f1f13"><code>213e006</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/pycqa/flake8/issues/1653">#1653</a> from asottile/lower-bound-importlib-metadata</li>
<li><a href="https://github.com/PyCQA/flake8/commit/e94ee2b5f1801354b940cfe830b9160852915aec"><code>e94ee2b</code></a> require sufficiently new importlib-metadata</li>
<li><a href="https://github.com/PyCQA/flake8/commit/318a86a4a12d28cdfc41030ead547332d3461f45"><code>318a86a</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/pycqa/flake8/issues/1646">#1646</a> from televi/main</li>
<li><a href="https://github.com/PyCQA/flake8/commit/7b8b374c9bc1a141ca7cf6670c8cee6708398490"><code>7b8b374</code></a> Clarify entry point naming</li>
<li><a href="https://github.com/PyCQA/flake8/commit/7160561028ee5db09ea58b8555db08c66ee092b3"><code>7160561</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/pycqa/flake8/issues/1649">#1649</a> from PyCQA/pre-commit-ci-update-config</li>
<li><a href="https://github.com/PyCQA/flake8/commit/84d56a8c25106b5e0a41cdf63b5de261f8da5c99"><code>84d56a8</code></a> [pre-commit.ci] pre-commit autoupdate</li>
<li><a href="https://github.com/PyCQA/flake8/commit/ff6569b87db8ae28c41b548071454de620ad14d5"><code>ff6569b</code></a> Release 5.0.3</li>
<li><a href="https://github.com/PyCQA/flake8/commit/e76b59ae44f46f7958d13b28bd2d7d9bdc0f5962"><code>e76b59a</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/pycqa/flake8/issues/1648">#1648</a> from PyCQA/invalid-syntax-partial-parse</li>
<li><a href="https://github.com/PyCQA/flake8/commit/25e8ff18b30b58f1dabc1d20546ebc20fd775560"><code>25e8ff1</code></a> ignore config files that partially parse as flake8 configs</li>
<li>Additional commits viewable in <a href="https://github.com/pycqa/flake8/compare/4.0.1...5.0.4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=flake8&package-manager=pip&previous-version=4.0.1&new-version=5.0.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 89d0c3a..09fa4c3 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,12 +1,12 @@
 black==22.6.0
 coverage==6.2
-flake8==4.0.1
+flake8==5.0.4
 mistune==2.0.3
 mypy==0.971
 pessimist==0.9.3
 setuptools_scm==6.4.2
 sphinx==4.3.2
-sphinx-mdinclude==0.5.1
+sphinx-mdinclude==0.5.2
 tox==3.25.1
 twine==3.8.0
 ufmt==1.3.3
diff --git a/setup.cfg b/setup.cfg
index 2d83454..ccf78c1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -37,8 +37,8 @@ include = usort/*
 omit = usort/tests/*
 
 [coverage:report]
-fail_under = 70
-precision = 1
+fail_under = 92
+precision = 0
 show_missing = True
 skip_covered = True
 
diff --git a/usort/sorting.py b/usort/sorting.py
index 666728c..4c5f336 100644
--- a/usort/sorting.py
+++ b/usort/sorting.py
@@ -163,7 +163,14 @@ class ImportSorter:
             # move imported names metadata
             for imp in new.imports:
                 for key in list(imp.imported_names):
-                    new.imported_names[key] = block.imported_names.pop(key)
+                    name = block.imported_names.pop(key, None)
+                    if name is not None:
+                        # Normally we wouldn't see multiple copies of a key because that
+                        # would have caused a block split already, but if an import is
+                        # just verbatim repeated, it's not technically shadowing.
+                        # If that's the case, we've already popped it and achieved the
+                        # goal of this block.
+                        new.imported_names[key] = name
 
         return new
 


Test patch:
diff --git a/usort/tests/__init__.py b/usort/tests/__init__.py
index 5ca0e12..67dfb7d 100644
--- a/usort/tests/__init__.py
+++ b/usort/tests/__init__.py
@@ -6,6 +6,7 @@
 from .cli import CliTest
 from .config import ConfigTest
 from .functional import BasicOrderingTest, UsortStringFunctionalTest
+from .sorting import SplitTest
 from .stdlibs import StdlibsTest
 from .translate import IsSortableTest, SortableImportTest
 from .types import TypesTest
@@ -21,4 +22,5 @@ __all__ = [
     "StdlibsTest",
     "TypesTest",
     "UtilTest",
+    "SplitTest",
 ]
diff --git a/usort/tests/functional.py b/usort/tests/functional.py
index c486cf1..b48d1cd 100644
--- a/usort/tests/functional.py
+++ b/usort/tests/functional.py
@@ -897,6 +897,21 @@ numpy = ["numpy", "pandas"]
             """,
         )
 
+    def test_sort_merging_and_implicit_blocks(self) -> None:
+        self.assertUsortResult(
+            """
+                from a import x
+                from b import y
+                from b import y
+                from c import x
+            """,
+            """
+                from a import x
+                from b import y
+                from c import x
+            """,
+        )
+
     def test_skip_directives(self) -> None:
         """Test both usort:skip and isort:skip on single line imports"""
         self.assertUsortResult(
diff --git a/usort/tests/sorting.py b/usort/tests/sorting.py
new file mode 100644
index 0000000..446740b
--- /dev/null
+++ b/usort/tests/sorting.py
@@ -0,0 +1,30 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+import unittest
+from pathlib import Path
+
+import libcst as cst
+
+from ..config import Config
+
+from ..sorting import ImportSorter
+
+
+class SplitTest(unittest.TestCase):
+    def test_split_block(self) -> None:
+        mod = cst.parse_module(
+            b"""\
+from a import x
+from b import y
+from b import y
+from c import x
+"""
+        )
+        x = ImportSorter(module=mod, path=Path(), config=Config())
+        blocks = x.sortable_blocks(mod.children)  # type: ignore
+        self.assertEqual(2, len(blocks))
+        self.assertEqual({"x": "a.x"}, blocks[0].imported_names)
+        self.assertEqual({"y": "b.y", "x": "c.x"}, blocks[1].imported_names)