wexample-wex-addon-dev-python 10.2.0__py3-none-any.whl → 11.0.0__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/commands/code/check/mypy.py +4 -3
- wexample_wex_addon_dev_python/commands/code/check/pylint.py +25 -19
- wexample_wex_addon_dev_python/commands/code/check/pyright.py +18 -14
- wexample_wex_addon_dev_python/commands/code/check.py +4 -7
- wexample_wex_addon_dev_python/commands/code/format/black.py +14 -15
- wexample_wex_addon_dev_python/commands/code/format/isort.py +14 -15
- wexample_wex_addon_dev_python/commands/code/format.py +8 -5
- wexample_wex_addon_dev_python/commands/code/rename.py +134 -0
- wexample_wex_addon_dev_python/commands/examples/validate.py +1 -2
- wexample_wex_addon_dev_python/common/pypi_registry_gateway.py +5 -3
- wexample_wex_addon_dev_python/config_value/python_package_readme_config_value.py +2 -8
- wexample_wex_addon_dev_python/file/python_app_iml_file.py +3 -2
- wexample_wex_addon_dev_python/file/python_pyproject_toml_file.py +7 -7
- wexample_wex_addon_dev_python/helpers/pdm.py +3 -2
- wexample_wex_addon_dev_python/middleware/each_python_file_middleware.py +12 -14
- wexample_wex_addon_dev_python/refactor/__init__.py +7 -0
- wexample_wex_addon_dev_python/refactor/python_package_rename_handler.py +215 -0
- wexample_wex_addon_dev_python/refactor/symbol_kind.py +19 -0
- wexample_wex_addon_dev_python/selection/abstract_python_code_selection.py +22 -9
- wexample_wex_addon_dev_python/selection/python_code_performance_selection.py +8 -9
- wexample_wex_addon_dev_python/services/python/commands/service/install.py +7 -10
- wexample_wex_addon_dev_python/services/python/commands/service/setup.py +5 -3
- wexample_wex_addon_dev_python/workdir/mixin/with_profiling_python_workdir_mixin.py +15 -16
- wexample_wex_addon_dev_python/workdir/python_package_workdir.py +9 -7
- wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py +2 -3
- wexample_wex_addon_dev_python/workdir/python_workdir.py +86 -20
- {wexample_wex_addon_dev_python-10.2.0.dist-info → wexample_wex_addon_dev_python-11.0.0.dist-info}/METADATA +10 -10
- {wexample_wex_addon_dev_python-10.2.0.dist-info → wexample_wex_addon_dev_python-11.0.0.dist-info}/RECORD +30 -26
- {wexample_wex_addon_dev_python-10.2.0.dist-info → wexample_wex_addon_dev_python-11.0.0.dist-info}/WHEEL +0 -0
- {wexample_wex_addon_dev_python-10.2.0.dist-info → wexample_wex_addon_dev_python-11.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -35,12 +35,13 @@ def _code_check_mypy(kernel: Kernel, file_path: str) -> bool:
|
|
|
35
35
|
# Build and check the file
|
|
36
36
|
source = BuildSource(path=file_path, module=None, text=None)
|
|
37
37
|
result = build.build(sources=[source], options=options, alt_lib_path=None)
|
|
38
|
-
|
|
38
|
+
errors = result.errors
|
|
39
|
+
if errors:
|
|
39
40
|
kernel.io.log_indent_up()
|
|
40
|
-
kernel.io.error(
|
|
41
|
+
kernel.io.error("Mypy errors:")
|
|
41
42
|
kernel.io.log_indent_up()
|
|
42
43
|
|
|
43
|
-
for error in
|
|
44
|
+
for error in errors:
|
|
44
45
|
kernel.io.error(message=error, symbol=False)
|
|
45
46
|
|
|
46
47
|
kernel.io.log_indent_down(number=2)
|
|
@@ -5,6 +5,16 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
6
|
from wexample_cli.context.execution_context import ExecutionContext
|
|
7
7
|
|
|
8
|
+
# Pre-computed once at import time; avoids list allocation + join on every call.
|
|
9
|
+
_PYLINT_DISABLE_FLAG = "--disable=" + ",".join([
|
|
10
|
+
"missing-module-docstring",
|
|
11
|
+
"import-outside-toplevel",
|
|
12
|
+
"no-name-in-module",
|
|
13
|
+
"broad-exception-caught",
|
|
14
|
+
"c-extension-no-member",
|
|
15
|
+
"line-too-long",
|
|
16
|
+
])
|
|
17
|
+
|
|
8
18
|
|
|
9
19
|
def _code_check_pylint(context: ExecutionContext, file_path: str) -> bool:
|
|
10
20
|
"""Check a Python file using pylint for code quality.
|
|
@@ -22,23 +32,13 @@ def _code_check_pylint(context: ExecutionContext, file_path: str) -> bool:
|
|
|
22
32
|
|
|
23
33
|
# Use subprocess to capture pylint output
|
|
24
34
|
# 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
35
|
cmd = [
|
|
36
36
|
sys.executable,
|
|
37
37
|
"-m",
|
|
38
38
|
"pylint",
|
|
39
39
|
file_path,
|
|
40
40
|
"--output-format=json",
|
|
41
|
-
|
|
41
|
+
_PYLINT_DISABLE_FLAG,
|
|
42
42
|
]
|
|
43
43
|
process = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
44
44
|
|
|
@@ -53,19 +53,25 @@ def _code_check_pylint(context: ExecutionContext, file_path: str) -> bool:
|
|
|
53
53
|
# Parse the JSON output
|
|
54
54
|
results = json.loads(json_output)
|
|
55
55
|
|
|
56
|
-
#
|
|
57
|
-
errors = [
|
|
58
|
-
warnings = [
|
|
59
|
-
conventions = [
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
# Categorise messages in a single pass instead of three separate comprehensions.
|
|
57
|
+
errors: list = []
|
|
58
|
+
warnings: list = []
|
|
59
|
+
conventions: list = []
|
|
60
|
+
for msg in results:
|
|
61
|
+
t = msg.get("type")
|
|
62
|
+
if t in ("error", "fatal"):
|
|
63
|
+
errors.append(msg)
|
|
64
|
+
elif t == "warning":
|
|
65
|
+
warnings.append(msg)
|
|
66
|
+
elif t in ("convention", "refactor", "info"):
|
|
67
|
+
conventions.append(msg)
|
|
62
68
|
|
|
63
69
|
# Display results if any issues found
|
|
64
70
|
if errors or warnings or conventions:
|
|
65
71
|
# Display errors
|
|
66
72
|
if errors:
|
|
67
73
|
context.io.log_indent_up()
|
|
68
|
-
context.io.error(
|
|
74
|
+
context.io.error("Pylint errors:")
|
|
69
75
|
context.io.log_indent_up()
|
|
70
76
|
|
|
71
77
|
for error in errors:
|
|
@@ -80,7 +86,7 @@ def _code_check_pylint(context: ExecutionContext, file_path: str) -> bool:
|
|
|
80
86
|
# Display warnings with detailed logging
|
|
81
87
|
if warnings:
|
|
82
88
|
context.io.log_indent_up()
|
|
83
|
-
context.io.warning(
|
|
89
|
+
context.io.warning("Pylint warnings:")
|
|
84
90
|
context.io.log_indent_up()
|
|
85
91
|
|
|
86
92
|
for warning in warnings:
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
3
6
|
from typing import TYPE_CHECKING
|
|
4
7
|
|
|
5
8
|
if TYPE_CHECKING:
|
|
@@ -16,10 +19,6 @@ def _code_check_pyright(kernel: Kernel, file_path: str) -> bool:
|
|
|
16
19
|
Returns:
|
|
17
20
|
bool: True if check passes, False otherwise
|
|
18
21
|
"""
|
|
19
|
-
import json
|
|
20
|
-
import subprocess
|
|
21
|
-
import sys
|
|
22
|
-
|
|
23
22
|
# Use subprocess to run pyright
|
|
24
23
|
cmd = [sys.executable, "-m", "pyright", file_path, "--outputjson"]
|
|
25
24
|
process = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
@@ -42,20 +41,25 @@ def _code_check_pyright(kernel: Kernel, file_path: str) -> bool:
|
|
|
42
41
|
# Parse the JSON output
|
|
43
42
|
results = json.loads(json_output)
|
|
44
43
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
# Classify diagnostics in a single pass (avoids 3× iteration and repeated .get("severity"))
|
|
45
|
+
errors: list = []
|
|
46
|
+
warnings: list = []
|
|
47
|
+
info: list = []
|
|
48
|
+
for diag in results.get("diagnostics", []):
|
|
49
|
+
sev = diag.get("severity")
|
|
50
|
+
if sev == "error":
|
|
51
|
+
errors.append(diag)
|
|
52
|
+
elif sev == "warning":
|
|
53
|
+
warnings.append(diag)
|
|
54
|
+
elif sev == "information":
|
|
55
|
+
info.append(diag)
|
|
52
56
|
|
|
53
57
|
# Display results if any issues found
|
|
54
58
|
if errors or warnings or info:
|
|
55
59
|
# Display errors
|
|
56
60
|
if errors:
|
|
57
61
|
kernel.io.log_indent_up()
|
|
58
|
-
kernel.io.error(
|
|
62
|
+
kernel.io.error("Pyright errors:")
|
|
59
63
|
|
|
60
64
|
for error in errors:
|
|
61
65
|
line = error.get("range", {}).get("start", {}).get("line", 0) + 1
|
|
@@ -70,7 +74,7 @@ def _code_check_pyright(kernel: Kernel, file_path: str) -> bool:
|
|
|
70
74
|
# Display warnings
|
|
71
75
|
if warnings:
|
|
72
76
|
kernel.io.log_indent_up()
|
|
73
|
-
kernel.io.warning(
|
|
77
|
+
kernel.io.warning("Pyright warnings:")
|
|
74
78
|
|
|
75
79
|
for warning in warnings:
|
|
76
80
|
line = warning.get("range", {}).get("start", {}).get("line", 0) + 1
|
|
@@ -85,7 +89,7 @@ def _code_check_pyright(kernel: Kernel, file_path: str) -> bool:
|
|
|
85
89
|
# Display information
|
|
86
90
|
if info:
|
|
87
91
|
kernel.io.log_indent_up()
|
|
88
|
-
kernel.io.info(
|
|
92
|
+
kernel.io.info("Pyright information:")
|
|
89
93
|
kernel.io.log_indent_up()
|
|
90
94
|
|
|
91
95
|
for item in info:
|
|
@@ -74,16 +74,13 @@ def python__code__check(
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
# Determine which tools to run
|
|
77
|
-
|
|
77
|
+
tool_lower = tool.lower() if tool else None
|
|
78
|
+
if tool_lower and tool_lower in tool_map:
|
|
78
79
|
# Run only the specified tool
|
|
79
|
-
check_functions = [tool_map[
|
|
80
|
+
check_functions = [tool_map[tool_lower]]
|
|
80
81
|
else:
|
|
81
82
|
# Run all tools if no specific tool is specified or if the specified tool is invalid
|
|
82
|
-
check_functions =
|
|
83
|
-
_code_check_mypy,
|
|
84
|
-
_code_check_pylint,
|
|
85
|
-
_code_check_pyright,
|
|
86
|
-
]
|
|
83
|
+
check_functions = list(tool_map.values())
|
|
87
84
|
|
|
88
85
|
# Track overall success
|
|
89
86
|
all_checks_passed = True
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
3
5
|
from typing import TYPE_CHECKING
|
|
4
6
|
|
|
5
7
|
if TYPE_CHECKING:
|
|
@@ -16,11 +18,8 @@ def _code_format_black(kernel: Kernel, file_path: str) -> bool:
|
|
|
16
18
|
Returns:
|
|
17
19
|
bool: True if formatting succeeds, False otherwise
|
|
18
20
|
"""
|
|
19
|
-
import subprocess
|
|
20
|
-
import sys
|
|
21
|
-
|
|
22
21
|
# Use subprocess to run black
|
|
23
|
-
cmd =
|
|
22
|
+
cmd = (sys.executable, "-m", "black", file_path)
|
|
24
23
|
process = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
25
24
|
|
|
26
25
|
# Check if the command was successful
|
|
@@ -30,17 +29,17 @@ def _code_format_black(kernel: Kernel, file_path: str) -> bool:
|
|
|
30
29
|
else:
|
|
31
30
|
kernel.io.success(f"Black: {file_path} already well formatted")
|
|
32
31
|
return True
|
|
33
|
-
else:
|
|
34
|
-
kernel.io.error(f"Black failed to format {file_path}")
|
|
35
|
-
kernel.io.log_indent_up()
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
kernel.io.error(f"Black failed to format {file_path}")
|
|
34
|
+
kernel.io.log_indent_up()
|
|
35
|
+
|
|
36
|
+
if process.stderr:
|
|
37
|
+
kernel.io.error(f"Error: {process.stderr}", symbol=False)
|
|
38
|
+
if process.stdout:
|
|
39
|
+
kernel.io.error(f"Output: {process.stdout}", symbol=False)
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
# Add detailed error properties
|
|
42
|
+
kernel.io.properties({"returncode": process.returncode, "command": cmd})
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
kernel.io.log_indent_down()
|
|
45
|
+
return False
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
3
5
|
from typing import TYPE_CHECKING
|
|
4
6
|
|
|
5
7
|
if TYPE_CHECKING:
|
|
@@ -16,13 +18,10 @@ def _code_format_isort(kernel: Kernel, file_path: str) -> bool:
|
|
|
16
18
|
Returns:
|
|
17
19
|
bool: True if formatting succeeds, False otherwise
|
|
18
20
|
"""
|
|
19
|
-
import subprocess
|
|
20
|
-
import sys
|
|
21
|
-
|
|
22
21
|
# Use subprocess to run isort
|
|
23
22
|
# --profile=black ensures compatibility with Black formatter
|
|
24
23
|
cmd = [sys.executable, "-m", "isort", "--profile=black", file_path]
|
|
25
|
-
process = subprocess.run(cmd, capture_output=True, text=True
|
|
24
|
+
process = subprocess.run(cmd, capture_output=True, text=True)
|
|
26
25
|
|
|
27
26
|
# Check if the command was successful
|
|
28
27
|
if process.returncode == 0:
|
|
@@ -31,17 +30,17 @@ def _code_format_isort(kernel: Kernel, file_path: str) -> bool:
|
|
|
31
30
|
else:
|
|
32
31
|
kernel.io.success(f"isort successfully reformatted imports in {file_path}")
|
|
33
32
|
return True
|
|
34
|
-
else:
|
|
35
|
-
kernel.io.error(f"isort failed to format imports in {file_path}")
|
|
36
|
-
kernel.io.log_indent_up()
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
kernel.io.error(f"isort failed to format imports in {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)
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
# Add detailed error properties
|
|
43
|
+
kernel.io.properties({"returncode": process.returncode, "command": cmd})
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
kernel.io.log_indent_down()
|
|
46
|
+
return False
|
|
@@ -61,13 +61,16 @@ def python__code__format(
|
|
|
61
61
|
"isort": _code_format_isort,
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
# Normalise once to avoid repeated .lower() calls
|
|
65
|
+
tool_lower = tool.lower() if tool else None
|
|
66
|
+
|
|
64
67
|
# Determine which tools to run
|
|
65
|
-
if
|
|
68
|
+
if tool_lower in tool_map:
|
|
66
69
|
# Run only the specified tool
|
|
67
|
-
format_functions = [tool_map[
|
|
70
|
+
format_functions = [tool_map[tool_lower]]
|
|
68
71
|
else:
|
|
69
|
-
#
|
|
70
|
-
if
|
|
72
|
+
# In the else branch, a truthy tool_lower is already known to be absent from tool_map
|
|
73
|
+
if tool_lower:
|
|
71
74
|
kernel.io.warning(f"Unknown tool '{tool}', running all available tools")
|
|
72
75
|
|
|
73
76
|
# Run isort first, then black (recommended order)
|
|
@@ -85,7 +88,7 @@ def python__code__format(
|
|
|
85
88
|
format_result = format_function(kernel, file)
|
|
86
89
|
|
|
87
90
|
# Update overall success status
|
|
88
|
-
all_formats_passed
|
|
91
|
+
all_formats_passed &= format_result
|
|
89
92
|
|
|
90
93
|
# Stop if a format fails and stop_on_failure is True
|
|
91
94
|
if not format_result and stop_on_failure:
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from wexample_cli.const.tags import AudienceTag, EffectTag, ScopeTag
|
|
6
|
+
from wexample_cli.decorator.command import command
|
|
7
|
+
from wexample_cli.decorator.middleware import middleware
|
|
8
|
+
from wexample_cli.decorator.option import option
|
|
9
|
+
from wexample_wex_addon_app.middleware.app_middleware import AppMiddleware
|
|
10
|
+
from wexample_wex_core.const.globals import COMMAND_TYPE_ADDON
|
|
11
|
+
|
|
12
|
+
from wexample_wex_addon_dev_python.const.tags import DomainTag
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from wexample_app.response.abstract_response import AbstractResponse
|
|
16
|
+
from wexample_cli.context.execution_context import ExecutionContext
|
|
17
|
+
from wexample_wex_addon_app.workdir.managed_workdir import ManagedWorkdir
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@option(
|
|
21
|
+
name="source",
|
|
22
|
+
type=str,
|
|
23
|
+
required=True,
|
|
24
|
+
description="Dotted source name to rename (e.g. 'mylib.helpers').",
|
|
25
|
+
)
|
|
26
|
+
@option(
|
|
27
|
+
name="target",
|
|
28
|
+
type=str,
|
|
29
|
+
required=True,
|
|
30
|
+
description="Dotted target name (e.g. 'mylib.helper').",
|
|
31
|
+
)
|
|
32
|
+
@option(
|
|
33
|
+
name="kind",
|
|
34
|
+
type=str,
|
|
35
|
+
required=False,
|
|
36
|
+
default="package",
|
|
37
|
+
description="Symbol kind to rename. Step 1 supports only 'package'.",
|
|
38
|
+
)
|
|
39
|
+
@option(
|
|
40
|
+
name="dry_run",
|
|
41
|
+
type=bool,
|
|
42
|
+
is_flag=True,
|
|
43
|
+
default=False,
|
|
44
|
+
description="Preview impacted files without writing. Default: changes are applied.",
|
|
45
|
+
)
|
|
46
|
+
@middleware(middleware=AppMiddleware)
|
|
47
|
+
@command(
|
|
48
|
+
type=COMMAND_TYPE_ADDON,
|
|
49
|
+
description=(
|
|
50
|
+
"Rename a Python package directory and propagate the change to every "
|
|
51
|
+
"import statement in the workdir. Dry-run by default."
|
|
52
|
+
),
|
|
53
|
+
tags=[
|
|
54
|
+
DomainTag.LANGUAGE_PYTHON,
|
|
55
|
+
EffectTag.WRITE,
|
|
56
|
+
AudienceTag.AGENT_SAFE,
|
|
57
|
+
ScopeTag.LOCAL,
|
|
58
|
+
ScopeTag.PACKAGE,
|
|
59
|
+
],
|
|
60
|
+
)
|
|
61
|
+
def python__code__rename(
|
|
62
|
+
context: ExecutionContext,
|
|
63
|
+
app_workdir: ManagedWorkdir,
|
|
64
|
+
source: str,
|
|
65
|
+
target: str,
|
|
66
|
+
kind: str = "package",
|
|
67
|
+
dry_run: bool = False,
|
|
68
|
+
) -> AbstractResponse:
|
|
69
|
+
from wexample_app.response.failure_response import FailureResponse
|
|
70
|
+
from wexample_app.response.table_response import TableResponse
|
|
71
|
+
|
|
72
|
+
from wexample_wex_addon_dev_python.refactor.python_package_rename_handler import (
|
|
73
|
+
PythonPackageRenameHandler,
|
|
74
|
+
)
|
|
75
|
+
from wexample_wex_addon_dev_python.refactor.symbol_kind import SymbolKind
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
kind_enum = SymbolKind(kind)
|
|
79
|
+
except ValueError:
|
|
80
|
+
return FailureResponse(
|
|
81
|
+
kernel=context.kernel,
|
|
82
|
+
message=(
|
|
83
|
+
f"Unknown --kind {kind!r}. Valid: "
|
|
84
|
+
f"{', '.join([k.value for k in SymbolKind])}"
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if kind_enum is not SymbolKind.PACKAGE:
|
|
89
|
+
return FailureResponse(
|
|
90
|
+
kernel=context.kernel,
|
|
91
|
+
message=(
|
|
92
|
+
f"--kind {kind_enum.value} not implemented yet "
|
|
93
|
+
"(step 1 covers 'package' only)."
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
workdir_path = app_workdir.get_path()
|
|
98
|
+
handler = PythonPackageRenameHandler(
|
|
99
|
+
workdir_path=workdir_path,
|
|
100
|
+
source=source,
|
|
101
|
+
target=target,
|
|
102
|
+
dry_run=dry_run,
|
|
103
|
+
)
|
|
104
|
+
try:
|
|
105
|
+
report = handler.run()
|
|
106
|
+
except FileNotFoundError:
|
|
107
|
+
return FailureResponse(
|
|
108
|
+
kernel=context.kernel,
|
|
109
|
+
message=(
|
|
110
|
+
f"No Python package matching '{source}' under {workdir_path}. "
|
|
111
|
+
"Check the dotted name (e.g. 'mylib.helpers') and that you're "
|
|
112
|
+
"in the right workdir."
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
except ValueError as e:
|
|
116
|
+
# Raised on ambiguous match (multiple packages) or invalid dotted name.
|
|
117
|
+
return FailureResponse(kernel=context.kernel, message=str(e))
|
|
118
|
+
|
|
119
|
+
mode = "APPLIED" if not dry_run else "DRY-RUN"
|
|
120
|
+
context.io.log(
|
|
121
|
+
f"[{mode}] {report.package_dir_old} → {report.package_dir_new}"
|
|
122
|
+
)
|
|
123
|
+
context.io.log(
|
|
124
|
+
f"[{mode}] scanned={report.files_scanned} changed={len(report.files_changed)}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
rows = [
|
|
128
|
+
[str(p.relative_to(workdir_path))] for p in report.files_changed
|
|
129
|
+
]
|
|
130
|
+
return TableResponse(
|
|
131
|
+
kernel=context.kernel,
|
|
132
|
+
content=rows,
|
|
133
|
+
headers=["File"],
|
|
134
|
+
)
|
|
@@ -27,5 +27,4 @@ def python__examples__validate(
|
|
|
27
27
|
ExamplePydanticClassWithPublicVarInternallyDefined,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
context.kernel.log(example_class)
|
|
30
|
+
context.kernel.log(ExamplePydanticClassWithPublicVarInternallyDefined())
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import base64
|
|
4
|
+
|
|
3
5
|
from wexample_api.common.abstract_gateway import AbstractGateway
|
|
4
6
|
from wexample_helpers.classes.field import public_field
|
|
5
7
|
from wexample_helpers.decorator.base_class import base_class
|
|
6
8
|
|
|
9
|
+
_HAS_VERSION_STATUS_CODES: tuple[int, ...] = (200, 404)
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
@base_class
|
|
9
13
|
class PypiRegistryGateway(AbstractGateway):
|
|
@@ -27,8 +31,6 @@ class PypiRegistryGateway(AbstractGateway):
|
|
|
27
31
|
def __attrs_post_init__(self) -> None:
|
|
28
32
|
super().__attrs_post_init__()
|
|
29
33
|
if self.token:
|
|
30
|
-
import base64
|
|
31
|
-
|
|
32
34
|
credentials = base64.b64encode(
|
|
33
35
|
f"{self.username or '__token__'}:{self.token}".encode()
|
|
34
36
|
).decode()
|
|
@@ -43,7 +45,7 @@ class PypiRegistryGateway(AbstractGateway):
|
|
|
43
45
|
"""
|
|
44
46
|
response = self.make_request(
|
|
45
47
|
endpoint=f"simple/{package}/",
|
|
46
|
-
expected_status_codes=
|
|
48
|
+
expected_status_codes=_HAS_VERSION_STATUS_CODES,
|
|
47
49
|
fatal_if_unexpected=False,
|
|
48
50
|
quiet=True,
|
|
49
51
|
)
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
3
|
from wexample_helpers.decorator.base_class import base_class
|
|
6
4
|
from wexample_wex_addon_app.config_value.app_readme_config_value import (
|
|
7
5
|
AppReadmeConfigValue,
|
|
8
6
|
)
|
|
9
7
|
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
pass
|
|
12
|
-
|
|
13
8
|
|
|
14
9
|
@base_class
|
|
15
10
|
class PythonPackageReadmeContentConfigValue(AppReadmeConfigValue):
|
|
@@ -22,9 +17,8 @@ class PythonPackageReadmeContentConfigValue(AppReadmeConfigValue):
|
|
|
22
17
|
def _get_app_homepage(self) -> str:
|
|
23
18
|
"""Extract homepage URL from pyproject.toml."""
|
|
24
19
|
project = self.workdir.get_app_config()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
)
|
|
20
|
+
raw_urls = project.get("urls", {})
|
|
21
|
+
urls = raw_urls if isinstance(raw_urls, dict) else {}
|
|
28
22
|
return urls.get("homepage") or urls.get("Homepage") or ""
|
|
29
23
|
|
|
30
24
|
def _get_project_license(self) -> str | None:
|
|
@@ -29,13 +29,14 @@ class PythonAppImlFile(ImlFile):
|
|
|
29
29
|
return ({"@type": "sourceFolder", "@forTests": "false"},)
|
|
30
30
|
|
|
31
31
|
def _default_source_folders(self) -> Iterable[dict[str, Any]]:
|
|
32
|
+
base = self.MODULE_DIR_URL
|
|
32
33
|
return (
|
|
33
34
|
{
|
|
34
|
-
"@url": f"{
|
|
35
|
+
"@url": f"{base}/src",
|
|
35
36
|
"@isTestSource": "false",
|
|
36
37
|
},
|
|
37
38
|
{
|
|
38
|
-
"@url": f"{
|
|
39
|
+
"@url": f"{base}/tests",
|
|
39
40
|
"@isTestSource": "true",
|
|
40
41
|
},
|
|
41
42
|
)
|
|
@@ -105,7 +105,7 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
105
105
|
deps = self._get_deps_array(optional=optional, group=group)
|
|
106
106
|
|
|
107
107
|
map = {}
|
|
108
|
-
for spec in
|
|
108
|
+
for spec in deps:
|
|
109
109
|
req = Requirement(spec)
|
|
110
110
|
# name: version
|
|
111
111
|
map[canonicalize_name(req.name)] = str(req.specifier)
|
|
@@ -140,7 +140,7 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
140
140
|
|
|
141
141
|
target = canonicalize_name(package_name)
|
|
142
142
|
filtered: list[str] = []
|
|
143
|
-
for existing in
|
|
143
|
+
for existing in deps:
|
|
144
144
|
try:
|
|
145
145
|
existing_name = canonicalize_name(Requirement(str(existing)).name)
|
|
146
146
|
except Exception:
|
|
@@ -298,12 +298,12 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
298
298
|
deps_arr = self._dependencies_array()
|
|
299
299
|
|
|
300
300
|
runtime_pkgs = {
|
|
301
|
-
package_normalize_name(toml_get_string_value(it)) for it in
|
|
301
|
+
package_normalize_name(toml_get_string_value(it)) for it in deps_arr
|
|
302
302
|
}
|
|
303
|
-
|
|
303
|
+
dev_set = {toml_get_string_value(it).strip() for it in dev_arr}
|
|
304
304
|
|
|
305
305
|
for pkg in ["pytest", "pytest-cov"]:
|
|
306
|
-
if pkg not in runtime_pkgs and pkg not in
|
|
306
|
+
if pkg not in runtime_pkgs and pkg not in dev_set:
|
|
307
307
|
dev_arr.append(pkg)
|
|
308
308
|
|
|
309
309
|
toml_sort_string_array(dev_arr)
|
|
@@ -326,7 +326,6 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
326
326
|
)
|
|
327
327
|
|
|
328
328
|
deps_arr = self._dependencies_array()
|
|
329
|
-
toml_sort_string_array(deps_arr)
|
|
330
329
|
|
|
331
330
|
# Read the keep list from [tool.filestate].keep
|
|
332
331
|
keep_packages: set[str] = set()
|
|
@@ -390,8 +389,9 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
390
389
|
existing_keys = list(d.keys())
|
|
391
390
|
|
|
392
391
|
# Build the new order: ordered keys first, then remaining keys
|
|
392
|
+
key_order_set = frozenset(key_order)
|
|
393
393
|
ordered_keys = [k for k in key_order if k in existing_keys]
|
|
394
|
-
remaining_keys = [k for k in existing_keys if k not in
|
|
394
|
+
remaining_keys = [k for k in existing_keys if k not in key_order_set]
|
|
395
395
|
new_order = ordered_keys + remaining_keys
|
|
396
396
|
|
|
397
397
|
# Reorder by removing and re-adding in the desired order
|
|
@@ -4,8 +4,9 @@ from __future__ import annotations
|
|
|
4
4
|
def apply_pdm_bin_dir(value: str) -> None:
|
|
5
5
|
import os
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
path = os.environ.get("PATH", "")
|
|
8
|
+
if value not in path:
|
|
9
|
+
os.environ["PATH"] = f"{value}:{path}"
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def detect_pdm_bin_dir() -> str | None:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from wexample_wex_core.middleware.each_file_middleware import EachFileMiddleware
|
|
@@ -32,12 +33,13 @@ class EachPythonFileMiddleware(EachFileMiddleware):
|
|
|
32
33
|
python_extension_only: bool = True
|
|
33
34
|
|
|
34
35
|
def __init__(self, **kwargs) -> None:
|
|
35
|
-
# Allow overriding the default settings
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
# Allow overriding the default settings (pop avoids a double-lookup)
|
|
37
|
+
self.python_extension_only = kwargs.pop(
|
|
38
|
+
"python_extension_only", self.python_extension_only
|
|
39
|
+
)
|
|
40
|
+
ignored = kwargs.pop("ignored_directories", None)
|
|
41
|
+
if ignored is not None:
|
|
42
|
+
self.ignored_directories = set(ignored)
|
|
41
43
|
|
|
42
44
|
super().__init__(**kwargs)
|
|
43
45
|
|
|
@@ -65,13 +67,9 @@ class EachPythonFileMiddleware(EachFileMiddleware):
|
|
|
65
67
|
Returns:
|
|
66
68
|
True if the item should be processed, False otherwise
|
|
67
69
|
"""
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
+
# Cheap string check before the expensive isfile() syscall: skip
|
|
71
|
+
# non-.py paths immediately when extension filtering is active.
|
|
72
|
+
if self.python_extension_only and not item_path.endswith(".py"):
|
|
70
73
|
return False
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
if self.python_extension_only:
|
|
74
|
-
return item_path.endswith(".py")
|
|
75
|
-
|
|
76
|
-
# Otherwise, accept all files
|
|
77
|
-
return True
|
|
75
|
+
return os.path.isfile(item_path)
|