wexample-filestate-python 7.0.0__tar.gz → 8.0.0__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.
Files changed (93) hide show
  1. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/PKG-INFO +6 -8
  2. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/README.md +3 -4
  3. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/pyproject.toml +3 -4
  4. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/config_option/mixin/with_stdout_wrapping_mixin.py +5 -5
  5. wexample_filestate_python-8.0.0/src/wexample_filestate_python/config_value/python_config_value.py +210 -0
  6. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/file/python_file.py +1 -1
  7. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/helpers/package.py +12 -24
  8. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/helpers/toml.py +4 -3
  9. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/add_future_annotations_option.py +10 -8
  10. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/add_return_types_option.py +38 -36
  11. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/class_name_matches_file_name_option.py +10 -9
  12. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/fix_attrs_option.py +1 -1
  13. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/fix_blank_lines_option.py +2 -3
  14. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/format_option.py +4 -3
  15. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/fstringify_option.py +1 -6
  16. wexample_filestate_python-8.0.0/src/wexample_filestate_python/option/python/modernize_typing_option.py +77 -0
  17. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/order_class_attributes_option.py +5 -0
  18. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/order_class_docstring_option.py +2 -0
  19. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/order_class_methods_option.py +5 -0
  20. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/order_constants_option.py +5 -0
  21. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/order_iterable_items_option.py +1 -2
  22. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/order_main_guard_option.py +2 -6
  23. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/order_module_docstring_option.py +2 -7
  24. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/order_module_functions_option.py +6 -9
  25. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/order_module_metadata_option.py +8 -12
  26. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/order_type_checking_block_option.py +3 -3
  27. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/relocate_imports_option.py +3 -4
  28. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/remove_unused_option.py +7 -11
  29. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/sort_imports_option.py +10 -4
  30. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/unquote_annotations_option.py +12 -9
  31. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python_option.py +4 -0
  32. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/options_provider/python_options_provider.py +3 -0
  33. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/cst_cache.py +3 -2
  34. wexample_filestate_python-8.0.0/src/wexample_filestate_python/utils/python_attrs_utils.py +95 -0
  35. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_blank_lines_utils.py +24 -81
  36. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_class_attributes_utils.py +10 -10
  37. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_class_docstring_utils.py +12 -14
  38. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_class_methods_utils.py +61 -75
  39. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_constants_utils.py +8 -11
  40. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_docstring_utils.py +2 -7
  41. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_functions_utils.py +73 -49
  42. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_iterable_utils.py +47 -48
  43. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_main_guard_utils.py +6 -8
  44. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_module_metadata_utils.py +22 -23
  45. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/python_type_checking_utils.py +2 -6
  46. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/relocate_imports/python_import_rewriter.py +15 -14
  47. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/relocate_imports/python_localize_runtime_imports.py +8 -15
  48. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/relocate_imports/python_parser_import_index.py +6 -6
  49. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/relocate_imports/python_runtime_symbol_collector.py +17 -14
  50. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/relocate_imports/python_usage_collector.py +20 -16
  51. wexample_filestate_python-7.0.0/src/wexample_filestate_python/config_value/python_config_value.py +0 -195
  52. wexample_filestate_python-7.0.0/src/wexample_filestate_python/option/python/modernize_typing_option.py +0 -25
  53. wexample_filestate_python-7.0.0/src/wexample_filestate_python/utils/python_attrs_utils.py +0 -111
  54. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/__init__.py +0 -0
  55. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/__pycache__/__init__.py +0 -0
  56. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/common/__init__.py +0 -0
  57. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/common/__pycache__/__init__.py +0 -0
  58. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/common/pipy_gateway.py +0 -0
  59. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/config_option/__init__.py +0 -0
  60. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/config_option/__pycache__/__init__.py +0 -0
  61. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/config_option/mixin/__init__.py +0 -0
  62. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/config_option/mixin/__pycache__/__init__.py +0 -0
  63. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/config_value/__init__.py +0 -0
  64. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/config_value/__pycache__/__init__.py +0 -0
  65. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/const/__init__.py +0 -0
  66. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/const/__pycache__/__init__.py +0 -0
  67. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/const/name_pattern.py +0 -0
  68. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/const/path.py +0 -0
  69. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/const/python_file.py +0 -0
  70. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/event/__init__.py +0 -0
  71. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/event/src/__init__.py +0 -0
  72. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/event/src/wexample_event/__init__.py +0 -0
  73. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/file/__init__.py +0 -0
  74. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/file/__pycache__/__init__.py +0 -0
  75. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/helpers/__init__.py +0 -0
  76. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/helpers/__pycache__/__init__.py +0 -0
  77. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/helpers/python/__init__.py +0 -0
  78. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/__init__.py +0 -0
  79. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/__pycache__/__init__.py +0 -0
  80. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/__init__.py +0 -0
  81. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/option/python/abstract_python_file_content_option.py +0 -0
  82. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/options_provider/__init__.py +0 -0
  83. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/options_provider/__pycache__/__init__.py +0 -0
  84. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/py.typed +0 -0
  85. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/__init__.py +0 -0
  86. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/__pycache__/__init__.py +0 -0
  87. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/relocate_imports/__init__.py +0 -0
  88. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/src/wexample_filestate_python/utils/relocate_imports/__pycache__/__init__.py +0 -0
  89. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/tests/helpers/__init__.py +0 -0
  90. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/tests/helpers/test_package.py +0 -0
  91. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/tests/helpers/test_toml.py +0 -0
  92. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/tests/tests/__init__.py +0 -0
  93. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.0.0}/tests/wexample_tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wexample-filestate-python
3
- Version: 7.0.0
3
+ Version: 8.0.0
4
4
  Summary: Helpers for Python.
5
5
  Author-Email: weeger <contact@wexample.com>
6
6
  License: MIT
@@ -17,10 +17,9 @@ Requires-Dist: flynt
17
17
  Requires-Dist: isort
18
18
  Requires-Dist: libcst
19
19
  Requires-Dist: packaging
20
- Requires-Dist: pyupgrade
21
20
  Requires-Dist: tomli
22
- Requires-Dist: wexample-api>=6.4.0
23
- Requires-Dist: wexample-filestate>=12.2.0
21
+ Requires-Dist: wexample-api>=6.5.0
22
+ Requires-Dist: wexample-filestate>=13.0.0
24
23
  Provides-Extra: dev
25
24
  Requires-Dist: pytest; extra == "dev"
26
25
  Requires-Dist: pytest-cov; extra == "dev"
@@ -28,7 +27,7 @@ Description-Content-Type: text/markdown
28
27
 
29
28
  # filestate_python
30
29
 
31
- Version: 7.0.0
30
+ Version: 8.0.0
32
31
 
33
32
  Helpers for Python.
34
33
 
@@ -116,10 +115,9 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
116
115
  - isort:
117
116
  - libcst:
118
117
  - packaging:
119
- - pyupgrade:
120
118
  - tomli:
121
- - wexample-api: >=6.4.0
122
- - wexample-filestate: >=12.2.0
119
+ - wexample-api: >=6.5.0
120
+ - wexample-filestate: >=13.0.0
123
121
 
124
122
  ## Versioning & Compatibility Policy
125
123
 
@@ -1,6 +1,6 @@
1
1
  # filestate_python
2
2
 
3
- Version: 7.0.0
3
+ Version: 8.0.0
4
4
 
5
5
  Helpers for Python.
6
6
 
@@ -88,10 +88,9 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
88
88
  - isort:
89
89
  - libcst:
90
90
  - packaging:
91
- - pyupgrade:
92
91
  - tomli:
93
- - wexample-api: >=6.4.0
94
- - wexample-filestate: >=12.2.0
92
+ - wexample-api: >=6.5.0
93
+ - wexample-filestate: >=13.0.0
95
94
 
96
95
  ## Versioning & Compatibility Policy
97
96
 
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "wexample-filestate-python"
9
- version = "7.0.0"
9
+ version = "8.0.0"
10
10
  description = "Helpers for Python."
11
11
  authors = [
12
12
  { name = "weeger", email = "contact@wexample.com" },
@@ -26,10 +26,9 @@ dependencies = [
26
26
  "isort",
27
27
  "libcst",
28
28
  "packaging",
29
- "pyupgrade",
30
29
  "tomli",
31
- "wexample-api>=6.4.0",
32
- "wexample-filestate>=12.2.0",
30
+ "wexample-api>=6.5.0",
31
+ "wexample-filestate>=13.0.0",
33
32
  ]
34
33
 
35
34
  [project.readme]
@@ -1,5 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import io
4
+ import sys
5
+
3
6
 
4
7
  class WithStdoutWrappingMixin:
5
8
  @classmethod
@@ -15,9 +18,6 @@ class WithStdoutWrappingMixin:
15
18
  Returns:
16
19
  The return value of the callback function
17
20
  """
18
- import io
19
- import sys
20
-
21
21
  old_stdout = sys.stdout
22
22
  old_stderr = sys.stderr
23
23
  captured_stdout = io.StringIO()
@@ -34,9 +34,9 @@ class WithStdoutWrappingMixin:
34
34
  stdout_content = captured_stdout.getvalue()
35
35
  stderr_content = captured_stderr.getvalue()
36
36
 
37
- if stdout_content.strip():
37
+ if stdout_content and stdout_content.strip():
38
38
  sys.stdout.write(stdout_content.rstrip() + "\n\n")
39
- if stderr_content.strip():
39
+ if stderr_content and stderr_content.strip():
40
40
  sys.stderr.write(stderr_content.rstrip() + "\n\n")
41
41
 
42
42
  return result
@@ -0,0 +1,210 @@
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
+ # Cached mapping: option-name → self attribute name.
10
+ # Built once on first call to to_option_raw_value() so the 22 deferred
11
+ # imports and get_name() class-method calls are paid only once per process.
12
+ _OPTION_NAME_TO_ATTR: dict[str, str] | None = None
13
+
14
+
15
+ def _build_option_name_to_attr() -> dict[str, str]:
16
+ from wexample_filestate_python.config_option.add_future_annotations_config_option import (
17
+ AddFutureAnnotationsConfigOption,
18
+ )
19
+ from wexample_filestate_python.config_option.add_return_types_config_option import (
20
+ AddReturnTypesConfigOption,
21
+ )
22
+ from wexample_filestate_python.config_option.fix_attrs_config_option import (
23
+ FixAttrsConfigOption,
24
+ )
25
+ from wexample_filestate_python.config_option.fix_blank_lines_config_option import (
26
+ FixBlankLinesConfigOption,
27
+ )
28
+ from wexample_filestate_python.config_option.format_config_option import (
29
+ FormatConfigOption,
30
+ )
31
+ from wexample_filestate_python.config_option.fstringify_config_option import (
32
+ FstringifyConfigOption,
33
+ )
34
+ from wexample_filestate_python.config_option.modernize_typing_config_option import (
35
+ ModernizeTypingConfigOption,
36
+ )
37
+ from wexample_filestate_python.config_option.order_class_attributes_config_option import (
38
+ OrderClassAttributesConfigOption,
39
+ )
40
+ from wexample_filestate_python.config_option.order_class_docstring_config_option import (
41
+ OrderClassDocstringConfigOption,
42
+ )
43
+ from wexample_filestate_python.config_option.order_class_methods_config_option import (
44
+ OrderClassMethodsConfigOption,
45
+ )
46
+ from wexample_filestate_python.config_option.order_constants_config_option import (
47
+ OrderConstantsConfigOption,
48
+ )
49
+ from wexample_filestate_python.config_option.order_iterable_items_config_option import (
50
+ OrderIterableItemsConfigOption,
51
+ )
52
+ from wexample_filestate_python.config_option.order_main_guard_config_option import (
53
+ OrderMainGuardConfigOption,
54
+ )
55
+ from wexample_filestate_python.config_option.order_module_docstring_config_option import (
56
+ OrderModuleDocstringConfigOption,
57
+ )
58
+ from wexample_filestate_python.config_option.order_module_functions_config_option import (
59
+ OrderModuleFunctionsConfigOption,
60
+ )
61
+ from wexample_filestate_python.config_option.order_module_metadata_config_option import (
62
+ OrderModuleMetadataConfigOption,
63
+ )
64
+ from wexample_filestate_python.config_option.order_spacing_config_option import (
65
+ OrderSpacingConfigOption,
66
+ )
67
+ from wexample_filestate_python.config_option.order_type_checking_block_config_option import (
68
+ OrderTypeCheckingBlockConfigOption,
69
+ )
70
+ from wexample_filestate_python.config_option.relocate_imports_config_option import (
71
+ RelocateImportsConfigOption,
72
+ )
73
+ from wexample_filestate_python.config_option.remove_unused_config_option import (
74
+ RemoveUnusedConfigOption,
75
+ )
76
+ from wexample_filestate_python.config_option.sort_imports_config_option import (
77
+ SortImportsConfigOption,
78
+ )
79
+ from wexample_filestate_python.config_option.unquote_annotations_config_option import (
80
+ UnquoteAnnotationsConfigOption,
81
+ )
82
+
83
+ return {
84
+ AddFutureAnnotationsConfigOption.get_name(): "add_future_annotations",
85
+ AddReturnTypesConfigOption.get_name(): "add_return_types",
86
+ FixAttrsConfigOption.get_name(): "fix_attrs",
87
+ FixBlankLinesConfigOption.get_name(): "fix_blank_lines",
88
+ FormatConfigOption.get_name(): "format",
89
+ FstringifyConfigOption.get_name(): "fstringify",
90
+ ModernizeTypingConfigOption.get_name(): "modernize_typing",
91
+ OrderClassAttributesConfigOption.get_name(): "order_class_attributes",
92
+ OrderClassDocstringConfigOption.get_name(): "order_class_docstring",
93
+ OrderClassMethodsConfigOption.get_name(): "order_class_methods",
94
+ OrderConstantsConfigOption.get_name(): "order_constants",
95
+ OrderIterableItemsConfigOption.get_name(): "order_iterable_items",
96
+ OrderMainGuardConfigOption.get_name(): "order_main_guard",
97
+ OrderModuleDocstringConfigOption.get_name(): "order_module_docstring",
98
+ OrderModuleFunctionsConfigOption.get_name(): "order_module_functions",
99
+ OrderModuleMetadataConfigOption.get_name(): "order_module_metadata",
100
+ OrderSpacingConfigOption.get_name(): "order_spacing",
101
+ OrderTypeCheckingBlockConfigOption.get_name(): "order_type_checking_block",
102
+ RelocateImportsConfigOption.get_name(): "relocate_imports",
103
+ RemoveUnusedConfigOption.get_name(): "remove_unused",
104
+ SortImportsConfigOption.get_name(): "sort_imports",
105
+ UnquoteAnnotationsConfigOption.get_name(): "unquote_annotations",
106
+ }
107
+
108
+
109
+ @base_class
110
+ class PythonConfigValue(ConfigValue):
111
+ add_future_annotations: bool | None = public_field(
112
+ default=None,
113
+ description="Add `from __future__ import annotations`",
114
+ )
115
+ add_return_types: bool | None = public_field(
116
+ default=None,
117
+ description="Add return type annotations",
118
+ )
119
+ fix_attrs: bool | None = public_field(
120
+ default=None,
121
+ description="Fix attrs usage (ensure kw_only=True, etc.)",
122
+ )
123
+ fix_blank_lines: bool | None = public_field(
124
+ default=None,
125
+ description="Fix blank lines in Python files",
126
+ )
127
+ format: bool | None = public_field(
128
+ default=None,
129
+ description="Format Python code",
130
+ )
131
+ fstringify: bool | None = public_field(
132
+ default=None,
133
+ description="Convert string formatting to f-strings",
134
+ )
135
+ modernize_typing: bool | None = public_field(
136
+ default=None,
137
+ description="Modernize typing annotations",
138
+ )
139
+ order_class_attributes: bool | None = public_field(
140
+ default=None,
141
+ description="Sort class attributes: special first, then public A–Z, then private/protected A–Z",
142
+ )
143
+ order_class_docstring: bool | None = public_field(
144
+ default=None,
145
+ description="Ensure class docstring is first statement after header/decorators",
146
+ )
147
+ order_class_methods: bool | None = public_field(
148
+ default=None,
149
+ description="Order class methods (dunders sequence, class/staticmethods, properties, instances)",
150
+ )
151
+ order_constants: bool | None = public_field(
152
+ default=None,
153
+ description="Sort flagged UPPER_CASE constant blocks at module level",
154
+ )
155
+ order_iterable_items: bool | None = public_field(
156
+ default=None,
157
+ description="Sort items inside flagged iterable literals (lists/dicts)",
158
+ )
159
+ order_main_guard: bool | None = public_field(
160
+ default=None,
161
+ description="Ensure if __name__ == '__main__' block is at the very end",
162
+ )
163
+ order_module_docstring: bool | None = public_field(
164
+ default=None,
165
+ description="Order module docstring to be at the top of the file",
166
+ )
167
+ order_module_functions: bool | None = public_field(
168
+ default=None,
169
+ description="Order module-level functions (public A–Z, then private)",
170
+ )
171
+ order_module_metadata: bool | None = public_field(
172
+ default=None,
173
+ description="Group and sort module metadata at module level",
174
+ )
175
+ order_spacing: bool | None = public_field(
176
+ default=None,
177
+ description="Normalize blank lines between program structures (spacing rules)",
178
+ )
179
+ order_type_checking_block: bool | None = public_field(
180
+ default=None,
181
+ description="Move TYPE_CHECKING blocks to after regular imports",
182
+ )
183
+ raw: Any = public_field(
184
+ default=None, description="Disabled raw value for this config."
185
+ )
186
+ relocate_imports: bool | None = public_field(
187
+ default=None,
188
+ description="Relocate imports by usage (runtime-in-method, class property types, type-only)",
189
+ )
190
+ remove_unused: bool | None = public_field(
191
+ default=None,
192
+ description="Remove unused imports",
193
+ )
194
+ sort_imports: bool | None = public_field(
195
+ default=None,
196
+ description="Sort imports",
197
+ )
198
+ unquote_annotations: bool | None = public_field(
199
+ default=None,
200
+ description="Unquote annotations (remove string annotations)",
201
+ )
202
+
203
+ def to_option_raw_value(self) -> Any:
204
+ global _OPTION_NAME_TO_ATTR
205
+ if _OPTION_NAME_TO_ATTR is None:
206
+ _OPTION_NAME_TO_ATTR = _build_option_name_to_attr()
207
+ return {
208
+ name: getattr(self, attr)
209
+ for name, attr in _OPTION_NAME_TO_ATTR.items()
210
+ }
@@ -9,4 +9,4 @@ class PythonFile(ItemTargetFile):
9
9
  EXTENSION_ENV: ClassVar[str] = "py"
10
10
 
11
11
  def _expected_file_name_extension(self) -> str | None:
12
- return PythonFile.EXTENSION_ENV
12
+ return self.EXTENSION_ENV
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ast
4
+ import re
4
5
  from typing import TYPE_CHECKING
5
6
 
6
7
  import tomli
@@ -19,32 +20,21 @@ def package_get_dependencies(root_dir: str | Path) -> dict[str, set[str]]:
19
20
  if not packages_root.exists() or not packages_root.is_dir():
20
21
  raise ValueError(f"Error: {packages_root} does not exist or is not a directory")
21
22
 
22
- dependencies = {}
23
- all_entries = list(packages_root.iterdir())
24
-
25
- # First pass: collect all local packages
26
- for package_dir in all_entries:
27
- if not package_dir.is_dir():
28
- continue
29
-
30
- package_info = package_get_info(package_dir)
31
- if package_info:
32
- name, _ = package_info
33
- dependencies[name] = set()
34
-
35
- # Second pass: analyze dependencies
36
- for package_dir in all_entries:
37
- if not package_dir.is_dir():
38
- continue
23
+ # Filter to directories once; avoids repeated is_dir() calls in two passes.
24
+ all_dirs = [e for e in packages_root.iterdir() if e.is_dir()]
39
25
 
26
+ # Single pass: collect all local package infos (name → raw deps).
27
+ pkg_infos: dict[str, set[str]] = {}
28
+ for package_dir in all_dirs:
40
29
  package_info = package_get_info(package_dir)
41
30
  if package_info:
42
31
  name, deps = package_info
43
- if name in dependencies:
44
- # Only keep dependencies that are local packages
45
- dependencies[name] = {dep for dep in deps if dep in dependencies}
32
+ pkg_infos[name] = deps
33
+
34
+ local_names: set[str] = set(pkg_infos) # O(1) membership for filtering
46
35
 
47
- return dependencies
36
+ # Keep only deps that resolve to a local package (set intersection).
37
+ return {name: deps & local_names for name, deps in pkg_infos.items()}
48
38
 
49
39
 
50
40
  def package_get_info(package_dir: Path) -> tuple[str, set[str]] | None:
@@ -72,10 +62,8 @@ def package_get_info(package_dir: Path) -> tuple[str, set[str]] | None:
72
62
 
73
63
 
74
64
  def package_normalize_name(val: str) -> str:
75
- import re as _re
76
-
77
65
  # strip extras, versions, markers
78
- base = _re.split(r"[\s<>=!~;\[]", val, maxsplit=1)[0]
66
+ base = re.split(r"[\s<>=!~;\[]", val, maxsplit=1)[0]
79
67
  return base.strip().lower()
80
68
 
81
69
 
@@ -96,11 +96,12 @@ def toml_sort_string_array(arr: Any) -> bool:
96
96
  if not items or not all(isinstance(i, String) for i in items):
97
97
  return False
98
98
 
99
- sorted_items = sorted(items, key=lambda i: i.value.lower())
100
-
101
- if items == sorted_items:
99
+ keys = [i.value.lower() for i in items]
100
+ sorted_keys = sorted(keys)
101
+ if keys == sorted_keys:
102
102
  return False
103
103
 
104
+ sorted_items = sorted(items, key=lambda i: i.value.lower())
104
105
  multiline_flag = arr._multiline
105
106
  # Clear and re-append to preserve tomlkit item identity
106
107
  while arr:
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import ast
4
+ import re
3
5
  from typing import TYPE_CHECKING
4
6
 
5
7
  from wexample_helpers.decorator.base_class import base_class
@@ -9,6 +11,10 @@ from .abstract_python_file_content_option import AbstractPythonFileContentOption
9
11
  if TYPE_CHECKING:
10
12
  from wexample_filestate.const.types_state_items import TargetFileOrDirectoryType
11
13
 
14
+ # PEP 263 encoding cookie matcher. Compiled once at import time, reused
15
+ # across every file the rectifier touches.
16
+ _ENCODING_COOKIE_RE = re.compile(r"^#.*coding[:=]\s*([-_.a-zA-Z0-9]+)")
17
+
12
18
 
13
19
  @base_class
14
20
  class AddFutureAnnotationsOption(AbstractPythonFileContentOption):
@@ -23,9 +29,6 @@ class AddFutureAnnotationsOption(AbstractPythonFileContentOption):
23
29
  if "from __future__ import annotations" in src:
24
30
  return src
25
31
 
26
- import ast
27
- import re
28
-
29
32
  lines = src.splitlines(keepends=True)
30
33
 
31
34
  # Detect shebang and encoding cookie positions
@@ -33,9 +36,8 @@ class AddFutureAnnotationsOption(AbstractPythonFileContentOption):
33
36
  if lines and lines[0].startswith("#!"):
34
37
  idx = 1
35
38
  # Encoding cookie can be on first or second line (after shebang)
36
- enc_re = re.compile(r"^#.*coding[:=]\s*([-_.a-zA-Z0-9]+)")
37
39
  for i in range(idx, min(idx + 2, len(lines))):
38
- if enc_re.match(lines[i]):
40
+ if _ENCODING_COOKIE_RE.match(lines[i]):
39
41
  idx = i + 1
40
42
 
41
43
  # Parse to find module docstring span
@@ -45,11 +47,11 @@ class AddFutureAnnotationsOption(AbstractPythonFileContentOption):
45
47
  # If parsing fails, be conservative: insert after header block
46
48
  insert_at = idx
47
49
  else:
48
- body = getattr(tree, "body", [])
50
+ body = tree.body
49
51
  if (
50
52
  body
51
53
  and isinstance(body[0], ast.Expr)
52
- and isinstance(getattr(body[0], "value", None), ast.Constant)
54
+ and isinstance(body[0].value, ast.Constant)
53
55
  and isinstance(body[0].value.value, str)
54
56
  ):
55
57
  # Module docstring present; insert after its end_lineno
@@ -59,7 +61,7 @@ class AddFutureAnnotationsOption(AbstractPythonFileContentOption):
59
61
  insert_at = idx
60
62
 
61
63
  # lines is 0-based; insert_at is 1-based line count from AST
62
- insert_index = max(0, min(len(lines), insert_at))
64
+ insert_index = min(len(lines), insert_at)
63
65
 
64
66
  # Ensure there is a newline after the inserted import if needed
65
67
  future_line = "from __future__ import annotations\n"
@@ -26,10 +26,7 @@ class AddReturnTypesOption(AbstractPythonFileContentOption):
26
26
  get_python_source_and_module,
27
27
  )
28
28
 
29
- try:
30
- src, module = get_python_source_and_module(target)
31
- except Exception:
32
- return target.get_local_file().read()
29
+ src, module = get_python_source_and_module(target)
33
30
 
34
31
  # We implement type inference and rewriting using LibCST to ensure
35
32
  # robust, formatting-preserving edits. We extend inference to:
@@ -50,9 +47,33 @@ class AddReturnTypesOption(AbstractPythonFileContentOption):
50
47
  return "float"
51
48
  return None
52
49
 
50
+ def _infer_class_call_type(call: cst.Call, known_types: set[str]) -> str | None:
51
+ """Return the class name if `call` instantiates a known type.
52
+
53
+ Recognizes `Foo()` and `module.Foo()` shapes; requires the matched
54
+ name to be uppercase-first to stay conservative. Single source of
55
+ truth shared by the assignment collector and the return-expression
56
+ transformer.
57
+ """
58
+ func = call.func
59
+ if isinstance(func, cst.Name):
60
+ if func.value in known_types and func.value[:1].isupper():
61
+ return func.value
62
+ return None
63
+ if isinstance(func, cst.Attribute) and isinstance(func.attr, cst.Name):
64
+ attr_name = func.attr.value
65
+ if attr_name in known_types and attr_name[:1].isupper():
66
+ return attr_name
67
+ return None
68
+
53
69
  class _ReturnCollector(cst.CSTVisitor):
54
70
  def __init__(self) -> None:
55
71
  self.returns: list[cst.Return] = []
72
+ # A function with any `yield` / `yield from` is a generator —
73
+ # its real return type is Iterator[X], not whatever the return
74
+ # statements imply. We can't infer Iterator[X] from this simple
75
+ # pass, so we record the flag and bail out at the caller.
76
+ self.has_yield: bool = False
56
77
 
57
78
  # Do not descend into nested scopes that could have their own returns
58
79
  def visit_FunctionDef(self, node: cst.FunctionDef) -> bool: # type: ignore[override]
@@ -67,6 +88,11 @@ class AddReturnTypesOption(AbstractPythonFileContentOption):
67
88
  def visit_Return(self, node: cst.Return) -> None: # type: ignore[override]
68
89
  self.returns.append(node)
69
90
 
91
+ def visit_Yield(self, node: cst.Yield) -> None: # type: ignore[override]
92
+ # libcst represents `yield from X` as a Yield whose value is
93
+ # `cst.From(item=X)`, so this single hook catches both forms.
94
+ self.has_yield = True
95
+
70
96
  class _KnownTypesCollector(cst.CSTVisitor):
71
97
  """Collect known simple type names from class defs and from-imports.
72
98
 
@@ -103,20 +129,6 @@ class AddReturnTypesOption(AbstractPythonFileContentOption):
103
129
  self.var_type: dict[str, str] = {}
104
130
  self.discarded: set[str] = set()
105
131
 
106
- def _infer_call_type(self, call: cst.Call) -> str | None:
107
- func = call.func
108
- if isinstance(func, cst.Name):
109
- # Only keep conservative matches: known type names
110
- if func.value in self.known_types and func.value[:1].isupper():
111
- return func.value
112
- elif isinstance(func, cst.Attribute):
113
- # module.MyClass(...) -> infer MyClass if it's a known type
114
- if isinstance(func.attr, cst.Name):
115
- attr_name = func.attr.value
116
- if attr_name in self.known_types and attr_name[:1].isupper():
117
- return attr_name
118
- return None
119
-
120
132
  def _record_assignment(
121
133
  self, target: cst.BaseExpression, value: cst.BaseExpression
122
134
  ) -> None:
@@ -127,7 +139,7 @@ class AddReturnTypesOption(AbstractPythonFileContentOption):
127
139
  return
128
140
  if not isinstance(value, cst.Call):
129
141
  return
130
- rtype = self._infer_call_type(value)
142
+ rtype = _infer_class_call_type(value, self.known_types)
131
143
  if rtype is None:
132
144
  return
133
145
  existing = self.var_type.get(var)
@@ -170,20 +182,7 @@ class AddReturnTypesOption(AbstractPythonFileContentOption):
170
182
 
171
183
  # Call to a known class
172
184
  if isinstance(expr, cst.Call):
173
- func = expr.func
174
- if (
175
- isinstance(func, cst.Name)
176
- and func.value in self.known_types
177
- and func.value[:1].isupper()
178
- ):
179
- return func.value
180
- if isinstance(func, cst.Attribute) and isinstance(
181
- func.attr, cst.Name
182
- ):
183
- attr_name = func.attr.value
184
- if attr_name in self.known_types and attr_name[:1].isupper():
185
- return attr_name
186
- return None
185
+ return _infer_class_call_type(expr, self.known_types)
187
186
 
188
187
  # Variable referring to a previously inferred var type
189
188
  if isinstance(expr, cst.Name):
@@ -194,9 +193,12 @@ class AddReturnTypesOption(AbstractPythonFileContentOption):
194
193
  def _infer_for_function(self, func_node: cst.FunctionDef) -> str | None:
195
194
  # Collect returns in the function body (non-nested)
196
195
  collector = _ReturnCollector()
197
- if not isinstance(func_node, cst.FunctionDef):
198
- return None
199
196
  func_node.body.visit(collector)
197
+ # Generator: never annotate via this simple pass. The function
198
+ # actually returns Iterator[X] / Generator[X, ...], which we
199
+ # can't infer here — leave it unannotated for a human to fill.
200
+ if collector.has_yield:
201
+ return None
200
202
  # If no return statements -> None
201
203
  if not collector.returns:
202
204
  return "None"
@@ -216,7 +218,7 @@ class AddReturnTypesOption(AbstractPythonFileContentOption):
216
218
  kinds.add(inferred)
217
219
 
218
220
  if len(kinds) == 1:
219
- return next(iter(kinds))
221
+ return kinds.pop()
220
222
  return None
221
223
 
222
224
  def leave_FunctionDef(
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
18
18
  class ClassNameMatchesFileNameOption(OptionMixin, AbstractConfigOption):
19
19
  @classmethod
20
20
  def get_scopes(cls) -> list[Scope]:
21
- return [Scope.NAME]
21
+ return [Scope.NAME, Scope.CONTENT]
22
22
 
23
23
  @staticmethod
24
24
  def _expected_class_name_from_path(path: Path) -> str | None:
@@ -31,12 +31,13 @@ class ClassNameMatchesFileNameOption(OptionMixin, AbstractConfigOption):
31
31
  if not parts:
32
32
  return None
33
33
 
34
- def normalize(part: str) -> str:
35
- head, tail = part[:1], part[1:]
36
- return head.upper() + tail.lower()
37
-
34
+ normalize = ClassNameMatchesFileNameOption._normalize_part
38
35
  return "".join(normalize(part) for part in parts)
39
36
 
37
+ @staticmethod
38
+ def _normalize_part(part: str) -> str:
39
+ return part[:1].upper() + part[1:].lower()
40
+
40
41
  def create_required_operation(
41
42
  self, target: TargetFileOrDirectoryType, scopes: set[Scope]
42
43
  ) -> AbstractOperation | None:
@@ -63,7 +64,7 @@ class ClassNameMatchesFileNameOption(OptionMixin, AbstractConfigOption):
63
64
  except SyntaxError:
64
65
  return False
65
66
 
66
- for node in module.body:
67
- if isinstance(node, ast.ClassDef) and node.name == expected_name:
68
- return True
69
- return False
67
+ return any(
68
+ isinstance(node, ast.ClassDef) and node.name == expected_name
69
+ for node in module.body
70
+ )
@@ -31,7 +31,7 @@ class FixAttrsOption(AbstractPythonFileContentOption):
31
31
  fix_attrs_kw_only,
32
32
  )
33
33
 
34
- src, module = get_python_source_and_module(target)
34
+ _, module = get_python_source_and_module(target)
35
35
 
36
36
  modified = fix_attrs_kw_only(module)
37
37
  return modified.code
@@ -41,7 +41,6 @@ class FixBlankLinesOption(AbstractPythonFileContentOption):
41
41
  fix_function_blank_lines,
42
42
  )
43
43
 
44
- src, module = get_python_source_and_module(target)
44
+ _, module = get_python_source_and_module(target)
45
45
 
46
- modified = fix_function_blank_lines(module)
47
- return modified.code
46
+ return fix_function_blank_lines(module).code