data-orchestrator 0.1.0__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.
- data_orchestrator-0.1.0/LICENSE +21 -0
- data_orchestrator-0.1.0/PKG-INFO +600 -0
- data_orchestrator-0.1.0/README.md +572 -0
- data_orchestrator-0.1.0/data_orchestrator.egg-info/PKG-INFO +600 -0
- data_orchestrator-0.1.0/data_orchestrator.egg-info/SOURCES.txt +46 -0
- data_orchestrator-0.1.0/data_orchestrator.egg-info/dependency_links.txt +1 -0
- data_orchestrator-0.1.0/data_orchestrator.egg-info/entry_points.txt +2 -0
- data_orchestrator-0.1.0/data_orchestrator.egg-info/requires.txt +22 -0
- data_orchestrator-0.1.0/data_orchestrator.egg-info/top_level.txt +1 -0
- data_orchestrator-0.1.0/orchestrator/__init__.py +17 -0
- data_orchestrator-0.1.0/orchestrator/cli.py +229 -0
- data_orchestrator-0.1.0/orchestrator/config/__init__.py +5 -0
- data_orchestrator-0.1.0/orchestrator/config/loader.py +136 -0
- data_orchestrator-0.1.0/orchestrator/config/settings.py +19 -0
- data_orchestrator-0.1.0/orchestrator/config/template.py +23 -0
- data_orchestrator-0.1.0/orchestrator/connectors/__init__.py +18 -0
- data_orchestrator-0.1.0/orchestrator/connectors/base.py +42 -0
- data_orchestrator-0.1.0/orchestrator/connectors/builtin/__init__.py +5 -0
- data_orchestrator-0.1.0/orchestrator/connectors/builtin/csv_file.py +72 -0
- data_orchestrator-0.1.0/orchestrator/connectors/builtin/http_api.py +120 -0
- data_orchestrator-0.1.0/orchestrator/connectors/builtin/postgres.py +59 -0
- data_orchestrator-0.1.0/orchestrator/connectors/loader.py +26 -0
- data_orchestrator-0.1.0/orchestrator/connectors/registry.py +46 -0
- data_orchestrator-0.1.0/orchestrator/core/__init__.py +18 -0
- data_orchestrator-0.1.0/orchestrator/core/api.py +102 -0
- data_orchestrator-0.1.0/orchestrator/core/pipeline.py +110 -0
- data_orchestrator-0.1.0/orchestrator/core/runner.py +393 -0
- data_orchestrator-0.1.0/orchestrator/core/schedule.py +25 -0
- data_orchestrator-0.1.0/orchestrator/core/scheduler.py +369 -0
- data_orchestrator-0.1.0/orchestrator/core/task.py +95 -0
- data_orchestrator-0.1.0/orchestrator/exceptions.py +42 -0
- data_orchestrator-0.1.0/orchestrator/log/__init__.py +5 -0
- data_orchestrator-0.1.0/orchestrator/log/models.py +55 -0
- data_orchestrator-0.1.0/orchestrator/log/reader.py +197 -0
- data_orchestrator-0.1.0/orchestrator/log/writer.py +83 -0
- data_orchestrator-0.1.0/orchestrator/notify/__init__.py +12 -0
- data_orchestrator-0.1.0/orchestrator/notify/base.py +39 -0
- data_orchestrator-0.1.0/orchestrator/notify/builtin/__init__.py +4 -0
- data_orchestrator-0.1.0/orchestrator/notify/builtin/feishu_notifier.py +41 -0
- data_orchestrator-0.1.0/orchestrator/notify/builtin/log_notifier.py +21 -0
- data_orchestrator-0.1.0/orchestrator/notify/manager.py +98 -0
- data_orchestrator-0.1.0/orchestrator/streamlit_thread.py +17 -0
- data_orchestrator-0.1.0/orchestrator/ui/__init__.py +3 -0
- data_orchestrator-0.1.0/orchestrator/ui/app.py +408 -0
- data_orchestrator-0.1.0/pyproject.toml +36 -0
- data_orchestrator-0.1.0/setup.cfg +4 -0
- data_orchestrator-0.1.0/tests/test_cli.py +188 -0
- data_orchestrator-0.1.0/tests/test_smoke.py +7 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Livid Su
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: data-orchestrator
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight private data orchestration framework.
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: pydantic>=2.0
|
|
9
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
10
|
+
Requires-Dist: apscheduler>=3.10
|
|
11
|
+
Requires-Dist: tenacity>=8.0
|
|
12
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
13
|
+
Requires-Dist: click>=8.0
|
|
14
|
+
Requires-Dist: pyyaml>=6.0
|
|
15
|
+
Requires-Dist: rich>=13.0
|
|
16
|
+
Requires-Dist: jinja2>=3.0
|
|
17
|
+
Requires-Dist: requests>=2.31
|
|
18
|
+
Provides-Extra: ui
|
|
19
|
+
Requires-Dist: streamlit>=1.30; extra == "ui"
|
|
20
|
+
Provides-Extra: postgres
|
|
21
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "postgres"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
25
|
+
Requires-Dist: responses; extra == "dev"
|
|
26
|
+
Requires-Dist: freezegun; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Data Orchestrator
|
|
30
|
+
|
|
31
|
+
> 轻量级数据编排框架。把所有"定期去某个地方拿数据、处理、再放到某个地方"的事情,统一管理起来。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 目录
|
|
36
|
+
|
|
37
|
+
- [为什么要用这个](#为什么要用这个)
|
|
38
|
+
- [核心特性](#核心特性)
|
|
39
|
+
- [快速开始](#快速开始)
|
|
40
|
+
- [核心概念](#核心概念)
|
|
41
|
+
- [编写 Connector](#编写-connector)
|
|
42
|
+
- [编写 Pipeline (YAML)](#编写-pipeline-yaml)
|
|
43
|
+
- [调度配置](#调度配置)
|
|
44
|
+
- [数据流传递](#数据流传递)
|
|
45
|
+
- [重试与超时](#重试与超时)
|
|
46
|
+
- [告警通知](#告警通知)
|
|
47
|
+
- [Web UI 界面](#web-ui-界面)
|
|
48
|
+
- [命令行与 API 管理](#命令行与-api-管理)
|
|
49
|
+
- [内置 Connector](#内置-connector)
|
|
50
|
+
- [与现有方案对比](#与现有方案对比)
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 为什么要用这个
|
|
55
|
+
|
|
56
|
+
你的数据任务大概是这个样子:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
❌ 现在
|
|
60
|
+
────────────────────────────────────────────────────────────
|
|
61
|
+
scripts/pull_orders.py ← 每天手动跑,或者一个 crontab
|
|
62
|
+
scripts/sync_feishu.py ← 偶尔失败,你不知道
|
|
63
|
+
scripts/generate_report.py ← 上次跑成功是什么时候?忘了
|
|
64
|
+
────────────────────────────────────────────────────────────
|
|
65
|
+
没有重试 没有日志 没有告警 出了问题靠命
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
✅ 用 Orchestrator 之后
|
|
70
|
+
────────────────────────────────────────────────────────────
|
|
71
|
+
pipelines/
|
|
72
|
+
daily_sync.yaml ← 描述你要做什么
|
|
73
|
+
weekly_report.yaml
|
|
74
|
+
connectors/
|
|
75
|
+
shopify.py ← 一次编写,永久复用
|
|
76
|
+
feishu.py
|
|
77
|
+
|
|
78
|
+
$ orchestrator run ← 启动,自带 Web UI
|
|
79
|
+
────────────────────────────────────────────────────────────
|
|
80
|
+
✓ 定时自动触发 ✓ 失败自动重试、超时控制
|
|
81
|
+
✓ DAG 并发执行与数据流传递 ✓ Web UI 可视化面板与日志检索
|
|
82
|
+
✓ HTTP API 调度管控 ✓ 飞书告警通知防抖
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 核心特性
|
|
88
|
+
|
|
89
|
+
- **业务与框架分离**:框架只提供骨架(调度、重试、日志、UI、API),Connector 和 Pipeline 完全放在你的项目中独立维护。升级框架不影响你的业务代码。
|
|
90
|
+
- **YAML 编排驱动**:支持任务 DAG 依赖(`depends_on`),可控制全局并发与失败阻断策略。
|
|
91
|
+
- **数据流传递**:支持上游任务输出无缝作为下游任务的输入(`pass_output_from`)。
|
|
92
|
+
- **运行时动态模板**:使用 Jinja 模板(如 `{{ today }}`、`{{ yesterday_iso }}`、`{{ run_id }}`),并在执行时而非加载时动态渲染。
|
|
93
|
+
- **内置 Web UI 与 API**:基于 Streamlit 的可视化看板,并内置轻量级 HTTP API,支持外部系统进行触发管控。
|
|
94
|
+
- **Pipeline 级 Hook**:支持 `on_success` 和 `on_failure`,轻松实现流水线串联或回调通知。
|
|
95
|
+
- **连接器生命周期管理**:连接器在一次 Pipeline 执行中跨 Task 自动复用,提供同步及默认的 `async_fetch` / `async_push` 异步封装。
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 快速开始
|
|
100
|
+
|
|
101
|
+
### 1. 安装
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install data-orchestrator[ui,postgres]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
*(可选:仅安装核心依赖 `pip install data-orchestrator`)*
|
|
108
|
+
|
|
109
|
+
### 2. 初始化项目
|
|
110
|
+
|
|
111
|
+
使用 `init` 命令快速生成骨架代码:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
orchestrator init my_data_project
|
|
115
|
+
cd my_data_project
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
这将自动生成如下目录结构:
|
|
119
|
+
```
|
|
120
|
+
my_data_project/
|
|
121
|
+
├── connectors/
|
|
122
|
+
│ └── demo.py # 你的自定义逻辑
|
|
123
|
+
├── pipelines/
|
|
124
|
+
│ └── demo.yaml # 任务编排配置
|
|
125
|
+
└── main.py # 启动脚本
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 3. 启动运行
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# 通过 CLI 启动
|
|
132
|
+
orchestrator run --config pipelines/ --plugins connectors/
|
|
133
|
+
|
|
134
|
+
# 或者直接运行 main.py
|
|
135
|
+
python main.py
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
启动后,访问 **http://localhost:8501** 即可看到内置的可视化看板。
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 核心概念
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
Connector ── 知道怎么和某个系统交互。不仅限 fetch / push,
|
|
146
|
+
你可以定义任意 action(如 send_report)。
|
|
147
|
+
↓
|
|
148
|
+
Task ── 用某个 Connector 执行具体的动作(拉数据/推数据)
|
|
149
|
+
↓
|
|
150
|
+
Pipeline ── 将多个 Task 按 DAG(有向无环图)组合成一条数据链路
|
|
151
|
+
↓
|
|
152
|
+
Scheduler ── 决定 Pipeline 的执行策略(cron / interval / manual)
|
|
153
|
+
↓
|
|
154
|
+
UI / API ── 可视化与管控接口
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**核心设计原则:框架提供骨架,业务代码在框架外。**
|
|
158
|
+
框架只负责调度、重试、日志、通知这些通用能力。你的 Connector 实现、YAML 配置,完全放在自己的项目里,通过注册机制接入框架。
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 编写 Connector
|
|
163
|
+
|
|
164
|
+
所有 Connector 继承 `BaseConnector`,只需实现自己用到的方法。
|
|
165
|
+
框架通过 YAML 里的 `action` 字段按名称调用方法,因此 Connector 上的任何公开方法都可以作为 action。`fetch` / `push` / `ping` 是常见约定,但不是强制要求。
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# connectors/shopify.py
|
|
169
|
+
import requests
|
|
170
|
+
from orchestrator import BaseConnector, register_connector
|
|
171
|
+
|
|
172
|
+
@register_connector("shopify")
|
|
173
|
+
class ShopifyConnector(BaseConnector):
|
|
174
|
+
|
|
175
|
+
def __init__(self, config: dict):
|
|
176
|
+
super().__init__(config)
|
|
177
|
+
self.shop_url = config["shop_url"]
|
|
178
|
+
self.access_token = config["access_token"]
|
|
179
|
+
self.session = requests.Session()
|
|
180
|
+
self.session.headers.update({"X-Shopify-Access-Token": self.access_token})
|
|
181
|
+
|
|
182
|
+
def fetch(self, endpoint: str, params: dict = None, **kwargs):
|
|
183
|
+
"""拉取数据:返回值将作为 TaskResult.output 传递给下游"""
|
|
184
|
+
url = f"{self.shop_url}/admin/api/2024-01/{endpoint}"
|
|
185
|
+
r = self.session.get(url, params=params)
|
|
186
|
+
r.raise_for_status()
|
|
187
|
+
return r.json()
|
|
188
|
+
|
|
189
|
+
def push(self, data, endpoint: str, **kwargs):
|
|
190
|
+
"""推送数据:data 参数可以通过 pass_output_from 由上游 Task 传入"""
|
|
191
|
+
url = f"{self.shop_url}/admin/api/2024-01/{endpoint}"
|
|
192
|
+
self.session.post(url, json=data)
|
|
193
|
+
|
|
194
|
+
def ping(self) -> bool:
|
|
195
|
+
"""连通性健康检查:返回 True/False,不要抛异常。框架启动时进行验证"""
|
|
196
|
+
try:
|
|
197
|
+
r = self.session.get(f"{self.shop_url}/admin/api/2024-01/shop.json", timeout=5)
|
|
198
|
+
return r.status_code == 200
|
|
199
|
+
except Exception:
|
|
200
|
+
return False
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
*(提示:Pipeline 执行过程中,框架会缓存并复用配置相同的 Connector 实例,执行完毕后统一调用 `close()`,降低连接建立开销)*
|
|
204
|
+
|
|
205
|
+
### 自定义 Action
|
|
206
|
+
|
|
207
|
+
不是所有场景都适合 `fetch` / `push` 的语义。你可以定义任意方法名:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
import requests
|
|
211
|
+
from orchestrator import BaseConnector, register_connector
|
|
212
|
+
|
|
213
|
+
@register_connector("feishu_daily")
|
|
214
|
+
class FeishuDailyConnector(BaseConnector):
|
|
215
|
+
def __init__(self, config: dict):
|
|
216
|
+
super().__init__(config)
|
|
217
|
+
self.webhook_url = config["webhook_url"]
|
|
218
|
+
|
|
219
|
+
def send_report(self, title: str = "日报", content: str = "", **kwargs):
|
|
220
|
+
payload = {"msg_type": "text", "content": {"text": f"{title}\n{content}"}}
|
|
221
|
+
resp = requests.post(self.webhook_url, json=payload, timeout=10)
|
|
222
|
+
resp.raise_for_status()
|
|
223
|
+
return resp.json()
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
在 YAML 里对应的使用方式:
|
|
227
|
+
|
|
228
|
+
```yaml
|
|
229
|
+
tasks:
|
|
230
|
+
- id: send_daily
|
|
231
|
+
connector: feishu_daily
|
|
232
|
+
action: send_report
|
|
233
|
+
action_kwargs:
|
|
234
|
+
title: "{{ today }} 日报"
|
|
235
|
+
content: "数据同步完成"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Connector 配置安全
|
|
239
|
+
|
|
240
|
+
Connector 的敏感配置(token、密码)建议写在 `.env` 中,YAML 里使用 `${VAR_NAME}` 引用:
|
|
241
|
+
|
|
242
|
+
```yaml
|
|
243
|
+
connector_config:
|
|
244
|
+
shop_url: "${SHOPIFY_SHOP_URL}"
|
|
245
|
+
access_token: "${SHOPIFY_ACCESS_TOKEN}"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 编写 Pipeline (YAML)
|
|
251
|
+
|
|
252
|
+
在 `pipelines/` 目录下创建 YAML 文件来编排你的任务:
|
|
253
|
+
|
|
254
|
+
```yaml
|
|
255
|
+
pipelines:
|
|
256
|
+
- id: daily_order_sync # 唯一 ID,CLI 触发时用
|
|
257
|
+
name: 每日订单同步 # 可读名称,显示在 UI 里
|
|
258
|
+
description: 从 Shopify 拉取昨日订单,写入 Postgres
|
|
259
|
+
|
|
260
|
+
# 调度配置:cron / interval / manual
|
|
261
|
+
schedule:
|
|
262
|
+
type: cron
|
|
263
|
+
cron_expr: "0 6 * * *"
|
|
264
|
+
timezone: "Asia/Shanghai"
|
|
265
|
+
|
|
266
|
+
# Pipeline 级别设置
|
|
267
|
+
max_concurrency: 4 # 无依赖任务的最大并发数
|
|
268
|
+
stop_on_failure: true # 某个 Task 失败后,是否停止后续 Task
|
|
269
|
+
|
|
270
|
+
# 钩子(可选),可串联流水线或触发回调
|
|
271
|
+
on_success: "trigger:another_pipeline_id"
|
|
272
|
+
on_failure: "my_module:my_alert_function"
|
|
273
|
+
|
|
274
|
+
# 通知配置(见下方"告警通知"章节)
|
|
275
|
+
notify:
|
|
276
|
+
on_task_failure: true
|
|
277
|
+
channels:
|
|
278
|
+
- name: feishu
|
|
279
|
+
config:
|
|
280
|
+
webhook_url: "${FEISHU_WEBHOOK}"
|
|
281
|
+
|
|
282
|
+
tasks:
|
|
283
|
+
- id: fetch_orders # Task ID,depends_on 里引用这个
|
|
284
|
+
name: 拉取订单
|
|
285
|
+
connector: shopify # 对应 @register_connector("shopify")
|
|
286
|
+
connector_config:
|
|
287
|
+
shop_url: "${SHOPIFY_URL}"
|
|
288
|
+
access_token: "${SHOPIFY_TOKEN}"
|
|
289
|
+
action: fetch # Connector 上的任意方法名
|
|
290
|
+
action_kwargs: # 传给 connector.fetch() 的参数
|
|
291
|
+
endpoint: orders.json
|
|
292
|
+
params:
|
|
293
|
+
created_at_min: "{{ yesterday_iso }}" # 运行时动态渲染内置变量
|
|
294
|
+
retry:
|
|
295
|
+
times: 3
|
|
296
|
+
delay_seconds: 10
|
|
297
|
+
backoff: 2.0 # 指数退避:10s → 20s → 40s
|
|
298
|
+
timeout_seconds: 60
|
|
299
|
+
|
|
300
|
+
- id: write_to_postgres
|
|
301
|
+
name: 写入数据库
|
|
302
|
+
connector: postgres
|
|
303
|
+
connector_config:
|
|
304
|
+
dsn: "${DATABASE_URL}"
|
|
305
|
+
action: push
|
|
306
|
+
depends_on: [fetch_orders] # 依赖 fetch_orders 完成后才执行
|
|
307
|
+
pass_output_from: fetch_orders # 把 fetch_orders 的输出作为 data 传入
|
|
308
|
+
action_kwargs:
|
|
309
|
+
table: shopify_orders
|
|
310
|
+
mode: upsert
|
|
311
|
+
upsert_key: order_id
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### 内置模板变量
|
|
315
|
+
|
|
316
|
+
在 `action_kwargs` 中可以使用 Jinja 语法注入时间上下文,**在任务实际执行时计算**(非启动时计算,避免长驻进程时间不更新的问题):
|
|
317
|
+
|
|
318
|
+
| 变量 | 含义 | 示例值 |
|
|
319
|
+
|---|---|---|
|
|
320
|
+
| `{{ now }}` | 当前时间(ISO 8601) | `2024-03-15T06:00:00+08:00` |
|
|
321
|
+
| `{{ today }}` | 今天日期 | `2024-03-15` |
|
|
322
|
+
| `{{ yesterday }}` | 昨天日期 | `2024-03-14` |
|
|
323
|
+
| `{{ yesterday_iso }}` | 昨天 ISO 格式 | `2024-03-14T00:00:00+08:00` |
|
|
324
|
+
| `{{ run_id }}` | 本次执行的唯一 ID | `run_20240315_060001_a3f2` |
|
|
325
|
+
| `{{ pipeline_id }}` | Pipeline ID | `daily_order_sync` |
|
|
326
|
+
| `{{ week_start }}` | 本周一日期 | `2024-03-11` |
|
|
327
|
+
| `{{ month_start }}` | 本月 1 号 | `2024-03-01` |
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## 调度配置
|
|
332
|
+
|
|
333
|
+
### Cron 触发
|
|
334
|
+
|
|
335
|
+
```yaml
|
|
336
|
+
schedule:
|
|
337
|
+
type: cron
|
|
338
|
+
cron_expr: "0 6 * * *" # 每天早上 6:00
|
|
339
|
+
timezone: "Asia/Shanghai" # 默认 Asia/Shanghai
|
|
340
|
+
start_date: "2024-01-01" # 可选:从某天开始
|
|
341
|
+
end_date: "2024-12-31" # 可选:到某天结束
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
常用 Cron 表达式参考:
|
|
345
|
+
- `"0 6 * * *"` : 每天 06:00
|
|
346
|
+
- `"0 */4 * * *"` : 每 4 小时
|
|
347
|
+
- `"0 9 * * 1"` : 每周一 09:00
|
|
348
|
+
|
|
349
|
+
### 间隔触发
|
|
350
|
+
|
|
351
|
+
```yaml
|
|
352
|
+
schedule:
|
|
353
|
+
type: interval
|
|
354
|
+
interval_seconds: 300 # 每 5 分钟
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### 手动触发(只能通过 CLI 或 UI 触发)
|
|
358
|
+
|
|
359
|
+
```yaml
|
|
360
|
+
schedule:
|
|
361
|
+
type: manual
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### 并发控制
|
|
365
|
+
|
|
366
|
+
```yaml
|
|
367
|
+
schedule:
|
|
368
|
+
type: cron
|
|
369
|
+
cron_expr: "*/5 * * * *"
|
|
370
|
+
max_instances: 1 # 上一次未完成时,不启动新实例(默认 1)
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 数据流传递
|
|
376
|
+
|
|
377
|
+
Task 之间可以传递数据,上游的输出可以自动成为下游的输入。这在 ETL 流程中非常常见。
|
|
378
|
+
|
|
379
|
+
```yaml
|
|
380
|
+
tasks:
|
|
381
|
+
- id: fetch_from_shopify
|
|
382
|
+
connector: shopify
|
|
383
|
+
action: fetch
|
|
384
|
+
# fetch() 返回的数据被存入内部 TaskResult.output
|
|
385
|
+
|
|
386
|
+
- id: transform
|
|
387
|
+
connector: my_transformer
|
|
388
|
+
action: process
|
|
389
|
+
depends_on: [fetch_from_shopify]
|
|
390
|
+
pass_output_from: fetch_from_shopify # fetch_from_shopify 的输出
|
|
391
|
+
# 会作为 data 参数传入这个 Task
|
|
392
|
+
|
|
393
|
+
- id: write_to_db
|
|
394
|
+
connector: postgres
|
|
395
|
+
action: push
|
|
396
|
+
depends_on: [transform]
|
|
397
|
+
pass_output_from: transform # transform 的输出传给 push(data=...)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
对应的 Transformer Connector 里:
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
class MyTransformer(BaseConnector):
|
|
404
|
+
def process(self, data=None, **kwargs): # data 参数接收来自上游的数据
|
|
405
|
+
# 处理数据
|
|
406
|
+
transformed = [clean(row) for row in data["orders"]]
|
|
407
|
+
return transformed # 返回值成为下游的 data
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## 重试与超时
|
|
413
|
+
|
|
414
|
+
你可以为每个 Task 单独配置重试策略和超时时间。
|
|
415
|
+
|
|
416
|
+
```yaml
|
|
417
|
+
tasks:
|
|
418
|
+
- id: fetch_api
|
|
419
|
+
retry:
|
|
420
|
+
times: 3 # 最多重试 3 次(不含第一次执行)
|
|
421
|
+
delay_seconds: 10 # 第一次重试前等待 10 秒
|
|
422
|
+
backoff: 2.0 # 指数退避:10s → 20s → 40s
|
|
423
|
+
timeout_seconds: 120 # 超过 120 秒强制终止该任务
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 告警通知
|
|
429
|
+
|
|
430
|
+
框架支持基于配置的通知分发机制,支持防抖。
|
|
431
|
+
|
|
432
|
+
### 飞书 Webhook
|
|
433
|
+
|
|
434
|
+
内置了飞书通知器,配置方式如下:
|
|
435
|
+
|
|
436
|
+
```yaml
|
|
437
|
+
# pipelines/daily_sync.yaml
|
|
438
|
+
notify:
|
|
439
|
+
on_task_failure: true
|
|
440
|
+
on_pipeline_failure: true
|
|
441
|
+
on_pipeline_success: false
|
|
442
|
+
failure_threshold: 1 # 连续失败几次才发通知(防抖)
|
|
443
|
+
channels:
|
|
444
|
+
- name: feishu
|
|
445
|
+
config:
|
|
446
|
+
webhook_url: "${FEISHU_WEBHOOK_URL}"
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### 框架内置的 LogOnlyNotifier
|
|
450
|
+
|
|
451
|
+
开发环境下如果不想配置 Webhook,可以使用内置的 log 通知器:
|
|
452
|
+
|
|
453
|
+
```yaml
|
|
454
|
+
notify:
|
|
455
|
+
channels:
|
|
456
|
+
- name: log # 内置,直接用,失败信息仅打印到 stdout
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Web UI 界面
|
|
462
|
+
|
|
463
|
+
安装了 `data-orchestrator[ui]` 后,框架自带基于 Streamlit 的监控大盘。使用 `orchestrator run` 启动时默认会拉起 UI,或单独启动:
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
orchestrator ui # 默认端口 8501
|
|
467
|
+
orchestrator ui --port 9000 # 自定义端口
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
UI 包含四个页面:
|
|
471
|
+
|
|
472
|
+
- **Dashboard**:今日总览。显示执行次数、成功率、耗时趋势图表;最近 10 次 Pipeline 执行状态;即将触发的任务列表。
|
|
473
|
+
- **Pipeline 管理**:运维操作。展示所有 Pipeline 的调度状态(运行中 / 已暂停);提供手动触发、暂停、恢复按钮。
|
|
474
|
+
- **Run Detail (执行详情)**:点进某次执行查看详情。展示依赖关系 DAG 可视化图;每个 Task 的状态、耗时、重试次数;失败 Task 可展开查看完整错误堆栈。
|
|
475
|
+
- **Log Search (日志查询)**:历史检索。支持按 Pipeline / 状态 / 时间范围过滤;支持关键词搜索错误信息;分页展示并支持结果导出为 CSV。
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## 命令行与 API 管理
|
|
480
|
+
|
|
481
|
+
Orchestrator 提供了丰富的 CLI 命令用于日常运维:
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
# 启动调度器并同时开启 UI
|
|
485
|
+
orchestrator run --config pipelines/ --plugins connectors/
|
|
486
|
+
|
|
487
|
+
# 无 UI 模式启动
|
|
488
|
+
orchestrator run --no-ui
|
|
489
|
+
|
|
490
|
+
# 仅启动 UI
|
|
491
|
+
orchestrator ui --port 9000
|
|
492
|
+
|
|
493
|
+
# 运维命令
|
|
494
|
+
orchestrator trigger daily_order_sync # 手动立即触发
|
|
495
|
+
orchestrator list # 查看所有注册的 Pipeline
|
|
496
|
+
orchestrator status daily_order_sync # 查看某个 Pipeline 的最近执行记录
|
|
497
|
+
orchestrator pause daily_order_sync # 暂停调度
|
|
498
|
+
orchestrator resume daily_order_sync # 恢复调度
|
|
499
|
+
orchestrator ping # 检查 Connector 连通性 (调用 ping)
|
|
500
|
+
orchestrator validate # 验证 YAML 配置合法性 (不运行)
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### HTTP API
|
|
504
|
+
|
|
505
|
+
当 Orchestrator 启动后,后台会自动启动一个轻量级的 HTTP API(默认运行在 8765 端口)。你甚至可以将 `ORCHESTRATOR_API_URL` 提供给外部系统进行调度触发与管控:
|
|
506
|
+
|
|
507
|
+
- `GET /pipelines` - 列出所有流水线状态及下次执行时间
|
|
508
|
+
- `GET /pipeline/{id}` - 获取特定流水线的详细配置
|
|
509
|
+
- `GET /upcoming?hours=2` - 获取未来指定小时内即将触发的任务
|
|
510
|
+
- `POST /trigger/{id}` - 异步触发流水线,返回 run_id
|
|
511
|
+
- `POST /pause/{id}` - 暂停指定流水线
|
|
512
|
+
- `POST /resume/{id}` - 恢复指定流水线
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## 内置 Connector
|
|
517
|
+
|
|
518
|
+
Orchestrator 内置了常见的通用 Connector,你可以直接在 YAML 中使用,无需额外编写代码:
|
|
519
|
+
|
|
520
|
+
### `postgres`
|
|
521
|
+
|
|
522
|
+
```yaml
|
|
523
|
+
connector: postgres
|
|
524
|
+
connector_config:
|
|
525
|
+
dsn: "${DATABASE_URL}"
|
|
526
|
+
action: fetch
|
|
527
|
+
action_kwargs:
|
|
528
|
+
query: "SELECT * FROM orders WHERE created_at >= :date"
|
|
529
|
+
params:
|
|
530
|
+
date: "{{ yesterday }}"
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
```yaml
|
|
534
|
+
action: push
|
|
535
|
+
action_kwargs:
|
|
536
|
+
table: orders
|
|
537
|
+
mode: upsert # 支持 insert / upsert / replace / truncate_insert
|
|
538
|
+
upsert_key: order_id
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### `http_api`
|
|
542
|
+
|
|
543
|
+
```yaml
|
|
544
|
+
connector: http_api
|
|
545
|
+
connector_config:
|
|
546
|
+
base_url: "https://api.example.com"
|
|
547
|
+
headers:
|
|
548
|
+
Authorization: "Bearer ${API_TOKEN}"
|
|
549
|
+
rate_limit_rps: 2 # 每秒最多 2 个请求,自动限速
|
|
550
|
+
action: fetch
|
|
551
|
+
action_kwargs:
|
|
552
|
+
endpoint: /v1/orders
|
|
553
|
+
method: GET # GET / POST / PUT / PATCH / DELETE
|
|
554
|
+
params:
|
|
555
|
+
status: paid
|
|
556
|
+
timeout: 30
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### `csv_file`
|
|
560
|
+
|
|
561
|
+
```yaml
|
|
562
|
+
connector: csv_file
|
|
563
|
+
connector_config:
|
|
564
|
+
base_dir: "/data/exports"
|
|
565
|
+
action: fetch
|
|
566
|
+
action_kwargs:
|
|
567
|
+
path: "orders_{{ today }}.csv"
|
|
568
|
+
encoding: utf-8
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
```yaml
|
|
572
|
+
action: push
|
|
573
|
+
action_kwargs:
|
|
574
|
+
path: "output_{{ today }}.csv"
|
|
575
|
+
mode: overwrite # overwrite / append
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## 与现有方案对比
|
|
581
|
+
|
|
582
|
+
| | Orchestrator | Airflow | Prefect | crontab + 脚本 |
|
|
583
|
+
|---|---|---|---|---|
|
|
584
|
+
| **部署复杂度** | **极低** (单进程+SQLite 即可) | 重(Webserver+Scheduler+Worker+DB) | 需连接 Prefect Cloud 或自建 Server | 极简 |
|
|
585
|
+
| **自定义 Connector** | **极简** (几十行代码,任意方法名) | 需编写自定义 Operator,有框架抽象限制 | 需适配框架生态 | 随意但无复用 |
|
|
586
|
+
| **配置管理** | **YAML + 环境变量 + 动态渲染** | Python 脚本生成 DAG | Python 脚本 | 硬编码 |
|
|
587
|
+
| **重试/并发/超时** | ✅ 内置 | ✅ | ✅ | ❌ 自己写 |
|
|
588
|
+
| **可视化与监控** | ✅ 内置 Streamlit UI + HTTP API | ✅ 功能完整 | ✅ 功能完整 | ❌ |
|
|
589
|
+
| **适合团队规模** | **1-5 人 / 中小项目** | 10 人以上数据团队 | 中大型团队 | 1 人 |
|
|
590
|
+
| **学习成本** | **低** | 高 | 中 | 零 |
|
|
591
|
+
|
|
592
|
+
**适合用 Orchestrator 的场景:**
|
|
593
|
+
- 小团队或个人,不想维护 Airflow 这类重型基础设施
|
|
594
|
+
- 数据接口高度定制化,通用适配器成本高
|
|
595
|
+
- 想完全掌控执行逻辑,出了问题能追到每一行代码
|
|
596
|
+
- 已有自己的服务器,只需要一个 Python 进程跑起来
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
*MIT License*
|