Files
pyflowx/tests/test_dataflow.py
T

154 lines
5.1 KiB
Python

"""数据流增强测试:outputs 引用解析、pipeline 语义化管道。
覆盖:
* RunReport.output_of() 基本路径解析("$" / 单键 / 嵌套键)。
* output_of() 错误场景(任务不存在 / 未声明 outputs / output_name 未声明)。
* Graph.pipeline() 语义化管道:数据流通过上下文注入传递。
* pipeline() 与 chain() 行为一致。
* outputs 在 to_dict 序列化中保留。
"""
from __future__ import annotations
import pytest
import pyflowx as px
from pyflowx import Graph, TaskSpec
def test_output_of_entire_result() -> None:
"""outputs={"value": "$"} → output_of 返回整个结果。"""
graph = Graph.from_specs([TaskSpec("a", fn=lambda: 42, outputs={"value": "$"})])
report = px.run(graph, strategy="sequential")
assert report.output_of("a", "value") == 42
def test_output_of_dict_key() -> None:
"""outputs={"path": "url"} → output_of 返回 result["url"]。"""
graph = Graph.from_specs([TaskSpec("a", fn=lambda: {"url": "http://x", "code": 200}, outputs={"path": "url"})])
report = px.run(graph, strategy="sequential")
assert report.output_of("a", "path") == "http://x"
def test_output_of_nested_path() -> None:
"""outputs={"host": "data.server.host"} → 嵌套字典路径解析。"""
graph = Graph.from_specs(
[
TaskSpec(
"a",
fn=lambda: {"data": {"server": {"host": "localhost"}}},
outputs={"host": "data.server.host"},
)
]
)
report = px.run(graph, strategy="sequential")
assert report.output_of("a", "host") == "localhost"
def test_output_of_task_not_found() -> None:
"""任务不在报告中时抛 KeyError。"""
graph = Graph.from_specs([TaskSpec("a", fn=lambda: 1)])
report = px.run(graph, strategy="sequential")
with pytest.raises(KeyError, match="不在报告中"):
report.output_of("nonexistent", "x")
def test_output_of_no_outputs_declared() -> None:
"""任务未声明 outputs 时抛 KeyError。"""
graph = Graph.from_specs([TaskSpec("a", fn=lambda: 1)])
report = px.run(graph, strategy="sequential")
with pytest.raises(KeyError, match="未声明输出"):
report.output_of("a", "x")
def test_output_of_output_name_not_declared() -> None:
"""output_name 不在 outputs 映射中时抛 KeyError。"""
graph = Graph.from_specs([TaskSpec("a", fn=lambda: 1, outputs={"x": "$"})])
report = px.run(graph, strategy="sequential")
with pytest.raises(KeyError, match="未声明输出"):
report.output_of("a", "y")
def test_pipeline_data_flow() -> None:
"""pipeline() 中前驱结果注入后继同名参数。"""
def extract() -> list[int]:
return [1, 2, 3]
def double(extract: list[int]) -> list[int]:
return [x * 2 for x in extract]
def total(double: list[int]) -> int:
return sum(double)
graph = Graph().pipeline(
TaskSpec("extract", fn=extract),
TaskSpec("double", fn=double),
TaskSpec("total", fn=total),
)
report = px.run(graph, strategy="sequential")
assert report["extract"] == [1, 2, 3]
assert report["double"] == [2, 4, 6]
assert report["total"] == 12
def test_pipeline_same_as_chain() -> None:
"""pipeline() 与 chain() 产生相同的依赖关系。"""
specs1 = [
TaskSpec("a", fn=lambda: 1),
TaskSpec("b", fn=lambda a: a + 1),
TaskSpec("c", fn=lambda b: b + 1),
]
specs2 = [
TaskSpec("a", fn=lambda: 1),
TaskSpec("b", fn=lambda a: a + 1),
TaskSpec("c", fn=lambda b: b + 1),
]
g1 = Graph().chain(*specs1)
g2 = Graph().pipeline(*specs2)
assert g1.deps == g2.deps
def test_pipeline_with_outputs() -> None:
"""pipeline() 配合 outputs 声明,通过 output_of 查询。"""
def make_data() -> dict[str, object]:
return {"items": [1, 2, 3], "count": 3}
graph = Graph().pipeline(
TaskSpec("make_data", fn=make_data, outputs={"items": "items", "count": "count"}),
)
report = px.run(graph, strategy="sequential")
assert report.output_of("make_data", "items") == [1, 2, 3]
assert report.output_of("make_data", "count") == 3
def test_pipeline_returns_self() -> None:
"""pipeline() 返回 self 支持链式调用。"""
graph = Graph()
result = graph.pipeline(TaskSpec("a", fn=lambda: 1))
assert result is graph
def test_output_of_with_strategy_dependency() -> None:
"""output_of 在 dependency 策略下也正常工作。"""
graph = Graph.from_specs(
[
TaskSpec(
"a",
fn=lambda: {"x": 10, "y": 20},
outputs={"x": "x", "y": "y"},
)
]
)
report = px.run(graph, strategy="dependency")
assert report.output_of("a", "x") == 10
assert report.output_of("a", "y") == 20
def test_output_of_list_result_with_dollar() -> None:
"""列表结果用 "$" 路径取整个值。"""
graph = Graph.from_specs([TaskSpec("a", fn=lambda: [1, 2, 3], outputs={"all": "$"})])
report = px.run(graph, strategy="sequential")
assert report.output_of("a", "all") == [1, 2, 3]