wexample-wex-addon-dev-python 0.0.44__py3-none-any.whl → 0.0.47__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 +48 -0
- wexample_wex_addon_dev_python/commands/code/check/pylint.py +108 -0
- wexample_wex_addon_dev_python/commands/code/check/pyright.py +104 -0
- wexample_wex_addon_dev_python/commands/code/check.py +103 -0
- wexample_wex_addon_dev_python/commands/code/format/__init__.py +1 -0
- wexample_wex_addon_dev_python/commands/code/format/black.py +46 -0
- wexample_wex_addon_dev_python/commands/code/format/isort.py +47 -0
- wexample_wex_addon_dev_python/commands/code/format.py +81 -0
- wexample_wex_addon_dev_python/commands/examples/__init__.py +0 -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 +92 -0
- wexample_wex_addon_dev_python/const/__init__.py +0 -0
- wexample_wex_addon_dev_python/const/package.py +21 -0
- wexample_wex_addon_dev_python/file/__init__.py +0 -0
- wexample_wex_addon_dev_python/file/python_package_toml_file.py +303 -0
- wexample_wex_addon_dev_python/middleware/__init__.py +0 -0
- wexample_wex_addon_dev_python/middleware/each_python_file_middleware.py +77 -0
- wexample_wex_addon_dev_python/python_addon_manager.py +19 -0
- wexample_wex_addon_dev_python/workdir/__init__.py +0 -0
- wexample_wex_addon_dev_python/workdir/python_package_workdir.py +214 -0
- wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py +158 -0
- wexample_wex_addon_dev_python/workdir/python_workdir.py +260 -0
- {wexample_wex_addon_dev_python-0.0.44.dist-info → wexample_wex_addon_dev_python-0.0.47.dist-info}/METADATA +12 -10
- wexample_wex_addon_dev_python-0.0.47.dist-info/RECORD +35 -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.47.dist-info}/WHEEL +0 -0
- {wexample_wex_addon_dev_python-0.0.44.dist-info → wexample_wex_addon_dev_python-0.0.47.dist-info}/entry_points.txt +0 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from wexample_wex_core.common.kernel import Kernel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _code_check_mypy(kernel: Kernel, file_path: str) -> bool:
|
|
10
|
+
"""Check a Python file using mypy for static type checking.
|
|
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 sys
|
|
20
|
+
|
|
21
|
+
from mypy import build
|
|
22
|
+
from mypy.modulefinder import BuildSource
|
|
23
|
+
from mypy.options import Options
|
|
24
|
+
|
|
25
|
+
# Configure mypy options
|
|
26
|
+
options = Options()
|
|
27
|
+
options.python_version = sys.version_info[:2]
|
|
28
|
+
options.show_traceback = True
|
|
29
|
+
options.disallow_untyped_defs = True
|
|
30
|
+
options.disallow_incomplete_defs = True
|
|
31
|
+
|
|
32
|
+
# Ignore import as file might be placed anywhere, we have no more context.
|
|
33
|
+
options.ignore_missing_imports = True
|
|
34
|
+
|
|
35
|
+
# Build and check the file
|
|
36
|
+
source = BuildSource(path=file_path, module=None, text=None)
|
|
37
|
+
result = build.build(sources=[source], options=options, alt_lib_path=None)
|
|
38
|
+
if result.errors:
|
|
39
|
+
kernel.io.log_indent_up()
|
|
40
|
+
kernel.io.error(f"Mypy errors:")
|
|
41
|
+
kernel.io.log_indent_up()
|
|
42
|
+
|
|
43
|
+
for error in result.errors:
|
|
44
|
+
kernel.io.error(message=error, symbol=False)
|
|
45
|
+
|
|
46
|
+
kernel.io.log_indent_down(number=2)
|
|
47
|
+
return False
|
|
48
|
+
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,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from wexample_wex_core.common.kernel import Kernel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _code_check_pyright(kernel: Kernel, file_path: str) -> bool:
|
|
10
|
+
"""Check a Python file using pyright for static type checking.
|
|
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 run pyright
|
|
24
|
+
cmd = [sys.executable, "-m", "pyright", file_path, "--outputjson"]
|
|
25
|
+
process = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
26
|
+
|
|
27
|
+
# Get the output from stdout
|
|
28
|
+
json_output = process.stdout.strip()
|
|
29
|
+
|
|
30
|
+
# If command failed or no output, handle the error
|
|
31
|
+
if process.returncode != 0 and not json_output:
|
|
32
|
+
kernel.io.error(f"Pyright failed to run on {file_path}")
|
|
33
|
+
if process.stderr:
|
|
34
|
+
kernel.io.error(f"Error: {process.stderr}")
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
# If no output, assume success
|
|
38
|
+
if not json_output:
|
|
39
|
+
kernel.io.success(f"No pyright issues found in {file_path}")
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
# Parse the JSON output
|
|
43
|
+
results = json.loads(json_output)
|
|
44
|
+
|
|
45
|
+
# Extract diagnostics
|
|
46
|
+
diagnostics = results.get("diagnostics", [])
|
|
47
|
+
|
|
48
|
+
# Filter by severity
|
|
49
|
+
errors = [diag for diag in diagnostics if diag.get("severity") == "error"]
|
|
50
|
+
warnings = [diag for diag in diagnostics if diag.get("severity") == "warning"]
|
|
51
|
+
info = [diag for diag in diagnostics if diag.get("severity") == "information"]
|
|
52
|
+
|
|
53
|
+
# Display results if any issues found
|
|
54
|
+
if errors or warnings or info:
|
|
55
|
+
# Display errors
|
|
56
|
+
if errors:
|
|
57
|
+
kernel.io.log_indent_up()
|
|
58
|
+
kernel.io.error(f"Pyright errors:")
|
|
59
|
+
|
|
60
|
+
for error in errors:
|
|
61
|
+
line = error.get("range", {}).get("start", {}).get("line", 0) + 1
|
|
62
|
+
message = error.get("message", "Unknown error")
|
|
63
|
+
rule = error.get("rule", "")
|
|
64
|
+
rule_text = f" ({rule})" if rule else ""
|
|
65
|
+
kernel.io.error(f"Line {line}: {message}{rule_text}", symbol=False)
|
|
66
|
+
kernel.io.properties(error)
|
|
67
|
+
|
|
68
|
+
kernel.io.log_indent_down(number=2)
|
|
69
|
+
|
|
70
|
+
# Display warnings
|
|
71
|
+
if warnings:
|
|
72
|
+
kernel.io.log_indent_up()
|
|
73
|
+
kernel.io.warning(f"Pyright warnings:")
|
|
74
|
+
|
|
75
|
+
for warning in warnings:
|
|
76
|
+
line = warning.get("range", {}).get("start", {}).get("line", 0) + 1
|
|
77
|
+
message = warning.get("message", "Unknown warning")
|
|
78
|
+
rule = warning.get("rule", "")
|
|
79
|
+
rule_text = f" ({rule})" if rule else ""
|
|
80
|
+
kernel.io.warning(f"Line {line}: {message}{rule_text}", symbol=False)
|
|
81
|
+
kernel.io.properties(warning)
|
|
82
|
+
|
|
83
|
+
kernel.io.log_indent_down(number=2)
|
|
84
|
+
|
|
85
|
+
# Display information
|
|
86
|
+
if info:
|
|
87
|
+
kernel.io.log_indent_up()
|
|
88
|
+
kernel.io.info(f"Pyright information:")
|
|
89
|
+
kernel.io.log_indent_up()
|
|
90
|
+
|
|
91
|
+
for item in info:
|
|
92
|
+
line = item.get("range", {}).get("start", {}).get("line", 0) + 1
|
|
93
|
+
message = item.get("message", "Unknown info")
|
|
94
|
+
rule = item.get("rule", "")
|
|
95
|
+
rule_text = f" ({rule})" if rule else ""
|
|
96
|
+
kernel.io.info(f"Line {line}: {message}{rule_text}", symbol=False)
|
|
97
|
+
kernel.io.properties(item)
|
|
98
|
+
|
|
99
|
+
kernel.io.log_indent_down(number=2)
|
|
100
|
+
|
|
101
|
+
# Only consider errors as failures
|
|
102
|
+
if errors:
|
|
103
|
+
return False
|
|
104
|
+
return True
|
|
@@ -0,0 +1,103 @@
|
|
|
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_app.response.failure_response import FailureResponse
|
|
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: {context.kernel.host_workdir.render_display_path(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
|
+
context.io.log_indent_down()
|
|
99
|
+
|
|
100
|
+
return FailureResponse(message="One check failed", kernel=context.kernel)
|
|
101
|
+
|
|
102
|
+
context.io.log_indent_down()
|
|
103
|
+
return all_checks_passed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from wexample_wex_core.common.kernel import Kernel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _code_format_black(kernel: Kernel, file_path: str) -> bool:
|
|
10
|
+
"""Format a Python file using Black.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
kernel: The application kernel
|
|
14
|
+
file_path: Path to the Python file to format
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
bool: True if formatting succeeds, False otherwise
|
|
18
|
+
"""
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
# Use subprocess to run black
|
|
23
|
+
cmd = [sys.executable, "-m", "black", file_path]
|
|
24
|
+
process = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
25
|
+
|
|
26
|
+
# Check if the command was successful
|
|
27
|
+
if process.returncode == 0:
|
|
28
|
+
if "reformatted" in process.stderr or "reformatted" in process.stdout:
|
|
29
|
+
kernel.io.success(f"Black successfully reformatted {file_path}")
|
|
30
|
+
else:
|
|
31
|
+
kernel.io.success(f"Black: {file_path} already well formatted")
|
|
32
|
+
return True
|
|
33
|
+
else:
|
|
34
|
+
kernel.io.error(f"Black failed to format {file_path}")
|
|
35
|
+
kernel.io.log_indent_up()
|
|
36
|
+
|
|
37
|
+
if process.stderr:
|
|
38
|
+
kernel.io.error(f"Error: {process.stderr}", symbol=False)
|
|
39
|
+
if process.stdout:
|
|
40
|
+
kernel.io.error(f"Output: {process.stdout}", symbol=False)
|
|
41
|
+
|
|
42
|
+
# Add detailed error properties
|
|
43
|
+
kernel.io.properties({"returncode": process.returncode, "command": cmd})
|
|
44
|
+
|
|
45
|
+
kernel.io.log_indent_down()
|
|
46
|
+
return False
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from wexample_wex_core.common.kernel import Kernel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _code_format_isort(kernel: Kernel, file_path: str) -> bool:
|
|
10
|
+
"""Format imports in a Python file using isort.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
kernel: The application kernel
|
|
14
|
+
file_path: Path to the Python file to format
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
bool: True if formatting succeeds, False otherwise
|
|
18
|
+
"""
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
# Use subprocess to run isort
|
|
23
|
+
# --profile=black ensures compatibility with Black formatter
|
|
24
|
+
cmd = [sys.executable, "-m", "isort", "--profile=black", file_path]
|
|
25
|
+
process = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
26
|
+
|
|
27
|
+
# Check if the command was successful
|
|
28
|
+
if process.returncode == 0:
|
|
29
|
+
if "Skipped" in process.stdout:
|
|
30
|
+
kernel.io.success(f"isort: {file_path} already well formatted")
|
|
31
|
+
else:
|
|
32
|
+
kernel.io.success(f"isort successfully reformatted imports in {file_path}")
|
|
33
|
+
return True
|
|
34
|
+
else:
|
|
35
|
+
kernel.io.error(f"isort failed to format imports in {file_path}")
|
|
36
|
+
kernel.io.log_indent_up()
|
|
37
|
+
|
|
38
|
+
if process.stderr:
|
|
39
|
+
kernel.io.error(f"Error: {process.stderr}", symbol=False)
|
|
40
|
+
if process.stdout:
|
|
41
|
+
kernel.io.error(f"Output: {process.stdout}", symbol=False)
|
|
42
|
+
|
|
43
|
+
# Add detailed error properties
|
|
44
|
+
kernel.io.properties({"returncode": process.returncode, "command": cmd})
|
|
45
|
+
|
|
46
|
+
kernel.io.log_indent_down()
|
|
47
|
+
return False
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from wexample_wex_core.decorator.command import command
|
|
6
|
+
from wexample_wex_core.decorator.middleware import middleware
|
|
7
|
+
from wexample_wex_core.decorator.option import option
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from wexample_wex_core.common.kernel import Kernel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@option(
|
|
14
|
+
name="tool",
|
|
15
|
+
type=str,
|
|
16
|
+
required=False,
|
|
17
|
+
description="Specific tool to run (black, isort). If not specified, all tools will be run.",
|
|
18
|
+
)
|
|
19
|
+
@option(
|
|
20
|
+
name="stop_on_failure",
|
|
21
|
+
type=bool,
|
|
22
|
+
required=False,
|
|
23
|
+
default=True,
|
|
24
|
+
description="Stop execution when a tool reports a failure",
|
|
25
|
+
)
|
|
26
|
+
@middleware(
|
|
27
|
+
name="each_python_file", should_exist=True, expand_glob=True, recursive=True
|
|
28
|
+
)
|
|
29
|
+
@command()
|
|
30
|
+
def python__code__format(
|
|
31
|
+
kernel: Kernel,
|
|
32
|
+
file: str,
|
|
33
|
+
tool: str | None = None,
|
|
34
|
+
stop_on_failure: bool = True,
|
|
35
|
+
) -> bool:
|
|
36
|
+
"""Format a Python file using various code formatting tools."""
|
|
37
|
+
from wexample_wex_addon_dev_python.commands.code.format.black import (
|
|
38
|
+
_code_format_black,
|
|
39
|
+
)
|
|
40
|
+
from wexample_wex_addon_dev_python.commands.code.format.isort import (
|
|
41
|
+
_code_format_isort,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Map tool names to their format functions
|
|
45
|
+
tool_map = {
|
|
46
|
+
"black": _code_format_black,
|
|
47
|
+
"isort": _code_format_isort,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Determine which tools to run
|
|
51
|
+
if tool and tool.lower() in tool_map:
|
|
52
|
+
# Run only the specified tool
|
|
53
|
+
format_functions = [tool_map[tool.lower()]]
|
|
54
|
+
else:
|
|
55
|
+
# Run all tools if no specific tool is specified or if the specified tool is invalid
|
|
56
|
+
if tool and tool.lower() not in tool_map:
|
|
57
|
+
kernel.io.warning(f"Unknown tool '{tool}', running all available tools")
|
|
58
|
+
|
|
59
|
+
# Run isort first, then black (recommended order)
|
|
60
|
+
format_functions = [
|
|
61
|
+
_code_format_isort,
|
|
62
|
+
_code_format_black,
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
# Track overall success
|
|
66
|
+
all_formats_passed = True
|
|
67
|
+
|
|
68
|
+
# Run each format function
|
|
69
|
+
for format_function in format_functions:
|
|
70
|
+
kernel.io.title(format_function.__name__)
|
|
71
|
+
format_result = format_function(kernel, file)
|
|
72
|
+
|
|
73
|
+
# Update overall success status
|
|
74
|
+
all_formats_passed = all_formats_passed and format_result
|
|
75
|
+
|
|
76
|
+
# Stop if a format fails and stop_on_failure is True
|
|
77
|
+
if not format_result and stop_on_failure:
|
|
78
|
+
kernel.io.warning("One formatting failed")
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
return all_formats_passed
|
|
File without changes
|
|
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
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from wexample_filestate.config_value.readme_content_config_value import (
|
|
7
|
+
ReadmeContentConfigValue,
|
|
8
|
+
)
|
|
9
|
+
from wexample_helpers.classes.field import public_field
|
|
10
|
+
from wexample_helpers.decorator.base_class import base_class
|
|
11
|
+
from wexample_wex_addon_dev_python.workdir.python_package_workdir import (
|
|
12
|
+
PythonPackageWorkdir,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from wexample_wex_addon_dev_python.workdir.python_package_workdir import (
|
|
17
|
+
PythonPackageWorkdir,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@base_class
|
|
22
|
+
class PythonPackageReadmeContentConfigValue(ReadmeContentConfigValue):
|
|
23
|
+
workdir: PythonPackageWorkdir = public_field(
|
|
24
|
+
description="The python package workdir"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def get_templates(self) -> list[str] | None:
|
|
28
|
+
# Use TOMLDocument from the workdir
|
|
29
|
+
doc = self.workdir.get_project_config()
|
|
30
|
+
project = doc.get("project", {}) if isinstance(doc, dict) else {}
|
|
31
|
+
|
|
32
|
+
# Extract information
|
|
33
|
+
description = project.get("description", "")
|
|
34
|
+
python_version = project.get("requires-python", "")
|
|
35
|
+
dependencies = project.get("dependencies", [])
|
|
36
|
+
urls = (
|
|
37
|
+
project.get("urls", {}) if isinstance(project.get("urls", {}), dict) else {}
|
|
38
|
+
)
|
|
39
|
+
# Accept both lowercase and capitalized homepage key variants
|
|
40
|
+
homepage = urls.get("homepage") or urls.get("Homepage") or ""
|
|
41
|
+
license_field = project.get("license", {})
|
|
42
|
+
if isinstance(license_field, dict):
|
|
43
|
+
license_info = license_field.get("text", "") or license_field.get(
|
|
44
|
+
"file", ""
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
license_info = str(license_field) if license_field else ""
|
|
48
|
+
|
|
49
|
+
# Format dependencies list
|
|
50
|
+
deps_list = "\n".join([f"- {dep}" for dep in dependencies])
|
|
51
|
+
|
|
52
|
+
package_name = self.workdir.get_package_name()
|
|
53
|
+
return [
|
|
54
|
+
f"# {package_name}\n\n"
|
|
55
|
+
f"{description}\n\n"
|
|
56
|
+
f"Version: {self.workdir.get_project_version()}\n\n"
|
|
57
|
+
f'{self._add_section_if_exists("features")}'
|
|
58
|
+
"## Requirements\n\n"
|
|
59
|
+
f"- Python {python_version}\n\n"
|
|
60
|
+
"## Dependencies\n\n"
|
|
61
|
+
f"{deps_list}\n\n"
|
|
62
|
+
"## Installation\n\n"
|
|
63
|
+
"```bash\n"
|
|
64
|
+
f"pip install {package_name}\n"
|
|
65
|
+
"```\n\n"
|
|
66
|
+
f'{self._add_section_if_exists("usage")}'
|
|
67
|
+
"## Links\n\n"
|
|
68
|
+
f"- Homepage: {homepage}\n\n"
|
|
69
|
+
"## License\n\n"
|
|
70
|
+
f"{license_info}"
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
def _add_section_if_exists(self, section: str) -> str:
|
|
74
|
+
"""
|
|
75
|
+
Returns section content if the documentation file exists
|
|
76
|
+
"""
|
|
77
|
+
doc_path = self._get_doc_path(section)
|
|
78
|
+
|
|
79
|
+
if os.path.exists(doc_path):
|
|
80
|
+
with open(doc_path, encoding="utf-8") as file:
|
|
81
|
+
content = file.read()
|
|
82
|
+
return f"## {section.title()}\n\n{content}\n\n"
|
|
83
|
+
|
|
84
|
+
return ""
|
|
85
|
+
|
|
86
|
+
def _get_doc_path(self, section: str) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Returns the path to a documentation section file
|
|
89
|
+
"""
|
|
90
|
+
return os.path.join(
|
|
91
|
+
self.workdir.get_path(), ".wex", "doc", "readme", f"{section}.md"
|
|
92
|
+
)
|
|
File without changes
|