wexample-filestate-python 8.0.0__tar.gz → 8.1.1__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.
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/PKG-INFO +6 -6
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/README.md +3 -3
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/pyproject.toml +3 -3
- wexample_filestate_python-8.1.1/src/wexample_filestate_python/file/python_test_stub_file.py +128 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/sort_imports_option.py +10 -6
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python_option.py +21 -7
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/options_provider/python_options_provider.py +8 -9
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_attrs_utils.py +1 -5
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_blank_lines_utils.py +4 -4
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_class_methods_utils.py +11 -5
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_functions_utils.py +3 -9
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_iterable_utils.py +6 -5
- {wexample_filestate_python-8.0.0/tests/wexample_tests → wexample_filestate_python-8.1.1/tests/unit}/__init__.py +0 -0
- wexample_filestate_python-8.1.1/tests/unit/helpers/__init__.py +0 -0
- wexample_filestate_python-8.1.1/tests/unit/helpers/test_package.py +16 -0
- wexample_filestate_python-8.1.1/tests/unit/helpers/test_toml.py +19 -0
- wexample_filestate_python-8.1.1/tests/wexample_tests/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/common/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/common/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/common/pipy_gateway.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/config_option/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/config_option/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/config_option/mixin/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/config_option/mixin/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/config_option/mixin/with_stdout_wrapping_mixin.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/config_value/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/config_value/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/config_value/python_config_value.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/const/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/const/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/const/name_pattern.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/const/path.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/const/python_file.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/event/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/event/src/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/event/src/wexample_event/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/file/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/file/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/file/python_file.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/helpers/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/helpers/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/helpers/package.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/helpers/python/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/helpers/toml.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/abstract_python_file_content_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/add_future_annotations_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/add_return_types_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/class_name_matches_file_name_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/fix_attrs_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/fix_blank_lines_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/format_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/fstringify_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/modernize_typing_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/order_class_attributes_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/order_class_docstring_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/order_class_methods_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/order_constants_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/order_iterable_items_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/order_main_guard_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/order_module_docstring_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/order_module_functions_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/order_module_metadata_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/order_type_checking_block_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/relocate_imports_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/remove_unused_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/option/python/unquote_annotations_option.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/options_provider/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/options_provider/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/py.typed +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/cst_cache.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_class_attributes_utils.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_class_docstring_utils.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_constants_utils.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_docstring_utils.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_main_guard_utils.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_module_metadata_utils.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/python_type_checking_utils.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/relocate_imports/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/relocate_imports/__pycache__/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/relocate_imports/python_import_rewriter.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/relocate_imports/python_localize_runtime_imports.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/relocate_imports/python_parser_import_index.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/relocate_imports/python_runtime_symbol_collector.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/src/wexample_filestate_python/utils/relocate_imports/python_usage_collector.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/tests/helpers/__init__.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/tests/helpers/test_package.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/tests/helpers/test_toml.py +0 -0
- {wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/tests/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wexample-filestate-python
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.1.1
|
|
4
4
|
Summary: Helpers for Python.
|
|
5
5
|
Author-Email: weeger <contact@wexample.com>
|
|
6
6
|
License: MIT
|
|
@@ -18,8 +18,8 @@ Requires-Dist: isort
|
|
|
18
18
|
Requires-Dist: libcst
|
|
19
19
|
Requires-Dist: packaging
|
|
20
20
|
Requires-Dist: tomli
|
|
21
|
-
Requires-Dist: wexample-api>=6.
|
|
22
|
-
Requires-Dist: wexample-filestate>=
|
|
21
|
+
Requires-Dist: wexample-api>=6.6.0
|
|
22
|
+
Requires-Dist: wexample-filestate>=14.1.0
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: pytest; extra == "dev"
|
|
25
25
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -27,7 +27,7 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
|
|
28
28
|
# filestate_python
|
|
29
29
|
|
|
30
|
-
Version: 8.
|
|
30
|
+
Version: 8.1.1
|
|
31
31
|
|
|
32
32
|
Helpers for Python.
|
|
33
33
|
|
|
@@ -116,8 +116,8 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
|
|
|
116
116
|
- libcst:
|
|
117
117
|
- packaging:
|
|
118
118
|
- tomli:
|
|
119
|
-
- wexample-api: >=6.
|
|
120
|
-
- wexample-filestate: >=
|
|
119
|
+
- wexample-api: >=6.6.0
|
|
120
|
+
- wexample-filestate: >=14.1.0
|
|
121
121
|
|
|
122
122
|
## Versioning & Compatibility Policy
|
|
123
123
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# filestate_python
|
|
2
2
|
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.1.1
|
|
4
4
|
|
|
5
5
|
Helpers for Python.
|
|
6
6
|
|
|
@@ -89,8 +89,8 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
|
|
|
89
89
|
- libcst:
|
|
90
90
|
- packaging:
|
|
91
91
|
- tomli:
|
|
92
|
-
- wexample-api: >=6.
|
|
93
|
-
- wexample-filestate: >=
|
|
92
|
+
- wexample-api: >=6.6.0
|
|
93
|
+
- wexample-filestate: >=14.1.0
|
|
94
94
|
|
|
95
95
|
## Versioning & Compatibility Policy
|
|
96
96
|
|
|
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "wexample-filestate-python"
|
|
9
|
-
version = "8.
|
|
9
|
+
version = "8.1.1"
|
|
10
10
|
description = "Helpers for Python."
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "weeger", email = "contact@wexample.com" },
|
|
@@ -27,8 +27,8 @@ dependencies = [
|
|
|
27
27
|
"libcst",
|
|
28
28
|
"packaging",
|
|
29
29
|
"tomli",
|
|
30
|
-
"wexample-api>=6.
|
|
31
|
-
"wexample-filestate>=
|
|
30
|
+
"wexample-api>=6.6.0",
|
|
31
|
+
"wexample-filestate>=14.1.0",
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[project.readme]
|
|
@@ -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
|
+
)
|
|
@@ -12,11 +12,16 @@ if TYPE_CHECKING:
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@lru_cache(maxsize=1)
|
|
15
|
-
def
|
|
16
|
-
"""Return
|
|
15
|
+
def _isort_setup() -> tuple:
|
|
16
|
+
"""Return cached (code_fn, Config) for isort with the 'black' profile.
|
|
17
|
+
|
|
18
|
+
Both objects are imported and constructed once; subsequent calls hit
|
|
19
|
+
the LRU cache, eliminating per-file import overhead for isort.code.
|
|
20
|
+
"""
|
|
21
|
+
from isort import code as _code
|
|
17
22
|
from isort.settings import Config
|
|
18
23
|
|
|
19
|
-
return Config(profile="black")
|
|
24
|
+
return _code, Config(profile="black")
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
@base_class
|
|
@@ -26,7 +31,6 @@ class SortImportsOption(AbstractPythonFileContentOption):
|
|
|
26
31
|
|
|
27
32
|
def _apply_content_change(self, target: TargetFileOrDirectoryType) -> str:
|
|
28
33
|
"""Sort Python imports using isort."""
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
_code, _config = _isort_setup()
|
|
31
35
|
src = target.get_local_file().read()
|
|
32
|
-
return
|
|
36
|
+
return _code(src, config=_config)
|
|
@@ -14,6 +14,12 @@ from wexample_helpers.decorator.base_class import base_class
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
15
|
from wexample_filestate.const.types_state_items import TargetFileOrDirectoryType
|
|
16
16
|
|
|
17
|
+
# Module-level caches (safe per the @base_class note below; only instance-level
|
|
18
|
+
# annotations become attrs fields, not plain module variables).
|
|
19
|
+
_SCOPES: list[Scope] = [Scope.CONTENT]
|
|
20
|
+
_RAW_VALUE_ALLOWED_TYPE: Any = None
|
|
21
|
+
_ALLOWED_OPTIONS: list[type[AbstractConfigOption]] | None = None
|
|
22
|
+
|
|
17
23
|
|
|
18
24
|
@base_class
|
|
19
25
|
class PythonOption(OptionMixin, AbstractNestedConfigOption):
|
|
@@ -23,15 +29,18 @@ class PythonOption(OptionMixin, AbstractNestedConfigOption):
|
|
|
23
29
|
# module-level constants or `functools.cache` on methods if needed.
|
|
24
30
|
@classmethod
|
|
25
31
|
def get_scopes(cls) -> list[Scope]:
|
|
26
|
-
return
|
|
32
|
+
return _SCOPES
|
|
27
33
|
|
|
28
34
|
@staticmethod
|
|
29
35
|
def get_raw_value_allowed_type() -> Any:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
global _RAW_VALUE_ALLOWED_TYPE
|
|
37
|
+
if _RAW_VALUE_ALLOWED_TYPE is None:
|
|
38
|
+
from wexample_filestate_python.config_value.python_config_value import (
|
|
39
|
+
PythonConfigValue,
|
|
40
|
+
)
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
_RAW_VALUE_ALLOWED_TYPE = Union[list[str], dict, PythonConfigValue]
|
|
43
|
+
return _RAW_VALUE_ALLOWED_TYPE
|
|
35
44
|
|
|
36
45
|
def create_required_operation(
|
|
37
46
|
self, target: TargetFileOrDirectoryType, scopes: set[Scope]
|
|
@@ -39,6 +48,10 @@ class PythonOption(OptionMixin, AbstractNestedConfigOption):
|
|
|
39
48
|
return self._create_child_required_operation(target=target, scopes=scopes)
|
|
40
49
|
|
|
41
50
|
def get_allowed_options(self) -> list[type[AbstractConfigOption]]:
|
|
51
|
+
global _ALLOWED_OPTIONS
|
|
52
|
+
if _ALLOWED_OPTIONS is not None:
|
|
53
|
+
return _ALLOWED_OPTIONS
|
|
54
|
+
|
|
42
55
|
# Import all the config options for each Python operation
|
|
43
56
|
from wexample_filestate_python.option.python.add_future_annotations_option import (
|
|
44
57
|
AddFutureAnnotationsOption,
|
|
@@ -105,7 +118,7 @@ class PythonOption(OptionMixin, AbstractNestedConfigOption):
|
|
|
105
118
|
UnquoteAnnotationsOption,
|
|
106
119
|
)
|
|
107
120
|
|
|
108
|
-
|
|
121
|
+
_ALLOWED_OPTIONS = [
|
|
109
122
|
# filestate: python-iterable-sort
|
|
110
123
|
AddFutureAnnotationsOption,
|
|
111
124
|
AddReturnTypesOption,
|
|
@@ -130,10 +143,11 @@ class PythonOption(OptionMixin, AbstractNestedConfigOption):
|
|
|
130
143
|
SortImportsOption,
|
|
131
144
|
UnquoteAnnotationsOption,
|
|
132
145
|
]
|
|
146
|
+
return _ALLOWED_OPTIONS
|
|
133
147
|
|
|
134
148
|
def set_value(self, raw_value: Any) -> None:
|
|
135
149
|
# Convert list form to dict form for consistency
|
|
136
150
|
if isinstance(raw_value, list):
|
|
137
|
-
raw_value =
|
|
151
|
+
raw_value = dict.fromkeys(raw_value, True)
|
|
138
152
|
|
|
139
153
|
super().set_value(raw_value=raw_value)
|
|
@@ -13,15 +13,14 @@ if TYPE_CHECKING:
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class PythonOptionsProvider(AbstractOptionsProvider):
|
|
16
|
-
# Do NOT cache the returned list in a `cls._options_cache` class attribute.
|
|
17
|
-
# The cache is inherited via MRO, so a write from the base shadows what
|
|
18
|
-
# subclasses should return. Roadmap the optim instead.
|
|
19
16
|
@classmethod
|
|
20
17
|
def get_options(cls) -> list[type[AbstractConfigOption]]:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
# Use `cls.__dict__` (not `hasattr`) so each class in the hierarchy
|
|
19
|
+
# maintains its own cache without the MRO inheritance side-effect.
|
|
20
|
+
if "_options_cache" not in cls.__dict__:
|
|
21
|
+
from wexample_filestate_python.option.python_option import (
|
|
22
|
+
PythonOption,
|
|
23
|
+
)
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
]
|
|
25
|
+
cls._options_cache: list[type[AbstractConfigOption]] = [PythonOption]
|
|
26
|
+
return cls._options_cache
|
|
@@ -33,11 +33,7 @@ def _is_attrs_decorator(decorator: cst.Decorator) -> bool:
|
|
|
33
33
|
base_decorator.value, cst.Name
|
|
34
34
|
):
|
|
35
35
|
module_name = base_decorator.value.value
|
|
36
|
-
attr_name =
|
|
37
|
-
base_decorator.attr.value
|
|
38
|
-
if isinstance(base_decorator.attr, cst.Name)
|
|
39
|
-
else None
|
|
40
|
-
)
|
|
36
|
+
attr_name = base_decorator.attr.value
|
|
41
37
|
return (module_name == "attrs" and attr_name in ("define", "frozen")) or (
|
|
42
38
|
module_name == "attr" and attr_name == "s"
|
|
43
39
|
)
|
|
@@ -190,6 +190,8 @@ def _is_class_property(node: cst.CSTNode) -> bool:
|
|
|
190
190
|
|
|
191
191
|
def _is_dataclass(class_node: cst.ClassDef) -> bool:
|
|
192
192
|
"""Check if a class has @dataclass decorator."""
|
|
193
|
+
if not class_node.decorators:
|
|
194
|
+
return False
|
|
193
195
|
for decorator in class_node.decorators:
|
|
194
196
|
dec = decorator.decorator
|
|
195
197
|
if isinstance(dec, cst.Name) and dec.value == "dataclass":
|
|
@@ -214,8 +216,7 @@ def _is_lowercase_property(node: cst.CSTNode) -> bool:
|
|
|
214
216
|
if _is_class_property(node):
|
|
215
217
|
assign = node.body[0]
|
|
216
218
|
target = assign.targets[0].target
|
|
217
|
-
|
|
218
|
-
return target.value.islower()
|
|
219
|
+
return target.value.islower() # _is_class_property already ensures cst.Name
|
|
219
220
|
return False
|
|
220
221
|
|
|
221
222
|
|
|
@@ -264,8 +265,7 @@ def _is_uppercase_property(node: cst.CSTNode) -> bool:
|
|
|
264
265
|
if _is_class_property(node):
|
|
265
266
|
assign = node.body[0]
|
|
266
267
|
target = assign.targets[0].target
|
|
267
|
-
|
|
268
|
-
return target.value.isupper()
|
|
268
|
+
return target.value.isupper() # _is_class_property already ensures cst.Name
|
|
269
269
|
return False
|
|
270
270
|
|
|
271
271
|
|
|
@@ -92,7 +92,7 @@ def reorder_class_methods(classdef: cst.ClassDef) -> cst.ClassDef:
|
|
|
92
92
|
for m in dunders:
|
|
93
93
|
(unknown if m["order"][0] == 999 else known).append(m)
|
|
94
94
|
dunder_ordered = [m["node"] for m in sorted(known, key=lambda m: m["order"])]
|
|
95
|
-
dunder_ordered += [m["node"] for m in sorted(unknown, key=
|
|
95
|
+
dunder_ordered += [m["node"] for m in sorted(unknown, key=_KEY_BY_NAME)]
|
|
96
96
|
|
|
97
97
|
# Sort classmethods/staticmethods and instances: public first then private
|
|
98
98
|
classmethods_ordered = _sort_by_visibility(classmethods)
|
|
@@ -188,6 +188,11 @@ def _is_dunder(name: str) -> bool:
|
|
|
188
188
|
def _is_private(name: str) -> bool:
|
|
189
189
|
return name.startswith("_") and not (name.startswith("__") and name.endswith("__"))
|
|
190
190
|
|
|
191
|
+
# Hoisted sort-key; avoids creating a new lambda/closure object on every
|
|
192
|
+
# call to _sort_by_visibility and reorder_class_methods.
|
|
193
|
+
def _KEY_BY_NAME(m: dict) -> tuple:
|
|
194
|
+
return _sort_key_alpha(m["name"])
|
|
195
|
+
|
|
191
196
|
|
|
192
197
|
def _prop_group_to_nodes(g: dict[str, cst.FunctionDef | None]) -> list[cst.FunctionDef]:
|
|
193
198
|
return [g[k] for k in _PROP_KINDS if g[k] is not None] # type: ignore
|
|
@@ -199,11 +204,12 @@ def _sort_by_visibility(items: list[dict]) -> list[cst.FunctionDef]:
|
|
|
199
204
|
priv: list[dict] = []
|
|
200
205
|
for i in items:
|
|
201
206
|
(priv if _is_private(i["name"]) else pub).append(i)
|
|
202
|
-
pub_sorted = [i["node"] for i in sorted(pub, key=
|
|
203
|
-
priv_sorted = [i["node"] for i in sorted(priv, key=
|
|
207
|
+
pub_sorted = [i["node"] for i in sorted(pub, key=_KEY_BY_NAME)]
|
|
208
|
+
priv_sorted = [i["node"] for i in sorted(priv, key=_KEY_BY_NAME)]
|
|
204
209
|
return pub_sorted + priv_sorted
|
|
205
210
|
|
|
206
211
|
|
|
207
212
|
def _sort_key_alpha(name: str) -> tuple:
|
|
208
|
-
# Case-insensitive, underscore after letters
|
|
209
|
-
|
|
213
|
+
# Case-insensitive, underscore after letters.
|
|
214
|
+
# name[0] == "_" avoids method-dispatch overhead; safe for non-empty identifiers.
|
|
215
|
+
return (name.lstrip("_").lower(), name[0] == "_")
|
|
@@ -152,15 +152,9 @@ def reorder_module_functions(module: cst.Module) -> cst.Module:
|
|
|
152
152
|
# Build function nodes preserving each group's comments/spacing on first element
|
|
153
153
|
rebuilt_functions: list[cst.CSTNode] = []
|
|
154
154
|
for g in sorted_groups:
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
for
|
|
158
|
-
if k == 0:
|
|
159
|
-
rebuilt_functions.append(
|
|
160
|
-
fn.with_changes(leading_lines=original_first_leading)
|
|
161
|
-
)
|
|
162
|
-
else:
|
|
163
|
-
rebuilt_functions.append(fn.with_changes(leading_lines=[]))
|
|
155
|
+
# First node keeps its original leading_lines (no-op with_changes avoided)
|
|
156
|
+
rebuilt_functions.append(g.nodes[0])
|
|
157
|
+
rebuilt_functions.extend(fn.with_changes(leading_lines=[]) for fn in g.nodes[1:])
|
|
164
158
|
|
|
165
159
|
# Insert functions as a contiguous block
|
|
166
160
|
new_body[insert_at:insert_at] = rebuilt_functions
|
|
@@ -35,9 +35,10 @@ def reorder_flagged_iterables(src: str) -> str:
|
|
|
35
35
|
groups = _split_into_groups(block)
|
|
36
36
|
# Compute keys once; reuse for both no-op check and sort order
|
|
37
37
|
keys = [_group_key(g) for g in groups]
|
|
38
|
-
sorted_keys = sorted(keys)
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
# O(n) pairwise check instead of O(n log n) sorted(); short-circuits on first
|
|
40
|
+
# out-of-order pair so the already-sorted case is also fast.
|
|
41
|
+
if len(keys) < 2 or all(keys[i] <= keys[i + 1] for i in range(len(keys) - 1)):
|
|
41
42
|
continue
|
|
42
43
|
|
|
43
44
|
order = sorted(range(len(groups)), key=lambda i: keys[i])
|
|
@@ -110,9 +111,9 @@ def _split_into_groups(block_lines: list[str]) -> list[list[str]]:
|
|
|
110
111
|
if ln.lstrip().startswith("#"):
|
|
111
112
|
pending_comments.append(ln)
|
|
112
113
|
continue
|
|
113
|
-
# item line
|
|
114
|
-
|
|
115
|
-
groups.append(
|
|
114
|
+
# item line: append in-place to avoid pending_comments + [ln] allocation
|
|
115
|
+
pending_comments.append(ln)
|
|
116
|
+
groups.append(pending_comments)
|
|
116
117
|
pending_comments = []
|
|
117
118
|
# Any trailing comments without item are ignored for sorting and left in place
|
|
118
119
|
# (shouldn't occur in expected usage). If present, attach to last group to preserve.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
def test_package_get_dependencies() -> None:
|
|
4
|
+
pass
|
|
5
|
+
|
|
6
|
+
def test_package_get_info() -> None:
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
def test_package_normalize_name() -> None:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
def test_package_parse_setup() -> None:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def test_package_parse_toml() -> None:
|
|
16
|
+
pass
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
def test_toml_ensure_array() -> None:
|
|
4
|
+
pass
|
|
5
|
+
|
|
6
|
+
def test_toml_ensure_array_multiline() -> None:
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
def test_toml_ensure_table() -> None:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
def test_toml_get_string_value() -> None:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def test_toml_set_array_multiline() -> None:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def test_toml_sort_string_array() -> None:
|
|
19
|
+
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/tests/helpers/__init__.py
RENAMED
|
File without changes
|
{wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/tests/helpers/test_package.py
RENAMED
|
File without changes
|
{wexample_filestate_python-8.0.0 → wexample_filestate_python-8.1.1}/tests/helpers/test_toml.py
RENAMED
|
File without changes
|
|
File without changes
|