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,413 @@
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 PythonImportRewriter(cst.CSTTransformer):
12
+ """Rewrite module-level imports by:
13
+ - Removing symbols that will be localized to functions (A) or moved under TYPE_CHECKING (C-only)
14
+ - Keeping symbols used at class-level property annotations (B)
15
+ - Adding a TYPE_CHECKING block with required imports for C-only symbols
16
+ - Ensuring `from typing import TYPE_CHECKING` is present
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ *,
22
+ used_in_B: set[str],
23
+ names_to_remove_from_module: set[str],
24
+ used_in_C_only: set[str],
25
+ idx: PythonParserImportIndex,
26
+ ) -> None:
27
+ super().__init__()
28
+ self.used_in_B = used_in_B
29
+ self.names_to_remove_from_module = names_to_remove_from_module
30
+ self.used_in_C_only = used_in_C_only
31
+ self.idx = idx
32
+ self.found_type_checking_import = False
33
+ # Track whether we are at module level; only prune at module level
34
+ self._inside_class_func_stack: list[str] = []
35
+ self.need_type_checking_block: bool = len(used_in_C_only) > 0
36
+ self._inside_type_checking_stack: list[bool] = []
37
+
38
+ @staticmethod
39
+ def _build_module_expr(mod: str | None) -> cst.BaseExpression | None:
40
+ if not mod:
41
+ return None
42
+ parts = mod.split(".")
43
+ expr: cst.BaseExpression = cst.Name(parts[0])
44
+ for p in parts[1:]:
45
+ expr = cst.Attribute(value=expr, attr=cst.Name(p))
46
+ return expr
47
+
48
+ @staticmethod
49
+ def _flatten_module_expr_to_str(module: cst.BaseExpression | None) -> str | None:
50
+ if module is None:
51
+ return None
52
+ if isinstance(module, cst.Name):
53
+ return module.value
54
+ if isinstance(module, cst.Attribute):
55
+ parts: list[str] = []
56
+ cur: cst.BaseExpression | None = module
57
+ while isinstance(cur, cst.Attribute):
58
+ if isinstance(cur.attr, cst.Name):
59
+ parts.append(cur.attr.value)
60
+ else:
61
+ break
62
+ cur = cur.value
63
+ if isinstance(cur, cst.Name):
64
+ parts.append(cur.value)
65
+ parts.reverse()
66
+ return ".".join(parts) if parts else None
67
+ return None
68
+
69
+ def leave_ClassDef(self, original_node: cst.ClassDef, updated_node: cst.ClassDef) -> cst.BaseStatement: # type: ignore[override]
70
+ self._inside_class_func_stack.pop()
71
+ return updated_node
72
+
73
+ def leave_FunctionDef(self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef) -> cst.BaseStatement: # type: ignore[override]
74
+ self._inside_class_func_stack.pop()
75
+ return updated_node
76
+
77
+ def leave_If(self, original_node: cst.If, updated_node: cst.If) -> cst.If: # type: ignore[override]
78
+ self._inside_type_checking_stack.pop()
79
+ return updated_node
80
+
81
+ def leave_Import(
82
+ self, original_node: cst.Import, updated_node: cst.Import
83
+ ) -> cst.RemovalSentinel | cst.BaseSmallStatement:
84
+ # Only handle module-level imports
85
+ if self._inside_class_func_stack:
86
+ return updated_node
87
+ kept_aliases: list[cst.ImportAlias] = []
88
+ removed_any = False
89
+ for alias in updated_node.names:
90
+ if not isinstance(alias, cst.ImportAlias):
91
+ continue
92
+
93
+ # Extract the base module name (e.g., "os" from "import os.path")
94
+ if isinstance(alias.name, cst.Name):
95
+ name = alias.name.value
96
+ elif isinstance(alias.name, cst.Attribute):
97
+ # For "import os.path", extract the base name "os"
98
+ full_name = self._flatten_module_expr_to_str(alias.name)
99
+ if full_name and "." in full_name:
100
+ name = full_name.split(".")[0]
101
+ else:
102
+ name = full_name
103
+ else:
104
+ continue
105
+
106
+ if not name:
107
+ continue
108
+ alias_ident = alias.asname.name.value if alias.asname else name
109
+
110
+ # Keep B at module level
111
+ if alias_ident in self.used_in_B:
112
+ kept_aliases.append(alias)
113
+ continue
114
+
115
+ # Drop if moved to TYPE_CHECKING or localized (A or C-only)
116
+ if alias_ident in self.names_to_remove_from_module:
117
+ removed_any = True
118
+ continue
119
+
120
+ kept_aliases.append(alias)
121
+
122
+ if not kept_aliases:
123
+ return cst.RemoveFromParent()
124
+
125
+ # If nothing changed (same number of aliases kept as original and none removed),
126
+ # return the original node to preserve exact formatting (commas, parentheses, whitespace).
127
+ try:
128
+ original_alias_count = len(original_node.names) if not isinstance(original_node.names, cst.ImportStar) else 0 # type: ignore[arg-type]
129
+ except Exception:
130
+ original_alias_count = -1
131
+ if original_alias_count == len(kept_aliases) and not removed_any:
132
+ return original_node
133
+
134
+ # Rebuild aliases freshly to drop any stale trailing comma tokens.
135
+ rebuilt_aliases: list[cst.ImportAlias] = []
136
+ for alias in kept_aliases:
137
+ rebuilt_aliases.append(
138
+ cst.ImportAlias(
139
+ name=alias.name,
140
+ asname=alias.asname,
141
+ )
142
+ )
143
+ return updated_node.with_changes(names=tuple(rebuilt_aliases))
144
+
145
+ def leave_ImportFrom(
146
+ self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom
147
+ ) -> cst.RemovalSentinel | cst.BaseSmallStatement:
148
+ # Only handle module-level imports
149
+ if self._inside_class_func_stack:
150
+ return updated_node
151
+ if updated_node.names is None or isinstance(updated_node.names, cst.ImportStar):
152
+ return updated_node
153
+
154
+ # If we're already inside a TYPE_CHECKING block, do not filter out C-only names here.
155
+ if self._inside_type_checking_stack and self._inside_type_checking_stack[-1]:
156
+ return updated_node
157
+
158
+ mod_str = self._flatten_module_expr_to_str(updated_node.module)
159
+
160
+ kept_aliases: list[cst.ImportAlias] = []
161
+ removed_any = False
162
+ for alias in updated_node.names:
163
+ if not isinstance(alias, cst.ImportAlias):
164
+ continue
165
+ name = alias.name.value if isinstance(alias.name, cst.Name) else None
166
+ if not name:
167
+ continue
168
+ alias_ident = alias.asname.name.value if alias.asname else name
169
+
170
+ # Always keep non-TYPE_CHECKING imports from typing at module level
171
+ if mod_str == "typing" and name != "TYPE_CHECKING":
172
+ kept_aliases.append(alias)
173
+ continue
174
+
175
+ # Keep B at module level
176
+ if alias_ident in self.used_in_B:
177
+ kept_aliases.append(alias)
178
+ continue
179
+
180
+ # Drop if moved to TYPE_CHECKING or localized (A or C-only)
181
+ if alias_ident in self.names_to_remove_from_module:
182
+ removed_any = True
183
+ continue
184
+
185
+ kept_aliases.append(alias)
186
+
187
+ if not kept_aliases:
188
+ return cst.RemoveFromParent()
189
+
190
+ # If nothing changed (same number of aliases kept as original and none removed),
191
+ # return the original node to preserve exact formatting (commas, parentheses, whitespace).
192
+ try:
193
+ original_alias_count = len(original_node.names) if not isinstance(original_node.names, cst.ImportStar) else 0 # type: ignore[arg-type]
194
+ except Exception:
195
+ original_alias_count = -1
196
+ if original_alias_count == len(kept_aliases) and not removed_any:
197
+ return original_node
198
+
199
+ # Rebuild aliases freshly to drop any stale trailing comma tokens.
200
+ rebuilt_aliases: list[cst.ImportAlias] = []
201
+ for alias in kept_aliases:
202
+ rebuilt_aliases.append(
203
+ cst.ImportAlias(
204
+ name=alias.name,
205
+ asname=alias.asname,
206
+ )
207
+ )
208
+ return updated_node.with_changes(names=tuple(rebuilt_aliases))
209
+
210
+ def leave_Module(
211
+ self, original_node: cst.Module, updated_node: cst.Module
212
+ ) -> cst.Module:
213
+ from collections import defaultdict
214
+
215
+ if not self.need_type_checking_block or not self.used_in_C_only:
216
+ return updated_node
217
+
218
+ # Build desired imports for C-only
219
+ desired_by_module: DefaultDict[str | None, set[str]] = defaultdict(set)
220
+ for ident in sorted(self.used_in_C_only):
221
+ mod, _ = self.idx.name_to_from.get(ident, (None, None))
222
+ # Never add typing.* under TYPE_CHECKING; keep them at module level only.
223
+ if mod == "typing":
224
+ continue
225
+ desired_by_module[mod].add(ident)
226
+
227
+ # Look for existing TYPE_CHECKING block(s)
228
+ existing_tc_index = None
229
+ existing_tc_body: list[cst.BaseStatement] | None = None
230
+ existing_imported: set[tuple[str | None, str]] = set()
231
+
232
+ for i, stmt in enumerate(updated_node.body):
233
+ if (
234
+ isinstance(stmt, cst.If)
235
+ and isinstance(stmt.test, cst.Name)
236
+ and stmt.test.value == "TYPE_CHECKING"
237
+ ):
238
+ existing_tc_index = i
239
+ existing_tc_body = list(stmt.body.body)
240
+ # Collect names already imported there
241
+ for s in existing_tc_body:
242
+ if (
243
+ isinstance(s, cst.SimpleStatementLine)
244
+ and len(s.body) == 1
245
+ and isinstance(s.body[0], cst.ImportFrom)
246
+ ):
247
+ imp: cst.ImportFrom = s.body[0]
248
+ mod = self._flatten_module_expr_to_str(imp.module)
249
+ if imp.names and not isinstance(imp.names, cst.ImportStar):
250
+ for alias in imp.names:
251
+ if isinstance(alias, cst.ImportAlias) and isinstance(
252
+ alias.name, cst.Name
253
+ ):
254
+ existing_imported.add((mod, alias.name.value))
255
+ break
256
+
257
+ # Compute missing imports
258
+ missing_by_module: DefaultDict[str | None, list[str]] = defaultdict(list)
259
+ for mod, names in desired_by_module.items():
260
+ for n in sorted(names):
261
+ if (mod, n) not in existing_imported:
262
+ missing_by_module[mod].append(n)
263
+
264
+ if existing_tc_index is not None and existing_tc_body is not None:
265
+ # Append missing imports to existing TYPE_CHECKING block
266
+ additions: list[cst.BaseStatement] = []
267
+ for mod, names in missing_by_module.items():
268
+ if not names:
269
+ continue
270
+ import_names = [
271
+ cst.ImportAlias(name=cst.Name(n)) for n in sorted(names)
272
+ ]
273
+ imp_stmt = cst.SimpleStatementLine(
274
+ (
275
+ cst.ImportFrom(
276
+ module=self._build_module_expr(mod),
277
+ names=tuple(import_names),
278
+ ),
279
+ )
280
+ )
281
+ additions.append(imp_stmt)
282
+ if not additions:
283
+ return original_node
284
+ new_tc = updated_node.body[existing_tc_index].with_changes(
285
+ body=cst.IndentedBlock(body=existing_tc_body + additions)
286
+ )
287
+ new_body = list(updated_node.body)
288
+ new_body[existing_tc_index] = new_tc
289
+ return updated_node.with_changes(body=new_body)
290
+
291
+ # Otherwise, create a new TYPE_CHECKING block placed after imports
292
+ type_checking_body: list[cst.BaseStatement] = []
293
+ for mod, names in missing_by_module.items():
294
+ if not names:
295
+ continue
296
+ import_names = [cst.ImportAlias(name=cst.Name(n)) for n in sorted(names)]
297
+ imp = cst.ImportFrom(
298
+ module=self._build_module_expr(mod), names=tuple(import_names)
299
+ )
300
+ type_checking_body.append(cst.SimpleStatementLine((imp,)))
301
+
302
+ if not type_checking_body:
303
+ return updated_node
304
+
305
+ typing_import_stmt = cst.SimpleStatementLine(
306
+ (
307
+ cst.ImportFrom(
308
+ module=cst.Name("typing"),
309
+ names=(cst.ImportAlias(name=cst.Name("TYPE_CHECKING")),),
310
+ ),
311
+ )
312
+ )
313
+
314
+ # Find insertion index: after module docstring (if any) and after __future__ imports,
315
+ # then after consecutive top-level imports. Never before the docstring or __future__.
316
+ insert_index = 0
317
+ i = 0
318
+ # Skip module docstring at very top
319
+ if i < len(updated_node.body):
320
+ stmt0 = updated_node.body[i]
321
+ if isinstance(stmt0, cst.SimpleStatementLine) and any(
322
+ isinstance(el, cst.Expr) and isinstance(el.value, cst.SimpleString)
323
+ for el in stmt0.body
324
+ ):
325
+ i += 1
326
+ insert_index = i
327
+
328
+ # Skip __future__ imports
329
+ while i < len(updated_node.body):
330
+ stmt = updated_node.body[i]
331
+ if (
332
+ isinstance(stmt, cst.SimpleStatementLine)
333
+ and stmt.body
334
+ and isinstance(stmt.body[0], cst.ImportFrom)
335
+ ):
336
+ imp: cst.ImportFrom = stmt.body[0]
337
+ if (
338
+ imp.module
339
+ and isinstance(imp.module, cst.Name)
340
+ and imp.module.value == "__future__"
341
+ ):
342
+ i += 1
343
+ insert_index = i
344
+ continue
345
+ break
346
+ # Walk through consecutive import statements
347
+ while i < len(updated_node.body):
348
+ stmt = updated_node.body[i]
349
+ if (
350
+ isinstance(stmt, cst.SimpleStatementLine)
351
+ and stmt.body
352
+ and (
353
+ isinstance(stmt.body[0], cst.ImportFrom)
354
+ or isinstance(stmt.body[0], cst.Import)
355
+ )
356
+ ):
357
+ i += 1
358
+ insert_index = i
359
+ else:
360
+ break
361
+
362
+ new_body = list(updated_node.body)
363
+ # Ensure typing import exists somewhere before the TYPE_CHECKING block
364
+ if not self.found_type_checking_import:
365
+ new_body.insert(insert_index, typing_import_stmt)
366
+ insert_index += 1
367
+
368
+ type_checking_if = cst.If(
369
+ test=cst.Name("TYPE_CHECKING"),
370
+ body=cst.IndentedBlock(body=type_checking_body),
371
+ )
372
+ new_body.insert(insert_index, type_checking_if)
373
+
374
+ return updated_node.with_changes(body=new_body)
375
+
376
+ def leave_SimpleStatementLine(
377
+ self,
378
+ original_node: cst.SimpleStatementLine,
379
+ updated_node: cst.SimpleStatementLine,
380
+ ) -> cst.BaseStatement:
381
+ # Detect `from typing import TYPE_CHECKING`
382
+ if len(updated_node.body) == 1 and isinstance(
383
+ updated_node.body[0], cst.ImportFrom
384
+ ):
385
+ imp: cst.ImportFrom = updated_node.body[0]
386
+ if (
387
+ imp.module
388
+ and isinstance(imp.module, cst.Name)
389
+ and imp.module.value == "typing"
390
+ and imp.names
391
+ and not isinstance(imp.names, cst.ImportStar)
392
+ ):
393
+ for alias in imp.names:
394
+ if isinstance(alias, cst.ImportAlias) and isinstance(
395
+ alias.name, cst.Name
396
+ ):
397
+ if alias.name.value == "TYPE_CHECKING":
398
+ self.found_type_checking_import = True
399
+ return updated_node
400
+
401
+ def visit_ClassDef(self, node: cst.ClassDef) -> bool: # type: ignore[override]
402
+ self._inside_class_func_stack.append("class")
403
+ return True
404
+
405
+ def visit_FunctionDef(self, node: cst.FunctionDef) -> bool: # type: ignore[override]
406
+ self._inside_class_func_stack.append("function")
407
+ return True
408
+
409
+ def visit_If(self, node: cst.If) -> bool: # type: ignore[override]
410
+ # Track whether we are under `if TYPE_CHECKING:`
411
+ inside = isinstance(node.test, cst.Name) and node.test.value == "TYPE_CHECKING"
412
+ self._inside_type_checking_stack.append(inside)
413
+ return True