codesorter 0.1.0__tar.gz → 0.2.0__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 (78) hide show
  1. {codesorter-0.1.0 → codesorter-0.2.0}/.pre-commit-config.yaml +8 -6
  2. codesorter-0.2.0/CHANGES.rst +71 -0
  3. {codesorter-0.1.0 → codesorter-0.2.0}/PKG-INFO +5 -1
  4. {codesorter-0.1.0 → codesorter-0.2.0}/README.rst +4 -0
  5. {codesorter-0.1.0 → codesorter-0.2.0}/codesorter/cli.py +7 -7
  6. codesorter-0.2.0/codesorter/const.py +42 -0
  7. codesorter-0.2.0/codesorter/sort_code.py +727 -0
  8. {codesorter-0.1.0 → codesorter-0.2.0}/docs/code_overview/sort_code.rst +0 -2
  9. codesorter-0.2.0/docs/contributing/pre_commit.rst +50 -0
  10. {codesorter-0.1.0 → codesorter-0.2.0}/docs/index.rst +6 -0
  11. {codesorter-0.1.0 → codesorter-0.2.0}/pyproject.toml +1 -0
  12. codesorter-0.2.0/tests/test_files/alias_function_input.py +15 -0
  13. codesorter-0.2.0/tests/test_files/alias_function_output.py +15 -0
  14. codesorter-0.2.0/tests/test_files/augmented_assignment_input.py +18 -0
  15. codesorter-0.2.0/tests/test_files/augmented_assignment_output.py +18 -0
  16. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/class_global_dependency_output.py +4 -4
  17. codesorter-0.2.0/tests/test_files/constant_ordering_input.py +34 -0
  18. codesorter-0.2.0/tests/test_files/constant_ordering_output.py +36 -0
  19. codesorter-0.2.0/tests/test_files/keyword_arguments_input.py +14 -0
  20. codesorter-0.2.0/tests/test_files/keyword_arguments_output.py +14 -0
  21. codesorter-0.2.0/tests/test_files/lazy_annotation_cycle_input.py +26 -0
  22. codesorter-0.2.0/tests/test_files/lazy_annotation_cycle_output.py +26 -0
  23. codesorter-0.2.0/tests/test_files/order_sensitive_input.py +46 -0
  24. codesorter-0.2.0/tests/test_files/order_sensitive_output.py +47 -0
  25. codesorter-0.2.0/tests/test_files/underscore_ordering_input.py +37 -0
  26. codesorter-0.2.0/tests/test_files/underscore_ordering_output.py +37 -0
  27. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_sort_code.py +148 -0
  28. codesorter-0.1.0/CHANGES.rst +0 -19
  29. codesorter-0.1.0/codesorter/const.py +0 -25
  30. codesorter-0.1.0/codesorter/sort_code.py +0 -334
  31. {codesorter-0.1.0 → codesorter-0.2.0}/.github/dependabot.yml +0 -0
  32. {codesorter-0.1.0 → codesorter-0.2.0}/.github/workflows/ci.yml +0 -0
  33. {codesorter-0.1.0 → codesorter-0.2.0}/.github/workflows/pre-commit_autoupdate.yml +0 -0
  34. {codesorter-0.1.0 → codesorter-0.2.0}/.github/workflows/prepare_release.yml +0 -0
  35. {codesorter-0.1.0 → codesorter-0.2.0}/.github/workflows/pypi.yml +0 -0
  36. {codesorter-0.1.0 → codesorter-0.2.0}/.github/workflows/scorecard.yml +0 -0
  37. {codesorter-0.1.0 → codesorter-0.2.0}/.github/workflows/stale_action.yml +0 -0
  38. {codesorter-0.1.0 → codesorter-0.2.0}/.github/workflows/tag_release.yml +0 -0
  39. {codesorter-0.1.0 → codesorter-0.2.0}/.gitignore +0 -0
  40. {codesorter-0.1.0 → codesorter-0.2.0}/.libcst.codemod.yaml +0 -0
  41. {codesorter-0.1.0 → codesorter-0.2.0}/.pre-commit-hooks.yaml +0 -0
  42. {codesorter-0.1.0 → codesorter-0.2.0}/.python-version +0 -0
  43. {codesorter-0.1.0 → codesorter-0.2.0}/.readthedocs.yaml +0 -0
  44. {codesorter-0.1.0 → codesorter-0.2.0}/LICENSE.txt +0 -0
  45. {codesorter-0.1.0 → codesorter-0.2.0}/codesorter/__init__.py +0 -0
  46. {codesorter-0.1.0 → codesorter-0.2.0}/codesorter/py.typed +0 -0
  47. {codesorter-0.1.0 → codesorter-0.2.0}/docs/Makefile +0 -0
  48. {codesorter-0.1.0 → codesorter-0.2.0}/docs/conf.py +0 -0
  49. {codesorter-0.1.0 → codesorter-0.2.0}/docs/docutils.conf +0 -0
  50. {codesorter-0.1.0 → codesorter-0.2.0}/docs/genindex.rst +0 -0
  51. {codesorter-0.1.0 → codesorter-0.2.0}/docs/make.bat +0 -0
  52. {codesorter-0.1.0 → codesorter-0.2.0}/docs/package_info/change_log.rst +0 -0
  53. {codesorter-0.1.0 → codesorter-0.2.0}/examples/after_example.py +0 -0
  54. {codesorter-0.1.0 → codesorter-0.2.0}/examples/before_example.py +0 -0
  55. {codesorter-0.1.0 → codesorter-0.2.0}/tests/__init__.py +0 -0
  56. {codesorter-0.1.0 → codesorter-0.2.0}/tests/conftest.py +0 -0
  57. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/basic_function_input.py +0 -0
  58. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/basic_function_output.py +0 -0
  59. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/class_global_dependency_input.py +0 -0
  60. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/class_inheritance_input.py +0 -0
  61. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/class_inheritance_output.py +0 -0
  62. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/class_method_input.py +0 -0
  63. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/class_method_output.py +0 -0
  64. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/classmethod_input.py +0 -0
  65. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/classmethod_output.py +0 -0
  66. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/comprehensive_input.py +0 -0
  67. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/comprehensive_output.py +92 -92
  68. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/custom_decorators_input.py +0 -0
  69. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/custom_decorators_output.py +17 -17
  70. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/mixed_decorators_input.py +0 -0
  71. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/mixed_decorators_output.py +0 -0
  72. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/property_input.py +0 -0
  73. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/property_output.py +0 -0
  74. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/pytest_fixtures_input.py +0 -0
  75. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/pytest_fixtures_output.py +1 -1
  76. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/staticmethod_input.py +0 -0
  77. {codesorter-0.1.0 → codesorter-0.2.0}/tests/test_files/staticmethod_output.py +0 -0
  78. {codesorter-0.1.0 → codesorter-0.2.0}/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,71 @@
1
+ ############
2
+ Change Log
3
+ ############
4
+
5
+ codesorter follows `semantic versioning <https://semver.org/>`_.
6
+
7
+ ********************
8
+ 0.2.0 (2026/06/14)
9
+ ********************
10
+
11
+ **Added**
12
+
13
+ - Sort module- and class-level assignments. Within every scope the order is now
14
+ assignments, then classes, then functions/methods, so methods always follow nested
15
+ classes and assignments are grouped at the top. Assignments are split into uppercase
16
+ ``CONSTANTS`` first and then other variables; each group sorts with a leading
17
+ underscore first (so ``__dunder__`` and ``_private`` precede public names), and
18
+ dependencies are respected (``B = A + 1`` stays after ``A``). As a result,
19
+ module-level functions now sort after module-level classes. The attribute order of
20
+ enums, dataclasses, and ``NamedTuple``/``TypedDict`` classes is preserved; the base or
21
+ decorator is resolved through ``QualifiedNameProvider`` so aliased imports (``from
22
+ enum import IntEnum as IE``) are recognized.
23
+ - Keep blank-line spacing with its position rather than the moved definition, so
24
+ reordering no longer drags a blank line onto a different statement (for example the
25
+ blank line after a class docstring stays at the top of the block). Comment lines that
26
+ sit directly above a definition still travel with it.
27
+ - Keep an augmented assignment anchored to the constant it augments, so ``__all__ +=
28
+ extra`` stays directly after ``__all__ = [...]`` instead of being left behind as a
29
+ fixed barrier when another assignment sorts between them.
30
+ - Sort keyword arguments in calls, keyword-only parameters in function definitions, and
31
+ string keys in dict literals alphabetically. In a call, keyword arguments are sorted
32
+ and ``**`` unpackings moved to the end (positional arguments and ``*`` unpackings stay
33
+ put), which is safe because a call raises ``TypeError`` on any duplicate keyword
34
+ regardless of order. In a dict literal, ``**`` spreads and non-string keys act as
35
+ barriers so last-wins merge semantics are preserved.
36
+
37
+ **Changed**
38
+
39
+ - Sort ``_``-prefixed (private and dunder) names ahead of public names at every level
40
+ rather than after capitalized names. A plain string sort placed ``_`` after
41
+ ``A``-``Z`` because of its higher code point; names are now ordered on a
42
+ leading-underscore flag first so private definitions, methods, keyword arguments, and
43
+ dict keys group together ahead of the public ones.
44
+
45
+ **Fixed**
46
+
47
+ - Ignore a name used only in a lazy annotation (under ``from __future__ import
48
+ annotations``) when ordering definitions, since the annotation is never evaluated at
49
+ runtime and imposes no real dependency. A forward reference in an annotation (for
50
+ example ``nxt: list[Instr]`` where ``Instr`` is a type-alias union of classes defined
51
+ later) previously forged a false dependency cycle that could hoist the runtime alias
52
+ above the classes it unions and raise ``NameError``.
53
+ - Order definitions with a proper priority topological sort so a class or function is
54
+ always placed after every sibling it depends on. The previous dependency heuristic
55
+ compared per-node dependency vectors and could emit a dependent before its dependency
56
+ (for example, ``Subreddit``'s ``SubredditFlair`` was sorted ahead of the
57
+ flair-template classes it instantiates).
58
+
59
+ ********************
60
+ 0.1.0 (2026/06/14)
61
+ ********************
62
+
63
+ **Added**
64
+
65
+ - Initial release of CodeSorter.
66
+ - CLI interface with directory walking, sensible default excludes, ``.gitignore``
67
+ honoring, and ``-e/--exclude`` / ``--no-default-excludes`` / ``--no-gitignore`` flags.
68
+ - Pre-commit hooks (``codesorter`` and ``codesorter-check``) for downstream consumers.
69
+ - Comprehensive test suite covering function, method, property, fixture, decorator, and
70
+ inheritance sort behaviors.
71
+ - 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.0
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.0"