codesorter 0.2.6__tar.gz → 0.2.7__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.6 → codesorter-0.2.7}/CHANGES.rst +15 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/PKG-INFO +1 -1
- {codesorter-0.2.6 → codesorter-0.2.7}/codesorter/const.py +1 -1
- {codesorter-0.2.6 → codesorter-0.2.7}/codesorter/sort_code.py +18 -1
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/augmented_assignment_input.py +4 -3
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/augmented_assignment_output.py +4 -3
- codesorter-0.2.7/tests/test_files/barrier_input.py +8 -0
- codesorter-0.2.7/tests/test_files/barrier_output.py +8 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/comprehension_dependency_input.py +6 -5
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/comprehension_dependency_output.py +2 -1
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/comprehensive_input.py +56 -58
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/comprehensive_output.py +6 -8
- codesorter-0.2.7/tests/test_files/enum_member_alias_input.py +12 -0
- codesorter-0.2.7/tests/test_files/enum_member_alias_output.py +12 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/name_rebinding_input.py +5 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/name_rebinding_output.py +5 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_sort_code.py +50 -0
- codesorter-0.2.6/tests/test_files/barrier_input.py +0 -11
- codesorter-0.2.6/tests/test_files/barrier_output.py +0 -11
- {codesorter-0.2.6 → codesorter-0.2.7}/.github/dependabot.yml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.github/workflows/ci.yml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.github/workflows/pre-commit_autoupdate.yml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.github/workflows/prepare_release.yml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.github/workflows/pypi.yml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.github/workflows/scorecard.yml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.github/workflows/stale_action.yml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.github/workflows/tag_release.yml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.gitignore +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.pre-commit-config.yaml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.pre-commit-hooks.yaml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.python-version +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/.readthedocs.yaml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/LICENSE.txt +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/README.rst +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/codesorter/__init__.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/codesorter/cli.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/codesorter/py.typed +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/Makefile +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/_static/custom.css +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/code_overview/sort_code.rst +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/conf.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/contributing/pre_commit.rst +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/docutils.conf +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/genindex.rst +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/index.rst +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/make.bat +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/package_info/change_log.rst +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/docs/usage/barriers.rst +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/examples/after_example.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/examples/before_example.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/pyproject.toml +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/__init__.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/conftest.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/alias_function_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/alias_function_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/basic_function_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/basic_function_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/class_global_dependency_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/class_global_dependency_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/class_inheritance_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/class_inheritance_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/class_method_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/class_method_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/classmethod_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/classmethod_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/constant_ordering_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/constant_ordering_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/custom_decorators_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/custom_decorators_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/keyword_arguments_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/keyword_arguments_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/lazy_annotation_cycle_input.py +7 -7
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/lazy_annotation_cycle_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/mixed_decorators_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/mixed_decorators_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/order_sensitive_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/order_sensitive_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/property_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/property_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/pytest_fixtures_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/pytest_fixtures_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/staticmethod_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/staticmethod_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/underscore_ordering_input.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/tests/test_files/underscore_ordering_output.py +0 -0
- {codesorter-0.2.6 → codesorter-0.2.7}/uv.lock +0 -0
|
@@ -4,6 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
codesorter follows `semantic versioning <https://semver.org/>`_.
|
|
6
6
|
|
|
7
|
+
********************
|
|
8
|
+
0.2.7 (2026/06/15)
|
|
9
|
+
********************
|
|
10
|
+
|
|
11
|
+
**Fixed**
|
|
12
|
+
|
|
13
|
+
- Do not treat a class's own attribute as a dependency on a same-named outer definition.
|
|
14
|
+
An enum member or class variable named like the module constant that aliases it (for
|
|
15
|
+
example ``CACHE_MISS = _Sentinel.CACHE_MISS`` beside ``class _Sentinel(Enum):
|
|
16
|
+
CACHE_MISS = auto()``) previously forged a false ``class`` -> ``constant`` edge that
|
|
17
|
+
closed a cycle with the real ``constant`` -> ``class`` edge, hoisting the constant
|
|
18
|
+
above the class it references and raising ``NameError`` at import. A name bound in a
|
|
19
|
+
class's own body is now recognized as belonging to that class's namespace and imposes
|
|
20
|
+
no ordering on outer definitions.
|
|
21
|
+
|
|
7
22
|
********************
|
|
8
23
|
0.2.6 (2026/06/14)
|
|
9
24
|
********************
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codesorter
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
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/
|
|
@@ -336,7 +336,24 @@ class SortCodeCommand(VisitorBasedCodemodCommand, m.MatcherDecoratableTransforme
|
|
|
336
336
|
# an annotation forging a false cycle with a real value-level edge.
|
|
337
337
|
if id(found["name"]) in self._lazy_annotation_names:
|
|
338
338
|
continue
|
|
339
|
-
|
|
339
|
+
name_node = cst.ensure_type(found["name"], cst.Name)
|
|
340
|
+
found_name = name_node.value
|
|
341
|
+
# A name bound at this class's own body level (an enum member or
|
|
342
|
+
# class variable) belongs to the class's namespace, so it must not
|
|
343
|
+
# forge a dependency on a same-named outer definition. Otherwise an
|
|
344
|
+
# enum member named like the module constant that aliases it (for
|
|
345
|
+
# example ``CACHE_MISS = _Sentinel.CACHE_MISS``) creates a false cycle
|
|
346
|
+
# that hoists the constant above the class it depends on. The scope
|
|
347
|
+
# must be ``node``'s own class scope; a name bound in the *enclosing*
|
|
348
|
+
# class (a sibling method an alias assignment references) is a real
|
|
349
|
+
# dependency and is kept.
|
|
350
|
+
name_scope = self.get_metadata(md.ScopeProvider, name_node, None)
|
|
351
|
+
if (
|
|
352
|
+
isinstance(name_scope, md.ClassScope)
|
|
353
|
+
and name_scope.node == node
|
|
354
|
+
and name_scope.assignments[found_name]
|
|
355
|
+
):
|
|
356
|
+
continue
|
|
340
357
|
is_import = isinstance(
|
|
341
358
|
next(iter(meta.assignments[found_name])),
|
|
342
359
|
md.ImportAssignment,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Augmented assignments stay anchored to the constant they augment."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import enum
|
|
4
|
+
import json
|
|
4
5
|
|
|
5
6
|
ZEBRA = 1
|
|
6
7
|
__version__ = "1.0"
|
|
7
8
|
__all__ = ["Widget"]
|
|
8
|
-
__all__ +=
|
|
9
|
-
__all__ +=
|
|
9
|
+
__all__ += json.__all__
|
|
10
|
+
__all__ += enum.__all__
|
|
10
11
|
APPLE = 2
|
|
11
12
|
|
|
12
13
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Augmented assignments stay anchored to the constant they augment."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import enum
|
|
4
|
+
import json
|
|
4
5
|
|
|
5
6
|
APPLE = 2
|
|
6
7
|
ZEBRA = 1
|
|
7
8
|
__all__ = ["Widget"]
|
|
8
|
-
__all__ +=
|
|
9
|
-
__all__ +=
|
|
9
|
+
__all__ += json.__all__
|
|
10
|
+
__all__ += enum.__all__
|
|
10
11
|
__version__ = "1.0"
|
|
11
12
|
|
|
12
13
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"""A module-level comprehension references a function eagerly, so it depends on it."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
def lazy():
|
|
5
|
+
"""A comprehension here is deferred, so it imposes no ordering."""
|
|
6
|
+
return [build(name) for name in ["c", "d"]]
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
def build(name):
|
|
7
|
-
"""Used eagerly by the module-level comprehension
|
|
10
|
+
"""Used eagerly by the module-level comprehension below."""
|
|
8
11
|
return f"value-{name}"
|
|
9
12
|
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
"""A comprehension here is deferred, so it imposes no ordering."""
|
|
13
|
-
return [build(name) for name in ["c", "d"]]
|
|
14
|
+
placeholders = {name: build(name) for name in ["a", "b"]}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""A comprehensive mix of constants, decorators, inheritance, and pytest fixtures."""
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
4
|
from abc import ABC, abstractmethod
|
|
3
5
|
from functools import wraps
|
|
@@ -10,7 +12,34 @@ API_KEY = "test-key-123"
|
|
|
10
12
|
DEBUG_MODE = os.getenv("DEBUG", "false").lower() == "true"
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
#
|
|
15
|
+
# Custom decorators (defined before the functions that use them)
|
|
16
|
+
def cache_result(func):
|
|
17
|
+
"""Cache the result of a function."""
|
|
18
|
+
cache = {}
|
|
19
|
+
|
|
20
|
+
@wraps(func)
|
|
21
|
+
def wrapper(*args, **kwargs):
|
|
22
|
+
key = str(args) + str(kwargs)
|
|
23
|
+
if key not in cache:
|
|
24
|
+
cache[key] = func(*args, **kwargs)
|
|
25
|
+
return cache[key]
|
|
26
|
+
|
|
27
|
+
return wrapper
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def validate_input(func):
|
|
31
|
+
"""Validate input parameters."""
|
|
32
|
+
|
|
33
|
+
@wraps(func)
|
|
34
|
+
def wrapper(*args, **kwargs):
|
|
35
|
+
for arg in args:
|
|
36
|
+
if arg is None:
|
|
37
|
+
raise ValueError("Input cannot be None")
|
|
38
|
+
return func(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
return wrapper
|
|
41
|
+
|
|
42
|
+
|
|
14
43
|
@cache_result
|
|
15
44
|
def expensive_calculation(n):
|
|
16
45
|
"""Perform an expensive calculation."""
|
|
@@ -23,24 +52,21 @@ def process_data(data):
|
|
|
23
52
|
return [item.upper() for item in data]
|
|
24
53
|
|
|
25
54
|
|
|
26
|
-
|
|
27
|
-
|
|
55
|
+
# Base classes with inheritance (each base defined before its subclass)
|
|
56
|
+
class Animal(ABC):
|
|
57
|
+
"""Base class for all animals."""
|
|
28
58
|
|
|
29
|
-
def __init__(self, name, age
|
|
30
|
-
|
|
31
|
-
self.
|
|
59
|
+
def __init__(self, name, age):
|
|
60
|
+
self.name = name
|
|
61
|
+
self.age = age
|
|
32
62
|
|
|
63
|
+
@abstractmethod
|
|
33
64
|
def make_sound(self):
|
|
34
|
-
"""Make a
|
|
35
|
-
return "Woof!"
|
|
36
|
-
|
|
37
|
-
def get_breed_info(self):
|
|
38
|
-
"""Get breed information."""
|
|
39
|
-
return f"Breed: {self.breed}"
|
|
65
|
+
"""Make a sound."""
|
|
40
66
|
|
|
41
|
-
def
|
|
42
|
-
"""
|
|
43
|
-
return f"{self.name} is
|
|
67
|
+
def get_info(self):
|
|
68
|
+
"""Get animal information."""
|
|
69
|
+
return f"{self.name} is {self.age} years old"
|
|
44
70
|
|
|
45
71
|
|
|
46
72
|
class Mammal(Animal):
|
|
@@ -59,29 +85,31 @@ class Mammal(Animal):
|
|
|
59
85
|
return f"Fur color: {self.fur_color}"
|
|
60
86
|
|
|
61
87
|
|
|
62
|
-
|
|
63
|
-
class
|
|
64
|
-
"""Base class for all animals."""
|
|
88
|
+
class Dog(Mammal):
|
|
89
|
+
"""Dog class."""
|
|
65
90
|
|
|
66
|
-
def __init__(self, name, age):
|
|
67
|
-
|
|
68
|
-
self.
|
|
91
|
+
def __init__(self, name, age, fur_color, breed):
|
|
92
|
+
super().__init__(name, age, fur_color)
|
|
93
|
+
self.breed = breed
|
|
69
94
|
|
|
70
|
-
@abstractmethod
|
|
71
95
|
def make_sound(self):
|
|
72
|
-
"""Make a sound."""
|
|
96
|
+
"""Make a dog sound."""
|
|
97
|
+
return "Woof!"
|
|
73
98
|
|
|
74
|
-
def
|
|
75
|
-
"""Get
|
|
76
|
-
return f"
|
|
99
|
+
def get_breed_info(self):
|
|
100
|
+
"""Get breed information."""
|
|
101
|
+
return f"Breed: {self.breed}"
|
|
102
|
+
|
|
103
|
+
def fetch(self):
|
|
104
|
+
"""Fetch behavior."""
|
|
105
|
+
return f"{self.name} is fetching"
|
|
77
106
|
|
|
78
107
|
|
|
79
|
-
# Classes with global dependencies
|
|
80
108
|
class DatabaseManager:
|
|
81
109
|
"""Manages database connections using global config."""
|
|
82
110
|
|
|
83
111
|
def __init__(self):
|
|
84
|
-
self.host = "localhost"
|
|
112
|
+
self.host = "localhost"
|
|
85
113
|
self.port = 5432
|
|
86
114
|
self.database = "test_db"
|
|
87
115
|
|
|
@@ -109,40 +137,11 @@ class APIClient:
|
|
|
109
137
|
return DEBUG_MODE
|
|
110
138
|
|
|
111
139
|
|
|
112
|
-
def validate_input(func):
|
|
113
|
-
"""Validate input parameters."""
|
|
114
|
-
|
|
115
|
-
@wraps(func)
|
|
116
|
-
def wrapper(*args, **kwargs):
|
|
117
|
-
for arg in args:
|
|
118
|
-
if arg is None:
|
|
119
|
-
raise ValueError("Input cannot be None")
|
|
120
|
-
return func(*args, **kwargs)
|
|
121
|
-
|
|
122
|
-
return wrapper
|
|
123
|
-
|
|
124
|
-
|
|
125
140
|
def regular_function():
|
|
126
141
|
"""A regular function without decorators."""
|
|
127
142
|
return "regular"
|
|
128
143
|
|
|
129
144
|
|
|
130
|
-
# Custom decorators
|
|
131
|
-
def cache_result(func):
|
|
132
|
-
"""Cache the result of a function."""
|
|
133
|
-
cache = {}
|
|
134
|
-
|
|
135
|
-
@wraps(func)
|
|
136
|
-
def wrapper(*args, **kwargs):
|
|
137
|
-
key = str(args) + str(kwargs)
|
|
138
|
-
if key not in cache:
|
|
139
|
-
cache[key] = func(*args, **kwargs)
|
|
140
|
-
return cache[key]
|
|
141
|
-
|
|
142
|
-
return wrapper
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
# Test functions
|
|
146
145
|
def test_something(database_connection, sample_data):
|
|
147
146
|
"""Test function using fixtures."""
|
|
148
147
|
assert database_connection["connected"]
|
|
@@ -171,7 +170,6 @@ def sample_data():
|
|
|
171
170
|
return ["item1", "item2", "item3"]
|
|
172
171
|
|
|
173
172
|
|
|
174
|
-
# Pytest fixtures
|
|
175
173
|
@pytest.fixture(scope="session")
|
|
176
174
|
def database_connection():
|
|
177
175
|
"""Provide a database connection for testing."""
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
"""A comprehensive mix of constants, decorators, inheritance, and pytest fixtures."""
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
4
|
from abc import ABC, abstractmethod
|
|
3
5
|
from functools import wraps
|
|
4
6
|
|
|
5
7
|
import pytest
|
|
6
|
-
API_KEY = "test-key-123"
|
|
7
8
|
|
|
9
|
+
API_KEY = "test-key-123"
|
|
8
10
|
# Global variables
|
|
9
11
|
DATABASE_URL = "sqlite:///test.db"
|
|
10
12
|
DEBUG_MODE = os.getenv("DEBUG", "false").lower() == "true"
|
|
@@ -25,7 +27,7 @@ class APIClient:
|
|
|
25
27
|
return f"GET {self.base_url}{endpoint}"
|
|
26
28
|
|
|
27
29
|
|
|
28
|
-
# Base classes with inheritance
|
|
30
|
+
# Base classes with inheritance (each base defined before its subclass)
|
|
29
31
|
class Animal(ABC):
|
|
30
32
|
"""Base class for all animals."""
|
|
31
33
|
|
|
@@ -42,12 +44,11 @@ class Animal(ABC):
|
|
|
42
44
|
return f"{self.name} is {self.age} years old"
|
|
43
45
|
|
|
44
46
|
|
|
45
|
-
# Classes with global dependencies
|
|
46
47
|
class DatabaseManager:
|
|
47
48
|
"""Manages database connections using global config."""
|
|
48
49
|
|
|
49
50
|
def __init__(self):
|
|
50
|
-
self.host = "localhost"
|
|
51
|
+
self.host = "localhost"
|
|
51
52
|
self.port = 5432
|
|
52
53
|
self.database = "test_db"
|
|
53
54
|
|
|
@@ -105,7 +106,6 @@ def setup_test_environment():
|
|
|
105
106
|
API_KEY = "test-key-123"
|
|
106
107
|
|
|
107
108
|
|
|
108
|
-
# Pytest fixtures
|
|
109
109
|
@pytest.fixture(scope="session")
|
|
110
110
|
def database_connection():
|
|
111
111
|
"""Provide a database connection for testing."""
|
|
@@ -118,7 +118,7 @@ def sample_data():
|
|
|
118
118
|
return ["item1", "item2", "item3"]
|
|
119
119
|
|
|
120
120
|
|
|
121
|
-
# Custom decorators
|
|
121
|
+
# Custom decorators (defined before the functions that use them)
|
|
122
122
|
def cache_result(func):
|
|
123
123
|
"""Cache the result of a function."""
|
|
124
124
|
cache = {}
|
|
@@ -133,7 +133,6 @@ def cache_result(func):
|
|
|
133
133
|
return wrapper
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
# Functions with decorators
|
|
137
136
|
@cache_result
|
|
138
137
|
def expensive_calculation(n):
|
|
139
138
|
"""Perform an expensive calculation."""
|
|
@@ -161,7 +160,6 @@ def test_global_dependencies():
|
|
|
161
160
|
assert api_client.is_debug_mode() == DEBUG_MODE
|
|
162
161
|
|
|
163
162
|
|
|
164
|
-
# Test functions
|
|
165
163
|
def test_something(database_connection, sample_data):
|
|
166
164
|
"""Test function using fixtures."""
|
|
167
165
|
assert database_connection["connected"]
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"""An assignment that rebinds a method name stays after the method it wraps."""
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
def cachedproperty(method, *, doc=None):
|
|
5
|
+
"""Minimal stand-in so the rebinding has something to call."""
|
|
6
|
+
return property(method)
|
|
7
|
+
|
|
8
|
+
|
|
4
9
|
class Klass:
|
|
5
10
|
def ten(self):
|
|
6
11
|
return 10
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"""An assignment that rebinds a method name stays after the method it wraps."""
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
def cachedproperty(method, *, doc=None):
|
|
5
|
+
"""Minimal stand-in so the rebinding has something to call."""
|
|
6
|
+
return property(method)
|
|
7
|
+
|
|
8
|
+
|
|
4
9
|
class Klass:
|
|
5
10
|
def alpha(self):
|
|
6
11
|
"""A method that sorts first alphabetically."""
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"""Tests for the SortCodeCommand codemod."""
|
|
2
2
|
|
|
3
|
+
import io
|
|
4
|
+
from contextlib import redirect_stderr, redirect_stdout
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
3
7
|
import libcst as cst
|
|
4
8
|
import pytest
|
|
5
9
|
from libcst.codemod import CodemodContext
|
|
@@ -183,6 +187,52 @@ class MyClass:
|
|
|
183
187
|
result = command.transform_module(cst.parse_module(code))
|
|
184
188
|
assert result.code.strip() == ""
|
|
185
189
|
|
|
190
|
+
def test_enum_member_alias(self, test_files):
|
|
191
|
+
"""Test that a class attribute sharing a name with an outer constant is not a dep.
|
|
192
|
+
|
|
193
|
+
An enum member (or class variable) named like the module constant that aliases
|
|
194
|
+
it (``CACHE_MISS = _Sentinel.CACHE_MISS``) is bound in the class's own
|
|
195
|
+
namespace, so it must not forge a dependency from the class back onto the
|
|
196
|
+
constant. Without that guard the false ``_Sentinel`` -> ``CACHE_MISS`` edge
|
|
197
|
+
closes a cycle with the real ``CACHE_MISS`` -> ``_Sentinel`` edge, and
|
|
198
|
+
cycle-breaking hoists the constant above the class it references (raising
|
|
199
|
+
NameError at import).
|
|
200
|
+
|
|
201
|
+
"""
|
|
202
|
+
input_code, expected_code = test_files
|
|
203
|
+
context = CodemodContext()
|
|
204
|
+
command = SortCodeCommand(context)
|
|
205
|
+
result = command.transform_module(cst.parse_module(input_code))
|
|
206
|
+
|
|
207
|
+
assert expected_code == result.code
|
|
208
|
+
|
|
209
|
+
def test_fixture_files_execute(self):
|
|
210
|
+
"""Every input/output fixture must import cleanly with no side effects.
|
|
211
|
+
|
|
212
|
+
Executing each file in a fresh namespace is a stronger guard than parsing: it
|
|
213
|
+
catches a fixture that parses but does not run (an invalid input, or a codemod
|
|
214
|
+
output that raises ``NameError`` because a definition was hoisted above one it
|
|
215
|
+
depends on). Each file is also required to be self-contained and side-effect
|
|
216
|
+
free, so anything written to stdout/stderr fails the test.
|
|
217
|
+
|
|
218
|
+
"""
|
|
219
|
+
test_files_dir = Path(__file__).parent / "test_files"
|
|
220
|
+
fixtures = sorted(test_files_dir.glob("*.py"))
|
|
221
|
+
assert fixtures, "no fixture files found"
|
|
222
|
+
failures = []
|
|
223
|
+
for path in fixtures:
|
|
224
|
+
captured = io.StringIO()
|
|
225
|
+
try:
|
|
226
|
+
code = compile(path.read_text(encoding="utf-8"), str(path), "exec")
|
|
227
|
+
with redirect_stdout(captured), redirect_stderr(captured):
|
|
228
|
+
exec(code, {"__name__": "__fixture__"}) # noqa: S102
|
|
229
|
+
except Exception as error: # noqa: BLE001
|
|
230
|
+
failures.append(f"{path.name}: {type(error).__name__}: {error}")
|
|
231
|
+
continue
|
|
232
|
+
if captured.getvalue():
|
|
233
|
+
failures.append(f"{path.name}: wrote to stdout/stderr {captured.getvalue()!r}")
|
|
234
|
+
assert not failures, "fixtures that do not execute cleanly:\n" + "\n".join(failures)
|
|
235
|
+
|
|
186
236
|
def test_keyword_arguments(self, test_files):
|
|
187
237
|
"""Test that keyword arguments, keyword-only params, and dict keys are sorted."""
|
|
188
238
|
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
|
|
@@ -3,12 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class Apple(_Base):
|
|
7
|
-
"""References the Instr alias only in a lazy annotation."""
|
|
8
|
-
|
|
9
|
-
nxt: list[Instr]
|
|
10
|
-
|
|
11
|
-
|
|
12
6
|
class _Base:
|
|
13
7
|
"""A shared base class."""
|
|
14
8
|
|
|
@@ -17,10 +11,16 @@ class Zebra(_Base):
|
|
|
17
11
|
"""Another member of the Instr union."""
|
|
18
12
|
|
|
19
13
|
|
|
20
|
-
|
|
14
|
+
class Apple(_Base):
|
|
15
|
+
"""References the Instr alias only in a lazy annotation."""
|
|
16
|
+
|
|
17
|
+
nxt: list[Instr]
|
|
21
18
|
|
|
22
19
|
|
|
23
20
|
class User:
|
|
24
21
|
"""Uses the Instr alias in a lazy annotation."""
|
|
25
22
|
|
|
26
23
|
items: list[Instr]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Instr = Apple | Zebra
|
|
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
|