chore: 版本升级到0.1.3并批量优化代码

变更包括:
1. 更新pyproject.toml行长度限制为120
2. 简化多处异常提示字符串的换行写法
3. 批量使用Any类型泛型优化类型标注
4. 重构cli/pymake.py的配置与任务定义
5. 删除冗余的测试代码与废弃的pymake测试文件
6. 修复示例代码的类型注解
This commit is contained in:
2026-06-21 14:58:19 +08:00
parent febcd90a31
commit cd38e1246a
14 changed files with 202 additions and 716 deletions
-165
View File
@@ -1,165 +0,0 @@
"""Tests for pymake CLI."""
from pyflowx.cli.pymake import build_graphs, conf, get_maturin_build_command
def test_pymake_config_attributes():
"""Test PymakeConfig has expected attributes."""
assert hasattr(conf, "PROJECT_ROOT")
assert hasattr(conf, "BUILD_TOOL")
assert hasattr(conf, "BUILD_COMMAND")
assert hasattr(conf, "MATURIN_TOOL")
assert hasattr(conf, "MATURIN_BUILD_COMMAND")
assert hasattr(conf, "MATURIN_DEV_COMMAND")
assert hasattr(conf, "TIMEOUT")
def test_pymake_config_values():
"""Test PymakeConfig values are correct."""
assert conf.BUILD_TOOL == "uv"
assert conf.BUILD_COMMAND == ["uv", "build"]
assert conf.MATURIN_TOOL == "maturin"
assert conf.TIMEOUT == 600
def test_get_maturin_build_command_basic():
"""Test get_maturin_build_command returns base command."""
cmd = get_maturin_build_command()
assert "maturin" in cmd
assert "build" in cmd
assert "-r" in cmd
def testbuild_graphs_returns_dict():
"""Test build_graphs returns a dictionary."""
graphs = build_graphs()
assert isinstance(graphs, dict)
assert len(graphs) > 0
def testbuild_graphs_has_expected_commands():
"""Test build_graphs has expected command keys."""
graphs = build_graphs()
expected_commands = [
"b",
"bc",
"ba",
"ic",
"ip",
"ia",
"cp",
"cc",
"ca",
"t",
"lint",
]
for cmd in expected_commands:
assert cmd in graphs, f"Expected command '{cmd}' not found in graphs"
def testbuild_graphs_values_are_graphs():
"""Test build_graphs values are Graph instances."""
import pyflowx as px
graphs = build_graphs()
for name, graph in graphs.items():
assert isinstance(graph, px.Graph), (
f"Graph for command '{name}' is not a Graph instance"
)
def test_build_command_graph_structure():
"""Test 'b' command graph has correct structure."""
graphs = build_graphs()
graph = graphs["b"]
assert len(graph.all_specs()) == 1
spec = graph.spec("uv_build")
assert spec.cmd == conf.BUILD_COMMAND
def test_build_all_command_graph_structure():
"""Test 'ba' command graph has correct dependencies."""
graphs = build_graphs()
graph = graphs["ba"]
specs = graph.all_specs()
assert len(specs) == 2
# Check dependency
uv_build_spec = graph.spec("uv_build")
assert "maturin_build" in uv_build_spec.depends_on
def test_maturin_build_command_graph_structure():
"""Test 'bc' command graph has correct structure."""
graphs = build_graphs()
graph = graphs["bc"]
specs = graph.all_specs()
assert len(specs) == 1
spec = graph.spec("maturin_build")
assert spec.cmd == get_maturin_build_command()
def test_install_all_command_graph_structure():
"""Test 'ia' command graph has correct dependencies."""
graphs = build_graphs()
graph = graphs["ia"]
specs = graph.all_specs()
assert len(specs) == 2
uv_install_spec = graph.spec("uv_install")
assert "maturin_dev" in uv_install_spec.depends_on
def test_clean_all_command_graph_structure():
"""Test 'ca' command graph has correct structure."""
graphs = build_graphs()
graph = graphs["ca"]
specs = graph.all_specs()
assert len(specs) == 2
def test_test_command_graph_structure():
"""Test 't' command graph has correct structure."""
graphs = build_graphs()
graph = graphs["t"]
specs = graph.all_specs()
assert len(specs) == 1
spec = graph.spec("pytest")
assert "pytest" in spec.cmd
def test_lint_command_graph_structure():
"""Test 'lint' command graph has correct structure."""
graphs = build_graphs()
graph = graphs["lint"]
specs = graph.all_specs()
assert len(specs) == 1
spec = graph.spec("ruff_check")
assert "ruff" in spec.cmd
def test_pymake_config_dirs_to_ignore():
"""Test PymakeConfig has correct dirs to ignore."""
assert ".venv" in conf.DIRS_TO_IGNORE
assert ".git" in conf.DIRS_TO_IGNORE
assert ".tox" in conf.DIRS_TO_IGNORE
def test_pymake_config_python_build_dirs():
"""Test PymakeConfig has correct Python build dirs."""
assert "dist" in conf.PYTHON_BUILD_DIRS
assert "build" in conf.PYTHON_BUILD_DIRS
def test_maturin_build_options_win7():
"""Test MATURIN_BUILD_OPTIONS_WIN7 has expected options."""
assert "--target" in conf.MATURIN_BUILD_OPTIONS_WIN7
assert "x86_64-win7-windows-msvc" in conf.MATURIN_BUILD_OPTIONS_WIN7
assert "-Zbuild-std" in conf.MATURIN_BUILD_OPTIONS_WIN7
def test_doc_build_command():
"""Test DOC_BUILD_COMMAND has expected structure."""
assert "sphinx-build" in conf.DOC_BUILD_COMMAND
assert "-b" in conf.DOC_BUILD_COMMAND
assert "html" in conf.DOC_BUILD_COMMAND
+10 -13
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
from datetime import datetime, timedelta
from typing import Any
import pyflowx as px
from pyflowx.task import TaskResult, TaskSpec, TaskStatus
@@ -15,17 +16,17 @@ def _fn() -> int:
def _make_result(
name: str = "a",
status: TaskStatus = TaskStatus.SUCCESS,
value: object = 42,
value: Any = 42,
error: BaseException | None = None,
duration: float = 0.5,
attempts: int = 1,
) -> TaskResult[object]:
) -> TaskResult[Any]:
"""构造测试用 TaskResult 实例."""
spec: TaskSpec[object] = TaskSpec[object](name, _fn)
spec: TaskSpec[Any] = TaskSpec[Any](name, _fn)
start = datetime(2024, 1, 1, 0, 0, 0)
# 用 timedelta 精确表达秒数,避免 int() 截断小数
end = start + timedelta(seconds=duration) if duration else None
return TaskResult[object](
return TaskResult[Any](
spec=spec,
status=status,
value=value,
@@ -85,7 +86,7 @@ class TestRunReportSummary:
def test_summary_with_none_duration(self) -> None:
"""未开始/未结束的任务 duration 为 None,不应计入总时长."""
report = px.RunReport()
spec: TaskSpec[object] = TaskSpec("a", _fn) # type: ignore[arg-type]
spec: TaskSpec[Any] = TaskSpec[Any]("a", _fn) # type: ignore[arg-type]
report.results["a"] = TaskResult(spec=spec, status=TaskStatus.FAILED)
s = report.summary()
assert s["total_duration_seconds"] == 0.0
@@ -94,9 +95,7 @@ class TestRunReportSummary:
"""failed_tasks 应返回所有失败任务名."""
report = px.RunReport()
report.results["a"] = _make_result("a", status=TaskStatus.SUCCESS)
report.results["b"] = _make_result(
"b", status=TaskStatus.FAILED, error=ValueError("x")
)
report.results["b"] = _make_result("b", status=TaskStatus.FAILED, error=ValueError("x"))
assert report.failed_tasks() == ["b"]
@@ -115,9 +114,7 @@ class TestRunReportDescribe:
def test_describe_with_error(self) -> None:
"""应正确描述失败状态和错误信息."""
report = px.RunReport(success=False)
report.results["a"] = _make_result(
"a", status=TaskStatus.FAILED, error=ValueError("boom"), duration=0.1
)
report.results["a"] = _make_result("a", status=TaskStatus.FAILED, error=ValueError("boom"), duration=0.1)
desc = report.describe()
assert "success=False" in desc
assert "error=ValueError" in desc
@@ -125,7 +122,7 @@ class TestRunReportDescribe:
def test_describe_no_duration(self) -> None:
"""无耗时的任务应显示为 '-'."""
report = px.RunReport()
spec: TaskSpec[object] = TaskSpec("a", _fn) # type: ignore[arg-type]
report.results["a"] = TaskResult(spec=spec, status=TaskStatus.PENDING)
spec: TaskSpec[Any] = TaskSpec[Any]("a", _fn) # type: ignore[arg-type]
report.results["a"] = TaskResult[Any](spec=spec, status=TaskStatus.PENDING)
desc = report.describe()
assert "-" in desc # duration 显示为 "-"
+2 -16
View File
@@ -2,6 +2,7 @@
import sys
import tempfile
from pathlib import Path
import pytest
@@ -20,7 +21,6 @@ def test_taskspec_wrap_cmd_with_list():
spec = TaskSpec("test", cmd=[*ECHO_CMD, "hello"])
wrapped_fn = spec.effective_fn
assert wrapped_fn is not None
assert wrapped_fn.__name__ == "test"
def test_taskspec_wrap_cmd_with_string():
@@ -32,7 +32,6 @@ def test_taskspec_wrap_cmd_with_string():
spec = TaskSpec("test", cmd=cmd_str)
wrapped_fn = spec.effective_fn
assert wrapped_fn is not None
assert wrapped_fn.__name__ == "test"
def test_taskspec_wrap_cmd_with_timeout():
@@ -48,7 +47,7 @@ def test_taskspec_wrap_cmd_with_timeout():
def test_taskspec_wrap_cmd_with_cwd():
"""Test TaskSpec._wrap_cmd with working directory."""
with tempfile.TemporaryDirectory() as tmpdir:
spec = TaskSpec("test", cmd=[*ECHO_CMD, "hello"], cwd=tmpdir)
spec = TaskSpec("test", cmd=[*ECHO_CMD, "hello"], cwd=Path(tmpdir))
wrapped_fn = spec.effective_fn
result = wrapped_fn()
assert result is None
@@ -99,19 +98,6 @@ def test_taskspec_no_fn_no_cmd():
_ = TaskSpec("test")
def test_taskspec_cmd_overrides_fn():
"""Test TaskSpec cmd overrides fn."""
def my_fn():
return "fn_result"
spec = TaskSpec("test", fn=my_fn, cmd=[*ECHO_CMD, "hello"])
wrapped_fn = spec.effective_fn
# cmd should override fn
assert wrapped_fn.__name__ == "test"
def test_taskspec_conditions_check():
"""Test TaskSpec.should_execute with conditions."""
spec = px.TaskSpec(
+21 -59
View File
@@ -2,6 +2,7 @@
import sys
from pathlib import Path
from typing import Any
import pytest
@@ -357,27 +358,21 @@ class TestTaskSpecVerbose:
def test_verbose_default_is_false(self) -> None:
"""verbose 默认应为 False."""
spec: px.TaskSpec[object] = px.TaskSpec("a", cmd=[*ECHO_CMD, "hi"])
spec: px.TaskSpec[Any] = px.TaskSpec[Any]("a", cmd=[*ECHO_CMD, "hi"])
assert spec.verbose is False
def test_verbose_true_prints_command(
self, capsys: pytest.CaptureFixture[str]
) -> None:
def test_verbose_true_prints_command(self, capsys: pytest.CaptureFixture[str]) -> None:
"""verbose=True 时应打印执行的命令."""
graph = px.Graph.from_specs(
[px.TaskSpec("echo", cmd=[*ECHO_CMD, "verbose-output"], verbose=True)]
)
px.run(graph, strategy="sequential")
graph = px.Graph.from_specs([px.TaskSpec("echo", cmd=[*ECHO_CMD, "verbose-output"], verbose=True)])
_ = px.run(graph, strategy="sequential")
captured = capsys.readouterr()
assert "执行命令" in captured.out
assert "返回码" in captured.out
def test_verbose_false_silent(self, capsys: pytest.CaptureFixture[str]) -> None:
"""verbose=False 时不应打印命令信息."""
graph = px.Graph.from_specs(
[px.TaskSpec("echo", cmd=[*ECHO_CMD, "silent"], verbose=False)]
)
px.run(graph, strategy="sequential")
graph = px.Graph.from_specs([px.TaskSpec[Any]("echo", cmd=[*ECHO_CMD, "silent"], verbose=False)])
_ = px.run(graph, strategy="sequential")
captured = capsys.readouterr()
assert "执行命令" not in captured.out
assert "返回码" not in captured.out
@@ -390,7 +385,7 @@ class TestTaskSpecVerbose:
shell_cmd = "echo 'shell-verbose'"
graph = px.Graph.from_specs([px.TaskSpec("shell", cmd=shell_cmd, verbose=True)])
px.run(graph, strategy="sequential")
_ = px.run(graph, strategy="sequential")
captured = capsys.readouterr()
assert "执行 Shell" in captured.out
@@ -399,16 +394,12 @@ class TestTaskSpecVerbose:
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
graph = px.Graph.from_specs(
[px.TaskSpec("ls", cmd=ECHO_CMD, cwd=Path(tmpdir), verbose=True)]
)
px.run(graph, strategy="sequential")
graph = px.Graph.from_specs([px.TaskSpec[Any]("ls", cmd=ECHO_CMD, cwd=Path(tmpdir), verbose=True)])
_ = px.run(graph, strategy="sequential")
captured = capsys.readouterr()
assert "工作目录" in captured.out
def test_verbose_failure_includes_returncode(
self, capsys: pytest.CaptureFixture[str]
) -> None:
def test_verbose_failure_includes_returncode(self, capsys: pytest.CaptureFixture[str]) -> None:
"""verbose=True 时失败也应打印返回码."""
from pyflowx.errors import TaskFailedError
@@ -422,7 +413,7 @@ class TestTaskSpecVerbose:
]
)
with pytest.raises(TaskFailedError):
px.run(graph, strategy="sequential")
_ = px.run(graph, strategy="sequential")
captured = capsys.readouterr()
assert "返回码" in captured.out
@@ -437,16 +428,11 @@ class TestTaskSpecCmdErrors:
"""命令不存在时应抛出 RuntimeError."""
from pyflowx.errors import TaskFailedError
graph = px.Graph.from_specs(
[px.TaskSpec("missing", cmd=["this-command-does-not-exist-xyz"])]
)
graph = px.Graph.from_specs([px.TaskSpec("missing", cmd=["this-command-does-not-exist-xyz"])])
with pytest.raises(TaskFailedError) as exc_info:
px.run(graph, strategy="sequential")
_ = px.run(graph, strategy="sequential")
# 错误信息应包含命令未找到
assert (
"命令未找到" in str(exc_info.value.cause)
or "not found" in str(exc_info.value.cause).lower()
)
assert "命令未找到" in str(exc_info.value.cause) or "not found" in str(exc_info.value.cause).lower()
def test_cmd_list_failure_includes_stderr(self) -> None:
"""命令失败时错误信息应包含 stderr."""
@@ -465,7 +451,7 @@ class TestTaskSpecCmdErrors:
]
)
with pytest.raises(TaskFailedError) as exc_info:
px.run(graph, strategy="sequential")
_ = px.run(graph, strategy="sequential")
# 非 verbose 模式下, stderr 应包含在错误信息中
assert "error-msg" in str(exc_info.value.cause)
@@ -473,19 +459,15 @@ class TestTaskSpecCmdErrors:
"""shell 命令不存在时应抛出 RuntimeError."""
from pyflowx.errors import TaskFailedError
graph = px.Graph.from_specs(
[px.TaskSpec("missing", cmd="this-command-does-not-exist-xyz-123")]
)
graph = px.Graph.from_specs([px.TaskSpec("missing", cmd="this-command-does-not-exist-xyz-123")])
with pytest.raises(TaskFailedError):
px.run(graph, strategy="sequential")
_ = px.run(graph, strategy="sequential")
def test_cmd_string_failure(self) -> None:
"""shell 命令失败时应抛出 RuntimeError."""
from pyflowx.errors import TaskFailedError
graph = px.Graph.from_specs(
[px.TaskSpec("fail", cmd='python -c "import sys; sys.exit(1)"')]
)
graph = px.Graph.from_specs([px.TaskSpec("fail", cmd='python -c "import sys; sys.exit(1)"')])
with pytest.raises(TaskFailedError) as exc_info:
_ = px.run(graph, strategy="sequential")
assert "Shell 命令执行失败" in str(exc_info.value.cause)
@@ -513,32 +495,12 @@ class TestTaskSpecCmdErrors:
"""shell 命令超时应抛出 RuntimeError."""
from pyflowx.errors import TaskFailedError
graph = px.Graph.from_specs(
[
px.TaskSpec(
"slow", cmd='python -c "import time; time.sleep(5)"', timeout=0.1
)
]
)
graph = px.Graph.from_specs([px.TaskSpec("slow", cmd='python -c "import time; time.sleep(5)"', timeout=0.1)])
with pytest.raises(TaskFailedError) as exc_info:
_ = px.run(graph, strategy="sequential")
assert "超时" in str(exc_info.value.cause)
def test_unsupported_cmd_type_raises(self) -> None:
"""不支持的 cmd 类型应在执行时抛出 TypeError."""
from pyflowx.errors import TaskFailedError
graph = px.Graph.from_specs(
[px.TaskSpec("bad", cmd=123)] # type: ignore[arg-type]
)
with pytest.raises((TypeError, TaskFailedError)):
_ = px.run(graph, strategy="sequential")
def test_no_fn_no_cmd_raises(self) -> None:
"""没有 fn 和 cmd 时应抛出 ValueError."""
with pytest.raises(ValueError, match="必须提供 fn 或 cmd"):
px.TaskSpec("empty")
if __name__ == "__main__":
pytest.main([__file__, "-v"])
_ = px.TaskSpec("empty")