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.
Files changed (80) hide show
  1. wexample_filestate_python/__init__.py +0 -0
  2. wexample_filestate_python/__pycache__/__init__.py +0 -0
  3. wexample_filestate_python/common/__init__.py +0 -0
  4. wexample_filestate_python/common/__pycache__/__init__.py +0 -0
  5. wexample_filestate_python/common/pipy_gateway.py +20 -0
  6. wexample_filestate_python/config_option/__init__.py +0 -0
  7. wexample_filestate_python/config_option/__pycache__/__init__.py +0 -0
  8. wexample_filestate_python/config_option/mixin/__init__.py +0 -0
  9. wexample_filestate_python/config_option/mixin/__pycache__/__init__.py +0 -0
  10. wexample_filestate_python/config_option/mixin/with_stdout_wrapping_mixin.py +46 -0
  11. wexample_filestate_python/config_value/__init__.py +0 -0
  12. wexample_filestate_python/config_value/__pycache__/__init__.py +0 -0
  13. wexample_filestate_python/config_value/python_config_value.py +195 -0
  14. wexample_filestate_python/const/__init__.py +0 -0
  15. wexample_filestate_python/const/__pycache__/__init__.py +0 -0
  16. wexample_filestate_python/const/name_pattern.py +4 -0
  17. wexample_filestate_python/const/python_file.py +5 -0
  18. wexample_filestate_python/file/__init__.py +0 -0
  19. wexample_filestate_python/file/__pycache__/__init__.py +0 -0
  20. wexample_filestate_python/file/python_file.py +12 -0
  21. wexample_filestate_python/helpers/__init__.py +0 -0
  22. wexample_filestate_python/helpers/__pycache__/__init__.py +0 -0
  23. wexample_filestate_python/helpers/package.py +122 -0
  24. wexample_filestate_python/helpers/toml.py +116 -0
  25. wexample_filestate_python/option/__init__.py +0 -0
  26. wexample_filestate_python/option/__pycache__/__init__.py +0 -0
  27. wexample_filestate_python/option/abstract_python_file_content_option.py +45 -0
  28. wexample_filestate_python/option/add_future_annotations_option.py +79 -0
  29. wexample_filestate_python/option/add_return_types_option.py +265 -0
  30. wexample_filestate_python/option/fix_attrs_option.py +37 -0
  31. wexample_filestate_python/option/fix_blank_lines_option.py +47 -0
  32. wexample_filestate_python/option/format_option.py +34 -0
  33. wexample_filestate_python/option/fstringify_option.py +34 -0
  34. wexample_filestate_python/option/modernize_typing_option.py +25 -0
  35. wexample_filestate_python/option/order_class_attributes_option.py +34 -0
  36. wexample_filestate_python/option/order_class_docstring_option.py +36 -0
  37. wexample_filestate_python/option/order_class_methods_option.py +37 -0
  38. wexample_filestate_python/option/order_constants_option.py +35 -0
  39. wexample_filestate_python/option/order_iterable_items_option.py +31 -0
  40. wexample_filestate_python/option/order_main_guard_option.py +44 -0
  41. wexample_filestate_python/option/order_module_docstring_option.py +73 -0
  42. wexample_filestate_python/option/order_module_functions_option.py +42 -0
  43. wexample_filestate_python/option/order_module_metadata_option.py +62 -0
  44. wexample_filestate_python/option/order_type_checking_block_option.py +51 -0
  45. wexample_filestate_python/option/python_option.py +164 -0
  46. wexample_filestate_python/option/relocate_imports_option.py +189 -0
  47. wexample_filestate_python/option/remove_unused_option.py +45 -0
  48. wexample_filestate_python/option/sort_imports_option.py +26 -0
  49. wexample_filestate_python/option/unquote_annotations_option.py +85 -0
  50. wexample_filestate_python/options_provider/__init__.py +0 -0
  51. wexample_filestate_python/options_provider/__pycache__/__init__.py +0 -0
  52. wexample_filestate_python/options_provider/python_options_provider.py +24 -0
  53. wexample_filestate_python/py.typed +0 -0
  54. wexample_filestate_python/utils/__init__.py +0 -0
  55. wexample_filestate_python/utils/__pycache__/__init__.py +0 -0
  56. wexample_filestate_python/utils/python_attrs_utils.py +112 -0
  57. wexample_filestate_python/utils/python_blank_lines_utils.py +568 -0
  58. wexample_filestate_python/utils/python_class_attributes_utils.py +275 -0
  59. wexample_filestate_python/utils/python_class_docstring_utils.py +85 -0
  60. wexample_filestate_python/utils/python_class_methods_utils.py +230 -0
  61. wexample_filestate_python/utils/python_constants_utils.py +302 -0
  62. wexample_filestate_python/utils/python_docstring_utils.py +117 -0
  63. wexample_filestate_python/utils/python_functions_utils.py +212 -0
  64. wexample_filestate_python/utils/python_iterable_utils.py +131 -0
  65. wexample_filestate_python/utils/python_main_guard_utils.py +80 -0
  66. wexample_filestate_python/utils/python_module_metadata_utils.py +147 -0
  67. wexample_filestate_python/utils/python_type_checking_utils.py +113 -0
  68. wexample_filestate_python/utils/relocate_imports/__init__.py +7 -0
  69. wexample_filestate_python/utils/relocate_imports/__pycache__/__init__.py +0 -0
  70. wexample_filestate_python/utils/relocate_imports/python_import_rewriter.py +413 -0
  71. wexample_filestate_python/utils/relocate_imports/python_localize_runtime_imports.py +324 -0
  72. wexample_filestate_python/utils/relocate_imports/python_parser_import_index.py +80 -0
  73. wexample_filestate_python/utils/relocate_imports/python_runtime_symbol_collector.py +33 -0
  74. wexample_filestate_python/utils/relocate_imports/python_usage_collector.py +410 -0
  75. wexample_filestate_python/workdir/__init__.py +0 -0
  76. wexample_filestate_python/workdir/__pycache__/__init__.py +0 -0
  77. wexample_filestate_python-0.0.48.dist-info/METADATA +191 -0
  78. wexample_filestate_python-0.0.48.dist-info/RECORD +80 -0
  79. wexample_filestate_python-0.0.48.dist-info/WHEEL +4 -0
  80. wexample_filestate_python-0.0.48.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from wexample_helpers.decorator.base_class import base_class
6
+
7
+ from .abstract_python_file_content_option import AbstractPythonFileContentOption
8
+
9
+ if TYPE_CHECKING:
10
+ from wexample_filestate.const.types_state_items import TargetFileOrDirectoryType
11
+
12
+
13
+ @base_class
14
+ class RemoveUnusedOption(AbstractPythonFileContentOption):
15
+ def get_description(self) -> str:
16
+ return "Remove unused imports from the Python file using autoflake."
17
+
18
+ def _apply_content_change(self, target: TargetFileOrDirectoryType) -> str:
19
+ """Remove unused Python imports using autoflake."""
20
+ from wexample_helpers.helpers.shell import shell_run
21
+ from wexample_helpers.helpers.system import system_get_venv_bin_path
22
+
23
+ result = shell_run(
24
+ cmd=[
25
+ f"{system_get_venv_bin_path()}/autoflake",
26
+ "--stdout",
27
+ "--remove-all-unused-imports",
28
+ "--remove-unused-variables",
29
+ "--expand-star-imports",
30
+ "--remove-duplicate-keys",
31
+ target.get_path(),
32
+ ],
33
+ )
34
+
35
+ if result.returncode != 0:
36
+ # Double line return is important to keep message visible event last line is erased by parent process.
37
+ target.io.error(f"Autoflake error: {result.stderr}\n\n")
38
+ return target.get_local_file().read() # Return original content on error
39
+
40
+ modified_content = result.stdout
41
+
42
+ if not modified_content.strip():
43
+ return target.get_local_file().read() # Return original content if empty
44
+
45
+ return modified_content
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from wexample_helpers.decorator.base_class import base_class
6
+
7
+ from .abstract_python_file_content_option import AbstractPythonFileContentOption
8
+
9
+ if TYPE_CHECKING:
10
+ from wexample_filestate.const.types_state_items import TargetFileOrDirectoryType
11
+
12
+
13
+ @base_class
14
+ class SortImportsOption(AbstractPythonFileContentOption):
15
+ def get_description(self) -> str:
16
+ return "Sort and group Python imports using isort."
17
+
18
+ def _apply_content_change(self, target: TargetFileOrDirectoryType) -> str:
19
+ """Sort Python imports using isort."""
20
+ from isort import code
21
+ from isort.settings import Config
22
+
23
+ src = target.get_local_file().read()
24
+ config = Config(profile="black")
25
+ formatted = code(src, config=config)
26
+ return formatted
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from wexample_helpers.decorator.base_class import base_class
6
+
7
+ from .abstract_python_file_content_option import AbstractPythonFileContentOption
8
+
9
+ if TYPE_CHECKING:
10
+ from wexample_filestate.const.types_state_items import TargetFileOrDirectoryType
11
+
12
+
13
+ @base_class
14
+ class UnquoteAnnotationsOption(AbstractPythonFileContentOption):
15
+ def get_description(self) -> str:
16
+ return "Unquote type annotations (arguments, returns, variables) using LibCST."
17
+
18
+ def _apply_content_change(self, target: TargetFileOrDirectoryType) -> str:
19
+ """Remove quotes around type annotations by turning stringized annotations back into expressions."""
20
+ import json
21
+
22
+ import libcst as cst
23
+
24
+ src = target.get_local_file().read()
25
+
26
+ class _Unquoter(cst.CSTTransformer):
27
+ @staticmethod
28
+ def _unquote_expr(s: cst.SimpleString) -> cst.BaseExpression | None:
29
+ try:
30
+ code = json.loads(s.value)
31
+ except Exception:
32
+ return None
33
+ try:
34
+ return cst.parse_expression(code)
35
+ except Exception:
36
+ return None
37
+
38
+ @staticmethod
39
+ def _process_annotation(
40
+ ann: cst.Annotation | None,
41
+ ) -> cst.Annotation | None:
42
+ if ann is None:
43
+ return None
44
+ node = ann.annotation
45
+ if isinstance(node, cst.SimpleString):
46
+ expr = _Unquoter._unquote_expr(node)
47
+ if expr is not None:
48
+ return cst.Annotation(annotation=expr)
49
+ return ann
50
+
51
+ def leave_Param(
52
+ self, original_node: cst.Param, updated_node: cst.Param
53
+ ) -> cst.Param:
54
+ return updated_node.with_changes(
55
+ annotation=self._process_annotation(updated_node.annotation)
56
+ )
57
+
58
+ def leave_FunctionDef(
59
+ self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
60
+ ) -> cst.FunctionDef:
61
+ return updated_node.with_changes(
62
+ returns=self._process_annotation(updated_node.returns)
63
+ )
64
+
65
+ def leave_AnnAssign(
66
+ self, original_node: cst.AnnAssign, updated_node: cst.AnnAssign
67
+ ) -> cst.AnnAssign:
68
+ return updated_node.with_changes(
69
+ annotation=self._process_annotation(updated_node.annotation)
70
+ )
71
+
72
+ def leave_TypeAlias(
73
+ self, original_node: cst.TypeAlias, updated_node: cst.TypeAlias
74
+ ) -> cst.TypeAlias:
75
+ # Python 3.12 'type X = ...' syntax
76
+ ann = updated_node.annotation
77
+ if isinstance(ann, cst.SimpleString):
78
+ expr = self._unquote_expr(ann)
79
+ if expr is not None:
80
+ return updated_node.with_changes(annotation=expr)
81
+ return updated_node
82
+
83
+ module = cst.parse_module(src)
84
+ new_mod = module.visit(_Unquoter())
85
+ return new_mod.code
File without changes
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from wexample_config.options_provider.abstract_options_provider import (
6
+ AbstractOptionsProvider,
7
+ )
8
+
9
+ if TYPE_CHECKING:
10
+ from wexample_config.config_option.abstract_config_option import (
11
+ AbstractConfigOption,
12
+ )
13
+
14
+
15
+ class PythonOptionsProvider(AbstractOptionsProvider):
16
+ @classmethod
17
+ def get_options(cls) -> list[type[AbstractConfigOption]]:
18
+ from wexample_filestate_python.option.python_option import (
19
+ PythonOption,
20
+ )
21
+
22
+ return [
23
+ PythonOption,
24
+ ]
File without changes
File without changes
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ import libcst as cst
4
+
5
+
6
+ def fix_attrs_kw_only(module: cst.Module) -> cst.Module:
7
+ """Ensure attrs decorators always use kw_only=True.
8
+
9
+ This applies to:
10
+ - @attrs.define
11
+ - @attr.s
12
+ - @attrs.frozen
13
+ """
14
+
15
+ class AttrsKwOnlyFixer(cst.CSTTransformer):
16
+ def leave_Decorator(
17
+ self, original_node: cst.Decorator, updated_node: cst.Decorator
18
+ ) -> cst.Decorator:
19
+ # Check if this is an attrs decorator
20
+ if not _is_attrs_decorator(updated_node):
21
+ return updated_node
22
+
23
+ # Get the decorator call
24
+ if isinstance(updated_node.decorator, cst.Call):
25
+ call = updated_node.decorator
26
+
27
+ # Check if kw_only is already present
28
+ has_kw_only = _has_kw_only_arg(call)
29
+
30
+ if not has_kw_only:
31
+ # Add kw_only=True to the call
32
+ new_args = list(call.args)
33
+ kw_only_arg = cst.Arg(
34
+ keyword=cst.Name("kw_only"), value=cst.Name("True")
35
+ )
36
+ new_args.append(kw_only_arg)
37
+
38
+ new_call = call.with_changes(args=new_args)
39
+ return updated_node.with_changes(decorator=new_call)
40
+ else:
41
+ # Check if kw_only is set to False and change it to True
42
+ new_args = []
43
+ for arg in call.args:
44
+ if (
45
+ isinstance(arg.keyword, cst.Name)
46
+ and arg.keyword.value == "kw_only"
47
+ and isinstance(arg.value, cst.Name)
48
+ and arg.value.value == "False"
49
+ ):
50
+ # Change False to True
51
+ new_arg = arg.with_changes(value=cst.Name("True"))
52
+ new_args.append(new_arg)
53
+ else:
54
+ new_args.append(arg)
55
+
56
+ if new_args != list(call.args):
57
+ new_call = call.with_changes(args=new_args)
58
+ return updated_node.with_changes(decorator=new_call)
59
+
60
+ elif isinstance(updated_node.decorator, (cst.Name, cst.Attribute)):
61
+ # Decorator without parentheses, add them with kw_only=True
62
+ kw_only_arg = cst.Arg(
63
+ keyword=cst.Name("kw_only"), value=cst.Name("True")
64
+ )
65
+
66
+ new_call = cst.Call(func=updated_node.decorator, args=[kw_only_arg])
67
+ return updated_node.with_changes(decorator=new_call)
68
+
69
+ return updated_node
70
+
71
+ transformer = AttrsKwOnlyFixer()
72
+ modified_module = module.visit(transformer)
73
+
74
+ return modified_module
75
+
76
+
77
+ def _has_kw_only_arg(call: cst.Call) -> bool:
78
+ """Check if the call already has a kw_only argument."""
79
+ for arg in call.args:
80
+ if isinstance(arg.keyword, cst.Name) and arg.keyword.value == "kw_only":
81
+ return True
82
+ return False
83
+
84
+
85
+ def _is_attrs_decorator(decorator: cst.Decorator) -> bool:
86
+ """Check if decorator is an attrs decorator (@attrs.define, @attr.s, etc.)."""
87
+ # Get the base decorator (without call)
88
+ base_decorator = decorator.decorator
89
+ if isinstance(base_decorator, cst.Call):
90
+ base_decorator = base_decorator.func
91
+
92
+ # Check for @attrs.define, @attrs.frozen, etc.
93
+ if isinstance(base_decorator, cst.Attribute):
94
+ if (
95
+ isinstance(base_decorator.value, cst.Name)
96
+ and base_decorator.value.value == "attrs"
97
+ and isinstance(base_decorator.attr, cst.Name)
98
+ and base_decorator.attr.value in ("define", "frozen")
99
+ ):
100
+ return True
101
+
102
+ # Check for @attr.s
103
+ if isinstance(base_decorator, cst.Attribute):
104
+ if (
105
+ isinstance(base_decorator.value, cst.Name)
106
+ and base_decorator.value.value == "attr"
107
+ and isinstance(base_decorator.attr, cst.Name)
108
+ and base_decorator.attr.value == "s"
109
+ ):
110
+ return True
111
+
112
+ return False