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.
Files changed (79) hide show
  1. {codesorter-0.2.1 → codesorter-0.2.3}/CHANGES.rst +23 -0
  2. {codesorter-0.2.1 → codesorter-0.2.3}/PKG-INFO +1 -1
  3. {codesorter-0.2.1 → codesorter-0.2.3}/codesorter/cli.py +18 -19
  4. {codesorter-0.2.1 → codesorter-0.2.3}/codesorter/const.py +1 -1
  5. {codesorter-0.2.1 → codesorter-0.2.3}/codesorter/sort_code.py +19 -3
  6. codesorter-0.2.3/tests/test_files/name_rebinding_input.py +16 -0
  7. codesorter-0.2.3/tests/test_files/name_rebinding_output.py +16 -0
  8. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_sort_code.py +17 -0
  9. {codesorter-0.2.1 → codesorter-0.2.3}/.github/dependabot.yml +0 -0
  10. {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/ci.yml +0 -0
  11. {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/pre-commit_autoupdate.yml +0 -0
  12. {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/prepare_release.yml +0 -0
  13. {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/pypi.yml +0 -0
  14. {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/scorecard.yml +0 -0
  15. {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/stale_action.yml +0 -0
  16. {codesorter-0.2.1 → codesorter-0.2.3}/.github/workflows/tag_release.yml +0 -0
  17. {codesorter-0.2.1 → codesorter-0.2.3}/.gitignore +0 -0
  18. {codesorter-0.2.1 → codesorter-0.2.3}/.libcst.codemod.yaml +0 -0
  19. {codesorter-0.2.1 → codesorter-0.2.3}/.pre-commit-config.yaml +0 -0
  20. {codesorter-0.2.1 → codesorter-0.2.3}/.pre-commit-hooks.yaml +0 -0
  21. {codesorter-0.2.1 → codesorter-0.2.3}/.python-version +0 -0
  22. {codesorter-0.2.1 → codesorter-0.2.3}/.readthedocs.yaml +0 -0
  23. {codesorter-0.2.1 → codesorter-0.2.3}/LICENSE.txt +0 -0
  24. {codesorter-0.2.1 → codesorter-0.2.3}/README.rst +0 -0
  25. {codesorter-0.2.1 → codesorter-0.2.3}/codesorter/__init__.py +0 -0
  26. {codesorter-0.2.1 → codesorter-0.2.3}/codesorter/py.typed +0 -0
  27. {codesorter-0.2.1 → codesorter-0.2.3}/docs/Makefile +0 -0
  28. {codesorter-0.2.1 → codesorter-0.2.3}/docs/code_overview/sort_code.rst +0 -0
  29. {codesorter-0.2.1 → codesorter-0.2.3}/docs/conf.py +0 -0
  30. {codesorter-0.2.1 → codesorter-0.2.3}/docs/contributing/pre_commit.rst +0 -0
  31. {codesorter-0.2.1 → codesorter-0.2.3}/docs/docutils.conf +0 -0
  32. {codesorter-0.2.1 → codesorter-0.2.3}/docs/genindex.rst +0 -0
  33. {codesorter-0.2.1 → codesorter-0.2.3}/docs/index.rst +0 -0
  34. {codesorter-0.2.1 → codesorter-0.2.3}/docs/make.bat +0 -0
  35. {codesorter-0.2.1 → codesorter-0.2.3}/docs/package_info/change_log.rst +0 -0
  36. {codesorter-0.2.1 → codesorter-0.2.3}/examples/after_example.py +0 -0
  37. {codesorter-0.2.1 → codesorter-0.2.3}/examples/before_example.py +0 -0
  38. {codesorter-0.2.1 → codesorter-0.2.3}/pyproject.toml +0 -0
  39. {codesorter-0.2.1 → codesorter-0.2.3}/tests/__init__.py +0 -0
  40. {codesorter-0.2.1 → codesorter-0.2.3}/tests/conftest.py +0 -0
  41. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/alias_function_input.py +0 -0
  42. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/alias_function_output.py +0 -0
  43. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/augmented_assignment_input.py +0 -0
  44. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/augmented_assignment_output.py +0 -0
  45. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/basic_function_input.py +0 -0
  46. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/basic_function_output.py +0 -0
  47. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_global_dependency_input.py +0 -0
  48. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_global_dependency_output.py +0 -0
  49. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_inheritance_input.py +0 -0
  50. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_inheritance_output.py +0 -0
  51. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_method_input.py +0 -0
  52. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/class_method_output.py +0 -0
  53. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/classmethod_input.py +0 -0
  54. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/classmethod_output.py +0 -0
  55. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/comprehension_dependency_input.py +0 -0
  56. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/comprehension_dependency_output.py +0 -0
  57. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/comprehensive_input.py +0 -0
  58. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/comprehensive_output.py +0 -0
  59. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/constant_ordering_input.py +0 -0
  60. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/constant_ordering_output.py +0 -0
  61. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/custom_decorators_input.py +0 -0
  62. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/custom_decorators_output.py +0 -0
  63. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/keyword_arguments_input.py +0 -0
  64. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/keyword_arguments_output.py +0 -0
  65. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/lazy_annotation_cycle_input.py +0 -0
  66. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/lazy_annotation_cycle_output.py +0 -0
  67. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/mixed_decorators_input.py +0 -0
  68. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/mixed_decorators_output.py +0 -0
  69. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/order_sensitive_input.py +0 -0
  70. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/order_sensitive_output.py +0 -0
  71. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/property_input.py +0 -0
  72. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/property_output.py +0 -0
  73. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/pytest_fixtures_input.py +0 -0
  74. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/pytest_fixtures_output.py +0 -0
  75. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/staticmethod_input.py +0 -0
  76. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/staticmethod_output.py +0 -0
  77. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/underscore_ordering_input.py +0 -0
  78. {codesorter-0.2.1 → codesorter-0.2.3}/tests/test_files/underscore_ordering_output.py +0 -0
  79. {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.1
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
- cst_argv = [
212
- "codemod",
213
- "-x",
214
- "codesorter.sort_code.SortCodeCommand",
215
- *files,
216
- ]
217
-
218
- if args.unified_diff:
219
- cst_argv.append("--unified-diff")
220
-
221
- if args.show_successes:
222
- cst_argv.append("--show-successes")
223
-
224
- if args.jobs is not None:
225
- cst_argv.extend(["--jobs", str(args.jobs)])
226
-
227
- sys.exit(libcst.tool.main("codesorter", cst_argv))
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)
@@ -39,4 +39,4 @@ PLAIN_DECORATOR_PARTS = 1
39
39
 
40
40
  PROPERTY_DECORATOR_PARTS = 2
41
41
 
42
- __version__ = "0.2.1"
42
+ __version__ = "0.2.3"
@@ -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
- if dependency_index != index and index not in dependents[dependency_index]:
279
- dependents[dependency_index].add(index)
280
- indegree[index] += 1
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