codesorter 0.2.0__tar.gz → 0.2.2__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 (77) hide show
  1. {codesorter-0.2.0 → codesorter-0.2.2}/CHANGES.rst +22 -0
  2. {codesorter-0.2.0 → codesorter-0.2.2}/PKG-INFO +1 -1
  3. {codesorter-0.2.0 → codesorter-0.2.2}/codesorter/cli.py +18 -19
  4. {codesorter-0.2.0 → codesorter-0.2.2}/codesorter/const.py +1 -1
  5. {codesorter-0.2.0 → codesorter-0.2.2}/codesorter/sort_code.py +8 -3
  6. codesorter-0.2.2/tests/test_files/comprehension_dependency_input.py +13 -0
  7. codesorter-0.2.2/tests/test_files/comprehension_dependency_output.py +13 -0
  8. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_sort_code.py +16 -0
  9. {codesorter-0.2.0 → codesorter-0.2.2}/.github/dependabot.yml +0 -0
  10. {codesorter-0.2.0 → codesorter-0.2.2}/.github/workflows/ci.yml +0 -0
  11. {codesorter-0.2.0 → codesorter-0.2.2}/.github/workflows/pre-commit_autoupdate.yml +0 -0
  12. {codesorter-0.2.0 → codesorter-0.2.2}/.github/workflows/prepare_release.yml +0 -0
  13. {codesorter-0.2.0 → codesorter-0.2.2}/.github/workflows/pypi.yml +0 -0
  14. {codesorter-0.2.0 → codesorter-0.2.2}/.github/workflows/scorecard.yml +0 -0
  15. {codesorter-0.2.0 → codesorter-0.2.2}/.github/workflows/stale_action.yml +0 -0
  16. {codesorter-0.2.0 → codesorter-0.2.2}/.github/workflows/tag_release.yml +0 -0
  17. {codesorter-0.2.0 → codesorter-0.2.2}/.gitignore +0 -0
  18. {codesorter-0.2.0 → codesorter-0.2.2}/.libcst.codemod.yaml +0 -0
  19. {codesorter-0.2.0 → codesorter-0.2.2}/.pre-commit-config.yaml +0 -0
  20. {codesorter-0.2.0 → codesorter-0.2.2}/.pre-commit-hooks.yaml +0 -0
  21. {codesorter-0.2.0 → codesorter-0.2.2}/.python-version +0 -0
  22. {codesorter-0.2.0 → codesorter-0.2.2}/.readthedocs.yaml +0 -0
  23. {codesorter-0.2.0 → codesorter-0.2.2}/LICENSE.txt +0 -0
  24. {codesorter-0.2.0 → codesorter-0.2.2}/README.rst +0 -0
  25. {codesorter-0.2.0 → codesorter-0.2.2}/codesorter/__init__.py +0 -0
  26. {codesorter-0.2.0 → codesorter-0.2.2}/codesorter/py.typed +0 -0
  27. {codesorter-0.2.0 → codesorter-0.2.2}/docs/Makefile +0 -0
  28. {codesorter-0.2.0 → codesorter-0.2.2}/docs/code_overview/sort_code.rst +0 -0
  29. {codesorter-0.2.0 → codesorter-0.2.2}/docs/conf.py +0 -0
  30. {codesorter-0.2.0 → codesorter-0.2.2}/docs/contributing/pre_commit.rst +0 -0
  31. {codesorter-0.2.0 → codesorter-0.2.2}/docs/docutils.conf +0 -0
  32. {codesorter-0.2.0 → codesorter-0.2.2}/docs/genindex.rst +0 -0
  33. {codesorter-0.2.0 → codesorter-0.2.2}/docs/index.rst +0 -0
  34. {codesorter-0.2.0 → codesorter-0.2.2}/docs/make.bat +0 -0
  35. {codesorter-0.2.0 → codesorter-0.2.2}/docs/package_info/change_log.rst +0 -0
  36. {codesorter-0.2.0 → codesorter-0.2.2}/examples/after_example.py +0 -0
  37. {codesorter-0.2.0 → codesorter-0.2.2}/examples/before_example.py +0 -0
  38. {codesorter-0.2.0 → codesorter-0.2.2}/pyproject.toml +0 -0
  39. {codesorter-0.2.0 → codesorter-0.2.2}/tests/__init__.py +0 -0
  40. {codesorter-0.2.0 → codesorter-0.2.2}/tests/conftest.py +0 -0
  41. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/alias_function_input.py +0 -0
  42. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/alias_function_output.py +0 -0
  43. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/augmented_assignment_input.py +0 -0
  44. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/augmented_assignment_output.py +0 -0
  45. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/basic_function_input.py +0 -0
  46. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/basic_function_output.py +0 -0
  47. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/class_global_dependency_input.py +0 -0
  48. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/class_global_dependency_output.py +0 -0
  49. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/class_inheritance_input.py +0 -0
  50. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/class_inheritance_output.py +0 -0
  51. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/class_method_input.py +0 -0
  52. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/class_method_output.py +0 -0
  53. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/classmethod_input.py +0 -0
  54. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/classmethod_output.py +0 -0
  55. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/comprehensive_input.py +0 -0
  56. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/comprehensive_output.py +0 -0
  57. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/constant_ordering_input.py +0 -0
  58. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/constant_ordering_output.py +0 -0
  59. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/custom_decorators_input.py +0 -0
  60. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/custom_decorators_output.py +0 -0
  61. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/keyword_arguments_input.py +0 -0
  62. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/keyword_arguments_output.py +0 -0
  63. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/lazy_annotation_cycle_input.py +0 -0
  64. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/lazy_annotation_cycle_output.py +0 -0
  65. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/mixed_decorators_input.py +0 -0
  66. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/mixed_decorators_output.py +0 -0
  67. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/order_sensitive_input.py +0 -0
  68. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/order_sensitive_output.py +0 -0
  69. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/property_input.py +0 -0
  70. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/property_output.py +0 -0
  71. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/pytest_fixtures_input.py +0 -0
  72. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/pytest_fixtures_output.py +0 -0
  73. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/staticmethod_input.py +0 -0
  74. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/staticmethod_output.py +0 -0
  75. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/underscore_ordering_input.py +0 -0
  76. {codesorter-0.2.0 → codesorter-0.2.2}/tests/test_files/underscore_ordering_output.py +0 -0
  77. {codesorter-0.2.0 → codesorter-0.2.2}/uv.lock +0 -0
@@ -4,6 +4,28 @@
4
4
 
5
5
  codesorter follows `semantic versioning <https://semver.org/>`_.
6
6
 
7
+ ********************
8
+ 0.2.2 (2026/06/14)
9
+ ********************
10
+
11
+ **Changed**
12
+
13
+ - Format reordered files with ``ruff`` by default instead of ``black``, so a downstream
14
+ project no longer needs a ``.libcst.codemod.yaml`` to use the ``codesorter`` CLI or
15
+ pre-commit hook. ``ruff`` must be importable on the ``PATH`` when sorting.
16
+
17
+ ********************
18
+ 0.2.1 (2026/06/14)
19
+ ********************
20
+
21
+ **Fixed**
22
+
23
+ - Treat a name used inside a module- or class-level comprehension as a real dependency.
24
+ Such a comprehension runs eagerly when the definition executes, so an assignment like
25
+ ``values = {key: build(key) for key in keys}`` now sorts after the ``build`` function
26
+ it calls instead of ahead of it (which raised ``NameError``). A comprehension inside a
27
+ function body stays deferred and still imposes no ordering.
28
+
7
29
  ********************
8
30
  0.2.0 (2026/06/14)
9
31
  ********************
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codesorter
3
- Version: 0.2.0
3
+ Version: 0.2.2
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.0"
42
+ __version__ = "0.2.2"
@@ -294,9 +294,14 @@ class SortCodeCommand(VisitorBasedCodemodCommand, m.MatcherDecoratableTransforme
294
294
  if isinstance(meta, (md.ClassScope, md.GlobalScope)):
295
295
 
296
296
  def _outer_scope(scope: object) -> bool:
297
- return isinstance(scope, (md.ClassScope, md.GlobalScope)) or (
298
- isinstance(scope, md.ClassScope) and scope.parent != meta
299
- )
297
+ # A comprehension at module or class level runs eagerly when the
298
+ # definition executes, so a name used inside it is a real dependency.
299
+ # Walk out through any comprehension scopes; a function scope in the
300
+ # chain means the reference is deferred (a lambda or method body) and is
301
+ # not a definition-time dependency.
302
+ while isinstance(scope, md.ComprehensionScope):
303
+ scope = scope.parent
304
+ return isinstance(scope, (md.ClassScope, md.GlobalScope))
300
305
 
301
306
  for found in self.extractall(
302
307
  node,
@@ -0,0 +1,13 @@
1
+ """A module-level comprehension references a function eagerly, so it depends on it."""
2
+
3
+ placeholders = {name: build(name) for name in ["a", "b"]}
4
+
5
+
6
+ def build(name):
7
+ """Used eagerly by the module-level comprehension above."""
8
+ return f"value-{name}"
9
+
10
+
11
+ def lazy():
12
+ """A comprehension here is deferred, so it imposes no ordering."""
13
+ return [build(name) for name in ["c", "d"]]
@@ -0,0 +1,13 @@
1
+ """A module-level comprehension references a function eagerly, so it depends on it."""
2
+
3
+ def build(name):
4
+ """Used eagerly by the module-level comprehension above."""
5
+ return f"value-{name}"
6
+
7
+
8
+ placeholders = {name: build(name) for name in ["a", "b"]}
9
+
10
+
11
+ def lazy():
12
+ """A comprehension here is deferred, so it imposes no ordering."""
13
+ return [build(name) for name in ["c", "d"]]
@@ -116,6 +116,22 @@ class MyClass:
116
116
  except SyntaxError as e:
117
117
  pytest.fail(f"Codemod produced invalid Python syntax: {e}")
118
118
 
119
+ def test_comprehension_dependency(self, test_files):
120
+ """Test that a name used in a module-level comprehension is a real dependency.
121
+
122
+ A module-level comprehension runs eagerly when the module executes, so an
123
+ assignment built from one must follow the function it calls (the reference lives
124
+ in a comprehension scope, which previously hid the dependency). A comprehension
125
+ inside a function body stays deferred and imposes no ordering.
126
+
127
+ """
128
+ input_code, expected_code = test_files
129
+ context = CodemodContext()
130
+ command = SortCodeCommand(context)
131
+ result = command.transform_module(cst.parse_module(input_code))
132
+
133
+ assert expected_code == result.code
134
+
119
135
  def test_comprehensive(self, test_files):
120
136
  """Test comprehensive scenario with pytest fixtures, custom decorators, inheritance, and global dependencies."""
121
137
  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