wexample-filestate-python 7.0.0__tar.gz → 8.1.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 (98) hide show
  1. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/PKG-INFO +6 -8
  2. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/README.md +3 -4
  3. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/pyproject.toml +3 -4
  4. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/config_option/mixin/with_stdout_wrapping_mixin.py +5 -5
  5. wexample_filestate_python-8.1.0/src/wexample_filestate_python/config_value/python_config_value.py +210 -0
  6. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/file/python_file.py +1 -1
  7. wexample_filestate_python-8.1.0/src/wexample_filestate_python/file/python_test_stub_file.py +128 -0
  8. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/helpers/package.py +12 -24
  9. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/helpers/toml.py +4 -3
  10. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/add_future_annotations_option.py +10 -8
  11. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/add_return_types_option.py +38 -36
  12. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/class_name_matches_file_name_option.py +10 -9
  13. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/fix_attrs_option.py +1 -1
  14. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/fix_blank_lines_option.py +2 -3
  15. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/format_option.py +4 -3
  16. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/fstringify_option.py +1 -6
  17. wexample_filestate_python-8.1.0/src/wexample_filestate_python/option/python/modernize_typing_option.py +77 -0
  18. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/order_class_attributes_option.py +5 -0
  19. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/order_class_docstring_option.py +2 -0
  20. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/order_class_methods_option.py +5 -0
  21. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/order_constants_option.py +5 -0
  22. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/order_iterable_items_option.py +1 -2
  23. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/order_main_guard_option.py +2 -6
  24. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/order_module_docstring_option.py +2 -7
  25. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/order_module_functions_option.py +6 -9
  26. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/order_module_metadata_option.py +8 -12
  27. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/order_type_checking_block_option.py +3 -3
  28. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/relocate_imports_option.py +3 -4
  29. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/remove_unused_option.py +7 -11
  30. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/sort_imports_option.py +16 -6
  31. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/unquote_annotations_option.py +12 -9
  32. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python_option.py +25 -7
  33. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/options_provider/python_options_provider.py +8 -6
  34. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/cst_cache.py +3 -2
  35. wexample_filestate_python-8.1.0/src/wexample_filestate_python/utils/python_attrs_utils.py +91 -0
  36. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_blank_lines_utils.py +28 -85
  37. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_class_attributes_utils.py +10 -10
  38. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_class_docstring_utils.py +12 -14
  39. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_class_methods_utils.py +69 -77
  40. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_constants_utils.py +8 -11
  41. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_docstring_utils.py +2 -7
  42. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_functions_utils.py +76 -58
  43. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_iterable_utils.py +48 -48
  44. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_main_guard_utils.py +6 -8
  45. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_module_metadata_utils.py +22 -23
  46. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/python_type_checking_utils.py +2 -6
  47. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/relocate_imports/python_import_rewriter.py +15 -14
  48. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/relocate_imports/python_localize_runtime_imports.py +8 -15
  49. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/relocate_imports/python_parser_import_index.py +6 -6
  50. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/relocate_imports/python_runtime_symbol_collector.py +17 -14
  51. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/relocate_imports/python_usage_collector.py +20 -16
  52. {wexample_filestate_python-7.0.0/tests/wexample_tests → wexample_filestate_python-8.1.0/tests/unit}/__init__.py +0 -0
  53. wexample_filestate_python-8.1.0/tests/unit/helpers/__init__.py +0 -0
  54. wexample_filestate_python-8.1.0/tests/unit/helpers/test_package.py +16 -0
  55. wexample_filestate_python-8.1.0/tests/unit/helpers/test_toml.py +19 -0
  56. wexample_filestate_python-8.1.0/tests/wexample_tests/__init__.py +0 -0
  57. wexample_filestate_python-7.0.0/src/wexample_filestate_python/config_value/python_config_value.py +0 -195
  58. wexample_filestate_python-7.0.0/src/wexample_filestate_python/option/python/modernize_typing_option.py +0 -25
  59. wexample_filestate_python-7.0.0/src/wexample_filestate_python/utils/python_attrs_utils.py +0 -111
  60. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/__init__.py +0 -0
  61. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/__pycache__/__init__.py +0 -0
  62. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/common/__init__.py +0 -0
  63. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/common/__pycache__/__init__.py +0 -0
  64. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/common/pipy_gateway.py +0 -0
  65. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/config_option/__init__.py +0 -0
  66. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/config_option/__pycache__/__init__.py +0 -0
  67. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/config_option/mixin/__init__.py +0 -0
  68. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/config_option/mixin/__pycache__/__init__.py +0 -0
  69. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/config_value/__init__.py +0 -0
  70. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/config_value/__pycache__/__init__.py +0 -0
  71. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/const/__init__.py +0 -0
  72. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/const/__pycache__/__init__.py +0 -0
  73. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/const/name_pattern.py +0 -0
  74. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/const/path.py +0 -0
  75. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/const/python_file.py +0 -0
  76. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/event/__init__.py +0 -0
  77. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/event/src/__init__.py +0 -0
  78. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/event/src/wexample_event/__init__.py +0 -0
  79. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/file/__init__.py +0 -0
  80. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/file/__pycache__/__init__.py +0 -0
  81. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/helpers/__init__.py +0 -0
  82. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/helpers/__pycache__/__init__.py +0 -0
  83. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/helpers/python/__init__.py +0 -0
  84. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/__init__.py +0 -0
  85. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/__pycache__/__init__.py +0 -0
  86. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/__init__.py +0 -0
  87. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/option/python/abstract_python_file_content_option.py +0 -0
  88. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/options_provider/__init__.py +0 -0
  89. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/options_provider/__pycache__/__init__.py +0 -0
  90. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/py.typed +0 -0
  91. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/__init__.py +0 -0
  92. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/__pycache__/__init__.py +0 -0
  93. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/relocate_imports/__init__.py +0 -0
  94. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/src/wexample_filestate_python/utils/relocate_imports/__pycache__/__init__.py +0 -0
  95. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/tests/helpers/__init__.py +0 -0
  96. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/tests/helpers/test_package.py +0 -0
  97. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/tests/helpers/test_toml.py +0 -0
  98. {wexample_filestate_python-7.0.0 → wexample_filestate_python-8.1.0}/tests/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.1.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.6.0
22
+ Requires-Dist: wexample-filestate>=14.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.1.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.6.0
120
+ - wexample-filestate: >=14.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.1.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.6.0
93
+ - wexample-filestate: >=14.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.1.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.6.0",
31
+ "wexample-filestate>=14.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
@@ -0,0 +1,128 @@
1
+ from __future__ import annotations
2
+
3
+ from wexample_helpers.decorator.base_class import base_class
4
+
5
+ from wexample_filestate_python.file.python_file import PythonFile
6
+
7
+
8
+ def _is_skippable_class(class_name: str, class_node) -> bool:
9
+ import libcst as cst
10
+
11
+ if class_name.startswith("Abstract"):
12
+ return True
13
+ for decorator in class_node.decorators:
14
+ expr = decorator.decorator
15
+ if isinstance(expr, cst.Name) and expr.value in (
16
+ "abstract_class",
17
+ "abstractmethod",
18
+ ):
19
+ return True
20
+ if isinstance(expr, cst.Call) and isinstance(expr.func, cst.Name):
21
+ if expr.func.value == "abstract_class":
22
+ return True
23
+ return False
24
+
25
+
26
+ def _public_function_names(module) -> list[str]:
27
+ import libcst as cst
28
+
29
+ return [
30
+ node.name.value
31
+ for node in module.body
32
+ if isinstance(node, cst.FunctionDef) and not node.name.value.startswith("_")
33
+ ]
34
+
35
+
36
+ def _public_method_names(class_node) -> list[str]:
37
+ import libcst as cst
38
+
39
+ statements = getattr(class_node.body, "body", ())
40
+ return [
41
+ stmt.name.value
42
+ for stmt in statements
43
+ if isinstance(stmt, cst.FunctionDef) and not stmt.name.value.startswith("_")
44
+ ]
45
+
46
+
47
+ def _render_stub(primary_module_name: str, module) -> str:
48
+ import libcst as cst
49
+
50
+ lines: list[str] = [
51
+ "from __future__ import annotations",
52
+ "",
53
+ ]
54
+
55
+ classes_emitted = False
56
+ for node in module.body:
57
+ if isinstance(node, cst.ClassDef):
58
+ class_name = node.name.value
59
+ if _is_skippable_class(class_name, node):
60
+ continue
61
+ methods = _public_method_names(node)
62
+ if not methods:
63
+ continue
64
+ lines.append(f"class Test{class_name}:")
65
+ for method_name in methods:
66
+ lines.extend([
67
+ f" def test_{method_name}(self) -> None:",
68
+ " pass",
69
+ "",
70
+ ])
71
+ classes_emitted = True
72
+
73
+ func_names = _public_function_names(module)
74
+ if func_names:
75
+ if classes_emitted:
76
+ lines.append("")
77
+ for func_name in func_names:
78
+ lines.extend([
79
+ f"def test_{func_name}() -> None:",
80
+ " pass",
81
+ "",
82
+ ])
83
+
84
+ if not classes_emitted and not func_names:
85
+ # No public surface — emit a placeholder so the file is valid pytest
86
+ # and the human knows the stub generator ran but found nothing.
87
+ lines.append(f"# Auto-generated stub for {primary_module_name}.py.")
88
+ lines.append(
89
+ "# No public functions/classes detected — add tests manually."
90
+ )
91
+ lines.append("")
92
+
93
+ return "\n".join(lines)
94
+
95
+
96
+ @base_class
97
+ class PythonTestStubFile(PythonFile):
98
+ """A pytest stub generated as a sidecar of a Python module.
99
+
100
+ When used in a `sidecar_of` declaration as the `cls`, this class is
101
+ instantiated for each missing test file the framework needs to create.
102
+ `build_default_content()` is the hook `SidecarOfOption` wires into the
103
+ standard `default_content` flow — it reads the primary module via the
104
+ shared `cst_cache`, enumerates its public top-level functions and the
105
+ public methods of each top-level class, and emits a minimal pytest
106
+ skeleton (one `test_*` per public callable, wrapped in a
107
+ `TestPascalCase` class when the primary has classes).
108
+
109
+ Intentionally minimal:
110
+ - Generates `pass` bodies so the human fills them in.
111
+ - Skips private callables (`_*`) and dunders.
112
+ - Skips classes that look abstract (decorated `@abstract_class` or
113
+ prefixed `Abstract`) — they don't need direct tests.
114
+ """
115
+
116
+ def build_default_content(self) -> str:
117
+ primary = self.get_sidecar_primary()
118
+ primary_path = primary.get_path()
119
+
120
+ from wexample_filestate_python.utils.cst_cache import (
121
+ get_python_source_and_module,
122
+ )
123
+
124
+ _, module = get_python_source_and_module(primary)
125
+ return _render_stub(
126
+ primary_module_name=primary_path.stem,
127
+ module=module,
128
+ )
@@ -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"