fix: 修复 py310 下 TaskSpec[Any]() 触发的 super() TypeError,CI 测试环境改为 py310,py314
CI / Lint, Typecheck & Test (push) Has been cancelled

This commit is contained in:
2026-07-08 21:36:16 +08:00
parent 8225f2fa3b
commit e0c9b0c821
6 changed files with 73 additions and 93 deletions
+2 -2
View File
@@ -30,5 +30,5 @@ jobs:
- name: Ruff check
run: ruff check src tests
- name: Tox test (py38, py313)
run: uvx tox run -e py38,py313
- name: Tox test (py310, py314)
run: uvx tox run -e py310,py314
+3 -3
View File
@@ -27,7 +27,7 @@ def _fn() -> int:
def _spec(name: str, deps: tuple[str, ...] = ()) -> TaskSpec[Any]:
return TaskSpec[Any](name, _fn, depends_on=deps)
return TaskSpec(name, _fn, depends_on=deps)
def _result(
@@ -40,7 +40,7 @@ def _result(
) -> TaskResult[Any]:
"""构造带时间戳的 TaskResult."""
end = start + timedelta(seconds=duration) if duration > 0 else start
return TaskResult[Any](
return TaskResult(
spec=_spec(name),
status=status,
value=None,
@@ -139,7 +139,7 @@ class TestToHtml:
start = datetime(2024, 1, 1, 0, 0, 0)
report = px.RunReport()
report.results["a"] = _result("a", start, 1.0)
report.results["b"] = TaskResult[Any](
report.results["b"] = TaskResult(
spec=_spec("b"),
status=TaskStatus.SKIPPED,
reason="skip",
+6 -8
View File
@@ -27,10 +27,10 @@ def _make_result( # noqa: PLR0913
attempts: int = 1,
) -> TaskResult[Any]:
"""构造测试用 TaskResult。"""
spec: TaskSpec[Any] = TaskSpec[Any](name, _fn, depends_on=depends_on, soft_depends_on=soft_depends_on)
spec: TaskSpec[Any] = TaskSpec(name, _fn, depends_on=depends_on, soft_depends_on=soft_depends_on)
start = datetime(2024, 1, 1, 0, 0, 0)
end = datetime(2024, 1, 1, 0, 0, 1) if status != TaskStatus.PENDING else None
return TaskResult[Any](
return TaskResult(
spec=spec,
status=status,
value=value,
@@ -507,12 +507,10 @@ class TestRunReportIntegration:
return 1
# continue_on_error=True 使失败不抛异常而写入报告(success 保持 True
graph = px.Graph.from_specs(
[
px.TaskSpec("boom", boom, continue_on_error=True),
px.TaskSpec("downstream", downstream, depends_on=("boom",)),
]
)
graph = px.Graph.from_specs([
px.TaskSpec("boom", boom, continue_on_error=True),
px.TaskSpec("downstream", downstream, depends_on=("boom",)),
])
report = px.run(graph, strategy="sequential")
# continue_on_error 不影响 success 标志,手动标记以触发诊断
report.success = False
+48 -66
View File
@@ -21,7 +21,7 @@ def _fn() -> int:
def _spec(name: str, deps: tuple[str, ...] = ()) -> TaskSpec[Any]:
return TaskSpec[Any](name, _fn, depends_on=deps)
return TaskSpec(name, _fn, depends_on=deps)
def _result(
@@ -34,7 +34,7 @@ def _result(
) -> TaskResult[Any]:
"""构造带时间戳的 TaskResult."""
end = start + timedelta(seconds=duration) if duration > 0 else start
return TaskResult[Any](
return TaskResult(
spec=_spec(name),
status=status,
value=None,
@@ -46,7 +46,7 @@ def _result(
def _skipped_result(name: str, reason: str = "skip") -> TaskResult[Any]:
"""构造 SKIPPED 结果(无时间戳)."""
return TaskResult[Any](
return TaskResult(
spec=_spec(name),
status=TaskStatus.SKIPPED,
reason=reason,
@@ -95,13 +95,11 @@ class TestProfileReportConstruction:
report.results["a"] = _result("a", start, 1.0)
report.results["b"] = _result("b", start + timedelta(seconds=1), 2.0)
report.results["c"] = _result("c", start + timedelta(seconds=3), 1.5)
graph = px.Graph.from_specs(
[
_spec("a"),
_spec("b", deps=("a",)),
_spec("c", deps=("b",)),
]
)
graph = px.Graph.from_specs([
_spec("a"),
_spec("b", deps=("a",)),
_spec("c", deps=("b",)),
])
profile = ProfileReport.from_report(report, graph)
@@ -138,13 +136,11 @@ class TestProfileReportConstruction:
report.results["a"] = _result("a", start, 1.0)
report.results["b"] = _result("b", start, 3.0)
report.results["c"] = _result("c", start + timedelta(seconds=3), 1.0)
graph = px.Graph.from_specs(
[
_spec("a"),
_spec("b"),
_spec("c", deps=("a", "b")),
]
)
graph = px.Graph.from_specs([
_spec("a"),
_spec("b"),
_spec("c", deps=("a", "b")),
])
profile = ProfileReport.from_report(report, graph)
@@ -192,12 +188,10 @@ class TestWaitTime:
report = px.RunReport()
report.results["a"] = _result("a", start, 1.0)
report.results["b"] = _result("b", start + timedelta(seconds=1.5), 1.0)
graph = px.Graph.from_specs(
[
_spec("a"),
_spec("b", deps=("a",)),
]
)
graph = px.Graph.from_specs([
_spec("a"),
_spec("b", deps=("a",)),
])
profile = ProfileReport.from_report(report, graph)
@@ -210,12 +204,10 @@ class TestWaitTime:
report.results["a"] = _result("a", start, 2.0)
# b 在 a 还没完成时就开始(不应该但可能发生)
report.results["b"] = _result("b", start + timedelta(seconds=1), 1.0)
graph = px.Graph.from_specs(
[
_spec("a"),
_spec("b", deps=("a",)),
]
)
graph = px.Graph.from_specs([
_spec("a"),
_spec("b", deps=("a",)),
])
profile = ProfileReport.from_report(report, graph)
@@ -228,12 +220,10 @@ class TestWaitTime:
report = px.RunReport()
report.results["a"] = _result("a", start, 1.0)
report.results["b"] = _skipped_result("b")
graph = px.Graph.from_specs(
[
_spec("a"),
_spec("b", deps=("a",)),
]
)
graph = px.Graph.from_specs([
_spec("a"),
_spec("b", deps=("a",)),
])
profile = ProfileReport.from_report(report, graph)
@@ -251,14 +241,12 @@ class TestCriticalPath:
report.results["b"] = _result("b", start + timedelta(seconds=1), 3.0)
report.results["c"] = _result("c", start + timedelta(seconds=1), 1.0)
report.results["d"] = _result("d", start + timedelta(seconds=4), 1.0)
graph = px.Graph.from_specs(
[
_spec("a"),
_spec("b", deps=("a",)),
_spec("c", deps=("a",)),
_spec("d", deps=("b", "c")),
]
)
graph = px.Graph.from_specs([
_spec("a"),
_spec("b", deps=("a",)),
_spec("c", deps=("a",)),
_spec("d", deps=("b", "c")),
])
profile = ProfileReport.from_report(report, graph)
@@ -291,7 +279,7 @@ class TestParallelism:
def test_no_timestamps_zero_parallelism(self) -> None:
"""所有任务无时间戳:并行度为 0."""
report = px.RunReport()
report.results["a"] = TaskResult[Any](spec=_spec("a"), status=TaskStatus.SUCCESS)
report.results["a"] = TaskResult(spec=_spec("a"), status=TaskStatus.SUCCESS)
graph = px.Graph.from_specs([_spec("a")])
profile = ProfileReport.from_report(report, graph)
@@ -405,14 +393,12 @@ class TestQueries:
report.results["b"] = _result("b", start + timedelta(seconds=1), 3.0)
report.results["c"] = _result("c", start + timedelta(seconds=1), 1.0)
report.results["d"] = _result("d", start + timedelta(seconds=4), 1.0)
graph = px.Graph.from_specs(
[
_spec("a"),
_spec("b", deps=("a",)),
_spec("c", deps=("a",)),
_spec("d", deps=("b", "c")),
]
)
graph = px.Graph.from_specs([
_spec("a"),
_spec("b", deps=("a",)),
_spec("c", deps=("a",)),
_spec("d", deps=("b", "c")),
])
profile = ProfileReport.from_report(report, graph)
@@ -551,13 +537,11 @@ class TestIntegrationWithRun:
time.sleep(0.01) # 确保任务有实际耗时,避免 duration 极小导致并行度计算为 0
return 1
graph = px.Graph.from_specs(
[
px.TaskSpec("a", slow),
px.TaskSpec("b", slow, depends_on=("a",)),
px.TaskSpec("c", slow, depends_on=("a",)),
]
)
graph = px.Graph.from_specs([
px.TaskSpec("a", slow),
px.TaskSpec("b", slow, depends_on=("a",)),
px.TaskSpec("c", slow, depends_on=("a",)),
])
report = px.run(graph, strategy="sequential")
profile = ProfileReport.from_report(report, graph)
@@ -576,13 +560,11 @@ class TestIntegrationWithRun:
time.sleep(0.05)
return 1
graph = px.Graph.from_specs(
[
px.TaskSpec("a", slow),
px.TaskSpec("b", slow),
px.TaskSpec("c", slow),
]
)
graph = px.Graph.from_specs([
px.TaskSpec("a", slow),
px.TaskSpec("b", slow),
px.TaskSpec("c", slow),
])
report = px.run(graph, strategy="thread", max_workers=3)
profile = ProfileReport.from_report(report, graph)
+11 -11
View File
@@ -32,11 +32,11 @@ def _make_result( # noqa: PLR0913
reason: str | None = None,
) -> TaskResult[Any]:
"""构造测试用 TaskResult 实例."""
spec: TaskSpec[Any] = TaskSpec[Any](name, _fn, tags=tags, depends_on=depends_on)
spec: TaskSpec[Any] = TaskSpec(name, _fn, tags=tags, depends_on=depends_on)
start = datetime(2024, 1, 1, 0, 0, 0)
# 用 timedelta 精确表达秒数,避免 int() 截断小数
end = start + timedelta(seconds=duration) if duration else None
return TaskResult[Any](
return TaskResult(
spec=spec,
status=status,
value=value,
@@ -98,7 +98,7 @@ class TestRunReportSummary:
def test_summary_with_none_duration(self) -> None:
"""未开始/未结束的任务 duration 为 None,不应计入总时长."""
report = px.RunReport()
spec: TaskSpec[Any] = TaskSpec[Any]("a", _fn) # type: ignore[arg-type]
spec: TaskSpec[Any] = TaskSpec("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
@@ -135,8 +135,8 @@ class TestRunReportDescribe:
def test_describe_no_duration(self) -> None:
"""无耗时的任务应显示为 '-'."""
report = px.RunReport()
spec: TaskSpec[Any] = TaskSpec[Any]("a", _fn) # type: ignore[arg-type]
report.results["a"] = TaskResult[Any](spec=spec, status=TaskStatus.PENDING)
spec: TaskSpec[Any] = TaskSpec("a", _fn) # type: ignore[arg-type]
report.results["a"] = TaskResult(spec=spec, status=TaskStatus.PENDING)
desc = report.describe()
assert "-" in desc # duration 显示为 "-"
@@ -181,8 +181,8 @@ class TestRunReportQueries:
def test_durations_no_duration(self) -> None:
"""无时长的任务应返回 0.0."""
report = px.RunReport()
spec: TaskSpec[Any] = TaskSpec[Any]("a", _fn) # type: ignore[arg-type]
report.results["a"] = TaskResult[Any](spec=spec, status=TaskStatus.PENDING)
spec: TaskSpec[Any] = TaskSpec("a", _fn) # type: ignore[arg-type]
report.results["a"] = TaskResult(spec=spec, status=TaskStatus.PENDING)
durs = report.durations()
assert durs["a"] == 0.0
@@ -257,8 +257,8 @@ class TestRunReportToDict:
def test_to_dict_no_timestamps(self) -> None:
"""未开始/未结束的任务 started_at/finished_at/duration 应为 None。"""
report = px.RunReport()
spec: TaskSpec[Any] = TaskSpec[Any]("a", _fn) # type: ignore[arg-type]
report.results["a"] = TaskResult[Any](spec=spec, status=TaskStatus.PENDING)
spec: TaskSpec[Any] = TaskSpec("a", _fn) # type: ignore[arg-type]
report.results["a"] = TaskResult(spec=spec, status=TaskStatus.PENDING)
d = report.to_dict()
entry = d["results"][0]
assert entry["started_at"] is None
@@ -439,7 +439,7 @@ class TestRunReportToHtml:
"""任务名含 HTML 特殊字符应被转义。"""
report = px.RunReport()
malicious = "<script>evil()</script>"
spec: TaskSpec[Any] = TaskSpec[Any](malicious, _fn)
spec: TaskSpec[Any] = TaskSpec(malicious, _fn)
result: TaskResult[Any] = TaskResult(
spec=spec,
status=TaskStatus.SUCCESS,
@@ -538,7 +538,7 @@ class TestRunReportToHtml:
"""无时间戳时应渲染占位符。"""
report = px.RunReport()
# 构造无时间戳的结果
spec: TaskSpec[Any] = TaskSpec[Any]("a", _fn)
spec: TaskSpec[Any] = TaskSpec("a", _fn)
result: TaskResult[Any] = TaskResult(
spec=spec,
status=TaskStatus.SUCCESS,
+3 -3
View File
@@ -354,7 +354,7 @@ class TestTaskSpecVerbose:
def test_verbose_default_is_false(self) -> None:
"""verbose 默认应为 False."""
spec: px.TaskSpec[Any] = px.TaskSpec[Any]("a", cmd=[*ECHO_CMD, "hi"])
spec: px.TaskSpec[Any] = px.TaskSpec("a", cmd=[*ECHO_CMD, "hi"])
assert spec.verbose is False
def test_verbose_true_prints_command(self, capsys: pytest.CaptureFixture[str]) -> None:
@@ -367,7 +367,7 @@ class TestTaskSpecVerbose:
def test_verbose_false_silent(self, capsys: pytest.CaptureFixture[str]) -> None:
"""verbose=False 时不应打印命令信息."""
graph = px.Graph.from_specs([px.TaskSpec[Any]("echo", cmd=[*ECHO_CMD, "silent"], verbose=False)])
graph = px.Graph.from_specs([px.TaskSpec("echo", cmd=[*ECHO_CMD, "silent"], verbose=False)])
_ = px.run(graph, strategy="sequential")
captured = capsys.readouterr()
assert "执行命令" not in captured.out
@@ -390,7 +390,7 @@ class TestTaskSpecVerbose:
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
graph = px.Graph.from_specs([px.TaskSpec[Any]("ls", cmd=ECHO_CMD, cwd=Path(tmpdir), verbose=True)])
graph = px.Graph.from_specs([px.TaskSpec("ls", cmd=ECHO_CMD, cwd=Path(tmpdir), verbose=True)])
_ = px.run(graph, strategy="sequential")
captured = capsys.readouterr()
assert "工作目录" in captured.out