wexample-filestate-python 0.0.48__py3-none-any.whl
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.
- wexample_filestate_python/__init__.py +0 -0
- wexample_filestate_python/__pycache__/__init__.py +0 -0
- wexample_filestate_python/common/__init__.py +0 -0
- wexample_filestate_python/common/__pycache__/__init__.py +0 -0
- wexample_filestate_python/common/pipy_gateway.py +20 -0
- wexample_filestate_python/config_option/__init__.py +0 -0
- wexample_filestate_python/config_option/__pycache__/__init__.py +0 -0
- wexample_filestate_python/config_option/mixin/__init__.py +0 -0
- wexample_filestate_python/config_option/mixin/__pycache__/__init__.py +0 -0
- wexample_filestate_python/config_option/mixin/with_stdout_wrapping_mixin.py +46 -0
- wexample_filestate_python/config_value/__init__.py +0 -0
- wexample_filestate_python/config_value/__pycache__/__init__.py +0 -0
- wexample_filestate_python/config_value/python_config_value.py +195 -0
- wexample_filestate_python/const/__init__.py +0 -0
- wexample_filestate_python/const/__pycache__/__init__.py +0 -0
- wexample_filestate_python/const/name_pattern.py +4 -0
- wexample_filestate_python/const/python_file.py +5 -0
- wexample_filestate_python/file/__init__.py +0 -0
- wexample_filestate_python/file/__pycache__/__init__.py +0 -0
- wexample_filestate_python/file/python_file.py +12 -0
- wexample_filestate_python/helpers/__init__.py +0 -0
- wexample_filestate_python/helpers/__pycache__/__init__.py +0 -0
- wexample_filestate_python/helpers/package.py +122 -0
- wexample_filestate_python/helpers/toml.py +116 -0
- wexample_filestate_python/option/__init__.py +0 -0
- wexample_filestate_python/option/__pycache__/__init__.py +0 -0
- wexample_filestate_python/option/abstract_python_file_content_option.py +45 -0
- wexample_filestate_python/option/add_future_annotations_option.py +79 -0
- wexample_filestate_python/option/add_return_types_option.py +265 -0
- wexample_filestate_python/option/fix_attrs_option.py +37 -0
- wexample_filestate_python/option/fix_blank_lines_option.py +47 -0
- wexample_filestate_python/option/format_option.py +34 -0
- wexample_filestate_python/option/fstringify_option.py +34 -0
- wexample_filestate_python/option/modernize_typing_option.py +25 -0
- wexample_filestate_python/option/order_class_attributes_option.py +34 -0
- wexample_filestate_python/option/order_class_docstring_option.py +36 -0
- wexample_filestate_python/option/order_class_methods_option.py +37 -0
- wexample_filestate_python/option/order_constants_option.py +35 -0
- wexample_filestate_python/option/order_iterable_items_option.py +31 -0
- wexample_filestate_python/option/order_main_guard_option.py +44 -0
- wexample_filestate_python/option/order_module_docstring_option.py +73 -0
- wexample_filestate_python/option/order_module_functions_option.py +42 -0
- wexample_filestate_python/option/order_module_metadata_option.py +62 -0
- wexample_filestate_python/option/order_type_checking_block_option.py +51 -0
- wexample_filestate_python/option/python_option.py +164 -0
- wexample_filestate_python/option/relocate_imports_option.py +189 -0
- wexample_filestate_python/option/remove_unused_option.py +45 -0
- wexample_filestate_python/option/sort_imports_option.py +26 -0
- wexample_filestate_python/option/unquote_annotations_option.py +85 -0
- wexample_filestate_python/options_provider/__init__.py +0 -0
- wexample_filestate_python/options_provider/__pycache__/__init__.py +0 -0
- wexample_filestate_python/options_provider/python_options_provider.py +24 -0
- wexample_filestate_python/py.typed +0 -0
- wexample_filestate_python/utils/__init__.py +0 -0
- wexample_filestate_python/utils/__pycache__/__init__.py +0 -0
- wexample_filestate_python/utils/python_attrs_utils.py +112 -0
- wexample_filestate_python/utils/python_blank_lines_utils.py +568 -0
- wexample_filestate_python/utils/python_class_attributes_utils.py +275 -0
- wexample_filestate_python/utils/python_class_docstring_utils.py +85 -0
- wexample_filestate_python/utils/python_class_methods_utils.py +230 -0
- wexample_filestate_python/utils/python_constants_utils.py +302 -0
- wexample_filestate_python/utils/python_docstring_utils.py +117 -0
- wexample_filestate_python/utils/python_functions_utils.py +212 -0
- wexample_filestate_python/utils/python_iterable_utils.py +131 -0
- wexample_filestate_python/utils/python_main_guard_utils.py +80 -0
- wexample_filestate_python/utils/python_module_metadata_utils.py +147 -0
- wexample_filestate_python/utils/python_type_checking_utils.py +113 -0
- wexample_filestate_python/utils/relocate_imports/__init__.py +7 -0
- wexample_filestate_python/utils/relocate_imports/__pycache__/__init__.py +0 -0
- wexample_filestate_python/utils/relocate_imports/python_import_rewriter.py +413 -0
- wexample_filestate_python/utils/relocate_imports/python_localize_runtime_imports.py +324 -0
- wexample_filestate_python/utils/relocate_imports/python_parser_import_index.py +80 -0
- wexample_filestate_python/utils/relocate_imports/python_runtime_symbol_collector.py +33 -0
- wexample_filestate_python/utils/relocate_imports/python_usage_collector.py +410 -0
- wexample_filestate_python/workdir/__init__.py +0 -0
- wexample_filestate_python/workdir/__pycache__/__init__.py +0 -0
- wexample_filestate_python-0.0.48.dist-info/METADATA +191 -0
- wexample_filestate_python-0.0.48.dist-info/RECORD +80 -0
- wexample_filestate_python-0.0.48.dist-info/WHEEL +4 -0
- wexample_filestate_python-0.0.48.dist-info/entry_points.txt +4 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from wexample_helpers.classes.field import public_field
|
|
4
|
+
from wexample_helpers.decorator.base_class import base_class
|
|
5
|
+
from wexample_helpers_api.common.abstract_gateway import AbstractGateway
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@base_class
|
|
9
|
+
class PipyGateway(AbstractGateway):
|
|
10
|
+
base_url: str | None = public_field(
|
|
11
|
+
default="https://pypi.org/", description="Base Pipy API URL"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
def package_release_exists(self, package_name: str, version: str) -> bool:
|
|
15
|
+
response = self.make_request(f"pypi/{package_name}/json")
|
|
16
|
+
# Package exists
|
|
17
|
+
if response.status_code == 200:
|
|
18
|
+
return bool(response.json().get("releases", {}).get(version))
|
|
19
|
+
|
|
20
|
+
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class WithStdoutWrappingMixin:
|
|
5
|
+
@classmethod
|
|
6
|
+
def _execute_and_wrap_stdout(cls, callback):
|
|
7
|
+
"""Execute a callback and wrap any stdout/stderr output with additional newlines.
|
|
8
|
+
|
|
9
|
+
This ensures that output from external tools doesn't interfere with progress indicators
|
|
10
|
+
by adding a newline after any captured output.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
callback: Function to execute that may produce stdout/stderr output
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
The return value of the callback function
|
|
17
|
+
"""
|
|
18
|
+
import io
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
old_stdout = sys.stdout
|
|
22
|
+
old_stderr = sys.stderr
|
|
23
|
+
captured_stdout = io.StringIO()
|
|
24
|
+
captured_stderr = io.StringIO()
|
|
25
|
+
sys.stdout = captured_stdout
|
|
26
|
+
sys.stderr = captured_stderr
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
result = callback()
|
|
30
|
+
finally:
|
|
31
|
+
sys.stdout = old_stdout
|
|
32
|
+
sys.stderr = old_stderr
|
|
33
|
+
|
|
34
|
+
stdout_content = captured_stdout.getvalue()
|
|
35
|
+
stderr_content = captured_stderr.getvalue()
|
|
36
|
+
|
|
37
|
+
if stdout_content.strip():
|
|
38
|
+
sys.stdout.write(stdout_content.rstrip())
|
|
39
|
+
sys.stdout.write("\n")
|
|
40
|
+
sys.stdout.write("\n")
|
|
41
|
+
if stderr_content.strip():
|
|
42
|
+
sys.stderr.write(stderr_content.rstrip())
|
|
43
|
+
sys.stderr.write("\n")
|
|
44
|
+
sys.stderr.write("\n")
|
|
45
|
+
|
|
46
|
+
return result
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from wexample_config.config_value.config_value import ConfigValue
|
|
6
|
+
from wexample_helpers.classes.field import public_field
|
|
7
|
+
from wexample_helpers.decorator.base_class import base_class
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@base_class
|
|
11
|
+
class PythonConfigValue(ConfigValue):
|
|
12
|
+
add_future_annotations: bool | None = public_field(
|
|
13
|
+
default=None,
|
|
14
|
+
description="Add `from __future__ import annotations`",
|
|
15
|
+
)
|
|
16
|
+
add_return_types: bool | None = public_field(
|
|
17
|
+
default=None,
|
|
18
|
+
description="Add return type annotations",
|
|
19
|
+
)
|
|
20
|
+
fix_attrs: bool | None = public_field(
|
|
21
|
+
default=None,
|
|
22
|
+
description="Fix attrs usage (ensure kw_only=True, etc.)",
|
|
23
|
+
)
|
|
24
|
+
fix_blank_lines: bool | None = public_field(
|
|
25
|
+
default=None,
|
|
26
|
+
description="Fix blank lines in Python files",
|
|
27
|
+
)
|
|
28
|
+
format: bool | None = public_field(
|
|
29
|
+
default=None,
|
|
30
|
+
description="Format Python code",
|
|
31
|
+
)
|
|
32
|
+
fstringify: bool | None = public_field(
|
|
33
|
+
default=None,
|
|
34
|
+
description="Convert string formatting to f-strings",
|
|
35
|
+
)
|
|
36
|
+
modernize_typing: bool | None = public_field(
|
|
37
|
+
default=None,
|
|
38
|
+
description="Modernize typing annotations",
|
|
39
|
+
)
|
|
40
|
+
order_class_attributes: bool | None = public_field(
|
|
41
|
+
default=None,
|
|
42
|
+
description="Sort class attributes: special first, then public A–Z, then private/protected A–Z",
|
|
43
|
+
)
|
|
44
|
+
order_class_docstring: bool | None = public_field(
|
|
45
|
+
default=None,
|
|
46
|
+
description="Ensure class docstring is first statement after header/decorators",
|
|
47
|
+
)
|
|
48
|
+
order_class_methods: bool | None = public_field(
|
|
49
|
+
default=None,
|
|
50
|
+
description="Order class methods (dunders sequence, class/staticmethods, properties, instances)",
|
|
51
|
+
)
|
|
52
|
+
order_constants: bool | None = public_field(
|
|
53
|
+
default=None,
|
|
54
|
+
description="Sort flagged UPPER_CASE constant blocks at module level",
|
|
55
|
+
)
|
|
56
|
+
order_iterable_items: bool | None = public_field(
|
|
57
|
+
default=None,
|
|
58
|
+
description="Sort items inside flagged iterable literals (lists/dicts)",
|
|
59
|
+
)
|
|
60
|
+
order_main_guard: bool | None = public_field(
|
|
61
|
+
default=None,
|
|
62
|
+
description="Ensure if __name__ == '__main__' block is at the very end",
|
|
63
|
+
)
|
|
64
|
+
order_module_docstring: bool | None = public_field(
|
|
65
|
+
default=None,
|
|
66
|
+
description="Order module docstring to be at the top of the file",
|
|
67
|
+
)
|
|
68
|
+
order_module_functions: bool | None = public_field(
|
|
69
|
+
default=None,
|
|
70
|
+
description="Order module-level functions (public A–Z, then private)",
|
|
71
|
+
)
|
|
72
|
+
order_module_metadata: bool | None = public_field(
|
|
73
|
+
default=None,
|
|
74
|
+
description="Group and sort module metadata at module level",
|
|
75
|
+
)
|
|
76
|
+
order_spacing: bool | None = public_field(
|
|
77
|
+
default=None,
|
|
78
|
+
description="Normalize blank lines between program structures (spacing rules)",
|
|
79
|
+
)
|
|
80
|
+
order_type_checking_block: bool | None = public_field(
|
|
81
|
+
default=None,
|
|
82
|
+
description="Move TYPE_CHECKING blocks to after regular imports",
|
|
83
|
+
)
|
|
84
|
+
raw: Any = public_field(
|
|
85
|
+
default=None, description="Disabled raw value for this config."
|
|
86
|
+
)
|
|
87
|
+
relocate_imports: bool | None = public_field(
|
|
88
|
+
default=None,
|
|
89
|
+
description="Relocate imports by usage (runtime-in-method, class property types, type-only)",
|
|
90
|
+
)
|
|
91
|
+
remove_unused: bool | None = public_field(
|
|
92
|
+
default=None,
|
|
93
|
+
description="Remove unused imports",
|
|
94
|
+
)
|
|
95
|
+
sort_imports: bool | None = public_field(
|
|
96
|
+
default=None,
|
|
97
|
+
description="Sort imports",
|
|
98
|
+
)
|
|
99
|
+
unquote_annotations: bool | None = public_field(
|
|
100
|
+
default=None,
|
|
101
|
+
description="Unquote annotations (remove string annotations)",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def to_option_raw_value(self) -> Any:
|
|
105
|
+
from wexample_filestate_python.config_option.add_future_annotations_config_option import (
|
|
106
|
+
AddFutureAnnotationsConfigOption,
|
|
107
|
+
)
|
|
108
|
+
from wexample_filestate_python.config_option.add_return_types_config_option import (
|
|
109
|
+
AddReturnTypesConfigOption,
|
|
110
|
+
)
|
|
111
|
+
from wexample_filestate_python.config_option.fix_attrs_config_option import (
|
|
112
|
+
FixAttrsConfigOption,
|
|
113
|
+
)
|
|
114
|
+
from wexample_filestate_python.config_option.fix_blank_lines_config_option import (
|
|
115
|
+
FixBlankLinesConfigOption,
|
|
116
|
+
)
|
|
117
|
+
from wexample_filestate_python.config_option.format_config_option import (
|
|
118
|
+
FormatConfigOption,
|
|
119
|
+
)
|
|
120
|
+
from wexample_filestate_python.config_option.fstringify_config_option import (
|
|
121
|
+
FstringifyConfigOption,
|
|
122
|
+
)
|
|
123
|
+
from wexample_filestate_python.config_option.modernize_typing_config_option import (
|
|
124
|
+
ModernizeTypingConfigOption,
|
|
125
|
+
)
|
|
126
|
+
from wexample_filestate_python.config_option.order_class_attributes_config_option import (
|
|
127
|
+
OrderClassAttributesConfigOption,
|
|
128
|
+
)
|
|
129
|
+
from wexample_filestate_python.config_option.order_class_docstring_config_option import (
|
|
130
|
+
OrderClassDocstringConfigOption,
|
|
131
|
+
)
|
|
132
|
+
from wexample_filestate_python.config_option.order_class_methods_config_option import (
|
|
133
|
+
OrderClassMethodsConfigOption,
|
|
134
|
+
)
|
|
135
|
+
from wexample_filestate_python.config_option.order_constants_config_option import (
|
|
136
|
+
OrderConstantsConfigOption,
|
|
137
|
+
)
|
|
138
|
+
from wexample_filestate_python.config_option.order_iterable_items_config_option import (
|
|
139
|
+
OrderIterableItemsConfigOption,
|
|
140
|
+
)
|
|
141
|
+
from wexample_filestate_python.config_option.order_main_guard_config_option import (
|
|
142
|
+
OrderMainGuardConfigOption,
|
|
143
|
+
)
|
|
144
|
+
from wexample_filestate_python.config_option.order_module_docstring_config_option import (
|
|
145
|
+
OrderModuleDocstringConfigOption,
|
|
146
|
+
)
|
|
147
|
+
from wexample_filestate_python.config_option.order_module_functions_config_option import (
|
|
148
|
+
OrderModuleFunctionsConfigOption,
|
|
149
|
+
)
|
|
150
|
+
from wexample_filestate_python.config_option.order_module_metadata_config_option import (
|
|
151
|
+
OrderModuleMetadataConfigOption,
|
|
152
|
+
)
|
|
153
|
+
from wexample_filestate_python.config_option.order_spacing_config_option import (
|
|
154
|
+
OrderSpacingConfigOption,
|
|
155
|
+
)
|
|
156
|
+
from wexample_filestate_python.config_option.order_type_checking_block_config_option import (
|
|
157
|
+
OrderTypeCheckingBlockConfigOption,
|
|
158
|
+
)
|
|
159
|
+
from wexample_filestate_python.config_option.relocate_imports_config_option import (
|
|
160
|
+
RelocateImportsConfigOption,
|
|
161
|
+
)
|
|
162
|
+
from wexample_filestate_python.config_option.remove_unused_config_option import (
|
|
163
|
+
RemoveUnusedConfigOption,
|
|
164
|
+
)
|
|
165
|
+
from wexample_filestate_python.config_option.sort_imports_config_option import (
|
|
166
|
+
SortImportsConfigOption,
|
|
167
|
+
)
|
|
168
|
+
from wexample_filestate_python.config_option.unquote_annotations_config_option import (
|
|
169
|
+
UnquoteAnnotationsConfigOption,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
AddFutureAnnotationsConfigOption.get_name(): self.add_future_annotations,
|
|
174
|
+
AddReturnTypesConfigOption.get_name(): self.add_return_types,
|
|
175
|
+
FixAttrsConfigOption.get_name(): self.fix_attrs,
|
|
176
|
+
FixBlankLinesConfigOption.get_name(): self.fix_blank_lines,
|
|
177
|
+
FormatConfigOption.get_name(): self.format,
|
|
178
|
+
FstringifyConfigOption.get_name(): self.fstringify,
|
|
179
|
+
ModernizeTypingConfigOption.get_name(): self.modernize_typing,
|
|
180
|
+
OrderClassAttributesConfigOption.get_name(): self.order_class_attributes,
|
|
181
|
+
OrderClassDocstringConfigOption.get_name(): self.order_class_docstring,
|
|
182
|
+
OrderClassMethodsConfigOption.get_name(): self.order_class_methods,
|
|
183
|
+
OrderConstantsConfigOption.get_name(): self.order_constants,
|
|
184
|
+
OrderIterableItemsConfigOption.get_name(): self.order_iterable_items,
|
|
185
|
+
OrderMainGuardConfigOption.get_name(): self.order_main_guard,
|
|
186
|
+
OrderModuleDocstringConfigOption.get_name(): self.order_module_docstring,
|
|
187
|
+
OrderModuleFunctionsConfigOption.get_name(): self.order_module_functions,
|
|
188
|
+
OrderModuleMetadataConfigOption.get_name(): self.order_module_metadata,
|
|
189
|
+
OrderSpacingConfigOption.get_name(): self.order_spacing,
|
|
190
|
+
OrderTypeCheckingBlockConfigOption.get_name(): self.order_type_checking_block,
|
|
191
|
+
RelocateImportsConfigOption.get_name(): self.relocate_imports,
|
|
192
|
+
RemoveUnusedConfigOption.get_name(): self.remove_unused,
|
|
193
|
+
SortImportsConfigOption.get_name(): self.sort_imports,
|
|
194
|
+
UnquoteAnnotationsConfigOption.get_name(): self.unquote_annotations,
|
|
195
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from wexample_filestate.item.item_target_file import ItemTargetFile
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PythonFile(ItemTargetFile):
|
|
9
|
+
EXTENSION_ENV: ClassVar[str] = "py"
|
|
10
|
+
|
|
11
|
+
def _expected_file_name_extension(self) -> str | None:
|
|
12
|
+
return PythonFile.EXTENSION_ENV
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import tomli
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def package_get_dependencies(root_dir: str | Path) -> dict[str, set[str]]:
|
|
13
|
+
"""
|
|
14
|
+
Get dependencies between packages in a directory.
|
|
15
|
+
"""
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
packages_root = Path(root_dir)
|
|
19
|
+
if not packages_root.exists() or not packages_root.is_dir():
|
|
20
|
+
raise ValueError(f"Error: {packages_root} does not exist or is not a directory")
|
|
21
|
+
|
|
22
|
+
dependencies = {}
|
|
23
|
+
|
|
24
|
+
# First pass: collect all local packages
|
|
25
|
+
for package_dir in packages_root.iterdir():
|
|
26
|
+
if not package_dir.is_dir():
|
|
27
|
+
continue
|
|
28
|
+
|
|
29
|
+
package_info = package_get_info(package_dir)
|
|
30
|
+
if package_info:
|
|
31
|
+
name, _ = package_info
|
|
32
|
+
dependencies[name] = set()
|
|
33
|
+
|
|
34
|
+
# Second pass: analyze dependencies
|
|
35
|
+
for package_dir in packages_root.iterdir():
|
|
36
|
+
if not package_dir.is_dir():
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
package_info = package_get_info(package_dir)
|
|
40
|
+
if package_info:
|
|
41
|
+
name, deps = package_info
|
|
42
|
+
if name in dependencies:
|
|
43
|
+
# Only keep dependencies that are local packages
|
|
44
|
+
dependencies[name] = {dep for dep in deps if dep in dependencies}
|
|
45
|
+
|
|
46
|
+
return dependencies
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def package_get_info(package_dir: Path) -> tuple[str, set[str]] | None:
|
|
50
|
+
"""
|
|
51
|
+
Get package name and its dependencies from setup.py or pyproject.toml.
|
|
52
|
+
"""
|
|
53
|
+
# Try pyproject.toml first
|
|
54
|
+
toml_path = package_dir / "pyproject.toml"
|
|
55
|
+
if toml_path.exists():
|
|
56
|
+
metadata = package_parse_toml(toml_path)
|
|
57
|
+
else:
|
|
58
|
+
# Fallback to setup.py
|
|
59
|
+
setup_py_path = package_dir / "setup.py"
|
|
60
|
+
if setup_py_path.exists():
|
|
61
|
+
metadata = package_parse_setup(setup_py_path)
|
|
62
|
+
else:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
name = metadata.get("name")
|
|
66
|
+
if not name:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
deps = metadata.get("install_requires", [])
|
|
70
|
+
return name, set(deps)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def package_normalize_name(val: str) -> str:
|
|
74
|
+
import re as _re
|
|
75
|
+
|
|
76
|
+
# strip extras, versions, markers
|
|
77
|
+
base = _re.split(r"[\s<>=!~;\[]", val, maxsplit=1)[0]
|
|
78
|
+
return base.strip().lower()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def package_parse_setup(path: Path) -> dict:
|
|
82
|
+
"""
|
|
83
|
+
Parse a setup.py file to extract metadata.
|
|
84
|
+
"""
|
|
85
|
+
with open(path) as f:
|
|
86
|
+
content = f.read()
|
|
87
|
+
|
|
88
|
+
tree = ast.parse(content)
|
|
89
|
+
for node in ast.walk(tree):
|
|
90
|
+
if (
|
|
91
|
+
isinstance(node, ast.Call)
|
|
92
|
+
and isinstance(node.func, ast.Name)
|
|
93
|
+
and node.func.id == "setup"
|
|
94
|
+
):
|
|
95
|
+
result = {}
|
|
96
|
+
for kw in node.keywords:
|
|
97
|
+
if isinstance(kw.value, ast.Str):
|
|
98
|
+
result[kw.arg] = kw.value.s
|
|
99
|
+
elif isinstance(kw.value, ast.List):
|
|
100
|
+
result[kw.arg] = [
|
|
101
|
+
elt.s for elt in kw.value.elts if isinstance(elt, ast.Str)
|
|
102
|
+
]
|
|
103
|
+
return result
|
|
104
|
+
return {}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def package_parse_toml(path: Path) -> dict:
|
|
108
|
+
"""
|
|
109
|
+
Parse a pyproject.toml file to extract metadata.
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
with open(path, "rb") as f:
|
|
113
|
+
data = tomli.load(f)
|
|
114
|
+
if "project" in data:
|
|
115
|
+
project_data = data["project"]
|
|
116
|
+
return {
|
|
117
|
+
"name": project_data.get("name"),
|
|
118
|
+
"install_requires": project_data.get("dependencies", []),
|
|
119
|
+
}
|
|
120
|
+
except Exception as e:
|
|
121
|
+
print(f"Error parsing {path}: {e}")
|
|
122
|
+
return {}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from tomlkit.items import Array
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def toml_ensure_array(tbl: Any, key: str) -> tuple[Any, bool]:
|
|
10
|
+
"""
|
|
11
|
+
Ensure an array exists at tbl[key] and return (array, changed).
|
|
12
|
+
Uses tomlkit.array() for creation.
|
|
13
|
+
"""
|
|
14
|
+
from tomlkit import array
|
|
15
|
+
|
|
16
|
+
arr = tbl.get(key) if isinstance(tbl, dict) else None
|
|
17
|
+
if arr is None:
|
|
18
|
+
arr = array()
|
|
19
|
+
tbl[key] = arr
|
|
20
|
+
return arr, True
|
|
21
|
+
return arr, False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def toml_ensure_array_multiline(tbl: Any, key: str) -> tuple[Array, bool]:
|
|
25
|
+
"""
|
|
26
|
+
Ensure an array exists at tbl[key] and force multiline formatting.
|
|
27
|
+
Returns (array, changed_created).
|
|
28
|
+
"""
|
|
29
|
+
from tomlkit.items import Array
|
|
30
|
+
|
|
31
|
+
arr, changed = toml_ensure_array(tbl, key)
|
|
32
|
+
# Force multiline for readability when dumping
|
|
33
|
+
if isinstance(arr, Array):
|
|
34
|
+
arr.multiline(True)
|
|
35
|
+
return arr, changed
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def toml_ensure_table(doc: Any, path: list[str]) -> tuple[Any, bool]:
|
|
39
|
+
"""
|
|
40
|
+
Ensure a nested TOML table exists and return (table, changed).
|
|
41
|
+
Path example: ["tool", "pdm", "build"]. Uses tomlkit.table() for missing parts.
|
|
42
|
+
"""
|
|
43
|
+
from tomlkit import table
|
|
44
|
+
|
|
45
|
+
if not isinstance(path, list) or not path:
|
|
46
|
+
raise ValueError("path must be a non-empty list of keys")
|
|
47
|
+
|
|
48
|
+
changed = False
|
|
49
|
+
current = doc
|
|
50
|
+
for key in path:
|
|
51
|
+
tbl = current.get(key) if isinstance(current, dict) else None
|
|
52
|
+
if not tbl or not isinstance(tbl, dict):
|
|
53
|
+
tbl = table()
|
|
54
|
+
current[key] = tbl
|
|
55
|
+
changed = True
|
|
56
|
+
current = tbl
|
|
57
|
+
return current, changed
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def toml_get_string_value(item: Any) -> str:
|
|
61
|
+
"""Return the string content of a tomlkit String or generic item as str."""
|
|
62
|
+
from tomlkit.items import String
|
|
63
|
+
|
|
64
|
+
if isinstance(item, String):
|
|
65
|
+
return item.value
|
|
66
|
+
return str(item)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def toml_set_array_multiline(tbl: Any, key: str, values: list[Any]) -> Array:
|
|
70
|
+
"""
|
|
71
|
+
Replace tbl[key] with a tomlkit array built from values and set multiline(True).
|
|
72
|
+
Returns the created Array instance.
|
|
73
|
+
"""
|
|
74
|
+
from tomlkit import array
|
|
75
|
+
|
|
76
|
+
arr = array(values)
|
|
77
|
+
arr.multiline(True)
|
|
78
|
+
tbl[key] = arr
|
|
79
|
+
return arr
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def toml_sort_string_array(arr: Any) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Sort a tomlkit Array of String items in-place (case-insensitive) while
|
|
85
|
+
preserving the existing multiline/style flags.
|
|
86
|
+
|
|
87
|
+
Returns True if the array was changed.
|
|
88
|
+
"""
|
|
89
|
+
from tomlkit.items import Array, String
|
|
90
|
+
|
|
91
|
+
# Validate array type
|
|
92
|
+
if not isinstance(arr, Array):
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
items = list(arr)
|
|
96
|
+
if not items or not all(isinstance(i, String) for i in items):
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
values = [i.value for i in items]
|
|
100
|
+
sorted_items = [
|
|
101
|
+
x
|
|
102
|
+
for _, x in sorted(zip([v.lower() for v in values], items), key=lambda t: t[0])
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
if items == sorted_items:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
multiline_flag = getattr(arr, "multiline", None)
|
|
109
|
+
# Clear and re-append to preserve tomlkit item identity
|
|
110
|
+
while len(arr):
|
|
111
|
+
arr.pop()
|
|
112
|
+
for item in sorted_items:
|
|
113
|
+
arr.append(item)
|
|
114
|
+
if multiline_flag is not None:
|
|
115
|
+
arr.multiline(multiline_flag)
|
|
116
|
+
return True
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from wexample_config.config_option.abstract_config_option import AbstractConfigOption
|
|
6
|
+
from wexample_filestate.option.mixin.option_mixin import OptionMixin
|
|
7
|
+
from wexample_helpers.classes.abstract_method import abstract_method
|
|
8
|
+
from wexample_helpers.decorator.base_class import base_class
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from wexample_filestate.const.types_state_items import TargetFileOrDirectoryType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@base_class
|
|
15
|
+
class AbstractPythonFileContentOption(OptionMixin, AbstractConfigOption):
|
|
16
|
+
@staticmethod
|
|
17
|
+
def get_raw_value_allowed_type() -> Any:
|
|
18
|
+
return bool
|
|
19
|
+
|
|
20
|
+
def create_required_operation(
|
|
21
|
+
self, target: TargetFileOrDirectoryType
|
|
22
|
+
) -> AbstractOperation | None:
|
|
23
|
+
from wexample_filestate.operation.file_write_operation import FileWriteOperation
|
|
24
|
+
|
|
25
|
+
"""Create FileWriteOperation if add_future_annotations is enabled and needed."""
|
|
26
|
+
# Get current content
|
|
27
|
+
current_content = target.get_local_file().read()
|
|
28
|
+
|
|
29
|
+
# Apply add_future_annotations transformation
|
|
30
|
+
new_content = self._apply_content_change(target=target)
|
|
31
|
+
|
|
32
|
+
# If content changed, create FileWriteOperation
|
|
33
|
+
if new_content != current_content:
|
|
34
|
+
return FileWriteOperation(
|
|
35
|
+
option=self,
|
|
36
|
+
target=target,
|
|
37
|
+
content=new_content,
|
|
38
|
+
description=self.get_description(),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
@abstract_method
|
|
44
|
+
def _apply_content_change(self, target: TargetFileOrDirectoryType) -> None:
|
|
45
|
+
pass
|