wexample-wex-addon-dev-python 0.0.44__py3-none-any.whl → 0.0.45__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wexample_wex_addon_dev_python/__init__.py +0 -0
- wexample_wex_addon_dev_python/commands/__init__.py +0 -0
- wexample_wex_addon_dev_python/commands/code/__init__.py +0 -0
- wexample_wex_addon_dev_python/commands/code/check/__init__.py +0 -0
- wexample_wex_addon_dev_python/commands/code/check/mypy.py +46 -0
- wexample_wex_addon_dev_python/commands/code/check/pylint.py +108 -0
- wexample_wex_addon_dev_python/commands/code/check/pyright.py +101 -0
- wexample_wex_addon_dev_python/commands/code/check.py +105 -0
- wexample_wex_addon_dev_python/commands/code/format/__init__.py +1 -0
- wexample_wex_addon_dev_python/commands/code/format/black.py +43 -0
- wexample_wex_addon_dev_python/commands/code/format/isort.py +44 -0
- wexample_wex_addon_dev_python/commands/code/format.py +77 -0
- wexample_wex_addon_dev_python/commands/examples/__init__.py +0 -0
- wexample_wex_addon_dev_python/commands/examples/classes/__init__.py +0 -0
- wexample_wex_addon_dev_python/commands/examples/classes/example_pydantic_class_with_public_var_internaly_defined.py +42 -0
- wexample_wex_addon_dev_python/commands/examples/utils/__init__.py +0 -0
- wexample_wex_addon_dev_python/commands/examples/utils/some_example_type.py +7 -0
- wexample_wex_addon_dev_python/commands/examples/validate.py +20 -0
- wexample_wex_addon_dev_python/commands/release/__init__.py +0 -0
- wexample_wex_addon_dev_python/config_value/__init__.py +0 -0
- wexample_wex_addon_dev_python/config_value/python_package_readme_config_value.py +81 -0
- wexample_wex_addon_dev_python/const/__init__.py +0 -0
- wexample_wex_addon_dev_python/const/package.py +20 -0
- wexample_wex_addon_dev_python/file/__init__.py +0 -0
- wexample_wex_addon_dev_python/file/python_package_toml_file.py +304 -0
- wexample_wex_addon_dev_python/middleware/__init__.py +0 -0
- wexample_wex_addon_dev_python/middleware/each_python_file_middleware.py +79 -0
- wexample_wex_addon_dev_python/python_addon_manager.py +15 -0
- wexample_wex_addon_dev_python/workdir/__init__.py +0 -0
- wexample_wex_addon_dev_python/workdir/python_package_workdir.py +206 -0
- wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py +165 -0
- wexample_wex_addon_dev_python/workdir/python_workdir.py +240 -0
- {wexample_wex_addon_dev_python-0.0.44.dist-info → wexample_wex_addon_dev_python-0.0.45.dist-info}/METADATA +8 -8
- wexample_wex_addon_dev_python-0.0.45.dist-info/RECORD +37 -0
- wexample_wex_addon_dev_python-0.0.44.dist-info/RECORD +0 -5
- {wexample_wex_addon_dev_python-0.0.44.dist-info → wexample_wex_addon_dev_python-0.0.45.dist-info}/WHEEL +0 -0
- {wexample_wex_addon_dev_python-0.0.44.dist-info → wexample_wex_addon_dev_python-0.0.45.dist-info}/entry_points.txt +0 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from wexample_wex_core.common.kernel import Kernel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _code_check_mypy(kernel: Kernel, file_path: str) -> bool:
|
|
7
|
+
"""Check a Python file using mypy for static type checking.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
kernel: The application kernel
|
|
11
|
+
file_path: Path to the Python file to check
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
bool: True if check passes, False otherwise
|
|
15
|
+
"""
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
# Import mypy modules
|
|
19
|
+
from mypy import build
|
|
20
|
+
from mypy.modulefinder import BuildSource
|
|
21
|
+
from mypy.options import Options
|
|
22
|
+
|
|
23
|
+
# Configure mypy options
|
|
24
|
+
options = Options()
|
|
25
|
+
options.python_version = sys.version_info[:2]
|
|
26
|
+
options.show_traceback = True
|
|
27
|
+
options.disallow_untyped_defs = True
|
|
28
|
+
options.disallow_incomplete_defs = True
|
|
29
|
+
|
|
30
|
+
# Ignore import as file might be placed anywhere, we have no more context.
|
|
31
|
+
options.ignore_missing_imports = True
|
|
32
|
+
|
|
33
|
+
# Build and check the file
|
|
34
|
+
source = BuildSource(path=file_path, module=None, text=None)
|
|
35
|
+
result = build.build(sources=[source], options=options, alt_lib_path=None)
|
|
36
|
+
if result.errors:
|
|
37
|
+
kernel.io.log_indent_up()
|
|
38
|
+
kernel.io.error(f"Mypy errors:")
|
|
39
|
+
kernel.io.log_indent_up()
|
|
40
|
+
|
|
41
|
+
for error in result.errors:
|
|
42
|
+
kernel.io.error(message=error, symbol=False)
|
|
43
|
+
|
|
44
|
+
kernel.io.log_indent_down(number=2)
|
|
45
|
+
return False
|
|
46
|
+
return True
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from wexample_wex_core.context.execution_context import ExecutionContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _code_check_pylint(context: ExecutionContext, file_path: str) -> bool:
|
|
10
|
+
"""Check a Python file using pylint for code quality.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
kernel: The application kernel
|
|
14
|
+
file_path: Path to the Python file to check
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
bool: True if check passes, False otherwise
|
|
18
|
+
"""
|
|
19
|
+
import json
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
# Use subprocess to capture pylint output
|
|
24
|
+
# This avoids issues with pylint's direct printing to stdout
|
|
25
|
+
# List of warnings to disable
|
|
26
|
+
disabled_warnings = [
|
|
27
|
+
"missing-module-docstring",
|
|
28
|
+
"import-outside-toplevel",
|
|
29
|
+
"no-name-in-module",
|
|
30
|
+
"broad-exception-caught",
|
|
31
|
+
"c-extension-no-member",
|
|
32
|
+
"line-too-long",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
cmd = [
|
|
36
|
+
sys.executable,
|
|
37
|
+
"-m",
|
|
38
|
+
"pylint",
|
|
39
|
+
file_path,
|
|
40
|
+
"--output-format=json",
|
|
41
|
+
f"--disable={','.join(disabled_warnings)}",
|
|
42
|
+
]
|
|
43
|
+
process = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
44
|
+
|
|
45
|
+
# Get the output from stdout
|
|
46
|
+
json_output = process.stdout.strip()
|
|
47
|
+
|
|
48
|
+
# If no output or invalid JSON, return empty list
|
|
49
|
+
if not json_output:
|
|
50
|
+
context.io.success(f"No pylint issues found in {file_path}")
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
# Parse the JSON output
|
|
54
|
+
results = json.loads(json_output)
|
|
55
|
+
|
|
56
|
+
# Filter messages by type
|
|
57
|
+
errors = [msg for msg in results if msg.get("type") in ("error", "fatal")]
|
|
58
|
+
warnings = [msg for msg in results if msg.get("type") == "warning"]
|
|
59
|
+
conventions = [
|
|
60
|
+
msg for msg in results if msg.get("type") in ("convention", "refactor", "info")
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# Display results if any issues found
|
|
64
|
+
if errors or warnings or conventions:
|
|
65
|
+
# Display errors
|
|
66
|
+
if errors:
|
|
67
|
+
context.io.log_indent_up()
|
|
68
|
+
context.io.error(f"Pylint errors:")
|
|
69
|
+
context.io.log_indent_up()
|
|
70
|
+
|
|
71
|
+
for error in errors:
|
|
72
|
+
context.io.error(
|
|
73
|
+
message=f"Line {error.get('line')}: "
|
|
74
|
+
f"{error.get('message')} ({error.get('symbol')})",
|
|
75
|
+
symbol=False,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
context.io.log_indent_down(number=2)
|
|
79
|
+
|
|
80
|
+
# Display warnings with detailed logging
|
|
81
|
+
if warnings:
|
|
82
|
+
context.io.log_indent_up()
|
|
83
|
+
context.io.warning(f"Pylint warnings:")
|
|
84
|
+
context.io.log_indent_up()
|
|
85
|
+
|
|
86
|
+
for warning in warnings:
|
|
87
|
+
context.io.warning(
|
|
88
|
+
f"Line {warning.get('line')}: "
|
|
89
|
+
f"{warning.get('message')} ({warning.get('symbol')})",
|
|
90
|
+
symbol=False,
|
|
91
|
+
)
|
|
92
|
+
context.io.properties(warning)
|
|
93
|
+
|
|
94
|
+
context.io.log_indent_down(number=2)
|
|
95
|
+
# Display conventions
|
|
96
|
+
if conventions:
|
|
97
|
+
context.io.info("Conventions:")
|
|
98
|
+
for convention in conventions:
|
|
99
|
+
context.io.base(
|
|
100
|
+
message=f" Line {convention.get('line')}: "
|
|
101
|
+
f"{convention.get('message')} ({convention.get('symbol')})"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Only consider errors as failures
|
|
105
|
+
if errors:
|
|
106
|
+
return False
|
|
107
|
+
return True
|
|
108
|
+
return True
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from wexample_wex_core.common.kernel import Kernel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _code_check_pyright(kernel: Kernel, file_path: str) -> bool:
|
|
7
|
+
"""Check a Python file using pyright for static type checking.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
kernel: The application kernel
|
|
11
|
+
file_path: Path to the Python file to check
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
bool: True if check passes, False otherwise
|
|
15
|
+
"""
|
|
16
|
+
import json
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
# Use subprocess to run pyright
|
|
21
|
+
cmd = [sys.executable, "-m", "pyright", file_path, "--outputjson"]
|
|
22
|
+
process = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
23
|
+
|
|
24
|
+
# Get the output from stdout
|
|
25
|
+
json_output = process.stdout.strip()
|
|
26
|
+
|
|
27
|
+
# If command failed or no output, handle the error
|
|
28
|
+
if process.returncode != 0 and not json_output:
|
|
29
|
+
kernel.io.error(f"Pyright failed to run on {file_path}")
|
|
30
|
+
if process.stderr:
|
|
31
|
+
kernel.io.error(f"Error: {process.stderr}")
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
# If no output, assume success
|
|
35
|
+
if not json_output:
|
|
36
|
+
kernel.io.success(f"No pyright issues found in {file_path}")
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
# Parse the JSON output
|
|
40
|
+
results = json.loads(json_output)
|
|
41
|
+
|
|
42
|
+
# Extract diagnostics
|
|
43
|
+
diagnostics = results.get("diagnostics", [])
|
|
44
|
+
|
|
45
|
+
# Filter by severity
|
|
46
|
+
errors = [diag for diag in diagnostics if diag.get("severity") == "error"]
|
|
47
|
+
warnings = [diag for diag in diagnostics if diag.get("severity") == "warning"]
|
|
48
|
+
info = [diag for diag in diagnostics if diag.get("severity") == "information"]
|
|
49
|
+
|
|
50
|
+
# Display results if any issues found
|
|
51
|
+
if errors or warnings or info:
|
|
52
|
+
# Display errors
|
|
53
|
+
if errors:
|
|
54
|
+
kernel.io.log_indent_up()
|
|
55
|
+
kernel.io.error(f"Pyright errors:")
|
|
56
|
+
|
|
57
|
+
for error in errors:
|
|
58
|
+
line = error.get("range", {}).get("start", {}).get("line", 0) + 1
|
|
59
|
+
message = error.get("message", "Unknown error")
|
|
60
|
+
rule = error.get("rule", "")
|
|
61
|
+
rule_text = f" ({rule})" if rule else ""
|
|
62
|
+
kernel.io.error(f"Line {line}: {message}{rule_text}", symbol=False)
|
|
63
|
+
kernel.io.properties(error)
|
|
64
|
+
|
|
65
|
+
kernel.io.log_indent_down(number=2)
|
|
66
|
+
|
|
67
|
+
# Display warnings
|
|
68
|
+
if warnings:
|
|
69
|
+
kernel.io.log_indent_up()
|
|
70
|
+
kernel.io.warning(f"Pyright warnings:")
|
|
71
|
+
|
|
72
|
+
for warning in warnings:
|
|
73
|
+
line = warning.get("range", {}).get("start", {}).get("line", 0) + 1
|
|
74
|
+
message = warning.get("message", "Unknown warning")
|
|
75
|
+
rule = warning.get("rule", "")
|
|
76
|
+
rule_text = f" ({rule})" if rule else ""
|
|
77
|
+
kernel.io.warning(f"Line {line}: {message}{rule_text}", symbol=False)
|
|
78
|
+
kernel.io.properties(warning)
|
|
79
|
+
|
|
80
|
+
kernel.io.log_indent_down(number=2)
|
|
81
|
+
|
|
82
|
+
# Display information
|
|
83
|
+
if info:
|
|
84
|
+
kernel.io.log_indent_up()
|
|
85
|
+
kernel.io.info(f"Pyright information:")
|
|
86
|
+
kernel.io.log_indent_up()
|
|
87
|
+
|
|
88
|
+
for item in info:
|
|
89
|
+
line = item.get("range", {}).get("start", {}).get("line", 0) + 1
|
|
90
|
+
message = item.get("message", "Unknown info")
|
|
91
|
+
rule = item.get("rule", "")
|
|
92
|
+
rule_text = f" ({rule})" if rule else ""
|
|
93
|
+
kernel.io.info(f"Line {line}: {message}{rule_text}", symbol=False)
|
|
94
|
+
kernel.io.properties(item)
|
|
95
|
+
|
|
96
|
+
kernel.io.log_indent_down(number=2)
|
|
97
|
+
|
|
98
|
+
# Only consider errors as failures
|
|
99
|
+
if errors:
|
|
100
|
+
return False
|
|
101
|
+
return True
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from wexample_wex_core.const.middleware import (
|
|
6
|
+
MIDDLEWARE_OPTION_VALUE_ALLWAYS,
|
|
7
|
+
MIDDLEWARE_OPTION_VALUE_OPTIONAL,
|
|
8
|
+
)
|
|
9
|
+
from wexample_wex_core.decorator.command import command
|
|
10
|
+
from wexample_wex_core.decorator.middleware import middleware
|
|
11
|
+
from wexample_wex_core.decorator.option import option
|
|
12
|
+
from wexample_wex_core.decorator.option_stop_on_failure import option_stop_on_failure
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from wexample_wex_core.context.execution_context import ExecutionContext
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@option(
|
|
19
|
+
name="tool",
|
|
20
|
+
type=str,
|
|
21
|
+
required=False,
|
|
22
|
+
description="Specific tool to run (mypy, pylint, pyright). If not specified, all tools will be run.",
|
|
23
|
+
)
|
|
24
|
+
@option_stop_on_failure()
|
|
25
|
+
@middleware(
|
|
26
|
+
name="each_python_file",
|
|
27
|
+
should_exist=True,
|
|
28
|
+
expand_glob=True,
|
|
29
|
+
stop_on_failure=MIDDLEWARE_OPTION_VALUE_OPTIONAL,
|
|
30
|
+
recursive=True,
|
|
31
|
+
parallel=MIDDLEWARE_OPTION_VALUE_OPTIONAL,
|
|
32
|
+
show_progress=MIDDLEWARE_OPTION_VALUE_ALLWAYS,
|
|
33
|
+
)
|
|
34
|
+
@command(
|
|
35
|
+
description="Check python code on every file: "
|
|
36
|
+
"bash cli/wex python::code/check --file ../../pip/wex-core/wexample_wex_core/ -sof"
|
|
37
|
+
)
|
|
38
|
+
def python__code__check(
|
|
39
|
+
context: ExecutionContext,
|
|
40
|
+
file: str,
|
|
41
|
+
tool: str | None = None,
|
|
42
|
+
stop_on_failure: bool = True,
|
|
43
|
+
parallel: bool = True,
|
|
44
|
+
) -> bool:
|
|
45
|
+
"""Check a Python file using various code quality tools."""
|
|
46
|
+
from wexample_helpers.helpers.cli import cli_make_clickable_path
|
|
47
|
+
from wexample_wex_addon_dev_python.commands.code.check.mypy import _code_check_mypy
|
|
48
|
+
from wexample_wex_addon_dev_python.commands.code.check.pylint import (
|
|
49
|
+
_code_check_pylint,
|
|
50
|
+
)
|
|
51
|
+
from wexample_wex_addon_dev_python.commands.code.check.pyright import (
|
|
52
|
+
_code_check_pyright,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Map tool names to their check functions
|
|
56
|
+
tool_map = {
|
|
57
|
+
"mypy": _code_check_mypy,
|
|
58
|
+
"pylint": _code_check_pylint,
|
|
59
|
+
"pyright": _code_check_pyright,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Determine which tools to run
|
|
63
|
+
if tool and tool.lower() in tool_map:
|
|
64
|
+
# Run only the specified tool
|
|
65
|
+
check_functions = [tool_map[tool.lower()]]
|
|
66
|
+
else:
|
|
67
|
+
# Run all tools if no specific tool is specified or if the specified tool is invalid
|
|
68
|
+
check_functions = [
|
|
69
|
+
_code_check_mypy,
|
|
70
|
+
_code_check_pylint,
|
|
71
|
+
_code_check_pyright,
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
# Track overall success
|
|
75
|
+
all_checks_passed = True
|
|
76
|
+
|
|
77
|
+
# Run each check function
|
|
78
|
+
for check_function in check_functions:
|
|
79
|
+
context.io.title(check_function.__name__)
|
|
80
|
+
context.io.log_indent_up()
|
|
81
|
+
|
|
82
|
+
context.io.log(
|
|
83
|
+
f"🐍 Python: {cli_make_clickable_path(context.kernel.host_workdir.get_resolved_target(file))}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
check_result = check_function(context, file)
|
|
87
|
+
|
|
88
|
+
if check_result:
|
|
89
|
+
context.io.success(f"No critical issue found for {check_function.__name__}")
|
|
90
|
+
|
|
91
|
+
# Update overall success status
|
|
92
|
+
all_checks_passed = all_checks_passed and check_result
|
|
93
|
+
|
|
94
|
+
# Stop if a check fails and stop_on_failure is True
|
|
95
|
+
if not check_result and stop_on_failure:
|
|
96
|
+
context.io.error("One check failed")
|
|
97
|
+
|
|
98
|
+
from wexample_app.response.failure_response import FailureResponse
|
|
99
|
+
|
|
100
|
+
context.io.log_indent_down()
|
|
101
|
+
|
|
102
|
+
return FailureResponse(message="One check failed", kernel=context.kernel)
|
|
103
|
+
|
|
104
|
+
context.io.log_indent_down()
|
|
105
|
+
return all_checks_passed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from wexample_wex_core.common.kernel import Kernel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _code_format_black(kernel: Kernel, file_path: str) -> bool:
|
|
7
|
+
"""Format a Python file using Black.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
kernel: The application kernel
|
|
11
|
+
file_path: Path to the Python file to format
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
bool: True if formatting succeeds, False otherwise
|
|
15
|
+
"""
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
# Use subprocess to run black
|
|
20
|
+
cmd = [sys.executable, "-m", "black", file_path]
|
|
21
|
+
process = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
22
|
+
|
|
23
|
+
# Check if the command was successful
|
|
24
|
+
if process.returncode == 0:
|
|
25
|
+
if "reformatted" in process.stderr or "reformatted" in process.stdout:
|
|
26
|
+
kernel.io.success(f"Black successfully reformatted {file_path}")
|
|
27
|
+
else:
|
|
28
|
+
kernel.io.success(f"Black: {file_path} already well formatted")
|
|
29
|
+
return True
|
|
30
|
+
else:
|
|
31
|
+
kernel.io.error(f"Black failed to format {file_path}")
|
|
32
|
+
kernel.io.log_indent_up()
|
|
33
|
+
|
|
34
|
+
if process.stderr:
|
|
35
|
+
kernel.io.error(f"Error: {process.stderr}", symbol=False)
|
|
36
|
+
if process.stdout:
|
|
37
|
+
kernel.io.error(f"Output: {process.stdout}", symbol=False)
|
|
38
|
+
|
|
39
|
+
# Add detailed error properties
|
|
40
|
+
kernel.io.properties({"returncode": process.returncode, "command": cmd})
|
|
41
|
+
|
|
42
|
+
kernel.io.log_indent_down()
|
|
43
|
+
return False
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from wexample_wex_core.common.kernel import Kernel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _code_format_isort(kernel: Kernel, file_path: str) -> bool:
|
|
7
|
+
"""Format imports in a Python file using isort.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
kernel: The application kernel
|
|
11
|
+
file_path: Path to the Python file to format
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
bool: True if formatting succeeds, False otherwise
|
|
15
|
+
"""
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
# Use subprocess to run isort
|
|
20
|
+
# --profile=black ensures compatibility with Black formatter
|
|
21
|
+
cmd = [sys.executable, "-m", "isort", "--profile=black", file_path]
|
|
22
|
+
process = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
23
|
+
|
|
24
|
+
# Check if the command was successful
|
|
25
|
+
if process.returncode == 0:
|
|
26
|
+
if "Skipped" in process.stdout:
|
|
27
|
+
kernel.io.success(f"isort: {file_path} already well formatted")
|
|
28
|
+
else:
|
|
29
|
+
kernel.io.success(f"isort successfully reformatted imports in {file_path}")
|
|
30
|
+
return True
|
|
31
|
+
else:
|
|
32
|
+
kernel.io.error(f"isort failed to format imports in {file_path}")
|
|
33
|
+
kernel.io.log_indent_up()
|
|
34
|
+
|
|
35
|
+
if process.stderr:
|
|
36
|
+
kernel.io.error(f"Error: {process.stderr}", symbol=False)
|
|
37
|
+
if process.stdout:
|
|
38
|
+
kernel.io.error(f"Output: {process.stdout}", symbol=False)
|
|
39
|
+
|
|
40
|
+
# Add detailed error properties
|
|
41
|
+
kernel.io.properties({"returncode": process.returncode, "command": cmd})
|
|
42
|
+
|
|
43
|
+
kernel.io.log_indent_down()
|
|
44
|
+
return False
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from wexample_wex_core.common.kernel import Kernel
|
|
4
|
+
from wexample_wex_core.decorator.command import command
|
|
5
|
+
from wexample_wex_core.decorator.middleware import middleware
|
|
6
|
+
from wexample_wex_core.decorator.option import option
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@option(
|
|
10
|
+
name="tool",
|
|
11
|
+
type=str,
|
|
12
|
+
required=False,
|
|
13
|
+
description="Specific tool to run (black, isort). If not specified, all tools will be run.",
|
|
14
|
+
)
|
|
15
|
+
@option(
|
|
16
|
+
name="stop_on_failure",
|
|
17
|
+
type=bool,
|
|
18
|
+
required=False,
|
|
19
|
+
default=True,
|
|
20
|
+
description="Stop execution when a tool reports a failure",
|
|
21
|
+
)
|
|
22
|
+
@middleware(
|
|
23
|
+
name="each_python_file", should_exist=True, expand_glob=True, recursive=True
|
|
24
|
+
)
|
|
25
|
+
@command()
|
|
26
|
+
def python__code__format(
|
|
27
|
+
kernel: Kernel,
|
|
28
|
+
file: str,
|
|
29
|
+
tool: str | None = None,
|
|
30
|
+
stop_on_failure: bool = True,
|
|
31
|
+
) -> bool:
|
|
32
|
+
"""Format a Python file using various code formatting tools."""
|
|
33
|
+
from wexample_wex_addon_dev_python.commands.code.format.black import (
|
|
34
|
+
_code_format_black,
|
|
35
|
+
)
|
|
36
|
+
from wexample_wex_addon_dev_python.commands.code.format.isort import (
|
|
37
|
+
_code_format_isort,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Map tool names to their format functions
|
|
41
|
+
tool_map = {
|
|
42
|
+
"black": _code_format_black,
|
|
43
|
+
"isort": _code_format_isort,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Determine which tools to run
|
|
47
|
+
if tool and tool.lower() in tool_map:
|
|
48
|
+
# Run only the specified tool
|
|
49
|
+
format_functions = [tool_map[tool.lower()]]
|
|
50
|
+
else:
|
|
51
|
+
# Run all tools if no specific tool is specified or if the specified tool is invalid
|
|
52
|
+
if tool and tool.lower() not in tool_map:
|
|
53
|
+
kernel.io.warning(f"Unknown tool '{tool}', running all available tools")
|
|
54
|
+
|
|
55
|
+
# Run isort first, then black (recommended order)
|
|
56
|
+
format_functions = [
|
|
57
|
+
_code_format_isort,
|
|
58
|
+
_code_format_black,
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
# Track overall success
|
|
62
|
+
all_formats_passed = True
|
|
63
|
+
|
|
64
|
+
# Run each format function
|
|
65
|
+
for format_function in format_functions:
|
|
66
|
+
kernel.io.title(format_function.__name__)
|
|
67
|
+
format_result = format_function(kernel, file)
|
|
68
|
+
|
|
69
|
+
# Update overall success status
|
|
70
|
+
all_formats_passed = all_formats_passed and format_result
|
|
71
|
+
|
|
72
|
+
# Stop if a format fails and stop_on_failure is True
|
|
73
|
+
if not format_result and stop_on_failure:
|
|
74
|
+
kernel.io.warning("One formatting failed")
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
return all_formats_passed
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, PrivateAttr
|
|
6
|
+
|
|
7
|
+
# Stay lazy as most as possible
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from wexample_wex_addon_dev_python.commands.examples.utils.some_example_type import (
|
|
10
|
+
SomeExampleType,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ExamplePydanticClassWithPublicVarInternallyDefined(BaseModel):
|
|
15
|
+
_internal_var: SomeExampleType = PrivateAttr()
|
|
16
|
+
|
|
17
|
+
# model_post_init runs AFTER Pydantic has validated/coerced model fields.
|
|
18
|
+
# Use it to initialize PrivateAttr that may rely on validated state.
|
|
19
|
+
# If you depend on other mixins' __init__ ordering, prefer a custom __init__ or finalize().
|
|
20
|
+
def model_post_init(self, __context: Any) -> None:
|
|
21
|
+
# Lazy import to avoid circular imports; runs after validation
|
|
22
|
+
from wexample_wex_addon_dev_python.commands.examples.utils.some_example_type import (
|
|
23
|
+
SomeExampleType,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
self._internal_var = SomeExampleType(property="Yes")
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def public_var(self) -> SomeExampleType:
|
|
30
|
+
return self._internal_var
|
|
31
|
+
|
|
32
|
+
@public_var.setter
|
|
33
|
+
def public_var(self, value: SomeExampleType) -> None:
|
|
34
|
+
from wexample_wex_addon_dev_python.commands.examples.utils.some_example_type import (
|
|
35
|
+
SomeExampleType,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Stay lazy as most as possible
|
|
39
|
+
# Check value at setting, avoid checking it
|
|
40
|
+
if not isinstance(value, SomeExampleType):
|
|
41
|
+
raise TypeError(f"public_var must be SomeExampleType, got {type(value)!r}")
|
|
42
|
+
self._internal_var = value
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from wexample_wex_core.decorator.command import command
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from wexample_wex_core.context.execution_context import ExecutionContext
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@command(description="Check python code on every file.")
|
|
12
|
+
def python__examples__validate(
|
|
13
|
+
context: ExecutionContext,
|
|
14
|
+
) -> None:
|
|
15
|
+
from wexample_wex_addon_dev_python.commands.examples.classes.example_pydantic_class_with_public_var_internaly_defined import (
|
|
16
|
+
ExamplePydanticClassWithPublicVarInternallyDefined,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
example_class = ExamplePydanticClassWithPublicVarInternallyDefined()
|
|
20
|
+
context.kernel.log(example_class)
|
|
File without changes
|
|
File without changes
|