codesorter 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codesorter-0.2.1 → codesorter-0.2.3}/CHANGES.rst +23 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/PKG-INFO +1 -1
- {codesorter-0.2.1 → codesorter-0.2.3}/codesorter/cli.py +18 -19
- {codesorter-0.2.1 → codesorter-0.2.3}/codesorter/const.py +1 -1
- {codesorter-0.2.1 → codesorter-0.2.3}/codesorter/sort_code.py +19 -3
- codesorter-0.2.3/tests/test_files/name_rebinding_input.py +16 -0
- codesorter-0.2.3/tests/test_files/name_rebinding_output.py +16 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_sort_code.py +17 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.github/dependabot.yml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/ci.yml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/pre-commit_autoupdate.yml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/prepare_release.yml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/pypi.yml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/scorecard.yml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/stale_action.yml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/tag_release.yml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.gitignore +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.libcst.codemod.yaml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.pre-commit-config.yaml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.pre-commit-hooks.yaml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.python-version +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/.readthedocs.yaml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/LICENSE.txt +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/README.rst +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/codesorter/__init__.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/codesorter/py.typed +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/docs/Makefile +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/docs/code_overview/sort_code.rst +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/docs/conf.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/docs/contributing/pre_commit.rst +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/docs/docutils.conf +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/docs/genindex.rst +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/docs/index.rst +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/docs/make.bat +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/docs/package_info/change_log.rst +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/examples/after_example.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/examples/before_example.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/pyproject.toml +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/__init__.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/conftest.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/alias_function_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/alias_function_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/augmented_assignment_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/augmented_assignment_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/basic_function_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/basic_function_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_global_dependency_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_global_dependency_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_inheritance_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_inheritance_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_method_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_method_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/classmethod_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/classmethod_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/comprehension_dependency_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/comprehension_dependency_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/comprehensive_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/comprehensive_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/constant_ordering_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/constant_ordering_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/custom_decorators_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/custom_decorators_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/keyword_arguments_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/keyword_arguments_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/lazy_annotation_cycle_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/lazy_annotation_cycle_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/mixed_decorators_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/mixed_decorators_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/order_sensitive_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/order_sensitive_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/property_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/property_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/pytest_fixtures_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/pytest_fixtures_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/staticmethod_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/staticmethod_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/underscore_ordering_input.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/underscore_ordering_output.py +0 -0
- {codesorter-0.2.1 → codesorter-0.2.3}/uv.lock +0 -0
|
@@ -4,6 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
codesorter follows `semantic versioning <https://semver.org/>`_.
|
|
6
6
|
|
|
7
|
+
********************
|
|
8
|
+
0.2.3 (2026/06/14)
|
|
9
|
+
********************
|
|
10
|
+
|
|
11
|
+
********************
|
|
12
|
+
0.2.2 (2026/06/14)
|
|
13
|
+
********************
|
|
14
|
+
|
|
15
|
+
**Changed**
|
|
16
|
+
|
|
17
|
+
- Format reordered files with ``ruff`` by default instead of ``black``, so a downstream
|
|
18
|
+
project no longer needs a ``.libcst.codemod.yaml`` to use the ``codesorter`` CLI or
|
|
19
|
+
pre-commit hook. ``ruff`` must be importable on the ``PATH`` when sorting.
|
|
20
|
+
|
|
21
|
+
**Fixed**
|
|
22
|
+
|
|
23
|
+
- Keep an assignment that rebinds a name also bound by a sibling in its original
|
|
24
|
+
position relative to that sibling. For example ``ten = cachedproperty(ten, ...)``
|
|
25
|
+
following ``def ten`` now stays after the method it wraps instead of being hoisted
|
|
26
|
+
ahead of it (which raised ``NameError`` and changed which binding wins). Property
|
|
27
|
+
getter/setter/deleter groups, which carry no assignment, are still ordered by their
|
|
28
|
+
sort key.
|
|
29
|
+
|
|
7
30
|
********************
|
|
8
31
|
0.2.1 (2026/06/14)
|
|
9
32
|
********************
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codesorter
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: A Python codemod that sorts and organizes code in your files.
|
|
5
5
|
Project-URL: Change Log, https://codesorter.readthedocs.io/en/latest/package_info/change_log.html
|
|
6
6
|
Project-URL: Documentation, https://codesorter.readthedocs.io/
|
|
@@ -8,9 +8,8 @@ from pathlib import Path
|
|
|
8
8
|
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
10
|
import libcst as cst
|
|
11
|
-
import libcst.tool
|
|
12
11
|
import pathspec
|
|
13
|
-
from libcst.codemod import CodemodContext
|
|
12
|
+
from libcst.codemod import CodemodContext, parallel_exec_transform_with_prettyprint
|
|
14
13
|
|
|
15
14
|
from codesorter.const import DEFAULT_EXCLUDES
|
|
16
15
|
from codesorter.sort_code import SortCodeCommand
|
|
@@ -208,20 +207,20 @@ def main(*, argv: list[str] | None = None) -> None:
|
|
|
208
207
|
if args.check:
|
|
209
208
|
sys.exit(_check_files(files=files))
|
|
210
209
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
sys.exit(
|
|
210
|
+
# Format with ruff so downstream projects need no ``.libcst.codemod.yaml`` (the
|
|
211
|
+
# libcst codemod tool would otherwise default to black).
|
|
212
|
+
result = parallel_exec_transform_with_prettyprint(
|
|
213
|
+
SortCodeCommand,
|
|
214
|
+
files,
|
|
215
|
+
format_code=True,
|
|
216
|
+
formatter_args=["ruff", "format", "-"],
|
|
217
|
+
jobs=args.jobs,
|
|
218
|
+
repo_root=".",
|
|
219
|
+
show_successes=args.show_successes,
|
|
220
|
+
unified_diff=5 if args.unified_diff else None,
|
|
221
|
+
)
|
|
222
|
+
sys.stderr.write(f"Finished codemodding {result.successes + result.skips + result.failures} files!\n")
|
|
223
|
+
sys.stderr.write(f" - Transformed {result.successes} files successfully.\n")
|
|
224
|
+
sys.stderr.write(f" - Skipped {result.skips} files.\n")
|
|
225
|
+
sys.stderr.write(f" - Failed to codemod {result.failures} files.\n")
|
|
226
|
+
sys.exit(1 if result.failures > 0 else 0)
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import enum
|
|
6
6
|
import heapq
|
|
7
|
+
import itertools
|
|
7
8
|
from collections import defaultdict
|
|
8
9
|
from enum import auto
|
|
9
10
|
from typing import TYPE_CHECKING, Protocol, TypeAlias, TypeVar, cast
|
|
@@ -272,12 +273,27 @@ class SortCodeCommand(VisitorBasedCodemodCommand, m.MatcherDecoratableTransforme
|
|
|
272
273
|
name_to_indexes[_sortable_name(item)].append(index)
|
|
273
274
|
dependents: list[set[int]] = [set() for _ in items]
|
|
274
275
|
indegree = [0] * len(items)
|
|
276
|
+
|
|
277
|
+
def _add_edge(earlier: int, later: int) -> None:
|
|
278
|
+
if earlier != later and later not in dependents[earlier]:
|
|
279
|
+
dependents[earlier].add(later)
|
|
280
|
+
indegree[later] += 1
|
|
281
|
+
|
|
275
282
|
for index, item in enumerate(items):
|
|
276
283
|
for dependency in self.dependencies.get(_sortable_name(item), ()):
|
|
277
284
|
for dependency_index in name_to_indexes.get(dependency, ()):
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
285
|
+
_add_edge(dependency_index, index)
|
|
286
|
+
# An assignment that rebinds a name also bound by a sibling (for example
|
|
287
|
+
# ``def x`` followed by ``x = wraps(x)``) must keep its original position, since
|
|
288
|
+
# the later binding wins at runtime and may reference the earlier one. Groups of
|
|
289
|
+
# same-named methods (property getter/setter/deleter) are excluded — they carry
|
|
290
|
+
# no assignment and are ordered safely by their sort key.
|
|
291
|
+
for indexes in name_to_indexes.values():
|
|
292
|
+
for earlier, later in itertools.pairwise(indexes):
|
|
293
|
+
if isinstance(items[earlier], cst.SimpleStatementLine) or isinstance(
|
|
294
|
+
items[later], cst.SimpleStatementLine
|
|
295
|
+
):
|
|
296
|
+
_add_edge(earlier, later)
|
|
281
297
|
return dependents, indegree
|
|
282
298
|
|
|
283
299
|
def _get_dependencies( # noqa: C901
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""An assignment that rebinds a method name stays after the method it wraps."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Klass:
|
|
5
|
+
def ten(self):
|
|
6
|
+
return 10
|
|
7
|
+
|
|
8
|
+
ten = cachedproperty(ten, doc="Return 10.")
|
|
9
|
+
|
|
10
|
+
def alpha(self):
|
|
11
|
+
"""A method that sorts first alphabetically."""
|
|
12
|
+
return 1
|
|
13
|
+
|
|
14
|
+
def beta(self):
|
|
15
|
+
"""A plain method."""
|
|
16
|
+
return 2
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""An assignment that rebinds a method name stays after the method it wraps."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Klass:
|
|
5
|
+
def alpha(self):
|
|
6
|
+
"""A method that sorts first alphabetically."""
|
|
7
|
+
return 1
|
|
8
|
+
|
|
9
|
+
def beta(self):
|
|
10
|
+
"""A plain method."""
|
|
11
|
+
return 2
|
|
12
|
+
|
|
13
|
+
def ten(self):
|
|
14
|
+
return 10
|
|
15
|
+
|
|
16
|
+
ten = cachedproperty(ten, doc="Return 10.")
|
|
@@ -217,6 +217,23 @@ from pathlib import Path
|
|
|
217
217
|
assert "import sys" in result.code
|
|
218
218
|
assert "from pathlib import Path" in result.code
|
|
219
219
|
|
|
220
|
+
def test_name_rebinding(self, test_files):
|
|
221
|
+
"""Test that an assignment rebinding a method name stays after that method.
|
|
222
|
+
|
|
223
|
+
``ten = cachedproperty(ten, ...)`` binds the same name as the ``def ten`` above
|
|
224
|
+
it and wraps that method, so the assignment must keep its position after the
|
|
225
|
+
method (moving it ahead would raise ``NameError`` and change which binding
|
|
226
|
+
wins). Property getter/setter/deleter groups are unaffected — they carry no
|
|
227
|
+
assignment.
|
|
228
|
+
|
|
229
|
+
"""
|
|
230
|
+
input_code, expected_code = test_files
|
|
231
|
+
context = CodemodContext()
|
|
232
|
+
command = SortCodeCommand(context)
|
|
233
|
+
result = command.transform_module(cst.parse_module(input_code))
|
|
234
|
+
|
|
235
|
+
assert expected_code == result.code
|
|
236
|
+
|
|
220
237
|
def test_order_sensitive(self, test_files):
|
|
221
238
|
"""Test that enum, dataclass, and named-tuple member order is preserved."""
|
|
222
239
|
input_code, expected_code = test_files
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|