wexample-wex-addon-dev-python 0.0.43__tar.gz → 0.0.45__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 (36) hide show
  1. {wexample_wex_addon_dev_python-0.0.43 → wexample_wex_addon_dev_python-0.0.45}/PKG-INFO +8 -8
  2. {wexample_wex_addon_dev_python-0.0.43 → wexample_wex_addon_dev_python-0.0.45}/README.md +4 -4
  3. {wexample_wex_addon_dev_python-0.0.43 → wexample_wex_addon_dev_python-0.0.45}/pyproject.toml +5 -5
  4. wexample_wex_addon_dev_python-0.0.43/src/wexample_wex_addon_dev_python/py.typed → wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/__init__.py +0 -0
  5. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/__init__.py +0 -0
  6. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/code/__init__.py +0 -0
  7. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/code/check/__init__.py +0 -0
  8. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/code/check/mypy.py +46 -0
  9. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/code/check/pylint.py +108 -0
  10. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/code/check/pyright.py +101 -0
  11. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/code/check.py +105 -0
  12. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/code/format/__init__.py +1 -0
  13. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/code/format/black.py +43 -0
  14. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/code/format/isort.py +44 -0
  15. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/code/format.py +77 -0
  16. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/examples/__init__.py +0 -0
  17. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/examples/classes/__init__.py +0 -0
  18. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/examples/classes/example_pydantic_class_with_public_var_internaly_defined.py +42 -0
  19. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/examples/utils/__init__.py +0 -0
  20. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/examples/utils/some_example_type.py +7 -0
  21. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/examples/validate.py +20 -0
  22. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/commands/release/__init__.py +0 -0
  23. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/config_value/__init__.py +0 -0
  24. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/config_value/python_package_readme_config_value.py +81 -0
  25. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/const/__init__.py +0 -0
  26. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/const/package.py +20 -0
  27. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/file/__init__.py +0 -0
  28. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/file/python_package_toml_file.py +304 -0
  29. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/middleware/__init__.py +0 -0
  30. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/middleware/each_python_file_middleware.py +79 -0
  31. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/py.typed +0 -0
  32. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/python_addon_manager.py +15 -0
  33. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/workdir/__init__.py +0 -0
  34. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/workdir/python_package_workdir.py +206 -0
  35. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py +165 -0
  36. wexample_wex_addon_dev_python-0.0.45/src/wexample_wex_addon_dev_python/workdir/python_workdir.py +240 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wexample-wex-addon-dev-python
3
- Version: 0.0.43
3
+ Version: 0.0.45
4
4
  Summary: Python dev addon for wex
5
5
  Author-Email: weeger <contact@wexample.com>
6
6
  License: MIT
@@ -12,9 +12,9 @@ Requires-Python: >=3.10
12
12
  Requires-Dist: pydantic<3,>=2
13
13
  Requires-Dist: pylint
14
14
  Requires-Dist: pyright
15
- Requires-Dist: wexample-filestate-python==0.0.39
16
- Requires-Dist: wexample-wex-addon-app==0.0.41
17
- Requires-Dist: wexample-wex-core==6.0.43
15
+ Requires-Dist: wexample-filestate-python==0.0.41
16
+ Requires-Dist: wexample-wex-addon-app==0.0.43
17
+ Requires-Dist: wexample-wex-core==6.0.45
18
18
  Provides-Extra: dev
19
19
  Requires-Dist: pytest; extra == "dev"
20
20
  Description-Content-Type: text/markdown
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
23
23
 
24
24
  Python dev addon for wex
25
25
 
26
- Version: 0.0.41
26
+ Version: 0.0.45
27
27
 
28
28
  ## Requirements
29
29
 
@@ -34,9 +34,9 @@ Version: 0.0.41
34
34
  - pydantic>=2,<3
35
35
  - pylint
36
36
  - pyright
37
- - wexample-filestate-python==0.0.37
38
- - wexample-wex-addon-app==0.0.39
39
- - wexample-wex-core==6.0.41
37
+ - wexample-filestate-python==0.0.41
38
+ - wexample-wex-addon-app==0.0.43
39
+ - wexample-wex-core==6.0.45
40
40
 
41
41
  ## Installation
42
42
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Python dev addon for wex
4
4
 
5
- Version: 0.0.41
5
+ Version: 0.0.45
6
6
 
7
7
  ## Requirements
8
8
 
@@ -13,9 +13,9 @@ Version: 0.0.41
13
13
  - pydantic>=2,<3
14
14
  - pylint
15
15
  - pyright
16
- - wexample-filestate-python==0.0.37
17
- - wexample-wex-addon-app==0.0.39
18
- - wexample-wex-core==6.0.41
16
+ - wexample-filestate-python==0.0.41
17
+ - wexample-wex-addon-app==0.0.43
18
+ - wexample-wex-core==6.0.45
19
19
 
20
20
  ## Installation
21
21
 
@@ -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.43"
9
+ version = "0.0.45"
10
10
  description = "Python dev addon for wex"
11
11
  authors = [
12
12
  { name = "weeger", email = "contact@wexample.com" },
@@ -21,9 +21,9 @@ dependencies = [
21
21
  "pydantic>=2,<3",
22
22
  "pylint",
23
23
  "pyright",
24
- "wexample-filestate-python==0.0.39",
25
- "wexample-wex-addon-app==0.0.41",
26
- "wexample-wex-core==6.0.43",
24
+ "wexample-filestate-python==0.0.41",
25
+ "wexample-wex-addon-app==0.0.43",
26
+ "wexample-wex-core==6.0.45",
27
27
  ]
28
28
 
29
29
  [project.readme]
@@ -46,7 +46,7 @@ distribution = true
46
46
 
47
47
  [tool.pdm.build]
48
48
  includes = [
49
- "src/wexample_wex_addon_dev_python/py.typed",
49
+ "src/wexample_wex_addon_dev_python/*",
50
50
  ]
51
51
  package-dir = "src"
52
52
  packages = [
@@ -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,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
@@ -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