codesorter 0.1.0__tar.gz → 0.2.1__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 (81) hide show
  1. {codesorter-0.1.0 → codesorter-0.2.1}/.pre-commit-config.yaml +8 -6
  2. codesorter-0.2.1/CHANGES.rst +83 -0
  3. {codesorter-0.1.0 → codesorter-0.2.1}/PKG-INFO +5 -1
  4. {codesorter-0.1.0 → codesorter-0.2.1}/README.rst +4 -0
  5. {codesorter-0.1.0 → codesorter-0.2.1}/codesorter/cli.py +7 -7
  6. codesorter-0.2.1/codesorter/const.py +42 -0
  7. codesorter-0.2.1/codesorter/sort_code.py +732 -0
  8. {codesorter-0.1.0 → codesorter-0.2.1}/docs/code_overview/sort_code.rst +0 -2
  9. codesorter-0.2.1/docs/contributing/pre_commit.rst +50 -0
  10. {codesorter-0.1.0 → codesorter-0.2.1}/docs/index.rst +6 -0
  11. {codesorter-0.1.0 → codesorter-0.2.1}/pyproject.toml +1 -0
  12. codesorter-0.2.1/tests/test_files/alias_function_input.py +15 -0
  13. codesorter-0.2.1/tests/test_files/alias_function_output.py +15 -0
  14. codesorter-0.2.1/tests/test_files/augmented_assignment_input.py +18 -0
  15. codesorter-0.2.1/tests/test_files/augmented_assignment_output.py +18 -0
  16. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/class_global_dependency_output.py +4 -4
  17. codesorter-0.2.1/tests/test_files/comprehension_dependency_input.py +13 -0
  18. codesorter-0.2.1/tests/test_files/comprehension_dependency_output.py +13 -0
  19. codesorter-0.2.1/tests/test_files/constant_ordering_input.py +34 -0
  20. codesorter-0.2.1/tests/test_files/constant_ordering_output.py +36 -0
  21. codesorter-0.2.1/tests/test_files/keyword_arguments_input.py +14 -0
  22. codesorter-0.2.1/tests/test_files/keyword_arguments_output.py +14 -0
  23. codesorter-0.2.1/tests/test_files/lazy_annotation_cycle_input.py +26 -0
  24. codesorter-0.2.1/tests/test_files/lazy_annotation_cycle_output.py +26 -0
  25. codesorter-0.2.1/tests/test_files/order_sensitive_input.py +46 -0
  26. codesorter-0.2.1/tests/test_files/order_sensitive_output.py +47 -0
  27. codesorter-0.2.1/tests/test_files/underscore_ordering_input.py +37 -0
  28. codesorter-0.2.1/tests/test_files/underscore_ordering_output.py +37 -0
  29. codesorter-0.2.1/tests/test_sort_code.py +333 -0
  30. codesorter-0.1.0/CHANGES.rst +0 -19
  31. codesorter-0.1.0/codesorter/const.py +0 -25
  32. codesorter-0.1.0/codesorter/sort_code.py +0 -334
  33. codesorter-0.1.0/tests/test_sort_code.py +0 -169
  34. {codesorter-0.1.0 → codesorter-0.2.1}/.github/dependabot.yml +0 -0
  35. {codesorter-0.1.0 → codesorter-0.2.1}/.github/workflows/ci.yml +0 -0
  36. {codesorter-0.1.0 → codesorter-0.2.1}/.github/workflows/pre-commit_autoupdate.yml +0 -0
  37. {codesorter-0.1.0 → codesorter-0.2.1}/.github/workflows/prepare_release.yml +0 -0
  38. {codesorter-0.1.0 → codesorter-0.2.1}/.github/workflows/pypi.yml +0 -0
  39. {codesorter-0.1.0 → codesorter-0.2.1}/.github/workflows/scorecard.yml +0 -0
  40. {codesorter-0.1.0 → codesorter-0.2.1}/.github/workflows/stale_action.yml +0 -0
  41. {codesorter-0.1.0 → codesorter-0.2.1}/.github/workflows/tag_release.yml +0 -0
  42. {codesorter-0.1.0 → codesorter-0.2.1}/.gitignore +0 -0
  43. {codesorter-0.1.0 → codesorter-0.2.1}/.libcst.codemod.yaml +0 -0
  44. {codesorter-0.1.0 → codesorter-0.2.1}/.pre-commit-hooks.yaml +0 -0
  45. {codesorter-0.1.0 → codesorter-0.2.1}/.python-version +0 -0
  46. {codesorter-0.1.0 → codesorter-0.2.1}/.readthedocs.yaml +0 -0
  47. {codesorter-0.1.0 → codesorter-0.2.1}/LICENSE.txt +0 -0
  48. {codesorter-0.1.0 → codesorter-0.2.1}/codesorter/__init__.py +0 -0
  49. {codesorter-0.1.0 → codesorter-0.2.1}/codesorter/py.typed +0 -0
  50. {codesorter-0.1.0 → codesorter-0.2.1}/docs/Makefile +0 -0
  51. {codesorter-0.1.0 → codesorter-0.2.1}/docs/conf.py +0 -0
  52. {codesorter-0.1.0 → codesorter-0.2.1}/docs/docutils.conf +0 -0
  53. {codesorter-0.1.0 → codesorter-0.2.1}/docs/genindex.rst +0 -0
  54. {codesorter-0.1.0 → codesorter-0.2.1}/docs/make.bat +0 -0
  55. {codesorter-0.1.0 → codesorter-0.2.1}/docs/package_info/change_log.rst +0 -0
  56. {codesorter-0.1.0 → codesorter-0.2.1}/examples/after_example.py +0 -0
  57. {codesorter-0.1.0 → codesorter-0.2.1}/examples/before_example.py +0 -0
  58. {codesorter-0.1.0 → codesorter-0.2.1}/tests/__init__.py +0 -0
  59. {codesorter-0.1.0 → codesorter-0.2.1}/tests/conftest.py +0 -0
  60. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/basic_function_input.py +0 -0
  61. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/basic_function_output.py +0 -0
  62. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/class_global_dependency_input.py +0 -0
  63. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/class_inheritance_input.py +0 -0
  64. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/class_inheritance_output.py +0 -0
  65. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/class_method_input.py +0 -0
  66. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/class_method_output.py +0 -0
  67. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/classmethod_input.py +0 -0
  68. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/classmethod_output.py +0 -0
  69. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/comprehensive_input.py +0 -0
  70. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/comprehensive_output.py +92 -92
  71. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/custom_decorators_input.py +0 -0
  72. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/custom_decorators_output.py +17 -17
  73. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/mixed_decorators_input.py +0 -0
  74. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/mixed_decorators_output.py +0 -0
  75. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/property_input.py +0 -0
  76. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/property_output.py +0 -0
  77. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/pytest_fixtures_input.py +0 -0
  78. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/pytest_fixtures_output.py +1 -1
  79. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/staticmethod_input.py +0 -0
  80. {codesorter-0.1.0 → codesorter-0.2.1}/tests/test_files/staticmethod_output.py +0 -0
  81. {codesorter-0.1.0 → codesorter-0.2.1}/uv.lock +0 -0
@@ -1,13 +1,15 @@
1
1
  repos:
2
- - repo: local
3
- hooks:
4
- - always_run: true
5
- entry: codesorter --exclude examples --exclude test_files
2
+ - hooks:
3
+ # Self-hosted: run codesorter from the local checkout rather than a pinned
4
+ # release so the repository is always sorted by its own current code.
5
+ - entry: codesorter
6
+ exclude: ^(examples|tests/test_files)/
6
7
  id: codesorter
7
8
  language: system
8
9
  name: codesorter
9
- pass_filenames: false
10
- types: [ python ]
10
+ require_serial: true
11
+ types: [python]
12
+ repo: local
11
13
 
12
14
  - hooks:
13
15
  - id: docstrfmt
@@ -0,0 +1,83 @@
1
+ ############
2
+ Change Log
3
+ ############
4
+
5
+ codesorter follows `semantic versioning <https://semver.org/>`_.
6
+
7
+ ********************
8
+ 0.2.1 (2026/06/14)
9
+ ********************
10
+
11
+ **Fixed**
12
+
13
+ - Treat a name used inside a module- or class-level comprehension as a real dependency.
14
+ Such a comprehension runs eagerly when the definition executes, so an assignment like
15
+ ``values = {key: build(key) for key in keys}`` now sorts after the ``build`` function
16
+ it calls instead of ahead of it (which raised ``NameError``). A comprehension inside a
17
+ function body stays deferred and still imposes no ordering.
18
+
19
+ ********************
20
+ 0.2.0 (2026/06/14)
21
+ ********************
22
+
23
+ **Added**
24
+
25
+ - Sort module- and class-level assignments. Within every scope the order is now
26
+ assignments, then classes, then functions/methods, so methods always follow nested
27
+ classes and assignments are grouped at the top. Assignments are split into uppercase
28
+ ``CONSTANTS`` first and then other variables; each group sorts with a leading
29
+ underscore first (so ``__dunder__`` and ``_private`` precede public names), and
30
+ dependencies are respected (``B = A + 1`` stays after ``A``). As a result,
31
+ module-level functions now sort after module-level classes. The attribute order of
32
+ enums, dataclasses, and ``NamedTuple``/``TypedDict`` classes is preserved; the base or
33
+ decorator is resolved through ``QualifiedNameProvider`` so aliased imports (``from
34
+ enum import IntEnum as IE``) are recognized.
35
+ - Keep blank-line spacing with its position rather than the moved definition, so
36
+ reordering no longer drags a blank line onto a different statement (for example the
37
+ blank line after a class docstring stays at the top of the block). Comment lines that
38
+ sit directly above a definition still travel with it.
39
+ - Keep an augmented assignment anchored to the constant it augments, so ``__all__ +=
40
+ extra`` stays directly after ``__all__ = [...]`` instead of being left behind as a
41
+ fixed barrier when another assignment sorts between them.
42
+ - Sort keyword arguments in calls, keyword-only parameters in function definitions, and
43
+ string keys in dict literals alphabetically. In a call, keyword arguments are sorted
44
+ and ``**`` unpackings moved to the end (positional arguments and ``*`` unpackings stay
45
+ put), which is safe because a call raises ``TypeError`` on any duplicate keyword
46
+ regardless of order. In a dict literal, ``**`` spreads and non-string keys act as
47
+ barriers so last-wins merge semantics are preserved.
48
+
49
+ **Changed**
50
+
51
+ - Sort ``_``-prefixed (private and dunder) names ahead of public names at every level
52
+ rather than after capitalized names. A plain string sort placed ``_`` after
53
+ ``A``-``Z`` because of its higher code point; names are now ordered on a
54
+ leading-underscore flag first so private definitions, methods, keyword arguments, and
55
+ dict keys group together ahead of the public ones.
56
+
57
+ **Fixed**
58
+
59
+ - Ignore a name used only in a lazy annotation (under ``from __future__ import
60
+ annotations``) when ordering definitions, since the annotation is never evaluated at
61
+ runtime and imposes no real dependency. A forward reference in an annotation (for
62
+ example ``nxt: list[Instr]`` where ``Instr`` is a type-alias union of classes defined
63
+ later) previously forged a false dependency cycle that could hoist the runtime alias
64
+ above the classes it unions and raise ``NameError``.
65
+ - Order definitions with a proper priority topological sort so a class or function is
66
+ always placed after every sibling it depends on. The previous dependency heuristic
67
+ compared per-node dependency vectors and could emit a dependent before its dependency
68
+ (for example, ``Subreddit``'s ``SubredditFlair`` was sorted ahead of the
69
+ flair-template classes it instantiates).
70
+
71
+ ********************
72
+ 0.1.0 (2026/06/14)
73
+ ********************
74
+
75
+ **Added**
76
+
77
+ - Initial release of CodeSorter.
78
+ - CLI interface with directory walking, sensible default excludes, ``.gitignore``
79
+ honoring, and ``-e/--exclude`` / ``--no-default-excludes`` / ``--no-gitignore`` flags.
80
+ - Pre-commit hooks (``codesorter`` and ``codesorter-check``) for downstream consumers.
81
+ - Comprehensive test suite covering function, method, property, fixture, decorator, and
82
+ inheritance sort behaviors.
83
+ - Example before/after files in ``examples/``.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codesorter
3
- Version: 0.1.0
3
+ Version: 0.2.1
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/
@@ -67,6 +67,10 @@ CodeSorter is a LibCST codemod that automatically sorts and organizes Python cod
67
67
  - **Decorator Awareness**: Properly handles ``@property``, ``@staticmethod``,
68
68
  ``@classmethod``, and ``@pytest.fixture`` decorators
69
69
  - **Hierarchical Organization**: Maintains logical grouping within classes and modules
70
+ - **Constant Grouping**: Orders each scope as constants, then classes, then functions,
71
+ sorting constants by dependency while preserving enum and dataclass field order
72
+ - **Keyword Sorting**: Alphabetizes keyword arguments in calls, keyword-only parameters,
73
+ and dict string keys, while preserving ``*``/``**`` unpacking semantics
70
74
  - **Pytest Integration**: Special handling for pytest fixtures with proper scope
71
75
  ordering
72
76
  - **CLI Interface**: Simple command-line interface for easy integration
@@ -12,6 +12,10 @@ CodeSorter is a LibCST codemod that automatically sorts and organizes Python cod
12
12
  - **Decorator Awareness**: Properly handles ``@property``, ``@staticmethod``,
13
13
  ``@classmethod``, and ``@pytest.fixture`` decorators
14
14
  - **Hierarchical Organization**: Maintains logical grouping within classes and modules
15
+ - **Constant Grouping**: Orders each scope as constants, then classes, then functions,
16
+ sorting constants by dependency while preserving enum and dataclass field order
17
+ - **Keyword Sorting**: Alphabetizes keyword arguments in calls, keyword-only parameters,
18
+ and dict string keys, while preserving ``*``/``**`` unpacking semantics
15
19
  - **Pytest Integration**: Special handling for pytest fixtures with proper scope
16
20
  ordering
17
21
  - **CLI Interface**: Simple command-line interface for easy integration
@@ -22,13 +22,13 @@ if TYPE_CHECKING:
22
22
  def _build_parser() -> argparse.ArgumentParser:
23
23
  """Build the argument parser for the code sorter CLI."""
24
24
  parser = argparse.ArgumentParser(
25
- prog="codesorter",
26
25
  description=(
27
26
  "Sort Python code in the specified package or file. This tool analyzes "
28
27
  "Python code and sorts classes and functions based on their dependencies "
29
28
  "and relationships."
30
29
  ),
31
30
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
31
+ prog="codesorter",
32
32
  )
33
33
  parser.add_argument(
34
34
  "-c",
@@ -39,8 +39,8 @@ def _build_parser() -> argparse.ArgumentParser:
39
39
  parser.add_argument(
40
40
  "-j",
41
41
  "--jobs",
42
- type=int,
43
42
  help="Number of jobs to use when processing files.",
43
+ type=int,
44
44
  )
45
45
  parser.add_argument(
46
46
  "-s",
@@ -57,11 +57,11 @@ def _build_parser() -> argparse.ArgumentParser:
57
57
  parser.add_argument(
58
58
  "-e",
59
59
  "--exclude",
60
- dest="extra_excludes",
61
60
  action="append",
62
61
  default=[],
63
- metavar="NAME",
62
+ dest="extra_excludes",
64
63
  help="Directory name to skip when walking paths. May be passed multiple times.",
64
+ metavar="NAME",
65
65
  )
66
66
  parser.add_argument(
67
67
  "--no-default-excludes",
@@ -75,8 +75,8 @@ def _build_parser() -> argparse.ArgumentParser:
75
75
  )
76
76
  parser.add_argument(
77
77
  "paths",
78
- nargs="*",
79
78
  help="Files or directories to sort. Defaults to the current directory.",
79
+ nargs="*",
80
80
  )
81
81
  return parser
82
82
 
@@ -130,7 +130,7 @@ def _collect_files(
130
130
  files.append(str(path))
131
131
  continue
132
132
 
133
- ignore_specs = _load_gitignore_specs(root=path, excludes=excludes) if honor_gitignore else []
133
+ ignore_specs = _load_gitignore_specs(excludes=excludes, root=path) if honor_gitignore else []
134
134
 
135
135
  for candidate in sorted(path.rglob("*.py")):
136
136
  relative_parts = candidate.relative_to(path).parts
@@ -148,8 +148,8 @@ def _collect_files(
148
148
 
149
149
  def _load_gitignore_specs(
150
150
  *,
151
- root: Path,
152
151
  excludes: set[str],
152
+ root: Path,
153
153
  ) -> list[tuple[Path, pathspec.PathSpec[Pattern]]]:
154
154
  """Collect (anchor_dir, spec) for every .gitignore at or below root.
155
155
 
@@ -0,0 +1,42 @@
1
+ """Package constants and version metadata."""
2
+
3
+ DEFAULT_EXCLUDES: tuple[str, ...] = (
4
+ ".bzr",
5
+ ".direnv",
6
+ ".eggs",
7
+ ".git",
8
+ ".hg",
9
+ ".mypy_cache",
10
+ ".nox",
11
+ ".pytest_cache",
12
+ ".ruff_cache",
13
+ ".svn",
14
+ ".tox",
15
+ ".venv",
16
+ "__pycache__",
17
+ "__pypackages__",
18
+ "build",
19
+ "dist",
20
+ "env",
21
+ "node_modules",
22
+ "venv",
23
+ )
24
+ # Bases and decorators that make a class body order-sensitive, so its attribute
25
+ # assignments must not be reordered (enum member order sets the values; dataclass and
26
+ # NamedTuple/TypedDict field order sets the generated signature).
27
+ ORDER_SENSITIVE_BASES: frozenset[str] = frozenset({
28
+ "Enum",
29
+ "IntEnum",
30
+ "StrEnum",
31
+ "Flag",
32
+ "IntFlag",
33
+ "ReprEnum",
34
+ "NamedTuple",
35
+ "TypedDict",
36
+ })
37
+ ORDER_SENSITIVE_DECORATORS: frozenset[str] = frozenset({"dataclass", "define", "frozen", "mutable", "attrs"})
38
+ PLAIN_DECORATOR_PARTS = 1
39
+
40
+ PROPERTY_DECORATOR_PARTS = 2
41
+
42
+ __version__ = "0.2.1"