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,324 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, DefaultDict
4
+
5
+ import libcst as cst
6
+
7
+ if TYPE_CHECKING:
8
+ from python_parser_import_index import PythonParserImportIndex
9
+
10
+
11
+ class PythonLocalizeRuntimeImports(cst.CSTTransformer):
12
+ """Inject local imports into functions for runtime-only names (category A).
13
+
14
+ For each function qualified name present in `functions_needing_local`, this transformer
15
+ inserts grouped `from <module> import Name` statements at the top of the function body,
16
+ after a docstring if present.
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ idx: PythonParserImportIndex,
22
+ functions_needing_local: DefaultDict[str, set[str]],
23
+ skip_local_names: set[str] | None = None,
24
+ ) -> None:
25
+ super().__init__()
26
+ self.idx = idx
27
+ self.functions_needing_local = functions_needing_local
28
+ self.skip_local_names = skip_local_names or set()
29
+ self.class_stack: list[str] = []
30
+
31
+ @staticmethod
32
+ def _build_module_expr(mod: str | None) -> cst.BaseExpression | None:
33
+ if not mod:
34
+ return None
35
+ parts = mod.split(".")
36
+ expr: cst.BaseExpression = cst.Name(parts[0])
37
+ for p in parts[1:]:
38
+ expr = cst.Attribute(value=expr, attr=cst.Name(p))
39
+ return expr
40
+
41
+ @staticmethod
42
+ def _flatten_module_expr_to_str(module: cst.BaseExpression | None) -> str | None:
43
+ if module is None:
44
+ return None
45
+ if isinstance(module, cst.Name):
46
+ return module.value
47
+ if isinstance(module, cst.Attribute):
48
+ parts: list[str] = []
49
+ cur: cst.BaseExpression | None = module
50
+ while isinstance(cur, cst.Attribute):
51
+ if isinstance(cur.attr, cst.Name):
52
+ parts.append(cur.attr.value)
53
+ else:
54
+ break
55
+ cur = cur.value
56
+ if isinstance(cur, cst.Name):
57
+ parts.append(cur.value)
58
+ parts.reverse()
59
+ return ".".join(parts) if parts else None
60
+ return None
61
+
62
+ def leave_AsyncFunctionDef(
63
+ self, original_node: cst.AsyncFunctionDef, updated_node: cst.AsyncFunctionDef
64
+ ) -> cst.AsyncFunctionDef:
65
+ func_qname = (
66
+ ".".join(self.class_stack + [original_node.name.value])
67
+ if self.class_stack
68
+ else original_node.name.value
69
+ )
70
+ to_inject, pairs = self._build_local_imports(func_qname)
71
+ if not to_inject:
72
+ return updated_node
73
+
74
+ # Collect existing imports
75
+ def _collect_current_pairs(
76
+ fn: cst.AsyncFunctionDef,
77
+ ) -> set[tuple[str | None, str]]:
78
+ found: set[tuple[str | None, str]] = set()
79
+
80
+ class _Find(cst.CSTVisitor):
81
+ def leave_ImportFrom(self, node: cst.ImportFrom) -> None: # type: ignore[override]
82
+ if node.names is None or isinstance(node.names, cst.ImportStar):
83
+ return
84
+ mod = PythonLocalizeRuntimeImports._flatten_module_expr_to_str(
85
+ node.module
86
+ )
87
+ for alias in node.names:
88
+ if isinstance(alias, cst.ImportAlias) and isinstance(
89
+ alias.name, cst.Name
90
+ ):
91
+ found.add((mod, alias.name.value))
92
+
93
+ fn.visit(_Find())
94
+ return found
95
+
96
+ existing = _collect_current_pairs(updated_node)
97
+ if pairs.issubset(existing):
98
+ return original_node
99
+
100
+ # Filter out pairs that already exist (exact module + name match)
101
+ # This avoids duplicate imports of the same symbol
102
+ pairs_to_add = pairs - existing
103
+
104
+ # If no new pairs to add after filtering, return original
105
+ if not pairs_to_add:
106
+ return original_node
107
+
108
+ class _PruneInner(cst.CSTTransformer):
109
+ def leave_ImportFrom(self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom): # type: ignore[override]
110
+ if updated_node.names is None or isinstance(
111
+ updated_node.names, cst.ImportStar
112
+ ):
113
+ return updated_node
114
+ mod = PythonLocalizeRuntimeImports._flatten_module_expr_to_str(
115
+ updated_node.module
116
+ )
117
+ kept_aliases: list[cst.ImportAlias] = []
118
+ for alias in updated_node.names:
119
+ if not isinstance(alias, cst.ImportAlias):
120
+ continue
121
+ name = (
122
+ alias.name.value if isinstance(alias.name, cst.Name) else None
123
+ )
124
+ if not name:
125
+ continue
126
+ if (mod, name) in pairs_to_add:
127
+ continue
128
+ kept_aliases.append(alias)
129
+ if not kept_aliases:
130
+ return cst.RemoveFromParent()
131
+ return updated_node.with_changes(names=tuple(kept_aliases))
132
+
133
+ pruned_node = updated_node.visit(_PruneInner())
134
+ body = list(pruned_node.body.body)
135
+ insert_at = 0
136
+ if (
137
+ body
138
+ and isinstance(body[0], cst.SimpleStatementLine)
139
+ and any(
140
+ isinstance(el, cst.Expr) and isinstance(el.value, cst.SimpleString)
141
+ for el in body[0].body
142
+ )
143
+ ):
144
+ insert_at = 1
145
+
146
+ # Build only the imports we need to add (filtered)
147
+ to_inject_filtered = self._build_import_statements_from_pairs(pairs_to_add)
148
+ new_body = body[:insert_at] + to_inject_filtered + body[insert_at:]
149
+ return pruned_node.with_changes(
150
+ body=pruned_node.body.with_changes(body=new_body)
151
+ )
152
+
153
+ def leave_ClassDef(
154
+ self, original_node: cst.ClassDef, updated_node: cst.ClassDef
155
+ ) -> cst.ClassDef: # type: ignore[override]
156
+ self.class_stack.pop()
157
+ return updated_node
158
+
159
+ def leave_FunctionDef(
160
+ self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
161
+ ) -> cst.FunctionDef:
162
+ func_qname = (
163
+ ".".join(self.class_stack + [original_node.name.value])
164
+ if self.class_stack
165
+ else original_node.name.value
166
+ )
167
+ # Build consolidated imports and list of pairs to hoist
168
+ to_inject, pairs = self._build_local_imports(func_qname)
169
+ if not to_inject:
170
+ return updated_node
171
+
172
+ # If all target imports are already present somewhere in the function body,
173
+ # avoid rewriting to preserve existing order/formatting.
174
+ def _collect_current_pairs(fn: cst.FunctionDef) -> set[tuple[str | None, str]]:
175
+ found: set[tuple[str | None, str]] = set()
176
+
177
+ class _Find(cst.CSTVisitor):
178
+ def leave_ImportFrom(self, node: cst.ImportFrom) -> None: # type: ignore[override]
179
+ if node.names is None or isinstance(node.names, cst.ImportStar):
180
+ return
181
+ mod = PythonLocalizeRuntimeImports._flatten_module_expr_to_str(
182
+ node.module
183
+ )
184
+ for alias in node.names:
185
+ if isinstance(alias, cst.ImportAlias) and isinstance(
186
+ alias.name, cst.Name
187
+ ):
188
+ found.add((mod, alias.name.value))
189
+
190
+ def leave_Import(self, node: cst.Import) -> None: # type: ignore[override]
191
+ return
192
+
193
+ fn.visit(_Find())
194
+ return found
195
+
196
+ existing = _collect_current_pairs(updated_node)
197
+ if pairs.issubset(existing):
198
+ return original_node
199
+
200
+ # Filter out pairs that already exist (exact module + name match)
201
+ # This avoids duplicate imports of the same symbol
202
+ pairs_to_add = pairs - existing
203
+
204
+ # If no new pairs to add after filtering, return original
205
+ if not pairs_to_add:
206
+ return original_node
207
+
208
+ # First prune matching imports anywhere within the function body
209
+ class _PruneInner(cst.CSTTransformer):
210
+ def leave_ImportFrom(self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom): # type: ignore[override]
211
+ if updated_node.names is None or isinstance(
212
+ updated_node.names, cst.ImportStar
213
+ ):
214
+ return updated_node
215
+ mod = PythonLocalizeRuntimeImports._flatten_module_expr_to_str(
216
+ updated_node.module
217
+ )
218
+ kept_aliases: list[cst.ImportAlias] = []
219
+ for alias in updated_node.names:
220
+ if not isinstance(alias, cst.ImportAlias):
221
+ continue
222
+ name = (
223
+ alias.name.value if isinstance(alias.name, cst.Name) else None
224
+ )
225
+ if not name:
226
+ continue
227
+ if (mod, name) in pairs_to_add:
228
+ continue
229
+ kept_aliases.append(alias)
230
+ if not kept_aliases:
231
+ return cst.RemoveFromParent()
232
+ return updated_node.with_changes(names=tuple(kept_aliases))
233
+
234
+ pruned_node = updated_node.visit(_PruneInner())
235
+ body = list(pruned_node.body.body)
236
+ insert_at = 0
237
+ if (
238
+ body
239
+ and isinstance(body[0], cst.SimpleStatementLine)
240
+ and any(
241
+ isinstance(el, cst.Expr) and isinstance(el.value, cst.SimpleString)
242
+ for el in body[0].body
243
+ )
244
+ ):
245
+ insert_at = 1
246
+
247
+ # Build only the imports we need to add (filtered)
248
+ to_inject_filtered = self._build_import_statements_from_pairs(pairs_to_add)
249
+ new_body = body[:insert_at] + to_inject_filtered + body[insert_at:]
250
+ return pruned_node.with_changes(
251
+ body=pruned_node.body.with_changes(body=new_body)
252
+ )
253
+
254
+ def visit_ClassDef(self, node: cst.ClassDef) -> bool: # type: ignore[override]
255
+ self.class_stack.append(node.name.value)
256
+ return True
257
+
258
+ def _build_import_statements_from_pairs(
259
+ self, pairs: set[tuple[str | None, str]]
260
+ ) -> list[cst.BaseStatement]:
261
+ """Build import statements from a set of (module, name) pairs."""
262
+ from collections import defaultdict
263
+
264
+ by_module: DefaultDict[str | None, list[str]] = defaultdict(list)
265
+ for mod, name in pairs:
266
+ by_module[mod].append(name)
267
+
268
+ stmts: list[cst.BaseStatement] = []
269
+ for mod, idents in by_module.items():
270
+ if not idents:
271
+ continue
272
+ import_names = [cst.ImportAlias(name=cst.Name(n)) for n in sorted(idents)]
273
+ stmts.append(
274
+ cst.SimpleStatementLine(
275
+ (
276
+ cst.ImportFrom(
277
+ module=self._build_module_expr(mod),
278
+ names=tuple(import_names),
279
+ ),
280
+ )
281
+ )
282
+ )
283
+ return stmts
284
+
285
+ def _build_local_imports(
286
+ self, func_qname: str
287
+ ) -> tuple[list[cst.BaseStatement], set[tuple[str | None, str]]]:
288
+ from collections import defaultdict
289
+
290
+ names = self.functions_needing_local.get(func_qname)
291
+ if not names:
292
+ return [], set()
293
+ # Group by module
294
+ by_module: DefaultDict[str | None, list[str]] = defaultdict(list)
295
+ for ident in sorted(names):
296
+ if ident in self.skip_local_names:
297
+ continue
298
+ mod, _ = self.idx.name_to_from.get(ident, (None, None))
299
+ # Skip unresolved modules to avoid invalid ImportFrom(module=None)
300
+ if mod is None:
301
+ continue
302
+ # Never inject local imports from typing; keep them at module level
303
+ if mod == "typing":
304
+ continue
305
+ by_module[mod].append(ident)
306
+ stmts: list[cst.BaseStatement] = []
307
+ pairs: set[tuple[str | None, str]] = set()
308
+ for mod, idents in by_module.items():
309
+ if not idents:
310
+ continue
311
+ for n in sorted(idents):
312
+ pairs.add((mod, n))
313
+ import_names = [cst.ImportAlias(name=cst.Name(n)) for n in sorted(idents)]
314
+ stmts.append(
315
+ cst.SimpleStatementLine(
316
+ (
317
+ cst.ImportFrom(
318
+ module=self._build_module_expr(mod),
319
+ names=tuple(import_names),
320
+ ),
321
+ )
322
+ )
323
+ )
324
+ return stmts, pairs
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ import libcst as cst
4
+
5
+
6
+ # Collect existing from-imports into a map: imported_name -> (module, alias)
7
+ # Only handle `from pkg import Name [as Alias]`. Skip star imports and bare `import pkg`.
8
+ class PythonParserImportIndex(cst.CSTVisitor):
9
+ def __init__(self) -> None:
10
+ super().__init__()
11
+ self.name_to_from: dict[str, tuple[str | None, str | None]] = {}
12
+ self.importfrom_nodes: list[cst.ImportFrom] = []
13
+ self.other_import_nodes: list[cst.Import] = []
14
+
15
+ @staticmethod
16
+ def _flatten_module_name(module: cst.BaseExpression | None) -> str | None:
17
+ if module is None:
18
+ return None
19
+ if isinstance(module, cst.Name):
20
+ return module.value
21
+ if isinstance(module, cst.Attribute):
22
+ parts: list[str] = []
23
+ cur: cst.BaseExpression | None = module
24
+ while isinstance(cur, cst.Attribute):
25
+ if isinstance(cur.attr, cst.Name):
26
+ parts.append(cur.attr.value)
27
+ else:
28
+ break
29
+ cur = cur.value
30
+ if isinstance(cur, cst.Name):
31
+ parts.append(cur.value)
32
+ parts.reverse()
33
+ return ".".join(parts) if parts else None
34
+ if isinstance(module, cst.SimpleString):
35
+ return module.evaluated_value
36
+ return None
37
+
38
+ def visit_Import(self, node: cst.Import) -> None: # type: ignore[override]
39
+ self.other_import_nodes.append(node)
40
+ # Index import statements: import os.path -> name_to_from["os"] = (None, None)
41
+ # The module name is stored as the imported name for bare imports
42
+ for name in node.names:
43
+ if isinstance(name, cst.ImportAlias):
44
+ # Get the module name (e.g., "os" from "import os.path")
45
+ if isinstance(name.name, cst.Name):
46
+ module_name = name.name.value
47
+ elif isinstance(name.name, cst.Attribute):
48
+ # For "import os.path", extract the base name "os"
49
+ module_name = self._flatten_module_name(name.name)
50
+ if module_name and "." in module_name:
51
+ # Store the base module name (first part before dot)
52
+ module_name = module_name.split(".")[0]
53
+ else:
54
+ continue
55
+
56
+ # Store the alias if present, otherwise the module name
57
+ alias_name = name.asname.name.value if name.asname else module_name
58
+ if alias_name:
59
+ # For bare imports, we store None as the module since it's not a from-import
60
+ self.name_to_from[alias_name] = (None, None)
61
+
62
+ def visit_ImportFrom(self, node: cst.ImportFrom) -> None: # type: ignore[override]
63
+ module_name = self._flatten_module_name(node.module)
64
+
65
+ # Skip __future__ imports - they must always stay at module top
66
+ if module_name == "__future__":
67
+ return
68
+
69
+ self.importfrom_nodes.append(node)
70
+ if node.names is None or isinstance(node.names, cst.ImportStar):
71
+ return
72
+ for alias in node.names:
73
+ if isinstance(alias, cst.ImportAlias):
74
+ asname = alias.asname.name.value if alias.asname else None
75
+ name = alias.name.value if isinstance(alias.name, cst.Name) else None
76
+ if name:
77
+ self.name_to_from[name if not asname else asname] = (
78
+ module_name,
79
+ asname,
80
+ )
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ import libcst as cst
4
+
5
+
6
+ class PythonRuntimeSymbolCollector(cst.CSTVisitor):
7
+ """Collect imported symbol names used in non-annotation runtime expressions.
8
+
9
+ It ignores names that appear inside annotations and records names that appear
10
+ elsewhere, so callers can conservatively treat them as runtime-used and keep
11
+ their imports at module level (and/or avoid moving them under TYPE_CHECKING).
12
+ """
13
+
14
+ def __init__(self, imported_value_names: set[str]) -> None:
15
+ super().__init__()
16
+ self.imported_value_names = imported_value_names
17
+ self.in_annotation_stack: list[bool] = []
18
+ self.runtime_used_anywhere: set[str] = set()
19
+
20
+ def leave_Annotation(self, node: cst.Annotation) -> None: # type: ignore[override]
21
+ self.in_annotation_stack.pop()
22
+
23
+ # Track entering/leaving annotations
24
+ def visit_Annotation(self, node: cst.Annotation) -> bool: # type: ignore[override]
25
+ self.in_annotation_stack.append(True)
26
+ return True
27
+
28
+ def visit_Name(self, node: cst.Name) -> None: # type: ignore[override]
29
+ if self.in_annotation_stack:
30
+ return
31
+ val = node.value
32
+ if val in self.imported_value_names:
33
+ self.runtime_used_anywhere.add(val)