PyFlowX
轻量、类型安全的 DAG 任务调度器。
PyFlowX 把"任务依赖"这件事做到极致简单:参数名就是依赖声明。无需装饰器、 无需样板包装器,写一个普通函数,框架按参数名自动注入上游结果。
特性
- 零样板 —— 参数名即依赖,框架自动注入上游结果
- 四种执行策略 ——
sequential(调试)/thread(I/O 密集同步)/async(I/O 密集异步)/dependency(依赖驱动,最大化并行) - 类型安全 ——
TaskSpec[T]把返回类型一路传到RunReport,mypy strict 通过 - DAG 校验 —— 构建时即时校验重名、缺失依赖、环
- 自动分层 —— Kahn 算法分组,同层任务可并行
- 重试与超时 —— 每个任务独立配置
RetryPolicy(max_attempts/delay/backoff/jitter/retry_on)与timeout - 软依赖 ——
soft_depends_on仅用于上下文注入,不参与拓扑分层 - 并发限制 ——
concurrency_key+concurrency_limits按组限流 - 任务钩子 ——
TaskHooks(pre_run/post_run/on_failure)生命周期回调 - 断点续跑 ——
MemoryBackend/JSONBackend,成功结果可缓存复用;batch()批量落盘;invalidate(key)单键失效 - 缓存键 ——
cache_key函数基于输入计算稳定键,使不同输入产生独立缓存 - 缓存驱逐 ——
MemoryBackend(maxsize=N)LRU 驱逐最旧条目;TTL 过期自动忽略 - 命令任务 ——
cmd参数直接执行外部命令,支持列表/shell/可调用对象 - 条件执行 ——
conditions参数按平台、环境变量、应用安装等条件跳过任务 - 图组合 ——
compose/GraphComposer编程式展开多图字符串引用 - 任务模板 ——
task_template工厂批量生成相似 TaskSpec - 图级默认值 ——
GraphDefaults统一配置 retry/timeout/concurrency 等 - CLI 运行器 ——
CliRunner把多个图映射为命令行子命令,替代 Makefile - 可观测 ——
on_event回调(RUNNING/SUCCESS/FAILED/SKIPPED)、dry_run预览、verbose生命周期日志、Mermaid 可视化 - 进度监控 ——
run(progress=True)开箱即用的 rich 进度条;ProgressCallback协议供程序化集成 - 任务通知 ——
Notifier协议支持飞书/钉钉/企业微信/Slack/Discord/Telegram webhook 与 Python 回调,全生命周期事件可配置 - 流式获取 ——
run_iter()生成器逐个 yield 任务结果,适用于大量任务逐个处理 - 任务取消 ——
CancelToken线程安全取消令牌,支持外部取消与KeyboardInterrupt优雅停止 - 子图执行 ——
run(only=[...], tags=[...])按名称或标签运行图的子集,自动包含传递依赖 - 结果序列化 ——
RunReport.to_json()/to_dict()/to_csv()/to_html()多格式导出运行结果,from_json()支持反序列化重建;HTML 报告自包含内联 CSS,适合浏览器直接打开或邮件分享 - CLI 可视化 ——
pf graph <yaml>终端 DAG 渲染(ASCII 分层树按标签着色 / Mermaid / 依赖表),pf yamlrun --progressrich 进度条,pf info [tool]工具详情,pf completion bash|zsh|fishshell 补全 - 失败诊断 ——
RunReport.diagnose()自动分析根因、依赖链回溯、相似失败聚类与可操作建议;CLI 失败时自动打印诊断摘要 - 可观测性 ——
run_id贯穿日志/序列化/诊断;结构化日志extra含task_name/status/attempts/error_type;profile(graph)离线性能分析(关键路径/并行度/瓶颈) - 状态后端 ——
MemoryBackend/JSONBackend/SQLiteBackend(WAL 模式,适合大规模任务) - YAML 任务编排 —— GitHub Actions 风格
jobs/needs/strategy.matrix/if条件,pf yamlrun pipeline.yaml一键执行 - 最小依赖 ——
rich+typer+typing-extensions(3.13 以下)+pyyaml;安装orjson可加速 JSON 序列化(pip install pyflowx[fast]) - 97% 测试覆盖 —— 分支覆盖率 >= 95%
安装
pip install pyflowx
或使用 uv:
uv add pyflowx
快速上手
import pyflowx as px
def extract() -> list[int]:
return [1, 2, 3]
# 参数名 extract 自动匹配上游任务名 → 自动注入
def double(extract: list[int]) -> list[int]:
return [x * 2 for x in extract]
graph = px.Graph.from_specs([
px.TaskSpec("extract", extract),
px.TaskSpec("double", double, ("extract",)),
])
report = px.run(graph, strategy="sequential")
print(report["double"]) # [2, 4, 6]
核心概念
TaskSpec —— 任务描述
TaskSpec 是不可变的任务描述符(Generic[T],返回类型一路传到 RunReport),是唯一需要配置的东西:
px.TaskSpec(
name="fetch_user", # 唯一标识
fn=fetch_user, # 同步或异步函数
cmd=["curl", "..."], # 或: 执行命令(覆盖 fn)
depends_on=("auth",), # 硬依赖(参与拓扑分层)
soft_depends_on=("cache",), # 软依赖(仅注入,不参与分层)
args=(uid,), # 静态位置参数(追加在注入参数后)
kwargs={"timeout": 30}, # 静态关键字参数
retry=px.RetryPolicy(max_attempts=3, delay=1.0, backoff=2.0), # 重试策略
timeout=30.0, # 超时秒数(None = 不限制)
tags=("api", "user"), # 自由标签,用于子图过滤
conditions=(is_prod,), # 条件函数列表(全部为 True 才执行)
priority=10, # 同层内优先级(高优先执行,默认 0)
concurrency_key="db", # 并发分组键(配合 concurrency_limits 限流)
cache_key=lambda ctx: str(ctx.get("uid")), # 缓存键函数(不同输入独立缓存)
hooks=px.TaskHooks(pre_run=..., post_run=..., on_failure=...), # 生命周期钩子
cwd=Path("/tmp"), # 命令工作目录(仅 cmd 模式)
env={"DEBUG": "1"}, # 环境变量覆盖(fn 与 cmd 模式均生效)
verbose=True, # 打印命令输出(仅 cmd 模式)
skip_if_missing=True, # 命令不存在时自动跳过(仅 list[str] cmd)
allow_upstream_skip=False, # 上游 SKIPPED/FAILED 时是否仍执行
continue_on_error=False, # 本任务失败是否不中断整体
)
支持两种任务形态:
- 函数任务(
fn):普通 Python 函数,参数名驱动自动注入 - 命令任务(
cmd):执行外部命令,支持list[str]、str(shell)、Callable三种形态
skip_if_missing=True 时,list[str] 类型的 cmd 会通过 shutil.which 检查命令是否存在,不存在则跳过任务(标记为 SKIPPED)而非失败。适用于构建工具场景,避免因未安装某些工具而导致整个图执行失败。
Graph —— DAG 构建
# 图级默认值:TaskSpec 字段为 None 时回退
defaults = px.GraphDefaults(retry=px.RetryPolicy(max_attempts=2), timeout=60.0)
graph = px.Graph.from_specs([...], defaults=defaults) # 整批校验(推荐)
# 或增量构建
graph = px.Graph(defaults=defaults)
graph.add(px.TaskSpec("a", fn_a))
graph.add(px.TaskSpec("b", fn_b, ("a",)))
graph.validate() # 显式校验(环检测)
graph.layers() # 拓扑分层(run() 入口已统一校验,直接调用需自行先 validate)
graph.to_mermaid() # Mermaid 可视化
graph.describe() # 人类可读摘要
graph.subgraph(("api",)) # 按标签切片
graph.subgraph_by_names(("a", "b")) # 按名称切片
graph.map("fetch", [1, 2, 3], lambda i: TaskSpec(f"fetch_{i}", ...)) # 批量 fan-out
图组合 —— compose
compose / GraphComposer 把带字符串引用的多个图展开为纯 Graph:
graphs = {
"build": px.Graph.from_specs([px.TaskSpec("b", cmd=["echo", "b"])]),
"all": px.Graph.from_specs(["build", px.TaskSpec("t", cmd=["echo", "t"])]),
}
resolved = px.compose(graphs) # "all" 图中的 "build" 引用被展开
引用格式:"command_name"(整个图)或 "command_name.task_name"(特定任务)。
CliRunner 内部自动调用 compose。
YAML 任务编排
GitHub Actions 风格的 YAML 文件直接加载为 Graph,支持 jobs/needs/strategy.matrix/if/continue-on-error/env/defaults,以及 matrix.include/exclude 过滤、条件依赖(needs: [{job: ..., if: ...}])、outputs 声明:
# pipeline.yaml
strategy: thread
defaults:
retry: {max_attempts: 3}
env: {CI: "true"}
jobs:
setup:
cmd: ["echo", "setup"]
runs-on: linux
build:
needs: [setup]
cmd: ["echo", "build-${{ matrix.version }}"]
strategy:
matrix:
version: ["3.10", "3.11", "3.12"]
os: ["linux", "macos"]
exclude:
- version: "3.10"
os: "macos"
include:
- version: "3.13"
os: "linux"
if: "env.CI"
outputs:
version: "${{ matrix.version }}"
deploy:
needs:
- job: build
if: "env.BRANCH == 'main'"
cmd: ["echo", "deploy"]
test:
needs: [build]
cmd: ["echo", "test"]
continue-on-error: true
import pyflowx as px
graph = px.Graph.from_yaml("pipeline.yaml")
# 或从字符串解析:graph = px.parse_yaml_string("...")
report = px.run(graph, strategy="thread")
CLI 一键执行:
pf yamlrun pipeline.yaml # 执行
pf yamlrun pipeline.yaml --dry-run # 仅预览,不执行
pf yamlrun pipeline.yaml --list # 列出所有任务
pf yamlrun pipeline.yaml --quiet # 静默模式
pf yamlrun pipeline.yaml --strategy sequential # 覆盖策略
字段映射:
| YAML 字段 | TaskSpec 字段 | 说明 |
|---|---|---|
cmd / run |
cmd |
cmd 为列表,run 为 shell 字符串 |
needs |
depends_on |
矩阵 job 依赖自动展开为所有变体 |
if |
conditions |
success()/always()/env.VAR/env.VAR == 'x'/env.VAR != 'x' |
strategy.matrix |
矩阵扇出 | 笛卡尔积,命名 {job}_{key}-{value} |
strategy.matrix.include |
矩阵追加 | 额外组合(可引入新键) |
strategy.matrix.exclude |
矩阵剔除 | 匹配所有 key-value 的组合被移除 |
${{ matrix.key }} |
占位符 | cmd/run/cwd/env/outputs 中替换 |
outputs |
outputs |
任务声明的输出键值映射(元数据,不参与执行) |
needs: [{job: ..., if: ...}] |
条件依赖 | if 不满足时跳过该依赖 |
continue-on-error |
continue_on_error |
字段名支持 hyphen/underscore |
runs-on |
tags |
追加到 tags 元组 |
defaults |
GraphDefaults |
图级默认值(retry/timeout/env/cwd 等) |
任务模板 —— task_template
task_template 工厂批量生成相似 TaskSpec:
fetch = px.task_template(
fn=fetch_url,
retry=px.RetryPolicy(max_attempts=5),
timeout=30.0,
tags=("api",),
)
graph = px.Graph.from_specs([
fetch("users", url="https://api.example.com/users"),
fetch("posts", url="https://api.example.com/posts"),
])
run —— 执行
report = px.run(
graph,
strategy="async", # sequential | thread | async | dependency
max_workers=8, # thread 策略的线程池大小
concurrency_limits={"db": 2}, # 按 concurrency_key 限流
dry_run=False, # True = 仅打印计划
verbose=False, # True = 打印任务生命周期日志
on_event=callback, # 状态转换回调(RUNNING/SUCCESS/FAILED/SKIPPED)
state=px.JSONBackend("state.json"), # 断点续跑后端
continue_on_error=False, # True = 单任务失败不中断整体
)
RunReport —— 结果
report["task_name"] # 任务返回值
report.result_of("task_name") # 完整 TaskResult
report.success # 整体是否成功
report.summary() # 统计字典
report.failed_tasks() # 失败任务名列表
report.describe() # 人类可读报告
上下文注入规则
按顺序求值:
- 标注为
Context的参数 → 接收完整上游结果映射 - 名称匹配依赖 的参数 → 接收该依赖的结果(含软依赖,缺失时注入默认值)
**kwargs参数 → 接收所有依赖结果(dict)TaskSpec.args/kwargs→ 为非依赖参数提供静态值
from typing import Any, Dict
def aggregate(ctx: px.Context) -> Dict[str, Any]:
"""ctx 包含所有 depends_on 任务的返回值。"""
return dict(ctx)
def merge(fetch_a: str, fetch_b: str) -> str:
"""fetch_a / fetch_b 自动注入。"""
return fetch_a + fetch_b
def fetch_user(uid: int) -> dict: # uid 来自 TaskSpec.args
...
执行策略对比
| 策略 | 并发模型 | 适用场景 | 同步任务 | 异步任务 |
|---|---|---|---|---|
sequential |
串行 | 调试、CPU 密集 | 直接调用 | 事件循环 |
thread |
线程池 | I/O 密集同步 | 线程池 | 不支持 |
async |
事件循环 | I/O 密集异步 | 卸载到线程池 | 事件循环 |
dependency |
依赖驱动 | 最大化并行度 | 卸载到线程池 | 事件循环 |
所有策略都遵循 RetryPolicy、timeout、上下文注入、状态后端、concurrency_limits,
并发出 TaskEvent(RUNNING/SUCCESS/FAILED/SKIPPED)。dependency 策略无层屏障:
任务在其所有硬依赖完成后立即启动。
命令任务
TaskSpec 的 cmd 参数支持执行外部命令,无需包装 Python 函数:
graph = px.Graph.from_specs([
# 命令列表(推荐,参数无需转义)
px.TaskSpec("list_files", cmd=["ls", "-la"]),
# shell 字符串(支持管道、重定向)
px.TaskSpec("check_git", cmd="git status | head"),
# 带工作目录与超时
px.TaskSpec("build", cmd=["make", "all"], cwd=Path("/project"), timeout=300),
# 命令不存在时自动跳过(而非失败)
px.TaskSpec("optional_tool", cmd=["maturin", "build"], skip_if_missing=True),
])
verbose=True 时打印执行的命令、工作目录、返回码与输出;verbose=False 时静默执行(失败信息仍包含 stderr)。
skip_if_missing=True 时,list[str] 类型的 cmd 会通过 shutil.which 检查命令是否存在,不存在则跳过任务(标记为 SKIPPED)而非失败。适用于构建工具场景,避免因未安装某些工具而导致整个图执行失败。对于 str(shell)和 Callable 类型的 cmd,此参数无效。
条件执行
conditions 参数让任务按条件跳过(标记为 SKIPPED):
from pyflowx.conditions import IS_WINDOWS, BuiltinConditions
graph = px.Graph.from_specs([
# 仅在 Windows 上运行
px.TaskSpec("win_only", cmd=["dir"], conditions=(IS_WINDOWS,)),
# 仅在 git 已安装时运行
px.TaskSpec(
"git_check",
cmd=["git", "--version"],
conditions=(BuiltinConditions.HAS_INSTALLED("git"),),
),
# 组合条件
px.TaskSpec(
"prod_deploy",
fn=deploy,
conditions=(
BuiltinConditions.ENV_VAR_EQUALS("ENV", "prod"),
BuiltinConditions.HAS_INSTALLED("docker"),
),
),
])
内置条件:IS_WINDOWS / IS_LINUX / IS_MACOS / IS_POSIX / PYTHON_VERSION / HAS_INSTALLED / ENV_VAR_EXISTS / ENV_VAR_EQUALS / NOT / AND / OR。
CLI 运行器
CliRunner 把多个 Graph 映射为命令行子命令,适合构建项目专属构建工具(替代 Makefile):
runner = px.CliRunner(
strategy="sequential",
description="My Build Tool",
graphs={
"clean": clean_graph,
"build": build_graph,
"test": test_graph,
},
)
runner.run_cli() # 解析 sys.argv 并执行
命令行用法:
pf pymake clean # 执行 clean 图
pf pymake build --strategy thread # 覆盖执行策略
pf pymake test --dry-run # 仅打印执行计划
pf pymake --list # 列出所有命令
pf pymake --quiet # 静默模式
verbose=True(默认)时打印任务生命周期(开始/成功/失败/跳过)与命令输出;--quiet 关闭。
断点续跑
from pyflowx import JSONBackend
# 第一次运行:成功结果写入 state.json
backend = JSONBackend("state.json", ttl=3600) # ttl 秒数,过期条目自动忽略
report = px.run(graph, strategy="sequential", state=backend)
# 第二次运行:已缓存任务自动跳过(状态为 SKIPPED)
report = px.run(graph, strategy="sequential", state=backend)
run() 内部以 backend.batch() 包裹整个执行:所有 save 延迟到运行结束时统一落盘一次
(JSONBackend 从 O(N²) 降为 O(N) 磁盘写入;MemoryBackend 为 no-op)。
缓存键:默认存储键为任务名。配置 cache_key 函数后,键为 "name:cache_key_value",
使不同输入产生独立缓存条目:
px.TaskSpec(
"fetch_user",
fn=fetch_user,
cache_key=lambda ctx: str(ctx.get("uid")), # 不同 uid 独立缓存
)
LRU 驱逐:MemoryBackend(maxsize=N) 限制最大条目数,超限时按 LRU(最近最少使用)
策略驱逐最旧条目。get/has 命中会将条目移到最近使用位置:
from pyflowx import MemoryBackend
backend = MemoryBackend(maxsize=100) # 最多 100 条,超出驱逐最旧
手动失效:invalidate(key) 删除单个键的缓存,clear() 清除全部:
backend.invalidate("fetch_user") # 删除单个键,返回是否已删除
backend.clear() # 清除全部
错误处理
所有错误都是 PyFlowXError 的子类:
| 错误 | 触发时机 |
|---|---|
DuplicateTaskError |
任务名重复注册 |
MissingDependencyError |
依赖了不存在的任务 |
CycleError |
依赖图存在环 |
TaskFailedError |
任务耗尽重试后仍失败 |
TaskTimeoutError |
任务超时 |
InjectionError |
上下文注入无法满足签名 |
StorageError |
状态后端持久化失败 |
try:
report = px.run(graph, strategy="async")
except px.TaskFailedError as exc:
print(f"{exc.task} 失败: {exc.cause}(尝试 {exc.attempts} 次)")
except px.PyFlowXError:
# 捕获整个错误家族
raise
与其他库对比
| 特性 | PyFlowX | Airflow | Prefect | Dask |
|---|---|---|---|---|
| 零样板 | 参数名即依赖 | 装饰器 + XCom | 装饰器 | 装饰器 |
| 运行时依赖 | rich + typer | 重型 | 中型 | 中型 |
| 类型安全 | mypy strict | 弱 | 中 | 中 |
| 异步原生 | 是 | 否 | 部分 | 否 |
| 断点续跑 | 内置 | 需配置 | 需配置 | 需配置 |
| 学习曲线 | 极低 | 高 | 中 | 中 |
| 适用规模 | 单机 | 集群 | 单机/集群 | 集群 |
PyFlowX 专注于单机 DAG 调度的极致简洁,适合 ETL、数据处理、CI 流水线等场景。
高级特性
并发限制
按 concurrency_key 分组限流,避免压垮下游资源:
graph = px.Graph.from_specs([
px.TaskSpec("q1", fn=query_db, concurrency_key="db"),
px.TaskSpec("q2", fn=query_db, concurrency_key="db"),
px.TaskSpec("q3", fn=query_db, concurrency_key="db"),
])
# 同一时刻最多 2 个 "db" 组任务运行
px.run(graph, strategy="async", concurrency_limits={"db": 2})
任务钩子
TaskHooks 在任务生命周期触发(异常仅记录,不影响任务状态):
hooks = px.TaskHooks(
pre_run=lambda spec: print(f"start {spec.name}"),
post_run=lambda spec, value: print(f"done {spec.name}"),
on_failure=lambda spec, exc: alert(spec.name, exc),
)
px.TaskSpec("task", fn=work, hooks=hooks)
优先级
同层内按 priority 降序执行(稳定排序):
px.TaskSpec("low", fn=work, priority=0)
px.TaskSpec("high", fn=work, priority=10) # 同层内先执行
任务取消
CancelToken 提供线程安全的取消信号,传入 run(cancel_event=...) 即可外部取消正在执行的图。
KeyboardInterrupt(Ctrl+C)也会触发相同的优雅取消流程:
token = px.CancelToken()
# 在另一个线程中触发取消
import threading
def cancel_later():
import time
time.sleep(2)
token.cancel("超时取消")
threading.Thread(target=cancel_later, daemon=True).start()
# 取消后:待运行任务标记 SKIPPED,运行中任务等待完成,report.success = False
report = px.run(graph, strategy="dependency", cancel_event=token)
for name, result in report.results.items():
print(f"{name}: {result.status.value}")
也支持标准 threading.Event 作为取消信号:
event = threading.Event()
px.run(graph, cancel_event=event)
子图执行
run(only=...) / run(tags=...) 只运行指定任务及其传递依赖,适用于调试单个任务或增量执行:
# 只运行 "report" 任务及其所有上游依赖
report = px.run(graph, only=["report"])
# 按标签过滤:只运行带 "ingest" 标签的任务及其依赖
report = px.run(graph, tags=["ingest"])
# 两者可组合(取并集)
report = px.run(graph, only=["report"], tags=["ingest"])
CLI 也支持 --only / --tags:
pf yamlrun pipeline.yaml --only report,deploy
pf yamlrun pipeline.yaml --tags ingest,report
结果序列化
RunReport 支持将运行结果导出为 JSON / dict / CSV,便于持久化、跨进程传输或对接外部工具:
report = px.run(graph, strategy="sequential")
# JSON 字符串(含 success/summary/results)
text = report.to_json(indent=2)
# JSON 兼容字典
data = report.to_dict()
# CSV 表格导出(不含 value 列以避免特殊字符干扰)
csv_text = report.to_csv(include_value=False)
任务返回值可能是任意 Python 对象(不一定 JSON 可序列化)。默认策略:
可序列化原样保留,不可序列化回退到 repr(value);调用方可通过
value_serializer 自定义:
# 自定义值序列化(如把 Path 转为字符串)
text = report.to_json(value_serializer=str)
from_json() 可从 JSON 字符串重建 RunReport,仅供查询/分析,不能重新
执行(TaskSpec 的 fn/cmd 等不可序列化字段无法恢复):
restored = px.RunReport.from_json(text)
print(restored["double"]) # 任务返回值
print(restored.result_of("a").status) # TaskStatus
CLI 可视化
pf graph <yaml> 在终端可视化任务图 DAG,无需运行即可检查依赖关系:
# 默认 ASCII 渲染:分层树(按标签着色) + 依赖关系表
pf graph pipeline.yaml
# 输出 Mermaid 语法(可粘贴到 mermaid.live)
pf graph pipeline.yaml --format mermaid
# 自定义方向
pf graph pipeline.yaml --format mermaid --orientation LR
# 仅列出任务名
pf graph pipeline.yaml --format list
# 纯依赖关系表
pf graph pipeline.yaml --format deps
# 关闭标签着色(统一单色)
pf graph pipeline.yaml --color-by none
ASCII 模式默认 --color-by tag,按任务首标签选择 rich 颜色并在图下方
显示标签颜色图例,便于视觉分组。
pf yamlrun 新增 --progress 选项,启用 rich 进度条显示运行中任务、完成
数与耗时:
pf yamlrun pipeline.yaml --progress
任务列表过滤
pf yamlrun --list 支持 --list-tag / --list-name 过滤,便于大型 YAML
中快速定位任务:
# 列出所有任务
pf yamlrun pipeline.yaml --list
# 只列出含 ingest 标签的任务
pf yamlrun pipeline.yaml --list --list-tag ingest
# 按任务名子串过滤(大小写不敏感)
pf yamlrun pipeline.yaml --list --list-name extract
# 组合过滤(交集)
pf yamlrun pipeline.yaml --list --list-tag ingest --list-name extract
工具详情与 Shell 补全
pf info [tool] 显示工具详情:
# 列出所有工具的简表(含子命令数、描述)
pf info
# 显示指定工具的子命令详情
pf info clr
pf info gittool
pf completion <shell> 生成 shell 补全脚本,提升日常使用体验:
# bash —— 添加到 ~/.bashrc
pf completion bash >> ~/.bashrc
# zsh —— 添加到 ~/.zshrc
pf completion zsh >> ~/.zshrc
# fish —— 保存到 ~/.config/fish/completions/pf.fish
pf completion fish > ~/.config/fish/completions/pf.fish
补全脚本覆盖所有内建命令(yamlrun/graph/info/completion)与
@px.tool 注册的工具名;二级补全交给具体子命令的 --help 自描述。
失败诊断
运行失败时,RunReport.diagnose() 自动生成结构化诊断报告,包含:
- 根因任务:FAILED 且所有硬依赖非 FAILED 的任务(依赖链中最先出问题的环节)
- 依赖链回溯:从根因到每个失败任务的路径
- 相似失败聚类:按
(异常类型, 消息前缀)聚类,发现批量失败模式 - 可操作建议:识别 FileNotFoundError/ImportError/TimeoutError 等常见异常并给出提示
report = px.run(graph, strategy="sequential")
if not report.success:
diag = report.diagnose()
if diag is not None:
print(diag.describe())
# 根因任务
print(diag.root_causes)
# 依赖链
for chain in diag.dependency_chains:
print(f"{chain.failed_task}: {' -> '.join(chain.chain)}")
pf yamlrun 失败时自动打印诊断摘要到 stderr:
pf yamlrun pipeline.yaml # 失败时自动输出诊断报告
report.describe() 在失败时也会附诊断摘要。
可观测性
每次 run() 自动生成 8 字符十六进制 run_id,作为本次运行的唯一追踪 ID,
贯穿日志、序列化结果与诊断报告:
report = px.run(graph)
print(report.run_id) # 如 "a3f4b2c1"
print(report.summary()["run_id"]) # summary 也含 run_id
run_id 通过结构化日志的 extra 字段注入所有执行器日志,便于跨系统关联。
日志格式遵循 logging 标准延迟格式化,extra 字段包括:
run_id—— 本次运行 IDtask_name—— 任务名(任务级日志)status—— 任务状态(running/success/failed/skipped)attempts—— 尝试次数(失败日志)error_type—— 异常类型名(失败日志)
import logging
logging.basicConfig(level=logging.INFO)
# 日志输出示例:
# INFO 运行开始: run_id=a3f4b2c1 strategy=dependency tasks=3
# INFO task 'a' skipped (cached)
# WARNING task 'b' failed (attempt 1/3): RuntimeError('boom'); retrying
# INFO 运行结束: run_id=a3f4b2c1 success=False tasks=3
to_json() / to_dict() / from_json() 同步保留 run_id,反序列化时可还原:
旧版 JSON(无 run_id 字段)会自动生成新 ID,向前兼容。
report.profile(graph) 返回 ProfileReport,基于 started_at/finished_at
做离线性能分析(关键路径、并行度、Top 瓶颈),零运行时开销:
profile = report.profile(graph)
print(profile.describe()) # 人类可读报告
print(profile.critical_path) # 关键路径任务序列
bottlenecks = profile.top_bottlenecks(5) # Top-5 瓶颈任务
开发
# 安装开发依赖
uv sync --extra dev
# 运行测试(含覆盖率,阈值 95%)
uv run pytest --cov=pyflowx --cov-fail-under=95
# 类型检查
uv run pyrefly check .
# 代码风格
uv run ruff check .
uv run ruff format --check .
模块结构
核心
| 模块 | 职责 |
|---|---|
task.py |
纯数据结构:TaskSpec、RetryPolicy、TaskHooks、TaskStatus |
graph.py |
DAG 构建、校验、分层、可视化 |
compose.py |
多图组合:GraphComposer / compose |
context.py |
上下文注入:参数名→依赖解析 |
command.py |
命令执行:run_command(list/shell/Callable) |
cancellation.py |
任务取消:CancelToken 线程安全取消令牌 |
conditions.py |
条件执行:内置条件与组合器 |
executors.py |
执行器与 run 入口:四种策略共享模块级辅助;verbose 统一应用到 spec |
storage.py |
状态后端:MemoryBackend / JSONBackend(batch flush) |
yaml_loader.py |
YAML 任务编排:load_yaml / parse_yaml_string(GitHub Actions 风格) |
runner.py |
CLI 运行器:CliRunner |
report.py |
运行结果:RunReport / TaskResult |
tools.py |
@px.tool 装饰器:ToolSpec / run_tool / list_tools / list_subcommands |
profiling.py |
性能分析:Profiler 任务耗时统计 |
errors.py |
错误家族:PyFlowXError 子类 |
ops/ |
CLI 工具模块集合(每个子模块用 @px.tool 注册一个工具) |
CLI 工具
| 模块 | 职责 |
|---|---|
cli/pf.py |
统一入口:pf <tool> [command],按需导入 ops/<tool>.py 触发 @px.tool 注册 |
cli/legacy/profiler.py |
性能分析 CLI(legacy) |
cli/legacy/emlmanager.py |
邮件管理 CLI(legacy) |
许可证
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyflowx-0.4.10.tar.gz.
File metadata
- Download URL: pyflowx-0.4.10.tar.gz
- Upload date:
- Size: 474.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
006a60f971c58a111f4e010454bb3b9bb0db5b8ce8859772f8aecd870c85d418
|
|
| MD5 |
9a440528eebcd65952d9a3b3f8c7690d
|
|
| BLAKE2b-256 |
15720e910526002241740eb5caa6189a365f687bf135907954522a006b1a58a2
|
File details
Details for the file pyflowx-0.4.10-py3-none-any.whl.
File metadata
- Download URL: pyflowx-0.4.10-py3-none-any.whl
- Upload date:
- Size: 191.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ed84744bd5e2929326970c41d7a9a433363e7d4532cd25b41d9c98da4a55c9b
|
|
| MD5 |
ad22a983a63ac78776e4152b8c9bf894
|
|
| BLAKE2b-256 |
cce803a70339c51ea612475b2e459c25e2dc5a16aac2ea2f8f77f5ddc8b80ef1
|