wexample-wex-addon-dev-python 0.0.44__tar.gz → 0.0.47__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {wexample_wex_addon_dev_python-0.0.44 → wexample_wex_addon_dev_python-0.0.47}/PKG-INFO +12 -10
  2. {wexample_wex_addon_dev_python-0.0.44 → wexample_wex_addon_dev_python-0.0.47}/README.md +6 -5
  3. {wexample_wex_addon_dev_python-0.0.44 → wexample_wex_addon_dev_python-0.0.47}/pyproject.toml +6 -8
  4. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/__init__.py +0 -0
  5. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/code/__init__.py +0 -0
  6. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/code/check/__init__.py +0 -0
  7. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/code/check/mypy.py +48 -0
  8. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/code/check/pylint.py +108 -0
  9. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/code/check/pyright.py +104 -0
  10. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/code/check.py +103 -0
  11. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/code/format/__init__.py +1 -0
  12. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/code/format/black.py +46 -0
  13. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/code/format/isort.py +47 -0
  14. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/code/format.py +81 -0
  15. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/examples/__init__.py +0 -0
  16. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/examples/utils/__init__.py +0 -0
  17. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/examples/utils/some_example_type.py +7 -0
  18. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/examples/validate.py +20 -0
  19. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/commands/release/__init__.py +0 -0
  20. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/config_value/__init__.py +0 -0
  21. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/config_value/python_package_readme_config_value.py +92 -0
  22. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/const/__init__.py +0 -0
  23. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/const/package.py +21 -0
  24. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/file/__init__.py +0 -0
  25. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/file/python_package_toml_file.py +303 -0
  26. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/middleware/__init__.py +0 -0
  27. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/middleware/each_python_file_middleware.py +77 -0
  28. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/py.typed +0 -0
  29. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/python_addon_manager.py +19 -0
  30. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/workdir/__init__.py +0 -0
  31. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/workdir/python_package_workdir.py +214 -0
  32. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py +158 -0
  33. wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/workdir/python_workdir.py +260 -0
  34. /wexample_wex_addon_dev_python-0.0.44/src/wexample_wex_addon_dev_python/py.typed → /wexample_wex_addon_dev_python-0.0.47/src/wexample_wex_addon_dev_python/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wexample-wex-addon-dev-python
3
- Version: 0.0.44
3
+ Version: 0.0.47
4
4
  Summary: Python dev addon for wex
5
5
  Author-Email: weeger <contact@wexample.com>
6
6
  License: MIT
@@ -9,12 +9,13 @@ Classifier: License :: OSI Approved :: MIT License
9
9
  Classifier: Operating System :: OS Independent
10
10
  Project-URL: homepage, https://github.com/wexample/python-wex-dev-python
11
11
  Requires-Python: >=3.10
12
- Requires-Dist: pydantic<3,>=2
12
+ Requires-Dist: attrs>=23.1.0
13
+ Requires-Dist: cattrs>=23.1.0
13
14
  Requires-Dist: pylint
14
15
  Requires-Dist: pyright
15
- Requires-Dist: wexample-filestate-python==0.0.40
16
- Requires-Dist: wexample-wex-addon-app==0.0.42
17
- Requires-Dist: wexample-wex-core==6.0.44
16
+ Requires-Dist: wexample-filestate-python==0.0.43
17
+ Requires-Dist: wexample-wex-addon-app==0.0.45
18
+ Requires-Dist: wexample-wex-core==6.0.47
18
19
  Provides-Extra: dev
19
20
  Requires-Dist: pytest; extra == "dev"
20
21
  Description-Content-Type: text/markdown
@@ -23,7 +24,7 @@ Description-Content-Type: text/markdown
23
24
 
24
25
  Python dev addon for wex
25
26
 
26
- Version: 0.0.44
27
+ Version: 0.0.46
27
28
 
28
29
  ## Requirements
29
30
 
@@ -31,12 +32,13 @@ Version: 0.0.44
31
32
 
32
33
  ## Dependencies
33
34
 
34
- - pydantic>=2,<3
35
+ - attrs>=23.1.0
36
+ - cattrs>=23.1.0
35
37
  - pylint
36
38
  - pyright
37
- - wexample-filestate-python==0.0.40
38
- - wexample-wex-addon-app==0.0.42
39
- - wexample-wex-core==6.0.44
39
+ - wexample-filestate-python==0.0.42
40
+ - wexample-wex-addon-app==0.0.44
41
+ - wexample-wex-core==6.0.46
40
42
 
41
43
  ## Installation
42
44
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Python dev addon for wex
4
4
 
5
- Version: 0.0.44
5
+ Version: 0.0.46
6
6
 
7
7
  ## Requirements
8
8
 
@@ -10,12 +10,13 @@ Version: 0.0.44
10
10
 
11
11
  ## Dependencies
12
12
 
13
- - pydantic>=2,<3
13
+ - attrs>=23.1.0
14
+ - cattrs>=23.1.0
14
15
  - pylint
15
16
  - pyright
16
- - wexample-filestate-python==0.0.40
17
- - wexample-wex-addon-app==0.0.42
18
- - wexample-wex-core==6.0.44
17
+ - wexample-filestate-python==0.0.42
18
+ - wexample-wex-addon-app==0.0.44
19
+ - wexample-wex-core==6.0.46
19
20
 
20
21
  ## Installation
21
22
 
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "wexample-wex-addon-dev-python"
9
- version = "0.0.44"
9
+ version = "0.0.47"
10
10
  description = "Python dev addon for wex"
11
11
  authors = [
12
12
  { name = "weeger", email = "contact@wexample.com" },
@@ -18,12 +18,13 @@ classifiers = [
18
18
  "Operating System :: OS Independent",
19
19
  ]
20
20
  dependencies = [
21
- "pydantic>=2,<3",
21
+ "attrs>=23.1.0",
22
+ "cattrs>=23.1.0",
22
23
  "pylint",
23
24
  "pyright",
24
- "wexample-filestate-python==0.0.40",
25
- "wexample-wex-addon-app==0.0.42",
26
- "wexample-wex-core==6.0.44",
25
+ "wexample-filestate-python==0.0.43",
26
+ "wexample-wex-addon-app==0.0.45",
27
+ "wexample-wex-core==6.0.47",
27
28
  ]
28
29
 
29
30
  [project.readme]
@@ -45,9 +46,6 @@ dev = [
45
46
  distribution = true
46
47
 
47
48
  [tool.pdm.build]
48
- includes = [
49
- "src/wexample_wex_addon_dev_python/py.typed",
50
- ]
51
49
  package-dir = "src"
52
50
  packages = [
53
51
  { include = "wexample_wex_addon_dev_python", from = "src" },
@@ -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,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
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class SomeExampleType(BaseModel):
7
+ property: str